2 पॉइंट द्वारा GN⁺ 6 일 전 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • एक ही SQLite फ़ाइल में durable queue, stream, pub/sub, scheduler को एकीकृत करके Redis·Celery जैसे अलग broker के बिना asynchronous कार्यों को प्रोसेस किया जा सकता है
  • PRAGMA data_version को 1ms अंतराल पर poll करके processes के बीच single-digit millisecond प्रतिक्रिया गति हासिल की जाती है, application-level polling या daemon की ज़रूरत नहीं
  • notify(), stream(), queue() सभी caller के transaction के भीतर लिखे जाते हैं, इसलिए business writes के साथ commit होते हैं या साथ में rollback होते हैं, जिससे dual-write समस्या कम होती है
  • job queue में retry, priority, delayed execution, dead-letter, scheduler, named lock, rate limiting शामिल हैं, और stream at-least-once delivery को support करता है जिसमें हर consumer का offset स्टोर होता है
  • SQLite को primary storage के रूप में इस्तेमाल करने वाले environments में application और asynchronous processing को एक database file में समेटकर operational complexity कम की जा सकती है
  • तीन मुख्य primitives उपलब्ध हैं
    • queue(): at-least-once job queue — retry, priority, delayed jobs, dead-letter, visibility timeout
    • stream(): durable pub/sub — per-consumer offset tracking, at-least-once replay
    • notify(): ephemeral pub/sub — fire-and-forget, history replay नहीं
  • Huey-शैली के @queue.task() decorator से functions को queue jobs में बदला जा सकता है, crontab() आधारित periodic jobs + leader election scheduler का support
  • queue schema में _honker_live table पर partial index लागू है, claim एक UPDATE … RETURNING से और ack एक DELETE से होता है, इसलिए dead rows की संख्या से स्वतंत्र स्थिर performance मिलती है
  • SQLite loadable extension (libhonker_ext) के रूप में सभी SQLite 3.9+ clients से एक ही table को access किया जा सकता है — Python worker दूसरे languages से push किए गए jobs को claim कर सकता है
  • SQLAlchemy, Django, Drizzle, Kysely, sqlx, GORM, ActiveRecord, Ecto सहित प्रमुख ORM के साथ integration गाइड उपलब्ध हैं
  • SIGKILL के दौरान के transactions भी SQLite ACID के कारण सुरक्षित रहते हैं, और worker crash होने पर visibility timeout समाप्त होने के बाद अपने-आप reclaim हो जाते हैं
  • Python, Node.js, Rust, Go, Ruby, Bun, Elixir, C++ 8 language bindings उपलब्ध हैं, और प्रत्येक को PyPI·npm·crates.io·Hex·RubyGems पर अलग से प्रकाशित किया गया है
  • Rust में implemented (honker-core + honker-extension)
  • Apache 2.0 license

