6 पॉइंट द्वारा GN⁺ 2023-09-25 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • अलग क्यू सिस्टम लाने से पहले, अगर पहले से इस्तेमाल हो रहे Postgres से ही background job queue बनाई जा सकती है, तो ऑपरेशन की सरलता और टीम की परिचितता का बड़ा फायदा मिल सकता है
  • Postgres 9.5 के बाद से NOTIFY/LISTEN और FOR UPDATE SKIP LOCKED नए कामों की सूचना और workers के बीच duplicate processing रोकने दोनों समस्याओं को हल करते हैं
  • Redis, Kafka, RabbitMQ, Amazon SQS मजबूत विकल्प हैं, लेकिन नई dependency development, testing और operations environments में failure modes और learning cost जोड़ती है
  • Postgres queue भी सर्वगुण-संपन्न नहीं है; Redis की तुलना में push/pop धीमा हो सकता है, और अलग queue database या server, अधिक बार VACUUM जैसी operational costs आ सकती हैं
  • background job code को queue backend से बंधा न रहने दें; परिचित तकनीक जरूरतें पूरी न कर पाए तभी दूसरी queue technologies पर विचार करना अधिक सुरक्षित है

Postgres queue कैसे काम करती है

  • Postgres queue तकनीक दो तत्वों से मिलकर बनी है
    • नए कामों की सूचना देने और प्राप्त करने वाला pub/sub
    • कई workers को एक ही काम process करने से रोकने वाला row locking
  • दोनों features 2016 में जारी Postgres 9.5 से built-in उपलब्ध हैं
  • NOTIFY और LISTEN को साथ इस्तेमाल करने पर application में pub/sub जोड़ा जा सकता है
  • FOR UPDATE SKIP LOCKED शर्तों से मेल खाने वाले records पर row lock लगाता है और पहले से locked records को छोड़ देता है
    • इसे job records पर लागू करने से SELECT * FROM jobs ORDER BY created_at FOR UPDATE SKIP LOCKED LIMIT 1 जैसी queue processing query बनाई जा सकती है
  • SKIP LOCKED data का “inconsistent” view देता है, लेकिन queue में यह विशेषता उपयोगी है
    • जो काम पहले से process हो रहा है, वह row lock के कारण दूसरे workers को दिखाई नहीं देता
    • इस behavior से distributed mutual exclusion संभव हो जाता है
  • NOTIFY के जरिए LISTEN कर रहे workers को नए कामों की सूचना देकर periodic batch processing और real-time job processing दोनों बनाई जा सकती हैं

Redis कैसे background jobs का default बना

  • लंबे समय तक चलने वाले jobs को कैसे handle करना है, यह कई trade-offs वाला technical choice है
  • व्यापक रूप से इस्तेमाल होने वाले queue/message broker विकल्प ये हैं
    • Redis: in-memory data store और कई background job libraries का backend
    • Apache Kafka: Apache Foundation द्वारा maintain किया जाने वाला distributed event streaming platform
    • RabbitMQ: व्यापक रूप से deployed message broker के रूप में परिचित
    • Amazon SQS: highly scalable queues प्रदान करने वाला Amazon SaaS
  • GitHub के background jobs topic में top 5 popular libraries सभी Redis-based हैं
    1. Sidekiq — Ruby
    2. resque — Ruby
    3. rq — Python
    4. Hangfire — C#
    5. asynq — Go
  • Redis data को memory में store करता है, इसलिए inserts और lookups तेज होते हैं; यह pub/sub API और list, set data structures देता है, इसलिए queue implementation के लिए अच्छी तरह फिट बैठता है
  • Redis की scalability कई developers के लिए default choice की तरह काम करती है, और technology selection में defaults का असर बहुत मजबूत होता है

scalability से पहले देखने लायक मानदंड

  • tech industry का “scale” obsession simplicity, maintainability और developer cognitive load कम करने जैसी बातों को पीछे धकेल सकता है
  • कई applications को Google, Facebook, Uber स्तर की scaling की जरूरत नहीं होती
  • किसी project या business की शुरुआत करते समय शुरू से ही scalability को optimize करने के बजाय पहले ये मानदंड देखने चाहिए
    • ऐसी technology जिसे टीम अच्छी तरह जानती हो
    • ऐसी काफी अच्छी technology जो user needs पूरी करती हो
    • ऐसा solution जो team capability के अनुकूल हो और सबसे कम मेहनत वाला हो
  • Postgres असल में अच्छी तरह scale करता है, लेकिन queue use cases में यह सबसे ज्यादा scale करने वाला specialized queue system नहीं है
  • general-purpose software होने के नाते Postgres कई काम काफी अच्छी तरह करता है, और queue को भी उनमें से एक के रूप में संभाल सकता है

