3 poin oleh GN⁺ 2024-10-28 | 1 komentar | Bagikan ke WhatsApp
  • OpenRun adalah platform deployment aplikasi web untuk tool internal; alih-alih menyimpan file statis, kode aplikasi, dan file konfigurasi di sistem berkas, OpenRun menyimpannya di SQLite sehingga status deployment dikelola dengan berpusat pada database
  • Tujuan utamanya adalah memproses pembaruan aplikasi yang mengubah beberapa file sekaligus sebagai satu transaksi, agar halaman web yang rusak tidak disajikan saat peralihan versi
  • Dengan memakai hash SHA256 sebelum kompresi sebagai primary key, OpenRun mengurangi penyimpanan file duplikat antarversi aplikasi serta antara aplikasi staging, preview, dan production
  • Pendekatan penyimpanan SQLite menyederhanakan rollback, backup, penyimpanan hash untuk ETag, dan penyimpanan dengan kompresi Brotli; jika diperlukan, data GZip maupun data tanpa kompresi juga dapat ditangani bersama dengan menambahkan kolom
  • Saat ini berjalan pada single node, dan saat dukungan multi-node ditambahkan, rencananya adalah memakai Postgres bersama serta cache file SQLite lokal untuk mengurangi latensi

Cara OpenRun menyimpan file

  • OpenRun adalah platform deployment open-source untuk tool internal yang code-first, dan men-deploy aplikasi web dengan cara GitOps ke single node atau klaster Kubernetes
  • Alih-alih menaruh konten statis di sistem berkas seperti server web pada umumnya, OpenRun menyimpan data aplikasi seperti file statis, kode aplikasi, dan file konfigurasi di SQLite
  • Karena metadata aplikasi dibuat secara dinamis, penyimpanan di database terasa alami, dan jika file juga ditangani pada lapisan penyimpanan yang sama, status deployment lebih mudah dikelola bersama
  • Saat aplikasi dibuat dan diperbarui, file diunggah dari GitHub atau disk lokal ke database SQLite
  • Sistem berkas lokal hanya digunakan dalam mode pengembangan

Alasan memilih SQLite

  • Pembaruan transaksional adalah keunggulan terbesarnya
    • Beberapa perubahan file dapat dikelompokkan dan diproses sebagai satu transaksi
    • Berkat isolasi, aplikasi web yang rusak tidak akan disajikan di tengah proses pembaruan
  • Jika terjadi error deployment, pembaruan dapat di-rollback pada level transaksi database
    • Bahkan ketika beberapa aplikasi diperbarui bersamaan, semuanya dapat dikembalikan sekaligus
    • Ini lebih sederhana daripada mencari dan membersihkan file yang berubah di sistem berkas
  • OpenRun secara otomatis melakukan versioning untuk semua pembaruan, dan data file disimpan dalam tabel dengan skema berikut
CREATE TABLE files (sha text, compression_type text, content blob, create_time datetime, PRIMARY KEY(sha));
  • Karena hash SHA256 dari konten sebelum kompresi digunakan sebagai primary key, isi file yang sama hanya disimpan sekali di berbagai versi
  • Setiap aplikasi production memiliki staging app, dan dapat memiliki beberapa preview apps, sehingga duplikasi file bisa terjadi
    • Penyimpanan berbasis SQLite memungkinkan file dengan konten yang sama tidak disimpan berulang antar-aplikasi

Backup, caching, dan penanganan kompresi

  • Seluruh status sistem, metadata, dan file dapat di-backup menggunakan tool backup SQLite seperti Litestream
  • Jika SHA konten yang diperlukan untuk header ETag caching browser disimpan sekali saat file diunggah, nilainya tidak perlu dihitung ulang nanti
  • Isi file disimpan dalam bentuk terkompresi Brotli di tabel SQLite
    • Dengan pendekatan database, data terkompresi GZip atau data tanpa kompresi juga dapat disimpan dengan menambahkan kolom ke tabel files

Performa dan rencana multi-node

  • Di OpenRun, pendekatan database SQLite memberikan performa yang baik
  • Karena tidak ada implementasi setara berbasis sistem berkas, benchmark perbandingan langsung tidak dilakukan
  • Menurut benchmark dari tim SQLite, pada sebagian workload SQLite dapat memberikan performa lebih baik daripada penggunaan langsung sistem berkas
  • OpenRun saat ini berjalan pada single node
  • Jika dukungan multi-node ditambahkan di masa depan, rencananya adalah menggunakan database Postgres bersama untuk menyimpan metadata dan data file, menggantikan SQLite lokal
    • Pendekatan ini dapat menimbulkan masalah latensi
    • Untuk menghindari latensi akses ke Postgres, rencananya adalah menggunakan database SQLite lokal sebagai cache file

