2 poin oleh GN⁺ 2023-11-08 | 1 komentar | Bagikan ke WhatsApp
  • PR refactoring PDS untuk Bluesky atproto #1705 mengubah PDS agar menggunakan datastore SQLite tenant tunggal, serta menyimpan repo per pengguna dan status akun privat masing-masing ke file SQLite tersendiri
  • DB pengguna disimpan dengan struktur path /${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}, dan kunci tanda tangan setiap repo disimpan berdampingan dengan file SQLite tersebut
  • Abstraksi akses data pengguna yang lama digantikan oleh ActorStore; karena SQLite tidak mendukung transaksi bersamaan, operasi tulis harus secara eksplisit membuat transaksi dengan store
  • Handle file DB yang terbuka dan kunci tanda tangan dikelola dengan LRUCache, mempertahankan hingga 30 ribu handle file terbuka dan 30 ribu kunci di memori, serta menutup handle file ketika DB terdepak dari cache
  • Untuk manajemen status layanan, diperkenalkan 3 DB SQLite terpisah dan dijalankan dalam mode WAL agar pembacaan bersamaan dan replikasi streaming dimungkinkan; distribusi PDS direncanakan menyertakan Litestream atau alat serupa

Perubahan inti PR

  • PR #1705 merefaktor PDS berbasis datastore SQLite tenant tunggal
  • Setiap pengguna memiliki file SQLite khusus sendiri, yang menyimpan repo pengguna tersebut dan status akun privatnya
  • DB pengguna disimpan pada path hierarkis menggunakan hash DID
    • Format path: /${dbDirectory}/${sha256Hex(did).slice(0,2)}/${did}
  • repo signing key setiap repo disimpan di lokasi yang sama dengan file SQLite

ActorStore dan model transaksi

  • Abstraksi akses data pengguna berubah dari “services” yang lama menjadi ActorStore
  • Perbedaan utama ActorStore adalah kelas untuk membaca dan menulis dipisahkan
  • Karena SQLite tidak mendukung transaksi bersamaan, operasi tulis harus secara jelas membuat transaksi dengan store
  • Log commit mencakup pengerjaan ulang reader dan transactor, penanganan race pada transaksi actor store, perapihan antarmuka store, dan lainnya

Manajemen cache dan handle file

  • LRUCache dipertahankan untuk kunci tanda tangan dan database
  • Batas yang ditetapkan adalah sebagai berikut
    • Maksimum handle file terbuka 30 ribu
    • Maksimum kunci yang dipertahankan di memori 30 ribu
  • Ketika database terdepak dari cache, handle file ditutup
  • Commit terkait mencakup actor store in lru cache dan fix open handles

3 DB SQLite untuk status layanan

  • Selain DB per pengguna, diperkenalkan 3 database SQLite terpisah untuk manajemen status layanan
    • service DB: mengelola informasi akun, kode undangan, refresh token, dan lainnya
    • did cache DB: hanya berisi satu tabel untuk caching DID resolution
    • sequencer DB: hanya berisi satu tabel yang mengelola urutan semua pembaruan repo dalam satu layanan
  • Setiap file SQLite dijalankan dalam WAL mode
  • Tujuan WAL mode adalah memungkinkan pembacaan bersamaan dan replikasi streaming
  • Distribusi PDS direncanakan menyertakan Litestream atau alat serupa

Status review dan merge

  • PR ini terdiri dari total 143 commit dan di-merge dari branch pds-sqlite-refactor ke branch pds-v2
  • Tanggal merge adalah 1 November 2023, dengan commit merge 8449ceb
  • Reviewer devinivy meninggalkan beberapa catatan dan komentar sebelum menyetujui perubahan
  • devinivy menilai refactoring ini memiliki “banyak penyederhanaan yang sangat baik” dan secara keseluruhan terasa rapi
  • Setelah merge, branch pds-sqlite-refactor dihapus

