2 poin oleh GN⁺ 2026-04-25 | 1 komentar | Bagikan ke WhatsApp
  • Mengintegrasikan queue tahan lama, stream, pub/sub, dan scheduler ke dalam satu file SQLite, sehingga pemrosesan tugas asinkron bisa dilakukan tanpa broker terpisah seperti Redis atau Celery
  • Mencapai waktu respons antaproses dalam hitungan milidetik satu digit dengan polling PRAGMA data_version setiap 1ms, tanpa perlu polling di level aplikasi atau daemon
  • notify(), stream(), dan queue() semuanya ditulis di dalam transaksi pemanggil, sehingga commit atau rollback terjadi bersama penulisan bisnis dan membantu mengurangi masalah dual-write
  • Queue tugas mencakup retry, prioritas, eksekusi tertunda, dead-letter, scheduler, named lock, dan rate limiting, sementara stream mendukung pengiriman at-least-once dengan menyimpan offset per konsumen
  • Di lingkungan yang menggunakan SQLite sebagai penyimpanan utama, aplikasi dan pemrosesan asinkron dapat dioperasikan bersama dalam satu file database untuk menurunkan kompleksitas operasional
  • Menyediakan tiga primitive inti
    • queue(): queue tugas at-least-once — retry, prioritas, tugas tertunda, dead-letter, visibility timeout
    • stream(): pub/sub tahan lama — pelacakan offset per konsumen, replay at-least-once
    • notify(): pub/sub sementara — fire-and-forget, tanpa replay riwayat
  • Mendukung dekorator gaya Huey @queue.task() untuk mengubah fungsi menjadi tugas queue, serta tugas periodik berbasis crontab() + scheduler dengan pemilihan leader
  • Skema queue menerapkan partial index pada tabel _honker_live; proses claim dilakukan dengan satu UPDATE … RETURNING, dan ack dengan satu DELETE, sehingga performa tetap konsisten terlepas dari jumlah baris dead
  • Sebagai ekstensi SQLite yang dapat dimuat (libhonker_ext), semua klien SQLite 3.9+ dapat mengakses tabel yang sama — worker Python dapat meng-claim tugas yang didorong dari bahasa lain
  • Menyediakan panduan integrasi dengan SQLAlchemy, Django, Drizzle, Kysely, sqlx, GORM, ActiveRecord, Ecto, dan ORM utama lainnya
  • Transaksi tetap aman oleh ACID SQLite bahkan saat SIGKILL, dan jika worker crash maka tugas akan otomatis di-claim ulang setelah visibility timeout habis
  • Menyediakan binding untuk 8 bahasa: Python, Node.js, Rust, Go, Ruby, Bun, Elixir, C++, masing-masing dipublikasikan secara terpisah di PyPI, npm, crates.io, Hex, dan RubyGems
  • Diimplementasikan dengan Rust (honker-core + honker-extension)
  • Lisensi Apache 2.0