“boring technology” के आधार पर निर्णय लेना

  • queue technology चुनते समय सबसे अहम सवाल है कि अभी कौन-सी technology इस्तेमाल हो रही है और टीम उसे कितनी अच्छी तरह समझती है
  • जो technology पहले से इस्तेमाल में है और अच्छी तरह जानी-पहचानी है, वह software stack पर कम बोझ जोड़ती है
  • अगर relational database पहले से इस्तेमाल हो रहा है और वह database Postgres है, तो अन्य software से पहले Postgres queue पर विचार किया जा सकता है
  • “boring technology” कोई fixed list नहीं है; यह इस बात पर निर्भर करती है कि अभी कौन-सी technology इस्तेमाल हो रही है
    • message delivery-centric application के लिए RabbitMQ boring technology हो सकती है
    • caching-centric application के लिए Redis boring technology हो सकती है
    • बहुत relational data वाली application के लिए Postgres boring technology हो सकती है
  • Redis, Kafka, RabbitMQ, SQS को सिर्फ background jobs के लिए नया introduce करने पर लागत बढ़ जाती है
    • development, testing और production environments में नई system dependency जुड़ती है
    • भविष्य में Developers, DBAs, SREs को नए system के failure modes और configuration समझने होंगे
    • failure recovery, troubleshooting और performance monitoring का ज्ञान चाहिए
    • टीम को अभी जो नहीं पता, वे unknown unknowns risk बने रहते हैं

Postgres queue की कीमत और चयन मानदंड

  • boring technology सर्वगुण-संपन्न नहीं होती, और Postgres भी अपवाद नहीं है
  • Postgres queue चुनने पर परिचितता, known failure modes और cost distribution जैसे फायदे मिलते हैं, लेकिन बदले में ये कीमतें चुकानी पड़ सकती हैं
    • Postgres queue का push/pop Redis की तुलना में काफी धीमा है
    • single relational database के बजाय application database और queue database की जरूरत पड़ सकती है
    • background jobs को independently scale करने के लिए अलग database server की जरूरत पड़ सकती है
    • ज्यादा बार VACUUM की जरूरत हो सकती है, जिससे performance overhead बन सकता है
  • Postgres, Redis, Kafka, RabbitMQ, SQS में से कोई भी automatic default नहीं होना चाहिए
  • technology selection लगातार चलने वाला trade-off है, और इसे team व application requirements के हिसाब से तय करना चाहिए
  • संदेह होने पर यह मानदंड लागू किया जा सकता है: “alternatives पर तभी विचार करें जब यह साबित हो जाए कि boring technology जरूरतें पूरी नहीं कर पा रही”

queue बदल सकने वाली background job संरचना

  • background job processing code किसी specific queue technology पर निर्भर नहीं होना चाहिए
  • application के बढ़ने पर जरूरत के अनुसार memcached या Redis जैसी technologies जोड़ी जा सकती हैं, और समय के साथ वे भी टीम के लिए परिचित “boring technology” बन सकती हैं
  • queue technology को abstract करने से users काम के हिसाब से queue चुन सकते हैं
  • GitHub की top popular background job libraries में Hangfire को छोड़कर कोई भी Redis के अलावा किसी queue technology की ओर निकलने का escape hatch नहीं देती
    • ऐसी structure में queue बदलने के लिए application code फिर से लिखना पड़ता है
  • Neoq इसी philosophy के तहत बनाई गई Go library है
    • queue backend के रूप में in-memory, Postgres, Redis इस्तेमाल किए जा सकते हैं
    • users application code बदले बिना दूसरे queue backend से initialize कर सकते हैं
    • in-memory और Postgres implementations first-class हैं, और Redis implementation asynq का इस्तेमाल करता है
  • Redis, Kafka, RabbitMQ, SQS शानदार technologies हो सकती हैं, लेकिन वे हमेशा उस job के लिए सही queue या उचित complexity नहीं होतीं