Pertanyaan setelahnya

  • Pada 28 Februari 2025, npetrangelo melihat skala perubahan PR ini dan meminta ringkasan trade-off antara arsitektur Postgres sebelumnya dan arsitektur SQLite yang diperkenalkan oleh PR ini
  • Teks yang diberikan tidak menyertakan jawaban dari pihak Bluesky atas pertanyaan tersebut

1 komentar

 
GN⁺ 2023-11-08
Komentar Hacker News
  • Saya suka SQLite, tetapi pendekatan memisahkan skema atau database untuk tiap tenant umumnya punya banyak kesulitan
    Jika memakai keamanan tingkat baris (RLS) pada instance bersama, seluruh rollback bisa dilakukan bila migrasi gagal. Namun pada skema per tenant, jika migrasi data gagal karena data yang tidak terduga, pengguna akan tertinggal di versi skema yang berbeda sampai penyebabnya ditemukan
    Saat mencapai skala sharding, hal serupa mungkin tetap terjadi, tetapi sebelum itu satu database tunggal adalah yang paling mudah, dan nantinya mungkin perlu menggabungkan data atau memindahkan kepemilikan resource secara atomik
    Saya tidak menentang konfigurasi ini dan memang ada tempat penggunaannya, tetapi di perusahaan kami sedang bergerak secepat mungkin keluar dari skema per tenant. Kalau tidak diinvestasikan dengan benar, masalahnya terlalu banyak, dan menurut saya jarang sekali orang sudah siap untuk itu saat pertama kali memunculkan idenya
    Yang menarik, sekitar 10 tahun lalu aplikasi dimulai dengan SQLite per tenant, lalu pindah ke skema per tenant di PostgreSQL, dan sekarang sedang menuju satu skema tunggal dengan RLS, jadi arahnya benar-benar berlawanan

    • Dari sudut pandang pernah menangani database raksasa di produksi, saya tidak ingin melakukannya lagi
      Begitu bebannya cukup besar, setiap perubahan menjadi berisiko, karena tidak mungkin menguji semua kondisi ekstrem performa secara menyeluruh
      Pengguna tier gratis menemukan jalur kode tanpa indeks lalu merusak produksi juga pola yang umum

    • Sebagian pengguna tertinggal di versi skema berbeda karena kegagalan migrasi data mungkin bukan masalah besar
      Jika layanannya sebesar dan sekompleks itu, biasanya upgrade skema dilakukan bertahap: 1. membuat kode kompatibel dengan skema masa depan, 2. memigrasikan data, 3. menghapus dukungan skema lama
      Jadi biasanya harus aman beroperasi lama dalam kondisi antara tahap 1 dan 2. Tentu bug baru adalah pengecualian, tetapi dari sudut pandang operasional, selama prosedur seperti ini dipakai, sistem yang kembali ke keadaan perantara migrasi juga saya anggap baik-baik saja

    • Jika pelanggan produk kurang dari 100 orang, berada di versi skema berbeda untuk tiap pengguna justru bisa lebih baik
      Tiap pelanggan bisa memiliki jadwal dan kebutuhan upgrade yang berbeda, dan saya juga tahu bisnis yang melakukan pekerjaan kustom untuk sebagian pelanggan sampai-sampai pada dasarnya tidak menjalankan kode yang sama
      Pada akhirnya itu bergantung pada struktur bisnis

    • Secara adil, 10 tahun lalu RLS belum ada. Fitur itu muncul di PostgreSQL 9.5 pada 2016

    • https://blog.turso.tech/introducing-embedded-replicas-deploy...

      https://electric-sql.com/

  • Saya tidak tahu apa maksud pernyataan “SQLite tidak mendukung transaksi konkuren”
    Setahu saya itu didukung selama file .db tidak diakses melalui file sharing seperti UNC atau NFS: https://www.sqlite.org/wal.html
    Saya pernah memakainya untuk membaca dan memperbarui database dari beberapa thread/proses di mesin yang sama, dan jika butuh tampilan yang konsisten atau tidak ingin menahan transaksi terlalu lama, snapshot juga bisa dibuat dengan sqlite backup API
    Mungkin ada sesuatu yang saya lewatkan, dan saya tidak sepenuhnya yakin karena sudah beberapa tahun tidak menyentuh SQLite

    • Ternyata tidak. Saya keliru. Sebenarnya lebih dekat ke multi-read, single-write
      Sepertinya selama ini saya hanya mengasumsikannya dan tidak memeriksanya cukup teliti. Namun sebagian besar database yang saya buat dengan SQLite memang lebih banyak membaca daripada menulis
      Saya koreksi

    • Kalau menunggu, hctree [1] akan menjadi stabil, dan kita akan bisa memilih antara mekanisme backend tradisional dan backend baru yang diimplementasikan dengan dukungan konkurensi

      [1] https://sqlite.org/hctree/doc/hctree/doc/hctree/index.html

    • Menurut dokumentasi, penulis hanya menambahkan konten baru ke akhir file WAL sehingga baca dan tulis bisa berjalan bersamaan, tetapi karena hanya ada satu file WAL, hanya ada satu penulis yang bisa menulis pada saat yang sama
      Sepertinya yang dimaksud tulisan asli adalah operasi update harus dijalankan secara berurutan

    • Jika traffic rendah, ini berjalan, tetapi jika transaksi membesar atau jumlah penulisan konkuren meningkat, meski WAL diaktifkan, pada titik tertentu akan muncul masalah database locked
      Sampai batas tertentu bisa diakali di level aplikasi, tetapi umumnya jika sudah mencapai titik itu, sebaiknya serius mempertimbangkan backend database lain

    • Setidaknya saat terakhir saya mengecek, kemungkinan maksudnya adalah tidak ada penguncian tingkat baris, dan penguncian tingkat tabel pun sangat terbatas
      Menurut dokumentasi, penulis tetap mengambil lock atas seluruh database

  • Menarik, dan saya suka strategi 1:1 antara 1 pengguna dan 1 database
    Namun saya penasaran bagaimana data yang perlu agregasi antar pengguna ditangani. Jika saya berlangganan pengguna lain dan pengguna itu memposting tulisan, bagaimana database saya diperbarui dengan tulisan baru itu? Atau apakah ini hanya untuk data persisten seperti data profil atau relasi follow, sementara data interaksi seperti feed ditangani secara terpisah?
    Saya juga suka bahwa “connection pooling” hanyalah membatasi jumlah handle terbuka dengan cache LRU. Menarik juga bahwa karena tiap koneksi DB bersifat single-thread, konkurensi ditangani di tingkat tenancy, bukan di tingkat koneksi
    Sepertinya rate limiting per database juga bisa dengan mudah ditambahkan di atas ini untuk mencegah penyalahgunaan oleh pengguna tertentu
    Saya juga penasaran apakah ada cara sederhana untuk mengonfigurasi Litestream bagi jumlah database yang arbitrer

  • Selalu menyenangkan melihat adopsi SQLite/Litestream di server makin meningkat. Kami juga memakainya saat membuat aplikasi baru
    SQLite + Litestream adalah pilihan yang lebih baik untuk database tenant, dan biaya replikasi serta backup ke S3/R2 jauh lebih murah daripada database terkelola cloud yang mahal [1]
    Hingga 3900% lebih murah dibanding SQLServer di Azure