1 komentar

 
GN⁺ 2026-04-25
Pendapat Hacker News
  • Saya yang membuat ini. Honker menambahkan NOTIFY/LISTEN lintas-proses ke SQLite, sehingga pengiriman event bergaya push dengan latensi satu digit ms bisa dilakukan hanya dengan file SQLite yang sudah ada, tanpa daemon atau broker
    Karena SQLite tidak punya server seperti Postgres, inti pendekatannya adalah memindahkan sumber polling ke stat(2) ringan terhadap file WAL alih-alih melakukan query secara berkala. SQLite juga efisien meski banyak query kecil dikirim (https://www.sqlite.org/np1queryprob.html), jadi ini mungkin bukan peningkatan yang luar biasa besar, tetapi menarik karena cukup memantau WAL dan memanggil fungsi SQLite saja, sehingga tidak terikat bahasa
    Di atas itu, saya juga menambahkan pub/sub ephemeral, durable work queue dengan retry dan dead-letter, serta event stream dengan offset per konsumen. Ketiganya berupa row di dalam file .db aplikasi yang sudah ada, sehingga bisa di-commit secara atomik bersama write bisnis, dan jika rollback terjadi maka keduanya hilang bersama
    Awalnya namanya litenotify/joblite, tetapi setelah iseng membeli honker.dev, saya melihat nama-nama seperti Oban, pg-boss, Huey, RabbitMQ, Celery, Sidekiq juga sama konyolnya, jadi saya pakai nama ini saja. Semoga berguna atau setidaknya lucu, dan peringatan bahwa ini software alpha tetap berlaku

    • Ini tampaknya terutama berguna untuk bahasa yang lebih mudah menangani hanya konkurensi berbasis proses
      Di Java/Go/Clojure/C# dan sejenisnya, karena SQLite tetap single writer, tampaknya lebih sederhana dan rapi jika aplikasi mengelola writer itu sendiri lalu memakai concurrent queue tingkat bahasa untuk mengetahui write mana yang terjadi dan hanya membangunkan thread terkait
      Meski begitu, pemanfaatan WAL dengan cara kreatif seperti ini tetap menarik, dan untuk bahasa seperti Python/JS/TS/Ruby yang umum memakai konkurensi berbasis proses, ini terlihat cukup cocok sebagai mekanisme notify
    • Saya baru sadar bahwa bahkan stat() setiap 1ms ternyata sangat murah
      Di perangkat keras saya, satu panggilan bahkan tidak sampai 1μs, jadi polling setingkat ini memakai CPU bahkan tidak sampai 0,1%
    • Mungkin saya melewatkan sesuatu, tetapi bukankah PRAGMA data_version lebih baik daripada stat(2)?
      https://sqlite.org/pragma.html#pragma_data_version
      Jika memakai C API, ada juga SQLITE_FCNTL_DATA_VERSION yang lebih langsung
      https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntldataversion
    • Cukup keren. Saya juga pernah membuat setengah jadi sesuatu yang mirip
      Saya penasaran apakah ini juga bisa dipakai sebagai stream pesan persisten seperti Kafka ringan. Apakah semantik seperti memutar ulang semua pesan lama+real-time untuk topic tertentu sejak timestamp tertentu juga memungkinkan?
      Mungkin bisa ditiru dengan polling seperti pub/sub, tetapi seperti yang Anda bilang, sepertinya bukan cara yang optimal
    • Mungkin akan lebih baik jika status subscriber juga disimpan
      Jika posisi baca, nama queue, filter, dan sebagainya disimpan, maka saat ada perubahan stat(2), alih-alih membangunkan semua thread subscription agar masing-masing melakukan SELECT N=1, thread polling bisa melakukan Events INNER JOIN Subscribers dan hanya membangunkan subscriber yang benar-benar cocok
  • Terima kasih atas masukannya. Saya sudah mengirim PR yang menerapkan usulan-usulan itu
    https://github.com/russellromney/honker/pulls/1
    Sekarang ini berubah menjadi struktur polling 3 lapis: PRAGMA data_version tiap 1ms, stat tiap 100ms, dan penanganan reconnect saat error

    1. PRAGMA data_version tiap 1ms kini dipakai untuk menggantikan deteksi perubahan size/mtime berbasis stat sebelumnya. Karena ini commit counter milik SQLite sendiri, nilainya monotonic, tidak terpengaruh clock skew, dan menangani WAL truncation maupun rollback dengan benar. Ini query nonblocking sekitar 3µs, dan saya menggantinya bukan karena performa melainkan karena akurasi. Bahkan sedikit lebih lambat. Ternyata risiko truncation juga lebih realistis dari yang saya kira
      Dari pengujian saya, SQLITE_FCNTL_DATA_VERSION di C API tidak bekerja antar-koneksi. Jadi saat ini saya masih menanggung biaya melewati layer VFS, dan secara eksplisit menerima tradeoff itu
    2. Jika query data_version gagal, saya mencoba reconnect dengan asumsi kasus seperti error disk sementara, hiccup NFS, atau koneksi korup, dan sebagai langkah pencegahan saya juga membangunkan subscriber
    3. Tiap 100ms, stat membandingkan (dev, ino) dengan nilai saat startup untuk mendeteksi penggantian file. Ini untuk kasus seperti atomic rename, restore litestream, atau remount volume; data_version mengikuti fd yang terbuka, jadi saat file berubah, ia tetap melihat inode lama dan tidak bisa mendeteksinya
      Berkat itu Honker jadi lebih baik, dan saya juga belajar banyak
  • Sedikit promosi, di PostgreSQL 19 mendatang, LISTEN/NOTIFY telah dioptimalkan agar jauh lebih scalable untuk selective signaling
    Patch ini ditujukan untuk kasus banyak backend yang masing-masing mendengarkan channel berbeda
    https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=282b1cde9

    • Promosinya bagus, dan juga sangat relevan dengan topik ini
  • Saya penasaran apakah perubahan WAL tidak bisa dipantau dengan inotify atau wrapper lintas-platform tanpa polling

    • Lintas-platform jadi rusak. Khususnya di Mac, kadang notifikasinya diam-diam tertelan sehingga sulit dipercaya
      stat ya bekerja di mana-mana
  • Yang membuat ini lebih menarik daripada IPC terpisah adalah bahwa ini di-commit atomik dengan data bisnis
    Pengiriman pesan eksternal selalu punya masalah seperti "notifikasi sudah terkirim tetapi transaksi di-rollback", dan ini cepat sekali jadi berantakan
    Satu hal yang saya penasaran adalah checkpoint WAL. Saat SQLite memangkas WAL kembali ke 0, saya tidak tahu apakah polling stat() menanganinya dengan benar. Rasanya seperti ada celah tempat event bisa terlewat

    • Atomisitas praktis adalah segalanya
      Saya pernah susah payah dengan kombinasi Postgres+SQS karena trigger mengirim enqueue di koneksi lain sebelum commit terlihat. Saya menambahkan retry logic, polling di sisi worker, dan akhirnya memindahkan enqueue ke dalam transaksi; setelah itu, sebenarnya saya hanya sedang membuat ulang apa yang dilakukan Honker dengan lebih banyak bagian bergerak
      Bug jenis "notifikasi sudah terkirim tetapi row belum di-commit" biasanya sunyi dan bergantung timing, jadi benar-benar menyiksa untuk dilacak
    • File WAL tetap ada dan hanya di-truncate, jadi itu sendiri terdeteksi sebagai update
      Namun saya memang belum punya pengujian untuk bagian ini, jadi masih perlu dipastikan. Poin yang bagus, akan saya cek
  • Terima kasih
    Banyak aplikasi kecil berbasis SQLite bermunculan, dan kebanyakan butuh queue dan scheduler
    Saya sendiri sudah mencoba menjalankan beberapa hal, tetapi selalu merasa elegansi solusi keluarga Postgres itu kurang
    Saya akan langsung mencoba ini

    • Ungkapan proliferasi kecil sangat pas untuk menggambarkan kumpulan yang terbentuk dari kebiasaan side project saya
      Jika menemui masalah, akan bagus kalau Anda meninggalkan PR atau issue di repo
  • Di sini saya jadi ingin memakai kqueue/FSEvents, tetapi setahu saya Darwin membuang notifikasi dari proses yang sama
    Jika publisher dan listener berada di proses yang sama, kadang listener sama sekali tidak terbangun, jadi pelacakannya bisa cukup berantakan. Polling stat memang terlihat jelek, tetapi pada akhirnya inilah yang benar-benar bekerja di mana-mana
    Saya juga penasaran apakah saat checkpoint WAL membuat file menyusut lagi, wakeup tetap terjadi, atau poller justru memfilter penurunan size

    • Komentar ini sepenuhnya salah
      Event VNODE kqueue dikirim selama proses tersebut punya izin akses ke file, dan tidak ada filter yang mengecualikan proses yang sama
    • Ini memang perlu diuji langsung
      Saya akan cek dan kabari lagi
  • Sangat keren. Saya penasaran saat diberi beban, apakah bottleneck utamanya ada di throughput write SQLite, atau di layer notifikasi WAL

    • Bottleneck ada di alur write dan claim/ack
      Ini juga sangat bergantung pada journal mode dan synchronous mode
      Notifikasi sangat murah, baik dengan cara stat(2) lama maupun pendekatan PRAGMA baru. Di komentar lain juga disebut stat(2) kira-kira berada di level 1µs
  • Proyek yang bagus. Saya juga sedang membuat sesuatu yang mendorong SQLite jauh melampaui penggunaan biasanya
    Melihat lebih banyak orang mengeksplorasi sampai sejauh mana SQLite sebenarnya bisa dipakai itu menggembirakan

  • Saya penasaran apakah ini juga bisa diintegrasikan saat memakai SQLAlchemy
    Dari tampilannya sekarang, sepertinya ini ingin membuat koneksi DB sendiri