Mengapa pendekatan sistem berkas lebih umum

  • Salah satu alasan sebagian besar server web menggunakan sistem berkas adalah kemudahan
    • File dapat disalin dan diperbarui dengan tool sistem berkas yang sudah ada seperti rsync dan tar
  • Alasan lainnya adalah latar belakang historis
    • Sistem berkas sudah digunakan jauh sebelum ada database relasional in-process yang baik
  • Untuk memakai database sebagai penyimpanan file, diperlukan antarmuka API untuk upload file, dan pendekatan ini tidak selalu dapat diwujudkan

1 komentar

 
GN⁺ 2024-10-28
Opini Hacker News
  • Beberapa tahun lalu saya pernah bereksperimen dengan ide ini, sebagian terinspirasi dari tulisan “35% Faster Than The Filesystem”: https://www.sqlite.org/fasterthanfs.html
    Catatan saya saat itu ada di sini: https://simonwillison.net/2020/Jul/30/fun-binary-data-and-sq...
    Saya membuat https://datasette.io/plugins/datasette-media sebagai plugin untuk menyajikan file statis dari SQLite di Datasette, dan itu bekerja dengan baik, tetapi jujur saja saya tidak banyak menggunakannya setelah membuatnya
    Konsep terkait lainnya adalah cara menyajikan tile peta dari SQLite, dan https://datasette.io/plugins/datasette-tiles melakukan hal itu. Ternyata format MBTiles adalah database SQLite yang penuh berisi PNG
    Jika ingin bereksperimen dengan SQLite untuk penyajian file, alat CLI “sqlite-utils insert-files” bisa berguna untuk konfigurasi awal database: https://sqlite-utils.datasette.io/en/stable/cli.html#inserti...

    • Dari sudut pandang performa, skema nama file berbasis hash konten lebih mudah diimplementasikan di penyimpanan SQLite
      Hash konten cukup dibuat sekali saat file diunggah, dan tidak perlu dibuat setiap kali web server direstart atau memiliki tahap build yang mengubah nama file sebenarnya. Ini juga bisa diterapkan secara dinamis pada file di filesystem (lihat implementasi embedFS di https://github.com/benbjohnson/hashfs), tetapi database membuatnya sedikit lebih mudah
    • Saya penasaran apakah ada cara melakukan sendfile seperti xsendfile dari database SQLite
      requests-cache, kalau saya ingat benar, menyimpan cache request di SQLite dengan (date, URI): https://github.com/requests-cache/requests-cache/blob/main/r...
      Pencarian pyfilesystem SQLite: https://www.google.com/search?q=pyfilesystem+sqlite
      Pencarian sendfile mmap SQLite: https://www.google.com/search?q=sendfile+mmap+sqlite
      https://github.com/adamobeng/wddbfs adalah “penyedia webdavfs yang dapat membaca isi database sqlite”
      Sepertinya ada juga cara yang baik untuk mengimplementasikan filesystem di atas SQLite dengan menambahkan izin file Unix dan izin atribut file tambahan xattrs
      Apakah SQLite lebih cepat atau lebih nyaman daripada, misalnya, ngx_http_memcached_module.c? Saya juga penasaran apakah SQLite punya ACL per sel
    • Dengan SQLite, Anda mempertahankan koneksi database yang terbuka, sehingga cukup mengirim request hanya untuk konten yang kemungkinan besar sudah di-cache oleh SQLite
      Saat membaca file statis, setiap request harus membuka, membaca, lalu menutup file, sehingga meskipun lapisan filesystem sudah men-cache isi file, context switch menjadi lebih banyak. Jika ingin membuat ini cepat, solusi yang tepat bukan mengubah semuanya menjadi database, melainkan menambahkan frontend caching. Itu lebih cepat daripada SQLite dan juga lebih mudah dipelihara serta di-troubleshoot
    • Saya merasa apa pun bisa menjadi 35% lebih cepat kalau tidak melakukan terlalu banyak system call dan penyalinan antara user space dan kernel
      Termasuk filesystem yang sepenuhnya berjalan di user space. FUSE dikecualikan karena pemanggilannya melewati kernel
  • Pernyataan bahwa “pembaruan transaksional” adalah manfaat utama punya keterbatasan. Baik server memakai SQLite maupun sistem file, itu saja tidak dapat mencegah webapp rusak saat pembaruan
    Setiap halaman di browser adalah pohon resource yang diambil lewat permintaan HTTP terpisah, sehingga bukan target sistem transaksi/pembaruan atomik di sisi server. Sekalipun semua resource di server diganti secara transaksional, browser tetap bisa melihat kombinasi resource lama dan baru
    Solusi umumnya adalah memberi semua sub-resource halaman (bundle JavaScript, stylesheet, media, dan sebagainya) nama (URL) yang memuat hash konten atau versi. Jika dokumen HTML root memuat versi X, semua sub-resource juga harus memuat versi X yang sesuai
    Selain itu, saat memperbarui dari X ke Y pun, setelah mulai menyajikan halaman Y, sub-resource X harus tetap disediakan untuk sementara waktu. Jika tidak dipertahankan sampai yakin secara masuk akal bahwa tidak ada lagi browser yang masih memuat halaman X, halaman X bisa rusak
    Jadi justru tidak tepat jika ingin memasukkan HTML root dan sub-resource ke dalam satu bundle yang diganti secara atomik. Sebab sub-resource lama yang masih mungkin dirujuk akan terhapus
    Dalam beberapa kasus, sebagian sub-resource seperti file media mungkin ingin dikelola versinya secara terpisah dari dokumen HTML. Jika ingin memperbaruinya tanpa membatalkan seluruh cache elemen struktur aplikasi seperti potongan JavaScript atau stylesheet, sistem build halaman juga mungkin perlu mempertimbangkan hal ini

    • “Sampai yakin browser tidak mungkin masih memuat halaman versi X” ternyata bisa jauh lebih lama dari perkiraan
      Saat sebuah perusahaan besar menguji ini (saat itu mereka melihat bagian yang cukup besar dari web), mayoritas pengguna (lebih dari 80%) bertahan di webapp selama sekitar 2–3 hari. Kemungkinan angkanya bias karena ada orang-orang yang membiarkan tab terbuka sepanjang akhir pekan
      Titik 95% sekitar 2 minggu, dan 100% sekitar 600 hari. Artinya ada pengguna yang membiarkan tab terbuka hampir 2 tahun
      Jika menargetkan 100%, harus menunggu cukup lama. Semua angka ini berdasarkan ingatan, dan saya sudah tidak bekerja di perusahaan itu
    • Pada webapp berbasis hypermedia, interaksi di dalam halaman mengembalikan potongan kecil HTML. Untuk perubahan UI yang lebih besar, penyegaran halaman penuh disarankan, dan ini lebih dekat ke pendekatan MPA
      Skenario pengguna yang lama berada di satu halaman lalu menerima tautan rusak lebih dekat ke masalah di sisi SPA
      Secara umum saya setuju, tetapi pembaruan transaksional hanya mencegah satu kategori masalah terkait pembaruan. Masalah lain di level aplikasi juga bisa menghasilkan pengalaman yang rusak
      Menyajikan terus versi lama konten statis yang dirujuk dengan hash konten memang memungkinkan, tetapi saat ini belum diimplementasikan di Clace
    • Pendekatan ini juga membantu cache frontend dan CDN
      Kiat utamanya adalah mengunggah perubahan non-HTML sebelum perubahan HTML, agar file yang belum ada tidak dirujuk. Jika ingin membuat aplikasi serumit mungkin, unggahan bisa menerapkan depth-first search. Namun jika lebih mementingkan kesehatan mental, lebih baik melunakkan masalahnya dan memilih unggah aset lebih dulu di aplikasi
  • Saat bekerja di studio game kecil pada 2011/2012, saya merekomendasikan untuk memindahkan semua aset di bawah 100KB ke DB sqlite3, lalu membuat “file pak” dan menyimpan offset file-file itu di dalam DB sqlite3
    Pilihan ini dipengaruhi oleh presentasi postmortem Richard Hipp, ketika ia mengatakan bahwa jika melihat ke belakang, akan lebih baik jika BLOB diperlakukan seperti inode: diletakkan pada offset lebih belakang di dalam database, sementara BLOB ditambahkan ke file
    Pemuatan aset menjadi sangat cepat. Karena ini game mobile, hanya sedikit sekali aset yang tidak ada di DB. Menarik juga melihat orang lain kemudian lebih banyak mengadopsi pendekatan ini
    Satu keunggulan lain yang mudah terlewat adalah kemampuan menempelkan metadata yang hampir tak terbatas di samping konten, sehingga file yang “mirip” bisa ditemukan lewat kueri database
    Kami memasukkan sangat banyak metadata ke DB, dan seingat saya file pak akhir berukuran 200MB, sementara databasenya sekitar 20MB. Sekali lagi, ini game mobile
    Hal terburuk di sisi klien adalah satu inner join ganda yang tidak bisa kami kurangi karena kompleksitas di sisi server. Rasanya menjengkelkan karena bukan kami yang mengimplementasikan server, dan pihak yang bekerja bersama kami sangat buruk dalam pengembangan perangkat lunak, sehingga mereka mengubah build tanpa memberi tahu seluruh spesifikasi backend dan build tiba-tiba rusak
    Untuk replay game, kami juga memakai database sqlite3 terpisah, dan setelah pertandingan selesai kami bisa memutar ulang seluruh game serta melihat apa yang dilakukan tiap lawan. Ini juga sangat bagus untuk pengujian otomatis

  • Sistem kontrol perubahan lix juga akhirnya memasukkan file ke SQLite alih-alih menangani sistem file dan git. Tulisan ini membahas masalah yang kami alami: https://opral.substack.com/i/150054233/breaking-git-compatib...
    Masalah seperti file locking dan concurrency diselesaikan oleh SQLite
    Dengan SQLite, file bisa dikueri dengan SQL alih-alih API sistem file yang berbeda-beda per platform
    Kueri SQL bisa ditulis secara type-safe dengan Kysely https://kysely.dev/ tanpa ORM

    • Menulis kueri SQL secara type-safe dengan Kysely terlihat lebih baik daripada pendekatan yang pernah saya lihat di F# type provider
    • Saya sangat mendukung penggunaan SQLite sebagai lapisan abstraksi di atas sistem file
      Namun perlu berhati-hati bahwa database SQLite tidak akan mengecil jika tidak di-vacuum. Pada dasarnya itu adalah proses menyalin data ke file terpisah lalu menghapus yang asli
      Ini harus dilakukan secara manual pada momen yang masuk akal di dalam aplikasi, jadi saat memakainya dengan pola menulis dan menghapus data biner, penggunaan disk perlu diperhatikan
    • Anda bisa menempelkan metadata sebanyak yang diinginkan pada file
    • Terkait proyek lix, saya penasaran apakah sudah melihat Fossil. Sepertinya hal yang ingin dilakukan bisa dicapai hanya dengan perubahan kecil
  • Menariknya, CMS static site generator yang saya buat bekerja persis kebalikan dari pendekatan di sini
    Selama mengembangkan/memperbarui website, semua halaman dan tulisan adalah entri di database SQLite, dan dimanipulasi lewat antarmuka web yang menampilkan versi website yang bisa diedit
    Setelah itu website di-dump ke sistem file sebagai halaman statis untuk dideploy langsung, atau diunduh sebagai zip lalu diunggah ke tempat lain untuk dideploy, termasuk layanan hosting yang sepenuhnya statis

    • Saat ini saya memakai Zola, tetapi karena berbagai alasan saya mempertimbangkan membuat sendiri agar bisa menyesuaikannya lebih fleksibel, dan pendekatan seperti ini terus terpikir. Saya penasaran bagaimana rasanya saat benar-benar dipakai
  • Menurut “Appropriate Uses For SQLite” dari SQLite https://www.sqlite.org/whentouse.html, jumlah traffic web yang bisa ditangani SQLite bergantung pada seberapa berat sebuah situs menggunakan database
    Secara umum, situs dengan kurang dari 100K hit per hari seharusnya berjalan baik dengan SQLite. 100K/hari adalah estimasi konservatif, bukan batas atas yang ketat. Ada kasus SQLite menangani traffic 10 kali lipat dari itu
    Situs web SQLite (https://www.sqlite.org/) tentu saja juga memakai SQLite, dan per 2015 menangani sekitar 400K~500K request HTTP per hari; 15~20% di antaranya adalah halaman dinamis yang menyentuh database. Konten dinamis menggunakan sekitar 200 statement SQL per halaman web
    Konfigurasi ini berjalan di satu VM yang berbagi server fisik dengan 23 VM lain, tetapi tetap menjaga load average di bawah 0,1 hampir sepanjang waktu. Referensi: https://news.ycombinator.com/item?id=33975635

    • 100K/hari bahkan kurang dari 2 per detik. Angka di halaman SQLite itu sepertinya perlu diperbarui
      Untuk workload yang dominan baca seperti serving file statis, SQLite bisa menangani jauh lebih banyak. Jika header caching konten disetel, browser akan meng-cache konten sehingga request ke server hanya diperlukan untuk klien baru
      Dalam kebanyakan use case, SQLite tampaknya tidak akan menjadi bottleneck
  • Ide menyajikan konten statis dengan SQLite hanya berdasarkan halaman “35% Faster Than The Filesystem” tahun 2017, secara halus, terlihat belum matang
    Web server modern seperti Nginx memakai strategi optimal untuk menangani file statis. Mulai dari sendfile hingga operasi io_uring dan splice, semuanya berjalan di dalam thread pool yang dirancang baik di atas fondasi yang sesuai seperti epoll, kqueue, atau eventport
    Sebaliknya, hal terbaik yang pada dasarnya bisa disediakan SQLite hanya sebatas dukungan memory-mapped I/O (https://www.sqlite.org/mmap.html)
    Pendekatan ini bisa cocok untuk layanan satu klien seperti webapp yang di-host lokal (lihat juga https://github.com/electron/asar). Namun untuk situs web besar, seperti disebut komentar-komentar lain, ini sama saja mencoba memecahkan masalah yang sebenarnya tidak ada

  • Saya sering melakukan komputasi ilmiah berkinerja tinggi, dan terutama saat mengakses data secara paralel, cara yang paling fleksibel dan cepat sering kali adalah database SQLite read-only di atas ramdisk
    Rasanya sangat hacky, tetapi sejauh ini lebih mudah dikonfigurasi dan lebih cepat daripada metode lain yang pernah saya temukan

    • Jujur saja, itu tidak terasa seperti hack. Pembacaan paralel yang cepat dan efisien adalah bagian yang memang secara eksplisit dirancang agar dilakukan SQLite dengan baik
      Saya pernah melihat seorang teman di bidang astronomi mengatakan bahwa banyak orang di ranah sains seharusnya membiasakan diri dengan database. Kalau tidak, pada akhirnya mereka tanpa sadar akan menghabiskan usaha besar untuk membuat database buatan sendiri yang buruk
    • Akan bagus kalau hasilnya ditulis menjadi artikel
    • Saya penasaran apakah performanya lebih baik daripada memakai HDF5
  • Alasan pendekatan ini tidak lebih umum adalah karena filesystem memang unggul dalam menangani file
    Jika butuh update atomik, cukup checkout ke direktori baru lalu ganti symbolic link-nya
    Saya sudah melihat beberapa versi penggunaan database layaknya filesystem; ada sisi baiknya, tetapi saat terjadi masalah, sisi buruknya bisa menjadi mimpi buruk

    • Saya berpikir persis sama. Ke mana perginya cara sederhana seperti membuat v2/index.html?
      Dengan begitu, kita juga bisa memakai sesuatu seperti btrfs untuk deduplikasi di lapisan filesystem
  • Ada masalah dengan klaim bahwa karena banyak file bisa berubah saat update aplikasi, memakai database memungkinkan semua perubahan diproses secara atomik dalam transaksi dan mencegah serving halaman web yang rusak saat pergantian versi
    Penyebabnya adalah file SQLite dikunci terhadap pembacaan selama penulisan demi mencapai isolasi serializable. Maka kesimpulannya justru lebih baik melakukan pekerjaan database pada file offline, lalu mengganti file lama di production dengan file baru
    Ini pada akhirnya lebih dekat ke saran untuk memakai file tar, atau memakai direktori terpisah yang akan diganti dengan konten baru
    File statis jauh lebih mudah disajikan secara statis. Tidak perlu disajikan dari program yang mengelola koneksi SQLite real-time dan mencoba mencapai semacam sihir “update concurrency” yang aneh. Masalah ini bisa diselesaikan tanpa kesulitan sama sekali
    Mengelola CMS sebagai database SQLite boleh saja, tetapi jika kontennya statis dan disajikan secara real-time, lebih baik memakai file statis