1 टिप्पणियां

 
GN⁺ 6 일 전
Hacker News टिप्पणियाँ
  • मैंने यह खुद बनाया है। Honker SQLite में cross-process NOTIFY/LISTEN जोड़ता है, ताकि daemon या broker के बिना सिर्फ मौजूदा SQLite फ़ाइल के सहारे single-digit ms latency के साथ push-style event delivery मिल सके।
    SQLite में Postgres जैसा server नहीं होता, इसलिए तय अंतराल पर query करने के बजाय polling source को WAL फ़ाइल पर हल्के stat(2) पर शिफ्ट करना इसका मुख्य विचार है। SQLite बहुत सारी छोटी queries चलाने में भी efficient है (https://www.sqlite.org/np1queryprob.html), इसलिए इसे बहुत बड़ा upgrade कहना मुश्किल है, लेकिन WAL को मॉनिटर करके और सिर्फ SQLite function कॉल करके यह language-agnostic बन जाता है, यही दिलचस्प बात है।
    इसके ऊपर ephemeral pub/sub, retry और dead-letter के साथ durable work queue, और per-consumer offset वाले event stream भी जोड़े हैं। ये तीनों मौजूदा app की .db फ़ाइल के अंदर rows के रूप में हैं, इसलिए इन्हें business writes के साथ atomically commit किया जा सकता है, और rollback होने पर दोनों साथ में गायब हो जाते हैं।
    पहले इसका नाम litenotify/joblite था, लेकिन मैंने मज़ाक में honker.dev खरीद लिया था, फिर देखा कि Oban, pg-boss, Huey, RabbitMQ, Celery, Sidekiq जैसे नाम भी वैसे ही मज़ेदार हैं, तो यही नाम रख दिया। उम्मीद है कि यह उपयोगी होगा, या कम से कम मज़ेदार तो होगा, और alpha software वाली चेतावनी अभी भी लागू है।

    • यह ज़्यादातर उन भाषाओं के लिए उपयोगी लगता है जहाँ process-based concurrency संभालना आसान होता है।
      Java/Go/Clojure/C# जैसी भाषाओं में SQLite वैसे भी single writer है, इसलिए application खुद उस writer को मैनेज करे और language-level concurrent queue के जरिए यह जाने कि कौन-सी write हुई है और सिर्फ संबंधित threads को जगाए, यह ज़्यादा simple और clean लगता है।
      फिर भी WAL का इस तरह creative इस्तेमाल काफ़ी मज़ेदार है, और Python/JS/TS/Ruby जैसी भाषाओं में जहाँ process-based concurrency आम है, वहाँ notify mechanism के रूप में यह काफ़ी अच्छा fit लगता है।
    • अब जाकर पता चला कि हर 1ms में stat() करना सोच से भी ज़्यादा सस्ता है।
      मेरे hardware पर इसमें प्रति call 1μs से भी कम लगता है, इसलिए इस स्तर की polling से CPU usage 0.1% भी नहीं होता।
    • हो सकता है मैं कुछ मिस कर रहा हूँ, लेकिन stat(2) से PRAGMA data_version बेहतर नहीं होगा?
      https://sqlite.org/pragma.html#pragma_data_version
      अगर C API इस्तेमाल कर रहे हैं तो और भी direct SQLITE_FCNTL_DATA_VERSION भी है।
      https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntldataversion
    • काफ़ी शानदार है। मैंने भी कुछ ऐसा आधा-अधूरा बनाया था।
      जानना चाहता हूँ कि क्या इसे हल्के Kafka की तरह persistent message stream के रूप में भी इस्तेमाल किया जा सकता है। क्या किसी खास topic के लिए किसी timestamp से past+live messages पूरे replay करने जैसी semantics भी संभव हैं?
      pub/sub की तरह polling से इसका approximation किया जा सकता है, लेकिन जैसा आपने कहा, शायद वह optimal नहीं होगा।
    • अगर subscriber state भी साथ स्टोर की जाए तो यह और बेहतर हो सकता है।
      read position, queue name, filter जैसी चीज़ें स्टोर कर दें, तो stat(2) बदलने पर हर subscription thread को जगाकर उनसे N=1 SELECT करवाने के बजाय polling thread Events INNER JOIN Subscribers चला सकता है और सिर्फ वास्तव में match होने वाले subscribers को जगा सकता है।
  • फ़ीडबैक के लिए धन्यवाद। सुझावों को शामिल करते हुए मैंने PR भेज दिया है।
    https://github.com/russellromney/honker/pulls/1
    अब यह 3-layer polling structure में बदल गया है: हर 1ms पर PRAGMA data_version, हर 100ms पर stat, और error होने पर reconnect handling।

    1. हर 1ms पर PRAGMA data_version इस्तेमाल करके पुराने stat-आधारित size/mtime change detection को replace किया है। यह खुद SQLite का commit counter है, इसलिए monotonic है, clock skew से प्रभावित नहीं होता, और WAL truncation या rollback को भी सही तरह संभालता है। यह लगभग 3µs का nonblocking query है, और मैंने इसे performance के लिए नहीं बल्कि correctness के लिए बदला है। उल्टा, यह थोड़ा धीमा है। truncation का risk भी उम्मीद से ज़्यादा real निकला।
      टेस्ट करने पर C API का SQLITE_FCNTL_DATA_VERSION connections के बीच काम नहीं कर रहा था। इसलिए अभी भी VFS layer से होकर जाने की cost देनी पड़ रही है, और इस tradeoff को मैंने स्पष्ट रूप से स्वीकार किया है।
    2. अगर data_version query fail होती है, तो disk temporary error, NFS hiccup, connection corruption जैसी स्थितियाँ मानकर reconnect की कोशिश की जाती है, और precaution के तौर पर subscriber को भी जगा दिया जाता है।
    3. हर 100ms पर stat से (dev, ino) की तुलना startup के समय के मान से करके file replacement पकड़ा जाता है। जैसे atomic rename, litestream restore, volume remount जैसी स्थितियाँ। data_version खुली हुई fd को follow करता है, इसलिए फ़ाइल बदल जाने पर भी वह original inode देखता रहता है और इसे पकड़ नहीं पाता।
      इससे Honker बेहतर हुआ है और मैंने भी बहुत कुछ सीखा है।
  • हल्का-सा प्रचार करूँ तो, आने वाले PostgreSQL 19 में LISTEN/NOTIFY को selective signaling में कहीं बेहतर scale करने के लिए optimize किया गया है।
    यह patch उन स्थितियों के लिए है जहाँ बहुत सारे backend अलग-अलग channels को listen कर रहे होते हैं।
    https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=282b1cde9

    • अच्छा प्रचार था, और विषय से भी बहुत अच्छे से जुड़ा हुआ है।
  • polling के बिना inotify या cross-platform wrapper से WAL changes को monitor क्यों न किया जाए?

    • cross-platform हिस्सा टूट जाता है। खासकर Mac पर यह कभी-कभी चुपचाप swallow हो जाता है, इसलिए इस पर भरोसा करना मुश्किल है।
      stat बस हर जगह काम करता है।
  • अलग IPC की तुलना में इसकी सबसे आकर्षक बात यह है कि यह business data के साथ atomic commit होता है।
    बाहरी message delivery में हमेशा "notification तो चला गया लेकिन transaction rollback हो गया" जैसी समस्या रहती है, और यह जल्दी messy हो जाता है।
    एक सवाल WAL checkpoint को लेकर है। जब SQLite WAL को वापस 0 पर truncate करता है, तो क्या stat() polling इसे सही तरीके से संभालती है? लगता है जैसे events miss होने की कोई window हो सकती है।

    • मेरे हिसाब से atomicity ही लगभग सब कुछ है।
      पहले Postgres+SQS के साथ मुझे बहुत परेशानी हुई थी, क्योंकि trigger enqueue को दूसरी connection में commit visible होने से पहले भेज देता था। फिर retry logic जोड़ा, worker side polling भी जोड़ी, और अंत में enqueue को transaction के भीतर लाना पड़ा। उसके बाद असल में वही चीज़ ज़्यादा moving parts के साथ दोबारा बन रही थी जो Honker कर रहा है।
      "notification चला गया लेकिन row अभी commit नहीं हुई" जैसे bugs आमतौर पर चुपचाप और timing-dependent होते हैं, इसलिए इन्हें trace करना बेहद दर्दनाक होता है।
    • WAL फ़ाइल बनी रहती है और सिर्फ truncate होती है, इसलिए उसे update के रूप में पकड़ा जाना चाहिए।
      हालाँकि इस हिस्से के लिए अभी test नहीं है, इसलिए और verify करना होगा। अच्छा point है, मैं इसे देखूँगा।
  • धन्यवाद।
    SQLite-आधारित छोटे apps अब बहुत बढ़ गए हैं, और उनमें से ज़्यादातर को queue और scheduler की ज़रूरत होती है।
    मैंने खुद कुछ solutions चलाए हैं, लेकिन हमेशा Postgres-आधारित solutions की elegance की कमी महसूस हुई।
    इसे मैं तुरंत आज़माने वाला हूँ।

    • छोटी proliferation वाली अभिव्यक्ति मेरे side projects की आदत से बने इस झुंड को बताने के लिए बिलकुल सही है।
      अगर कोई समस्या मिले तो repo में PR या issue छोड़ना अच्छा रहेगा।
  • यहाँ kqueue/FSEvents इस्तेमाल करने का मन तो करता है, लेकिन मेरी समझ से Darwin उसी process की notifications drop कर देता है।
    अगर publisher और listener एक ही process में हों, तो listener कभी-कभी जागता ही नहीं, और इसे trace करना काफ़ी messy हो जाता है। stat polling भले ही बदसूरत लगे, लेकिन आख़िर में वही तरीका है जो हर जगह सचमुच काम करता है।
    WAL checkpoint के समय जब फ़ाइल फिर छोटी हो जाती है, तब wakeup होता है या poller size decrease को filter कर देता है, यह भी जानना चाहूँगा।

    • यह टिप्पणी पूरी तरह गलत है।
      kqueue के VNODE events तब deliver होते हैं जब process के पास फ़ाइल तक पहुँच की अनुमति हो, same-process होने के आधार पर कोई filtering नहीं होती।
    • इसके लिए वास्तव में test की ज़रूरत है।
      मैं जाँचकर फिर बताऊँगा।
  • बहुत शानदार। load आने पर bottleneck मुख्य रूप से SQLite write throughput होता है या WAL notification layer, यह जानना चाहूँगा।

    • bottleneck writes और claim/ack flow में है।
      यह journal mode और synchronous mode पर भी काफी निर्भर करता है।
      notification चाहे पुराना stat(2) तरीका हो या नया PRAGMA-आधारित, बहुत सस्ता है। दूसरी टिप्पणियों में भी कहा गया कि stat(2) लगभग 1µs के स्तर का है।
  • अच्छा project है। मैं भी कुछ ऐसा बना रहा हूँ जो SQLite को उसके सामान्य उपयोग से कहीं आगे तक push करता है।
    यह देखना उत्साहजनक है कि ज़्यादा लोग खोज रहे हैं कि SQLite वास्तव में कहाँ तक जा सकता है।

  • जानना चाहूँगा कि क्या SQLAlchemy इस्तेमाल करने पर भी इसका integration संभव है।
    अभी के रूप से तो लगता है कि यह अपना DB connection खुद बनाना चाहता है।