1 टिप्पणियां

 
GN⁺ 2023-09-25
Hacker News की रायें
  • अब तक मैंने distributed job system तीन बार बनाए हैं, और कई सालों से मेरी सुझाई हुई rule of thumb यह है: “मौजूदा scale के 10 गुना को आधार मानकर design करो”
    अगर 70 requests प्रति सेकंड हैं तो 700 requests को, और अगर 20 batch job servers चाहिए तो 200 को आधार मानो। हर साल 100% बढ़ने वाला startup भी 3 साल बाद 8 गुना scale पर होगा, इसलिए बढ़ते हुए फिर से लिखने का समय मिल जाता है
    पहले बनाए गए system में “scalability” की वजह से SQL से बचा था, लेकिन transaction integrity की जरूरत वाले बहुत-से edge cases मिलने पर काफी परेशानी हुई
    बाद में बनाए गए दो distributed job systems में PostgreSQL को coordinator की तरह इस्तेमाल किया, और सचमुच SELECT FOR UPDATE SKIP LOCKED के इर्द-गिर्द बनाया। एक आम तौर पर 350 workers को control करता है, और दूसरा हजारों jobs पर complex priorities लागू करता है
    दोनों सालाना कई मिलियन डॉलर revenue के scale तक बिना दिक्कत टिकेंगे, और उदाहरण के लिए 350 workers वाला system थोड़ी tweaking से करीब 2,000 CPUs तक scale हो सकता है
    जब तक आपको यह एहसास न हो कि transactions चाहिए, hyperscale technology काफी सस्ती लगती है। लेकिन उसी क्षण से eventually consistent store के ऊपर transaction semantics की नकल करना engineering nightmare बन जाता है
    इसलिए बैठकर हिसाब लगाना चाहिए। अगर company सालाना 100 million डॉलर कमाए तो distributed system कितना बड़ा होना चाहिए, क्या उस load को handle करने वाला PostgreSQL instance आसानी से मिल सकता है, या client-wise sharding काफी है—यह सब देखकर, अगर संभव हो तो PostgreSQL पर गंभीरता से विचार करने लायक है। सौ छोटी-छोटी चीजें आसान हो जाती हैं

    • मेरा भी मिलता-जुलता अनुभव है। SQL-based queue को मैंने कई बार उस scale से दहाई गुना आगे तक push किया है, जिसके बारे में लोग कहते हैं कि “SQL यहां टूट जाएगा और distributed solution जरूरी है”
      SQL solution आम तौर पर ज्यादा simple होता है, compute resources भी कम खाता है, और operational support भी आसान होता है
      हालांकि इसे चलाने के लिए database को इतना अच्छे से जानना पड़ता है कि SELECT FOR UPDATE SKIP LOCKED जैसी feature शुरू से मौजूद है, यह पता हो। आजकल कई engineers ऐसे environment में बढ़ रहे हैं जहां भारी ORM, DBMS की असली क्षमता के रास्ते में आ जाते हैं, इसलिए यह knowledge काफी rare होती जा रही है
    • मौजूदा scale के 10 गुना को आधार मानकर design और test करें, लेकिन implementation सिर्फ उतना करें जितना अभी जरूरत है
      system को peak load संभालना होगा, और अगर peak नहीं पता तो safety margin रखें या जरूरत पड़ने पर jobs को drop करने या पीछे धकेलने का तरीका रखें
      सब कुछ trade-off है, और optimization सिर्फ उन्हीं चीजों की करनी चाहिए जिन्हें इसकी जरूरत है; कौन-सी चीजें उस category में आती हैं, यह पहचानने की क्षमता अच्छे engineer की निशानी है
    • “eventually consistent store के ऊपर transaction semantics की नकल करना engineering nightmare है” वाला वाक्य वाकई अहम है
      अगर यह आपके लिए नया है, तो आपने अभी काम के समय HN एक हफ्ता पढ़ने की कीमत वसूल कर ली है; इसे कागज पर लिखकर ATM card पर चिपका देने लायक है
    • PostgreSQL में advisory lock-based queue भी बनाई जा सकती है, और यह काफी धीमी होती है लेकिन इसके अपने फायदे भी हैं
  • कई projects में मैंने ज्यादा simple approach चुनी: normal table और SELECT FOR UPDATE SKIP LOCKED का इस्तेमाल, जो हर language के ORM/query DSL framework में सीधे काम करता है
    https://www.pgcasts.com/episodes/the-skip-locked-feature-in-...
    “web scale” तो नहीं है, लेकिन मेरे अनुभव में हजारों background jobs तक आसानी से scale हो जाता है

    • Transaction logic न होने की वजह से मैंने कभी बिना lock के और भी simple तरीका अपनाया था। एक row चुनो, फिर “ले लिया गया” वाला field update करने की कोशिश करो; अगर affected rows 1 है तो वह मेरा job है, और 0 है तो किसी ने पहले ले लिया, इसलिए नई row चुनो
      बड़े organization के काम में भी यह तरीका बिना समस्या इस्तेमाल किया। कोई special deployment या नया infrastructure नहीं चाहिए, बस app के अंदर कुछ worker threads चला दो। abandoned jobs को reset करने वाला thread भी रखा जा सकता है
      हालांकि 3 साल में सच में ऐसी स्थिति आई ही नहीं, और सब कुछ try/catch के अंदर था, इसलिए fail होने पर queue में वापस डाल दिया जाता था; Java app भी बहुत stable था
    • हाल ही में PostgreSQL में इसी तरीके के लिए manifesto और code snippets publish किए गए
      delete from task
      where task_id in
      ( select task_id
      from task
      order by random() -- use tablesample for better performance
      for update
      skip locked
      limit 1
      )
      returning task_id, task_type, params::jsonb as params
      [1] https://taylor.town/pg-task
    • Queue system सही से scale नहीं करता, यह ऐसी problem है जिसके बारे में सबसे आखिर में पता नहीं चलना चाहिए। क्योंकि queue system structurally unscalable है यह पता लगने के बाद data loss के बिना उसे ठीक करना मुश्किल होता है
      “हजारों background jobs” कहा गया, लेकिन queues को आम तौर पर Little's law के आधार पर देखना चाहिए, और average job enqueue rate प्रति सेकंड व average job duration जैसे ratios बताने चाहिए। raw count अपने-आप में ज्यादा मायने नहीं रखता
      शुरुआत में naive UPDATE ... SET से भी काम चल जाता है, लेकिन यह बहुत ज्यादा locks लेता है। UPDATE में SELECT subquery इस्तेमाल करके या SELECT FOR UPDATE SKIP LOCKED से dequeue lock को efficient बनाया जा सकता है, फिर भी अंततः dequeue queries एक-दूसरे के locks को block करके queue रोक सकती हैं
      उस समय DB को सांस लेने का मौका देने के लिए ingestion बंद कर सकते हैं, लेकिन खोई हुई ingestion से data loss होता है, और आम तौर पर core problem यही होती है कि dequeues आपस में एक-दूसरे को block कर रहे होते हैं
      jobs table को जल्दबाजी में shard करके locks से बचा जा सकता है, लेकिन कई workers में deploy करना fragile होता है और data loss तक ले जा सकता है। कुछ random jobs drop भी कर सकते हैं, लेकिन वह जाहिर तौर पर data loss है
      production के दौरान ऐसे options बेहद stressful होते हैं, और ground-up redesign के बिना recovery भी बहुत मुश्किल होती है। अगर सिर्फ कुछ customers हैं और पक्का है कि हमेशा प्रति सेकंड कुछ दर्जन jobs के level पर ही रहेंगे, तो अलग बात है; ऐसी situation खुद झेलने के बाद मैं जहां संभव हो database के बजाय असली queue technology चुनता हूं
    • Graphile का https://github.com/graphile/worker भी लगभग इसी तरीके से implement किया गया है
  • “इंजीनियर अपनी ज़रूरत से बिल्कुल अलग होने के बावजूद cool दिखने के लिए FAANG infrastructure की नकल करते हैं” जैसी आलोचना अक्सर दिखती है, लेकिन मेरे हिसाब से इसका बड़ा हिस्सा ज्ञान और documentation की समस्या है
    अगर आप FAANG या दूसरे startups को follow करके अनंत रूप से scalable queue-based architecture बनाना चाहते हैं, तो Redis, SQS जैसे scalable solutions को कुछ घंटों में set up करने में मदद करने वाली दर्जनों high-quality guides, tutorials और white papers मौजूद हैं
    maintenance cost ज़्यादा होती है, लेकिन अच्छी प्रतिष्ठा वाले sources से commands, code और configuration copy-paste करके शुरुआत की जा सकती है
    इसके उलट, अगर आप PostgreSQL का NOTIFY इस्तेमाल करना चाहते हैं और “SQLALchemy notify listen postgres” search करते हैं, तो बस कुछ unanswered Stack Overflow questions और context के बिना GitHub Gist जैसे results मिलते हैं
    किसी side project में मैं यह तरीका सच में आज़माना चाहता हूँ, लेकिन अकेले 2–3 दिन लगाकर इसे समझने की फुरसत नहीं है
    बाहर से यह “सरल लेकिन scale नहीं होता, यानी सिर्फ PostgreSQL इस्तेमाल करना” और “complex लेकिन scale होता है, यानी Redis/SQS आदि” के चुनाव जैसा दिखता है, और तब बात ऐसी बनती है कि मैं fancy technology और FAANG से प्रभावित होकर ऐसी scalability चुन रहा हूँ जिसकी ज़रूरत नहीं है
    लेकिन guides और resources को भी ध्यान में रखें तो असली विकल्प “complex और scalability भी unpredictable, क्योंकि मुझे implementation और pitfalls नहीं पता” और “सरल और scalable, जिसे असल में सब अपनाते हैं” के करीब हैं। तब FAANG को follow करने वाले engineer का चुनाव कहीं ज़्यादा rational दिखता है

    • PostgreSQL जैसी “boring” technology का फायदा यह है कि उसके docs बेहतरीन हैं। NOTIFY इस्तेमाल करना हो तो बस https://www.postgresql.org/docs/current/sql-notify.html पढ़ लें, search skills की ज़रूरत नहीं
    • scalability की कीमत चुकानी पड़ती है। जब ज़रूरत न हो तब उसे जोड़ने से flexibility घटती है, और startup के लिए यही वह स्थिति है जो वह नहीं चाहता
      उदाहरण के लिए, low-load PostgreSQL इस्तेमाल करने पर schema migration, नए constraints जोड़ना, analytics आदि लगभग मामूली काम बन जाते हैं
      SQS, Cassandra आदि इस्तेमाल करने पर scalability/availability मिलती है, लेकिन जब पता चलता है कि मूल design fit नहीं बैठ रहा, तो उसे बदलने में कहीं ज़्यादा समय लगता है। जैसे business कहे कि “foo type के users को bar values को साथ में combine नहीं करना चाहिए” जैसा constraint जोड़ना है
      PostgreSQL के बिना भी implementation संभव है, लेकिन खासकर बदलाव चाहिए हो तो यह आसान या सरल नहीं होता
      इसलिए flexibility बनाए रखनी हो तो PostgreSQL इस्तेमाल करना, और जब पता हो कि चीज़ नहीं बदलेगी तब PostgreSQL के ऊपर कुछ और साथ में इस्तेमाल करना बेहतर लगता है। बेशक इस case में infrastructure और maintenance overhead जुड़ता है
      आखिरकार यह हमेशा trade-off है, और यह जानना पड़ता है कि क्या, कब, किसके बदले छोड़ना है
    • यह लेख application developers की बजाय library developers को ज़्यादा target करता है
      हर किसी का अपने application में PostgreSQL backend हो या कोई और queue, custom background job worker खुद implement करना ideal नहीं लगता
      background jobs में कई subtle बातें और implementation details होती हैं जिनमें गलती आसानी से हो सकती है, इसलिए आम तौर पर इन्हें किसी ज़्यादा comprehensive dedicated library या framework को संभालना बेहतर होता है
      अगर हर Rails application में Sidekiq/Active Jobs न होते और सभी अपने custom background workers implement करते, तो reliability के मामले में Rails apps की reputation शायद आज से कहीं खराब होती
    • “maintenance ज़्यादा है” यही बात छोटे startups को खत्म कर देती है। बेशक बदले में बहुत कुछ मिलता है, लेकिन company बंद हो जाए तो उसका कोई मतलब नहीं
      ऐसा solution चुनना चाहिए जो main goal से बहुत दूर न ले जाते हुए सबसे बड़ा benefit दे
      मैंने एक startup देखा था जहाँ developers अपना लगभग 80% समय tools और infrastructure से लड़ने में लगा रहे थे। runway 3 महीने की थी, और अब उसके अंत में बस एक बड़ा गड्ढा बचा है। सोचकर आज भी सिहरन होती है
    • अगर तर्क यह है कि FAANG-level design अपनाने से समय बचेगा, तो उसका मुख्य आधार सिर्फ इतना है कि online guides मिल जाती हैं
      लगता है complexity के प्रति सम्मान पर्याप्त नहीं है
  • PostgreSQL को publish/subscribe bus के तौर पर LISTEN/NOTIFY के साथ इस्तेमाल करते समय सबसे बड़ा नुकसान यह है कि LISTEN session feature है, इसलिए यह statement-level connection pooling के साथ fit नहीं बैठता
    अगर यह तरीका अपनाते हैं तो advisory locks इस्तेमाल करना बेहतर है। दूसरे explicit locks database पर ज़्यादा बोझ डालते हैं, लेकिन advisory locks को जानबूझकर बहुत lightweight बनाया गया है
    मेरा पसंदीदा implementation example que है, जिसे कई भाषाओं में port किया गया है

    • NOTIFY/LISTEN पसंद न करने की एक वजह यह है कि समस्या आने पर इसे diagnose करना मुश्किल होता है
      हाल ही में भी एक issue था जहाँ कुछ समय बाद सभी NOTIFY/LISTEN रुक जाते थे, और database restart करने पर ही समाधान होता था, इसलिए इसका use बंद करना पड़ा https://dba.stackexchange.com/questions/325104/error-could-n...
    • जानना चाहता हूँ कि Que का Node version है या नहीं
  • Skype ने कई साल पहले सभी CDR प्रोसेस करने के लिए एक छोटे plugin के साथ PostgreSQL को queue के रूप में इस्तेमाल किया था। पता नहीं वे अभी भी करते हैं या नहीं, लेकिन 10 साल पहले के हिसाब से यह “web scale” था, और जब इंटरनेट पर लोग database को queue की तरह इस्तेमाल करने को anti-pattern बताकर बहस कर रहे थे, तब यह बस अच्छी तरह चल रहा था
    transactions होना काफी सुविधाजनक है
    https://wiki.postgresql.org/wiki/SkyTools
    काम में इसे काफी इस्तेमाल किया था, इसलिए Sydpy में इस विषय पर कुछ presentations भी दिए थे। अगर आप पहले से PostgreSQL को अच्छी तरह operate और support कर रहे हैं तो यह उपयोगी है
    हालांकि आज के समय में शायद मैं dedicated queue इस्तेमाल करूंगा, और RabbitMQ से जरूर बचूंगा

    • “RabbitMQ से जरूर बचूंगा” वाले हिस्से को थोड़ा और समझाएं तो अच्छा होगा। दूसरों के अनुभव या विचार भी जानना चाहूंगा
    • आज dedicated queue इस्तेमाल करने वाली बात से सहमत हूं। मुख्य वजह यह है कि cloud environment में ज़्यादातर cloud providers बहुत आसान और सस्ती hosted queue technology देते हैं
      vendor lock-in की चिंता हो तब भी queue के basic operations असल में push/pop जैसे छोटे ही होते हैं, इसलिए जरूरत पड़ने पर migrate करना आसान रहे, ऐसा लिखना भी तुलनात्मक रूप से आसान है
    • जानना चाहूंगा कि RabbitMQ के साथ आपका खराब अनुभव रहा है या नहीं। अगर रहा है, तो क्या वह technology की वजह से था या documentation/community जैसे आसपास के factors की वजह से
    • database को queue की तरह इस्तेमाल करने का तरीका जब अच्छा चलता है तो शानदार होता है, लेकिन जिस तरह से यह टूटता है, वह आपको ऐसी स्थिति में धकेल देता है जिससे recover करना बहुत मुश्किल होता है
      अगर आपने पहले से database चल रहा है इसलिए queue भी database में डाल दी, तो यह logic दोधारी तलवार है। बधाई की बात तो नहीं है, लेकिन queue की गड़बड़ी अब primary data store को भी नीचे गिरा सकती है
  • PostgreSQL को application queue के रूप में इस्तेमाल करने का एक सबसे बड़ा फायदा यह है कि schedule किए गए सभी asynchronous jobs को transactionality का लाभ मिलता है
    उदाहरण के लिए, signup के बाद email भेजने वाला कोई जटिल backend change है, और email भेजने का job डालने के बाद उसी transaction के बाद वाले हिस्से में failure होकर rollback हो जाता है, तो वह email job शुरू से queue में गया ही नहीं होगा

    • यह स्पष्ट करना चाहिए कि दूसरे non-idempotent systems से जुड़ते समय अनिवार्य रूप से at least once या at most once semantics में से एक चुनना पड़ता है
      email के मामले में, अगर भेजने की confirmation का इंतजार करते हुए failure हो, तो आपको चुनना होगा कि transaction fail करके duplicate email की संभावना स्वीकार करें, या आगे बढ़कर email खो जाने की संभावना स्वीकार करें
      बड़ा फायदा उन code paths से आता है जो asynchronously DB को modify करते हैं। job consumption और DB update एक ही transaction के अंदर होते हैं, इसलिए exactly-once semantics को पूरी तरह transactionally handle किया जा सकता है
    • यह वाकई बहुत महत्वपूर्ण point है। SQS job processing cluster की auto-scaling आसान बना देता है, इसलिए PostgreSQL और SQS का combination अक्सर इस्तेमाल करता हूं
      PostgreSQL में एक transaction log table रखता हूं जिसमें triggered event और उस transaction का pg_current_xact_id() होता है। row का built-in xmin भी इस्तेमाल किया जा सकता है, लेकिन transaction wraparound का ध्यान रखना पड़ता है। इस row में insert करने पर NOTIFY होता है
      एक background process बार-बार चलता है, और पिछले run के xmin और मौजूदा pg_snapshot_xmin(pg_current_snapshot()) के बीच transaction IDs वाली सभी rows transaction table से चुनता है। उन events को jobs में map करके SQS में submit करता है, xmin record करता है, फिर अगले NOTIFY के लिए LISTEN करता है
    • हम भी queue addition को commit के बाद execute होने के लिए schedule करके इस समस्या को थोड़ा कम करते हैं। फिर भी कुछ असुरक्षित हिस्से बचते हैं, और RabbitMQ connection down हो जाए तो मुश्किल हो जाती है
    • database को यह बताना कि कुछ process हो गया है और RabbitMQ जैसी जगह पर message भेजना 100% transactional नहीं हो सकता। इस approach की सबसे बड़ी वजह यही है
      PostgreSQL queue में email भेजने का job डालने के बाद बाद में transaction fail हो जाए तो email queue में नहीं जाएगा—यह बात सही है
      database code जिसे पूरी तरह अलग होना चाहिए, उसे अलग transactions में बांटना worthwhile है। उल्टा, अगर user DB में बना ही नहीं है तो आप signup email भी नहीं भेजना चाहेंगे, इसलिए यह स्थिति पर निर्भर करता है
    • एक और फायदा यह है कि job pick होने से पहले transaction complete होने की guarantee होती है
      Redis-based queue या असल में लगभग किसी भी दूसरी queue में आप जल्दी ही ऐसी स्थिति देखेंगे जहां database record मौजूद है मानकर job चल जाता है, जबकि transaction अभी commit नहीं हुआ होता। इसे ठीक करने वाला code आमतौर पर awkward और complex हो जाता है
  • Kafka में मुझे जो बात पसंद है वह यह है कि यह बस append-only log है, और clients मूल रूप से सिर्फ offsets रखते हैं
    conceptually समझना बहुत आसान है, persistent है और failures के खिलाफ काफी मजबूत भी है, क्योंकि आप किसी भी offset पर लौटकर फिर से पढ़ सकते हैं
    दुर्भाग्य से Kafka अपनी distributed nature की वजह से पर्याप्त complexity साथ लाता है, इसलिए ज्यादातर use cases में आखिरकार उसकी कीमत वसूल नहीं होती
    निजी तौर पर, अच्छा होगा अगर ऐसा ही कुछ हो जो operate करने में ज्यादा आसान हो। single node पर प्रति सेकंड सैकड़ों, शायद हजारों events handle कर सकेगा, और distributed complexity न हो तो काफी अच्छा होगा
    theory में PostgreSQL से भी यह किया जा सकता है, बस rows कभी delete न करें। शायद वही answer हो सकता है

    • PostgreSQL में native “offset” के बराबर auto-increment ID है और date-based partitioning भी संभव है, इसलिए मुझे यह simple Kafka replacement के लिए बहुत अच्छा candidate लगता है
      अगर consumer groups, partitions जैसी पूरी feature set की जरूरत नहीं है, तो consumer setup भी बहुत सरल हो जाएगा
    • मैं उम्मीद करता था कि FasterLog कभी पर्याप्त mature हो जाएगा
  • Windmill में बिल्कुल यही तरीका इस्तेमाल हो रहा है। Windmill एक open-source Retool alternative और modern Airflow जैसा tool है, और हम रोज benchmarks चलाते हैं
    एक ठीक-ठाक GitHub CI instance पर Windmill worker और PostgreSQL को containers में चलाने पर benchmark 1,200 jobs per second process करता है। workers जोड़ने पर यह स्थिर रूप से 5,000 jobs per second तक scale करता है
    multi-tenant instance में 5,000 jobs per second की बाधा पार करने के लिए Citus इस्तेमाल करने पर विचार कर रहे हैं
    https://github.com/windmill-labs/windmill/tree/benchmarks

  • जब प्रति सेकंड लगभग 10 messages का स्तर था, तब कुछ queues के लिए PostgreSQL इस्तेमाल किया था और वह काफ़ी scale भी हुआ। लेकिन सच कहें तो AWS, GCP, Azure में SQS या कोई दूसरा queue stack सेट करना बेहद आसान है, वे इसी उद्देश्य के लिए बने हैं, और DLQ जैसी चीज़ें भी built-in होती हैं
    मुझे समझ नहीं आता कि जानबूझकर ऐसा रास्ता क्यों चुनें जिसमें यह चिंता रहे कि अगर वह system गिरा तो बाकी DB state पर भी असर पड़ेगा
    “सबसे सरल tool इस्तेमाल करो” वाला सिद्धांत मुझे पसंद है, लेकिन कभी-कभी लगता है कि engineers इसे बहुत दूर ले जाते हैं, और जब mainstream विकल्प अपेक्षाकृत सस्ते और सरल होते हैं तब भी अस्पष्ट caveats वाले सबसे बेवकूफ़ tool तक ही सीमित रह जाते हैं

    • जिस तरीके पर मैं पहुँचा हूँ वह है अधिकांश job state DB में store करना, और job queue का उपयोग सिर्फ़ workers को जगाकर वह job process कराने के लिए करना
      job state को DB में store करने से state को अच्छे से query किया जा सकता है। भले ही वह actual state को बिल्कुल सही न दिखाए, production में incidents analyze करते समय मदद मिलती है। खासकर क्योंकि ज़्यादातर job queues processed records को बस delete कर देती हैं
      और अगर सभी background jobs को idempotent बनाया जाए, तो “इस job को process करने के लिए job queue में message भेजना” जैसा तरीका लगभग हमेशा safe होता है
      सिर्फ़ message queue पर निर्भर रहने पर performance problem आने पर क्या हो रहा है, यह समझना अक्सर मुश्किल होता है। उदाहरण के लिए RabbitMQ queue size बता सकता है, लेकिन internal data inspect करने की सुविधा लगभग नहीं होती या बहुत सीमित होती है
    • वजह transactions और data consistency है। यह जवाब SQS में नहीं मिल सकता
    • क्योंकि हर software की requirements अलग होती हैं। on-premises/B2B appliance में PostgreSQL के बाहर किसी external service की requirement न होना अपने-आप में एक feature हो सकता है
      कुछ software के बारे में उम्मीद की जा सकती है कि वह PostgreSQL की क्षमता से कभी आगे नहीं जाएगा, और अगर गया भी तो उसे किसी दूसरी service पर ले जाना आसान बनाया जा सकता है
      और आप PostgreSQL के अंदर ही transactions वाला job system सरल तरीके से बनाना चाह सकते हैं
  • हम Elixir के Oban का उपयोग करके PostgreSQL के अंदर हर दिन लाखों से लेकर कई मिलियन jobs process करते हैं
    transaction सफल होने पर ही email schedule करने जैसी चीज़ों के लिए background jobs के आसपास transactional semantics होना बेहद सुविधाजनक है
    autovacuum की थोड़ी tuning करनी पड़ती है, लेकिन साफ़-सुथरा कर लेने के बाद यह हमारे लिए बहुत अच्छा fit रहा