- Melalui ekstensi SQLite dan berbagai binding bahasa, ini memungkinkan pub/sub durable, antrean kerja, dan stream event ditangani bersama dalam file
.db yang sama tanpa polling klien atau daemon·broker terpisah
notify(), stream(), dan queue() semuanya dicatat di dalam transaksi milik pemanggil, lalu di-commit bersama penulisan bisnis atau di-rollback bersama, sehingga mengurangi masalah dual-write
- Mekanisme membangunkan antarproses berjalan dengan memeriksa
PRAGMA **data_version** setiap 1ms, ditujukan untuk mencapai latensi tingkat milidetik satu digit dan biaya query yang sangat kecil
- Antrean kerja 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
- Untuk lingkungan yang menggunakan SQLite sebagai penyimpanan utama, ini adalah konfigurasi yang menyatukan aplikasi dan pemrosesan asinkron dalam satu file database untuk menurunkan kompleksitas operasional, dan API-nya masih berstatus Experimental
Ikhtisar
- Dengan ekstensi SQLite dan berbagai binding bahasa, ini menambahkan perilaku
NOTIFY/LISTEN ala Postgres ke SQLite, serta memungkinkan pub/sub durable, antrean kerja, dan stream event ditangani dalam file .db yang sama tanpa polling klien atau daemon·broker terpisah
- Berdasarkan layout on-disk yang didefinisikan sekali di Rust, binding Python, Node, Bun, Ruby, Go, Elixir, dan C++ disusun sebagai pembungkus tipis di atas loadable extension yang sama
- Pendekatan membaca database setiap 1ms menggantikan polling di level aplikasi, dan biaya kueri
PRAGMA data_version berada di tingkat mikrodetik satu digit sementara pengiriman notifikasi antarproses berada di tingkat milidetik satu digit
- Jika SQLite digunakan sebagai penyimpanan utama, penulisan bisnis dan pemuatan ke antrean dapat di-commit atau di-rollback dalam transaksi yang sama, sehingga mengurangi kebutuhan mengoperasikan datastore terpisah dan masalah dual-write
- API masih berstatus Experimental dan dapat berubah
- Dijelaskan secara tegas bahwa bila Anda sudah menjalankan Postgres, menggunakan
pg_notify, pg-boss, atau Oban akan lebih sesuai
Fitur utama
- Menyediakan notify/listen antarproses, antrean kerja dengan retry, prioritas, eksekusi tertunda, dan tabel dead-letter, serta durable stream dengan offset per konsumen dalam satu file
.db
- Semua operasi send dapat digabungkan secara atomik dengan penulisan bisnis sehingga di-commit bersama atau di-rollback bersama
- Waktu respons lintas proses berada di tingkat milidetik satu digit, dan juga mencakup handler timeout, retry berbasis exponential backoff, delayed jobs, task expiration, named lock, dan rate limiting
- Juga mendukung scheduler berbasis leader election, periodic task bergaya crontab, serta penyimpanan hasil task secara opt-in
enqueue mengembalikan id, worker menyimpan nilai balik, dan pemanggil dapat menunggu hasil dengan queue.wait_result(id)
- Disediakan dalam bentuk SQLite loadable extension sehingga klien SQLite apa pun dapat membaca tabel yang sama
- Juga berjalan di dalam koneksi SQLite yang dimiliki ORM, dan panduan ORM membahas integrasi dengan SQLAlchemy, SQLModel, Django, Drizzle, Kysely, sqlx, GORM, ActiveRecord, dan Ecto
- Sebaliknya, cakupan yang sengaja tidak dimasukkan juga dijelaskan dengan jelas
- task pipeline, chain, group, dan chord tidak didukung
- replikasi multi-writer tidak didukung
- orkestrasi workflow berbasis DAG tidak didukung
Mulai cepat
-
Queue Python
- Buka database dengan
honker.open("app.db") dan dapatkan queue seperti db.queue("emails") untuk mengantrekan dan mengonsumsi pekerjaan
- Di dalam blok
with db.transaction() as tx:, jika INSERT pesanan dan emails.enqueue(..., tx=tx) dijalankan bersama, penulisan pesanan dan pengantrean tugas email akan terikat dalam transaksi yang sama
- Worker mengambil pekerjaan satu per satu dalam bentuk
async for job in emails.claim("worker-1"): lalu memprosesnya dengan job.ack() saat berhasil, atau job.retry(delay_s=60, error=str(e)) saat gagal
claim() adalah iterator asinkron yang secara internal memanggil claim_batch(worker_id, 1) pada setiap iterasi
- Ia akan bangun pada commit apa pun di database, dan hanya kembali ke paranoia poll 5 detik jika commit watcher tidak bisa berfungsi
- Untuk pekerjaan batch, penggunaan dipisahkan agar langsung memakai
claim_batch(worker_id, n) dan queue.ack_batch(ids, worker_id), dengan visibility default 300 detik
-
Task Python
- Dengan dekorator
@emails.task(retries=3, timeout_s=30), pemanggilan fungsi akan langsung diubah menjadi enqueue ke queue dan mengembalikan TaskResult
- Dari sisi pemanggil, ini bisa digunakan seperti
send_email("alice@example.com", "Hi"), lalu menunggu hasil eksekusi worker dengan r.get(timeout=10)
- Worker bisa dijalankan sebagai proses terpisah atau in-process, misalnya
python -m honker worker myapp.tasks:db --queue=emails --concurrency=4
- Nama otomatis adalah
{module}.{qualname}, dan di lingkungan produksi disarankan memakai nama eksplisit seperti @emails.task(name="...") agar job pending tidak menjadi yatim karena perubahan nama
- Periodic task menggunakan bentuk
@emails.periodic_task(crontab("0 3 * * *"))
- Contoh lebih lengkap ada di
packages/honker/examples/tasks.py
-
Stream Python
db.stream("user-events") menyediakan pub/sub yang durable, dan UPDATE bisnis serta stream.publish(..., tx=tx) bisa dijalankan dalam transaksi yang sama
- Jika berlangganan dengan
async for event in stream.subscribe(consumer="dashboard"): maka baris setelah offset yang tersimpan akan diputar ulang terlebih dahulu, lalu setelah itu beralih ke pengiriman real-time berbasis commit
- Offset untuk tiap named consumer disimpan di tabel
_honker_stream_consumers
- Penyimpanan offset otomatis secara default hanya dilakukan setiap 1000 event atau sekali per 1 detik, agar slot single-writer tidak terlalu sering dihantam bahkan pada throughput tinggi
- Ini bisa diatur dengan
save_every_n= dan save_every_s=; jika keduanya diatur ke 0, penyimpanan otomatis dimatikan dan stream.save_offset(consumer, offset, tx=tx) bisa dipanggil secara langsung
- Jika terjadi crash, event in-flight setelah offset terakhir yang sudah di-flush akan dikirim ulang, mengikuti model at-least-once
-
Notify Python
- Berlangganan pub/sub ephemeral dengan
async for n in db.listen("orders"): dan kirim notifikasi di dalam transaksi dengan tx.notify("orders", {"id": 42})
- Listener saat ini mulai dari titik
MAX(id), sehingga riwayat lama tidak diputar ulang
- Jika membutuhkan replay yang durable, gunakan
db.stream()
- Tabel notifications tidak dibersihkan secara otomatis, sehingga pada pekerjaan terjadwal perlu memanggil
db.prune_notifications(older_than_s=…, max_keep=…)
- Payload task harus valid sebagai JSON, dan writer Python serta reader Node dapat berbagi channel yang sama
-
Node.js
- Di binding Node juga tersedia pola fungsi yang sama, seperti
open('app.db'), db.transaction(), tx.notify(...), dan db.listen('orders')
- Penulisan bisnis dan notify terikat pada commit yang sama, dan listen akan bangun pada commit apa pun di database lalu memfilter berdasarkan channel
-
Ekstensi SQLite
- Setelah
.load ./libhonker_ext, inisialisasi dengan SELECT honker_bootstrap();, lalu fitur queue, lock, rate limit, scheduler, stream, dan penyimpanan hasil bisa digunakan hanya dengan fungsi SQL
- Tersedia fungsi seperti
honker_claim_batch, honker_ack_batch, honker_sweep_expired, honker_lock_acquire, honker_rate_limit_try, honker_scheduler_tick, honker_stream_publish, honker_stream_read_since, dan honker_result_save
- Binding Python dan extension berbagi
_honker_live, _honker_dead, dan _honker_notifications, sehingga pekerjaan yang dimasukkan bahasa lain lewat extension bisa diambil oleh worker Python
- Kompatibilitas skema dikunci di
tests/test_extension_interop.py
Desain
- Repositori ini mencakup loadable extension SQLite
honker bersama binding Python, Node, Rust, Go, Ruby, Bun, dan Elixir
- Ditujukan untuk aplikasi yang menggunakan SQLite sebagai penyimpanan utama, dengan fokus memindahkan package logic ke SQLite extension agar bisa dipakai dengan cara yang serupa di berbagai bahasa dan framework
- Ada tiga primitive inti
notify() sebagai pub/sub ephemeral
stream() sebagai pub/sub durable dengan offset per konsumen
queue() sebagai work queue at-least-once
- Ketiga primitive ini semuanya dicatat sebagai INSERT di dalam transaksi pemanggil, sehingga pengiriman pekerjaan dan penulisan bisnis akan di-commit bersama atau di-rollback bersama
- Tujuannya adalah mewujudkan perilaku mirip
NOTIFY/LISTEN tanpa polling di level aplikasi agar waktu respons tetap cepat
- Jika file SQLite yang ada dipakai apa adanya, semua commit di database akan membangunkan worker, dan sebagian besar trigger mungkin hanya akan membaca pesan atau queue lalu berakhir dengan hasil kosong tanpa pemrosesan nyata
- Overtriggering ini adalah tradeoff yang disengaja, dipilih demi perilaku yang lebih mendekati push dan waktu respons yang cepat
Default WAL yang direkomendasikan
- Language binding secara default menggunakan
journal_mode = WAL, yang menyediakan struktur reader konkuren dan single writer, batching fsync yang efisien, serta pengaturan wal_autocheckpoint = 10000
- Mode lain seperti DELETE, TRUNCATE, dan MEMORY juga berfungsi, dan deteksi commit dilakukan berdasarkan
PRAGMA data_version yang meningkat di semua journal mode
- Yang hilang di mode non-WAL hanyalah karakteristik write saat read bersamaan; correctness dan wake antarproses sendiri tidak bergantung pada WAL
- Seluruh sistem terdiri dari satu file
.db, dan saat WAL diaktifkan, sidecar .db-wal dan .db-shm dapat ditambahkan
- Claim ditangani dengan satu kali
UPDATE … RETURNING melalui partial index, dan ack dengan satu kali DELETE
- Pada journal mode apa pun, hanya ada satu writer pada satu waktu, dan keuntungan concurrent reader disediakan oleh WAL
PRAGMA data_version meningkat pada setiap commit dan checkpoint, sehingga situasi seperti WAL truncation, pembuatan dan penghapusan file journal, serta reuse dengan ukuran yang sama juga ditangani dengan benar
- SQLite tidak memiliki wire protocol, jadi server push tidak dimungkinkan; konsumen harus memulai pembacaan sendiri
- Sinyal wake adalah kenaikan counter
- Setelah itu pengambilan aktual dilakukan dengan
SELECT
- Karena transaksi itu murah, jobs, events, dan notifications dicatat seperti pola outbox di dalam blok
with db.transaction() yang sudah dibuka oleh pemanggil
- Alih-alih memakai cara melihat ukuran file WAL·mtime lewat
stat(2) atau kernel watcher seperti FSEvents·inotify·kqueue, digunakan PRAGMA data_version
data_version adalah counter monotonic yang dinaikkan SQLite untuk commit dari koneksi mana pun
- Menangani dengan benar WAL truncation, clock skew, dan transaksi yang di-rollback
- Kernel watcher di macOS bisa melewatkan write dari proses yang sama, dan
stat(2) berbasis (size, mtime) bisa melewatkan commit ketika WAL di-truncate lalu membesar lagi ke ukuran yang sama
- Bekerja sama di Linux, macOS, dan Windows, dan biaya CPU pada resolusi tingkat 1ms sangat kecil
- Disebutkan bahwa biaya per kueri sekitar 3.5µs, atau total sekitar 3.5ms/detik pada 1kHz
- Model lock SQLite mengasumsikan single machine, single writer, dan jika dua server menulis ke
.db yang sama di atas NFS, akan terjadi korupsi
- Untuk kasus seperti ini diperlukan sharding per file atau beralih ke Postgres
Arsitektur
-
Jalur wake
- Untuk setiap
Database, ada satu PRAGMA poll thread yang memeriksa data_version setiap 1ms
- Saat counter berubah, tick di-fan-out ke bounded channel milik tiap subscriber
- Tiap subscriber menjalankan
SELECT … WHERE id > last_seen yang memanfaatkan partial index dan mengembalikan baris baru, lalu menunggu lagi
- Bahkan jika ada 100 subscriber, cukup satu poll thread
- Listener idle sama sekali tidak menjalankan kueri SQL
- Biaya idle hanyalah satu kueri
PRAGMA data_version per database setiap 1ms, dan jumlah listener bisa bertambah nyaris gratis berkat struktur yang memakai pembacaan counter SQLite
SharedWalWatcher di honker-core memiliki poll thread dan melakukan fan-out ke channel bounded SyncSender<()> per subscriber id
- Setiap pemanggilan
db.wal_events() mendaftarkan subscriber, dan handle yang dikembalikan akan otomatis unsubscribe saat Drop
- Saat listener di-drop,
rx.recv() -> Err terjadi di bridge thread dan thread itu akan membersihkan diri lalu berhenti
-
Skema queue
_honker_live berisi baris dengan status pending dan processing
- Partial index berbentuk
(queue, priority DESC, run_at, id) WHERE state IN ('pending','processing')
- Claim dilakukan dengan satu kali
UPDATE … RETURNING melalui indeks ini
- Ack adalah satu kali
DELETE
- Baris yang melampaui batas retry dipindahkan ke
_honker_dead dan tidak dipindai lagi di jalur claim
- Berkat partial index pada state, hot path claim dibatasi oleh ukuran working set, bukan ukuran seluruh history
- Bahkan jika ada 100k dead row, kecepatan claim tetap sama seperti queue tanpa dead row
-
Iterator claim
async for job in q.claim(id) berulang kali memanggil claim_batch(id, 1) dan mengeluarkan pekerjaan satu per satu
Job.ack() adalah satu DELETE di dalam transaksinya sendiri, dan nilai kembali adalah True jika claim masih valid, atau False jika visibility window telah lewat dan worker lain telah mengambil ulang
- Akan terbangun pada commit database dari proses mana pun, dan paranoia poll 5 detik adalah satu-satunya fallback
- Untuk pekerjaan batch harus langsung memakai
claim_batch(worker_id, n) dan queue.ack_batch(ids, worker_id)
- Library tidak menyembunyikan batch di balik iterator, agar biaya transaksi dan perilaku visibility at-most-once bisa ditangani dengan lebih jelas
-
Penggabungan transaksi
notify() adalah fungsi skalar SQL yang didaftarkan pada writer connection
- Fungsi ini melakukan INSERT ke
_honker_notifications di bawah transaksi terbuka milik pemanggil
queue.enqueue(…, tx=tx) dan stream.publish(…, tx=tx) juga bekerja dengan cara yang sama
- Jika terjadi rollback, job, event, dan notification juga ikut hilang
- Ini adalah pola transactional outbox bawaan, yang menangani business write dan enqueue side effect bersama tanpa instalasi library terpisah
- Tidak ada dispatch table atau dispatcher process terpisah; row side effect itu sendiri menjadi baris yang di-commit, dan proses mana pun yang memantau database dapat mengambilnya dalam sekitar 1ms
-
Over-triggering yang lebih cepat daripada polling
- Perubahan
data_version membangunkan semua subscriber dari Database tersebut, bukan hanya membangunkan secara selektif channel yang di-commit
- Biaya jika terbangun secara keliru hanyalah satu kali
SELECT terindeks pada tingkat mikrodetik
- Sebaliknya, jika target yang seharusnya dibangunkan terlewat, itu akan menjadi bug correctness yang diam-diam
- Filtering channel ditangani di jalur
SELECT, bukan pada tahap trigger notification
- SQLite juga dapat menangani secara efisien pola menjalankan banyak kueri kecil
-
Kebijakan retensi
- Pekerjaan queue tetap ada sampai di-ack, dan jika melampaui batas retry akan dipindahkan ke
_honker_dead
- Event stream dipertahankan, dan tiap named consumer melacak offset-nya sendiri
- Notify bersifat fire-and-forget dan tidak ada pembersihan otomatis
- Kebijakan retensi dipilih pemanggil per primitive, dan
db.prune_notifications(older_than_s=…, max_keep=…) harus dipanggil langsung
- Pendekatannya adalah membuat kebijakan retensi terlihat di kode pemanggil, bukan menyembunyikannya di balik default library
Pemulihan crash
- rollback akan menghapus jobs, events, dan notifications bersama dengan penulisan bisnis sesuai karakteristik ACID SQLite
- tetap aman meski
SIGKILL terjadi di tengah transaksi, dan saat open berikutnya atomic commit rollback SQLite tidak meninggalkan stale state
- penggunaan WAL atau rollback journal mengikuti journal mode
- verifikasi dilakukan di
tests/test_crash_recovery.py, dengan menghentikan subprocess sebelum COMMIT lalu memeriksa PRAGMA integrity_check == 'ok' dan alur notify yang baru
- jika worker mati saat memproses pekerjaan, worker lain akan melakukan claim ulang setelah
visibility_timeout_s berlalu
- nilai default adalah 300 detik
attempts akan bertambah
- jika melebihi default
max_attempts sebanyak 3 kali, baris akan dipindahkan ke _honker_dead
- listener yang sedang offline saat prune berlangsung akan melewatkan event yang sudah dibersihkan; jika membutuhkan replay yang durable, gunakan
db.stream() yang menyimpan offset per konsumen
Integrasi framework web
- plugin framework tidak disediakan; pendekatannya adalah menghubungkan lewat beberapa baris glue code karena API-nya kecil
- pada FastAPI, disediakan contoh untuk menjalankan worker loop saat startup, lalu melakukan business write dan queue enqueue bersama-sama di dalam transaksi saat menangani request
- endpoint SSE dapat dibangun di atas
db.listen(channel) atau db.stream(name).subscribe(...) dalam bentuk async def stream(...): yield f"data: ...\n\n" hanya dalam sekitar 30 baris
- pada Django dan Flask, direkomendasikan menjalankan worker sebagai proses CLI terpisah dengan pola seperti Celery atau RQ
Penggunaan ORM
- load
libhonker_ext pada koneksi ORM, lalu panggil fungsi SQL di dalam transaksi milik ORM agar enqueue di-commit secara atomik bersama business write
- pada contoh SQLAlchemy, extension dimuat di event connect dan
SELECT honker_bootstrap() dijalankan, lalu di dalam transaksi s.begin() model INSERT dan SELECT honker_enqueue(...) dipanggil bersama
- worker berjalan sebagai proses terpisah yang menggunakan
honker.open("app.db"), dan commit watcher akan terbangun oleh commit dari koneksi mana pun ke file yang sama
- panduan Using with an ORM mencakup integrasi Django, SQLModel, Drizzle, Kysely, sqlx, GORM, ActiveRecord, Ecto, pola wrapper
TypedQueue[T] untuk SQLModel/Pydantic, serta caveat terkait Prisma
Performa
- disebut mampu memproses ribuan pesan per detik di laptop modern
- latensi wake antarproses dibatasi oleh poll cadence 1ms, dengan median sekitar 1~2ms pada M-series
- pengukuran pada hardware nyata dapat dilakukan dengan
bench/wake_latency_bench.py dan bench/real_bench.py
Konfigurasi pengembangan
-
Tata letak repositori
honker-core/: rlib Rust yang dibagikan semua binding, disertakan in-tree, dan juga didistribusikan ke crates.io
honker-extension/: cdylib untuk SQLite loadable extension, disertakan in-tree, dan juga didistribusikan ke crates.io
packages/honker/: paket Python yang mencakup PyO3 cdylib serta Queue, Stream, Outbox, Scheduler
packages/honker-node/: binding Node.js dan merupakan git submodule
packages/honker-rs/: wrapper ergonomis untuk Rust dan merupakan git submodule
packages/honker-go/: binding Go dan merupakan git submodule
packages/honker-ruby/: binding Ruby dan merupakan git submodule
packages/honker-bun/: binding Bun dan merupakan git submodule
packages/honker-ex/: binding Elixir dan merupakan git submodule
packages/honker-cpp/: binding C++ dan merupakan git submodule
tests/: direktori integration test lintas paket
bench/: direktori benchmark
site/: situs honker.dev, berbasis Astro, dan merupakan git submodule
- tiap repositori binding didistribusikan secara terpisah ke PyPI, npm, crates.io, Hex, RubyGems, dan lainnya, sementara fondasi bersama
honker-core dan honker-extension disertakan langsung di repositori ini
- saat clone, diperlukan
git clone --recursive atau git submodule update --init --recursive
Pengujian dan cakupan
make test secara default menjalankan tes Rust, Python, dan Node, dengan jalur cepat memakan waktu sekitar 10 detik
make test-python-slow mencakup soak test dan tes cron real-time, memakan waktu sekitar 2 menit
make test-all menjalankan seluruh tes termasuk mark yang lambat
make build menjalankan PyO3 maturin develop dan build loadable extension
- benchmark dapat dijalankan dengan
python bench/wake_latency_bench.py --samples 500, python bench/real_bench.py --workers 4 --enqueuers 2 --seconds 15, python bench/ext_bench.py
- untuk memasang alat coverage gunakan
make install-coverage-deps, yang menginstal coverage.py dan cargo-llvm-cov
make coverage menghasilkan dua laporan HTML di coverage/, dan make coverage-python membuat laporan untuk jalur Python, sementara make coverage-rust membuat laporan berdasarkan Rust unit test honker-core
- coverage Python disebut sekitar 92% untuk
packages/honker/
- coverage Rust hanya mencerminkan
cargo test; berbagai jalur di honker_ops.rs hanya dieksekusi oleh test suite Python sehingga tidak tertangkap dalam laporan Rust
- penggabungan cross-language coverage melalui penggabungan data profil LLVM lintas batas PyO3 sulit dilakukan dan masih ditunda
Lisensi
- menggunakan lisensi Apache 2.0
- detail lebih lanjut ada di LICENSE
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