Honker - Ekstensi yang Menghadirkan Postgres NOTIFY/LISTEN ke SQLite
(github.com/russellromney)- 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_versionsetiap 1ms, tanpa perlu polling di level aplikasi atau daemon notify(),stream(), danqueue()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 berbasiscrontab()+ scheduler dengan pemilihan leader - Skema queue menerapkan partial index pada tabel
_honker_live; proses claim dilakukan dengan satuUPDATE … RETURNING, dan ack dengan satuDELETE, 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
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 bahasaDi 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
.dbaplikasi yang sudah ada, sehingga bisa di-commit secara atomik bersama write bisnis, dan jika rollback terjadi maka keduanya hilang bersamaAwalnya 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 berlakuDi 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
Di perangkat keras saya, satu panggilan bahkan tidak sampai 1μs, jadi polling setingkat ini memakai CPU bahkan tidak sampai 0,1%
PRAGMA data_versionlebih baik daripadastat(2)?https://sqlite.org/pragma.html#pragma_data_version
Jika memakai C API, ada juga
SQLITE_FCNTL_DATA_VERSIONyang lebih langsunghttps://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntldataversion
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
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 melakukanEvents INNER JOIN Subscribersdan hanya membangunkan subscriber yang benar-benar cocokTerima 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_versiontiap 1ms,stattiap 100ms, dan penanganan reconnect saat errorPRAGMA data_versiontiap 1ms kini dipakai untuk menggantikan deteksi perubahan size/mtime berbasisstatsebelumnya. 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 kiraDari pengujian saya,
SQLITE_FCNTL_DATA_VERSIONdi C API tidak bekerja antar-koneksi. Jadi saat ini saya masih menanggung biaya melewati layer VFS, dan secara eksplisit menerima tradeoff itudata_versiongagal, saya mencoba reconnect dengan asumsi kasus seperti error disk sementara, hiccup NFS, atau koneksi korup, dan sebagai langkah pencegahan saya juga membangunkan subscriberstatmembandingkan(dev, ino)dengan nilai saat startup untuk mendeteksi penggantian file. Ini untuk kasus seperti atomic rename, restore litestream, atau remount volume;data_versionmengikuti fd yang terbuka, jadi saat file berubah, ia tetap melihat inode lama dan tidak bisa mendeteksinyaBerkat 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
Saya penasaran apakah perubahan WAL tidak bisa dipantau dengan inotify atau wrapper lintas-platform tanpa polling
statya bekerja di mana-manaYang 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 terlewatSaya 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
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
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
statmemang terlihat jelek, tetapi pada akhirnya inilah yang benar-benar bekerja di mana-manaSaya juga penasaran apakah saat checkpoint WAL membuat file menyusut lagi, wakeup tetap terjadi, atau poller justru memfilter penurunan size
Event VNODE kqueue dikirim selama proses tersebut punya izin akses ke file, dan tidak ada filter yang mengecualikan proses yang sama
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
Ini juga sangat bergantung pada journal mode dan synchronous mode
Notifikasi sangat murah, baik dengan cara
stat(2)lama maupun pendekatanPRAGMAbaru. Di komentar lain juga disebutstat(2)kira-kira berada di level 1µsProyek 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