[1] https://docs.servicestack.net/ormlite/litestream

  • Tidak mengerti apa maksudnya 3900% lebih murah

  • Di tempat kerja fintech sebelumnya, perusahaan menyimpan akun pelanggan sebagai file sqlite3 terenkripsi di blob storage, dan itu cukup cocok dengan pola aksesnya

    • Penasaran bagaimana mereka menangani penguncian file saat mengunggah ulang setelah dimodifikasi
  • Sekilas dari luar, ini terlihat seperti kombinasi antara yang terburuk dan yang mengerikan
    Semoga ada yang menulis artikel bagus yang menjelaskan kelebihannya dengan angka nyata dan menganalisis kekurangan yang diperkirakan. Kalau dipelajari dengan benar, ini bisa jadi topik yang sangat menarik

    • Bisa jelaskan kenapa ini terlihat seperti “kombinasi antara yang terburuk dan yang mengerikan”?
      Sekilas, terutama jika diasumsikan sedang membuat sistem terdistribusi yang akan dijalankan dan dideploy oleh banyak pengguna yang bukan administrator sistem profesional, ini terlihat sebagai pilihan yang cukup masuk akal
      Sepertinya memang itu juga tujuannya, dan saya menduga tujuan desainnya adalah menghindari kebutuhan untuk menyiapkan, mengonfigurasi, dan mengelola database tambahan atau server lain
  • Semoga ada orang yang lebih memahami Bluesky menjelaskan data apa yang disimpan di SQLite dan data apa yang tidak
    Saya berasumsi ini bukan hal seperti pesan antar pengguna

    • Saya pikir pesan antar pengguna juga disimpan di database SQLite tersebut
      Bayangkan email. Jika Anda mengirim email dan men-CC lima orang, tujuh orang masing-masing menyimpan salinan email yang sama di server email mereka sendiri
      Jadi strukturnya bukan ada database pusat yang menyimpan satu email lalu dirujuk oleh orang lain
      Sharding database relasional pada dasarnya juga bekerja seperti ini
      Denormalisasi data seperti ini hampir wajib ketika aplikasi makin besar, terutama pada aplikasi many-to-many dengan rasio baca terhadap tulis yang tinggi
      Jika rasio baca terhadap tulis rendah, struktur database relasional dengan satu master dan beberapa slave pun bisa menangani jumlah request dan data yang mengejutkan besarnya
    • Isinya semua postingan dan balasan yang Anda unggah sebagai pengguna
      Saat ini Bluesky pada dasarnya meng-host sendiri satu-satunya PDS, tetapi tujuan akhirnya adalah setiap pengguna akhir memiliki PDS-nya sendiri
      Inrupt/SOLID menyebut konsep ini “pod”
      Dalam praktiknya, kemarin mereka meng-onboarding PDS produksi kedua, jadi ada kemajuan
    • Jika yang dimaksud pesan adalah pesan langsung, yaitu pesan privat antara dua pihak, saat ini Bluesky tidak memiliki fitur seperti itu
      Yang ada hanya pesan publik yang disiarkan ke seluruh dunia
      Saya belum menelusuri apakah ada rencana untuk pesan langsung
  • Kenapa pengguna di-hash dengan sha256 lalu dibagi ke direktori target dua karakter?
    Bukankah md5 jauh lebih cepat dan menyelesaikan masalah yang sama?

    • Dugaan saya, hash itu dilakukan relatif jarang, jadi perbedaan performanya tenggelam dalam noise
      Nilai lebih besarnya adalah tidak perlu menjawab pertanyaan “kenapa memakai hash yang tidak aman”, sekaligus menghilangkan atau setidaknya meminimalkan satu kelas potensi masalah keamanan
    • Pada skala itu, mereka mungkin juga khawatir soal collision
      Atau mungkin seperti saya, mereka terkubur oleh tool keamanan perusahaan, sehingga tidak ingin membuat pengecualian tersendiri untuk setiap penggunaan md5
    • Membiarkan hash kriptografis yang sudah rusak berkeliaran itu tidak sehat
      Kalau tidak membutuhkan hash yang aman, ada banyak hash non-kriptografis yang cepat
    • Ini kemungkinan besar karena batasan filesystem, yaitu jumlah maksimum file dalam satu direktori, bukan karena collision
  • Apakah Bluesky masih berbasis undangan?

    • Ya, tetapi bukan karena alasan seperti “growth hacking”
      Itu cara untuk membatasi pertumbuhan sementara mereka menskalakan sistem dari sisi backend dan pencegahan penyalahgunaan
      Ada antrean khusus untuk developer, dan Anda bisa mendapatkan akses cukup cepat: https://atproto.com/blog/call-for-developers