प्रतिनिधि इंटरव्यू विषय

एक Idempotent Create-Order API डिज़ाइन करें

बैकएंडकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

POST /orders के लिए idempotency अनुबंध (contract) डिज़ाइन करें: एक क्लाइंट को टाइमआउट के बाद पुनः प्रयास करने में सक्षम होना चाहिए, समवर्ती डुप्लिकेट केवल एक ऑर्डर बना सकते हैं, अलग-अलग पैरामीटर के साथ की (key) के पुन: उपयोग को अस्वीकार किया जाना चाहिए, और डिज़ाइन को कमिट के बाद खोए हुए रिस्पॉन्स, पहले से प्रगति पर मौजूद ऑपरेशन और डाउनस्ट्रीम साइड इफ़ेक्ट को संभालना चाहिए।

समस्या और दायरा

POST /orders के लिए idempotency अनुबंध डिज़ाइन करें। एक मोबाइल क्लाइंट किसी अविश्वसनीय नेटवर्क पर टाइमआउट हो सकता है और स्वचालित रूप से पुनः प्रयास कर सकता है। एक गेटवे भी अपना कनेक्शन टूटने के बाद अनुरोध को दोबारा भेज (replay) सकता है। चाहे एक तार्किक ऑपरेशन एक बार आए या कई बार, सर्वर को केवल एक ऑर्डर बनाना चाहिए। एक अनुरोध के सफल होने के बाद, समान पुनः प्रयास पर पहले परिणाम को दोबारा दिखाना (replay) चाहिए; जब वही idempotency key विभिन्न ऑर्डर पैरामीटर का प्रतिनिधित्व करती है, तो सर्वर को इसे अस्वीकार कर देना चाहिए।

मान लें कि कॉलर Idempotency-Key में एक तार्किक ऑपरेशन के लिए एक रैंडम पहचानकर्ता (identifier) भेजता है, और सर्वर PostgreSQL का उपयोग करता है। ऑर्डर राइट्स और एक लंबित इवेंट एक स्थानीय डेटाबेस लेनदेन (transaction) साझा कर सकते हैं। बाहरी भुगतान और इन्वेंट्री सिस्टम उस लेनदेन में भाग नहीं ले सकते। API मल्टी-टेनेंट है, और इसके रिस्पॉन्स में ऑर्डर डेटा हो सकता है जिसके लिए ऑथराइजेशन की आवश्यकता होती है।

दायरे में अनुरोध डीडुप्लिकेशन, समवर्ती प्रतिस्पर्धा (concurrent races), लेनदेन की सीमाएं (transaction boundaries), परिणाम रीप्ले, अवधारण (retention), और डाउनस्ट्रीम साइड इफ़ेक्ट शामिल हैं। क्रॉस-रीजन डेटाबेस प्रतिकृति (replication), भुगतान डोमेन की अपनी स्टेट मशीन, और मैसेज-ब्रोकर का चयन दायरे से बाहर हैं। यह एक बैकएंड प्रश्न है क्योंकि मुख्य कार्य API सिमेंटिक्स को डेटाबेस बाधाओं, विफलता से उबरने (failure recovery), और एक अवलोकनीय (observable) अनुरोध प्रवाह में बदलना है, न कि एक व्यापक बहु-घटक सिस्टम को डिज़ाइन करना।

साक्षात्कारकर्ता क्या मूल्यांकन करते हैं

पहला संकेत यह है कि उम्मीदवार पुनः प्रयास पहचान (retry identity) को व्यावसायिक पहचान (business identity) से अलग करता है या नहीं। एक idempotency key कॉलर के दृष्टिकोण से एक ऑपरेशन की पहचान करती है। यह प्रमाणीकरण (authentication), टेनेंट अलगाव (tenant isolation), या डोमेन-स्तरीय ऑर्डर की का स्थान नहीं लेती है। सर्वर को इसे कम से कम (tenant_id, endpoint, idempotency_key) द्वारा स्कोप करना चाहिए। केवल नग्न (bare) की (key) द्वारा लुकअप करने से उन दो टेनेंट्स को, जो संयोग से एक ही स्ट्रिंग चुनते हैं, एक-दूसरे का परिणाम पढ़ने की अनुमति मिल सकती है।

दूसरा संकेत यह है कि समवर्ती शुद्धता (concurrency correctness) एप्लिकेशन टाइमिंग के बजाय विशिष्टता बाधा (uniqueness constraint) पर निर्भर करती है या नहीं। एक SELECT के बाद INSERT दो अनुरोधों को कोई पंक्ति (row) न देखने और दोनों को पहले अनुरोध के रूप में कार्य करने की अनुमति देता है। INSERT ... ON CONFLICT के साथ एक यूनिक इंडेक्स डेटाबेस को एक स्वामी (owner) चुनने देता है। तुलना में एक अनुरोध फ़िंगरप्रिंट भी शामिल होना चाहिए; अन्यथा, किसी पुरानी की का आकस्मिक पुन: उपयोग एक पुराने ऑर्डर को एक अलग ऑर्डर अनुरोध के परिणाम के रूप में वापस कर सकता है।

तीसरा संकेत एक सटीक कमिट सीमा (commit boundary) है। ऑर्डर, idempotency परिणाम, और आउटबॉक्स इवेंट को एक साथ कमिट होना चाहिए। कमिट से पहले विफलता तीनों को रोलबैक कर देती है। यदि कमिट सफल होता है लेकिन HTTP रिस्पॉन्स खो जाता है, तो एक पुनः प्रयास पूर्ण रिकॉर्ड को पढ़ता है और उसे रीप्ले करता है। भुगतान और इन्वेंट्री कॉल लेनदेन के बाहर एक आउटबॉक्स उपभोक्ता (consumer) के माध्यम से होती हैं, जो डाउनस्ट्रीम में एक व्युत्पन्न (derived) idempotency पहचानकर्ता पास करता है।

एक मजबूत उत्तर अवधारण (retention), समवर्ती डुप्लिकेट पर रिस्पॉन्स, किन विफलताओं को संग्रहीत किया जाता है, लंबे समय तक चलने वाले ऑपरेशन को कैसे पुनर्प्राप्त किया जाता है, और की के टकराव तथा अटके हुए कार्यों की निगरानी कैसे की जाती है, इसे भी परिभाषित करता है। “Redis लॉक का उपयोग करें” कहने से लॉक समाप्ति, प्रोसेस क्रैश, परिणाम रीप्ले, और डेटाबेस कमिट के बाद की रेस की स्थिति अनुत्तरित रह जाती है।

उत्तर देने से पहले स्पष्ट करने योग्य प्रश्न

  • डुप्लिकेट क्या माना जाता है? यहाँ, समान टेनेंट, एंडपॉइंट, और idempotency key एक ऑपरेशन की पहचान करते हैं। यदि डोमेन में पहले से ही एक गैर-पुन: प्रयोज्य checkout_id है, तो ऑर्डर तालिका (orders table) में भी उस पर एक स्वतंत्र विशिष्टता बाधा होनी चाहिए।
  • क्या कॉलर संसाधन आईडी (resource ID) चुन सकता है? यदि कॉलर के पास एक स्थिर ऑर्डर आईडी है, तो PUT /orders/{clientOrderId} एक विकल्प है क्योंकि HTTP PUT को idempotent के रूप में परिभाषित करता है। Idempotency key के साथ POST सर्वर द्वारा जनरेट की गई ऑर्डर आईडी के लिए उपयुक्त है।
  • क्या ऑर्डर निर्माण पूरी तरह से डेटाबेस का काम है? यदि यह एक छोटे लेनदेन में फिट बैठता है, तो सिंगल-ट्रांजेक्शन डिज़ाइन का उपयोग करें। एक वर्कफ़्लो जो दसियों सेकंड लेता है, उसे लंबे समय तक चलने वाले डेटाबेस लेनदेन के बजाय एक दृश्यमान (visible) IN_PROGRESS रिकॉर्ड, एक लीज (lease), और एक टेकओवर टोकन की आवश्यकता होती है।
  • रीप्ले कितना सटीक होना चाहिए? यह डिज़ाइन पहले HTTP स्टेटस और एक रिस्पॉन्स बॉडी को संग्रहीत करता है जिसे रीप्ले करना सुरक्षित है। यदि किसी रिस्पॉन्स में अल्पकालिक हस्ताक्षर (short-lived signature) या लाइव फ़ील्ड हैं, तो संसाधन आईडी संग्रहीत करें और एक स्पष्ट अनुबंध के तहत रिस्पॉन्स का पुनर्निर्माण करें।
  • की (keys) कितने समय तक रखी जाती हैं? अवधारण को क्लाइंट के सबसे लंबे पुनः प्रयास या ऑफ़लाइन रीप्ले विंडो को कवर करना चाहिए। यदि उस विंडो के बाद दोहराव असंभव रहना चाहिए, तो एक स्थायी डोमेन की जोड़ें; लंबा कैश TTL किसी व्यावसायिक बाधा का विकल्प नहीं है।
  • किन विफलताओं को याद रखा जाना चाहिए? स्वामित्व प्राप्त करने से पहले विफल होने वाला सत्यापन (validation) संग्रहीत नहीं किया जाता है। लेनदेन के अंदर एक नियतात्मक व्यावसायिक अस्वीकृति (deterministic business rejection) को संग्रहीत और रीप्ले किया जा सकता है। एक बुनियादी ढांचा विफलता जो लेनदेन को रोलबैक करती है, कोई पूर्ण परिणाम नहीं छोड़ती है, इसलिए क्लाइंट पुनः प्रयास कर सकता है।

30-सेकंड का उत्तर

“मैं प्रत्येक idempotency key को टेनेंट और POST /orders पर स्कोप करूँगा, फिर सामान्यीकृत (normalized) ऑर्डर पैरामीटर का फ़िंगरप्रिंट बनाऊँगा। (tenant_id, endpoint, idempotency_key) पर एक यूनिक बाधा समवर्ती अनुरोधों में मध्यस्थता करती है। पहला अनुरोध एक लेनदेन में idempotency रिकॉर्ड, ऑर्डर और आउटबॉक्स इवेंट लिखता है, फिर कमिट करने से पहले स्टेटस और रिस्पॉन्स बॉडी संग्रहीत करता है। उसी फ़िंगरप्रिंट के साथ एक पुनः प्रयास उस परिणाम को रीप्ले करता है; एक अलग फ़िंगरप्रिंट टकराव (conflict) लौटाता है। इसलिए कमिट के बाद खोया हुआ रिस्पॉन्स दूसरा ऑर्डर नहीं बना सकता है। भुगतान और इन्वेंट्री आउटबॉक्स से व्युत्पन्न idempotency keys के साथ अतुल्यकालिक रूप से (asynchronously) चलते हैं। मैं अधिकतम पुनः प्रयास विंडो के लिए अनुरोध रिकॉर्ड को बनाए रखूँगा और स्थायी डीडुप्लिकेशन के लिए एक अलग ऑर्डर-डोमेन विशिष्टता बाधा का उपयोग करूँगा।”

चरण-दर-चरण विस्तृत विवरण

प्रोटोकॉल से शुरुआत करें। एक प्रमाणित कॉलर एक “ऑर्डर बनाएं” इरादे के लिए एक उच्च-एन्ट्रापी की (high-entropy key) उत्पन्न करता है और प्रत्येक पुनः प्रयास के लिए इसका पुन: उपयोग करता है। एक नए ऑर्डर के लिए एक नई की की आवश्यकता होती है। की में ईमेल पता, फ़ोन नंबर या अन्य संवेदनशील डेटा नहीं होना चाहिए। सर्वर इसकी लंबाई और वर्ण सेट को सीमित करता है, लेकिन एक रैंडम दिखने वाली की कभी भी ऑथराइजेशन क्रेडेंशियल नहीं होती है। POST में डिफ़ॉल्ट रूप से PUT के idempotent सिमेंटिक्स नहीं होते हैं; सुरक्षित पुनः प्रयास इस एप्लिकेशन अनुबंध से आते हैं।

कच्चे JSON बाइट्स से नहीं, बल्कि सर्वर-सत्यापित व्यावसायिक फ़ील्ड से अनुरोध फ़िंगरप्रिंट बनाएं। फ़ील्ड क्रम, महत्वहीन व्हाइटस्पेस और डिफ़ॉल्ट मानों को एक ही अनुरोध को अलग-अलग फ़िंगरप्रिंट नहीं देने चाहिए। ट्रेस आईडी, टाइमस्टैम्प और अन्य गैर-व्यावसायिक फ़ील्ड को बाहर रखें। एक व्यावहारिक इनपुट सामान्यीकृत DTO, एंडपॉइंट संस्करण और ऑर्डर परिणाम को प्रभावित करने वाले प्रत्येक फ़ील्ड की एक स्थिर एन्कोडिंग है। उस एन्कोडिंग को हैश करें। डाइजेस्ट की (key) के दुरुपयोग का पता लगाता है; यह कोई हस्ताक्षर या प्रमाणीकरण तंत्र नहीं है।

यह न्यूनतम स्कीमा आवश्यक बाधाओं को व्यक्त करता है; SQL केवल उदाहरण के लिए है:

sql
CREATE TABLE idempotency_requests (
  tenant_id text NOT NULL,
  endpoint text NOT NULL,
  idempotency_key text NOT NULL,
  request_fingerprint text NOT NULL,
  response_status integer,
  response_body jsonb,
  resource_id uuid,
  created_at timestamptz NOT NULL,
  expires_at timestamptz NOT NULL,
  PRIMARY KEY (tenant_id, endpoint, idempotency_key)
);

एक छोटे लेनदेन के लिए सामान्य पथ है:

  1. लेनदेन के बाहर, प्रमाणित करें, की प्रारूप और अनुरोध को मान्य करें, और फ़िंगरप्रिंट की गणना करें।
  2. एक लेनदेन शुरू करें और INSERT ... ON CONFLICT DO NOTHING RETURNING ... के साथ की पर दावा करने का प्रयास करें।
  3. जो अनुरोध पंक्ति सम्मिलित (insert) करता है वह स्वामी होता है। यह ऑर्डर बनाता है, आउटबॉक्स इवेंट जोड़ता है, स्टेटस, रिस्पॉन्स बॉडी और ऑर्डर आईडी के साथ idempotency पंक्ति को अपडेट करता है, और एक बार कमिट करता है।
  4. जिस अनुरोध ने सम्मिलित नहीं किया, वह मौजूदा पंक्ति को पढ़ता है। एक अलग फ़िंगरप्रिंट 409 idempotency_key_reused लौटाता है। वही फ़िंगरप्रिंट पहले संग्रहीत स्टेटस और बॉडी लौटाता है, वैकल्पिक रूप से एक API-परिभाषित रीप्ले मार्कर के साथ।
  5. HTTP रिस्पॉन्स केवल कमिट के बाद भेजें। यदि कमिट के बाद कनेक्शन टूट जाता है, तो अगला अनुरोध ऑर्डर बनाने के बजाय चरण 4 का पालन करता है।

PostgreSQL का यूनिक इंडेक्स एक ही की के समवर्ती इंसर्ट को प्रतीक्षा कराता है और फिर उन्हें एक इंसर्ट या एक टकराव (conflict) में हल करता है। उस प्रतीक्षा को सीमित (bound) करें। यदि पहला लेनदेन जल्दी से कमिट हो जाता है, तो दूसरा अनुरोध परिणाम को पढ़ और रीप्ले कर सकता है। यदि प्रतीक्षा अपनी सीमा तक पहुँच जाती है, तो पुनः प्रयास मार्गदर्शन के साथ एक स्पष्ट idempotency_in_progress परिणाम लौटाएं। सम्मिलित करने का निर्णय लेने से पहले एप्लिकेशन-स्तरीय SELECT का उपयोग न करें, और टकराव होने पर संग्रहीत फ़िंगरप्रिंट या परिणाम को कभी अधिलेखित (overwrite) न करें।

केंद्रीय अपरिवर्तनीयता (central invariant) यह है कि जब भी ऑर्डर पंक्ति दृश्यमान होती है, उसका idempotency परिणाम और आउटबॉक्स इवेंट भी दृश्यमान होते हैं; जब लेनदेन रोलबैक होता है, तो उनमें से कोई भी दृश्यमान नहीं होता है। विफलता के मामले सीधे इसके अनुरूप होते हैं। ऑर्डर निर्माण से पहले एक प्रोसेस क्रैश रोलबैक हो जाता है, इसलिए एक पुनः प्रयास फिर से प्रतिस्पर्धा कर सकता है। ऑर्डर कमिट होने के बाद खोया हुआ रिस्पॉन्स रीप्ले हो जाता है। विभिन्न मापदंडों वाली एक ही की कभी निष्पादित नहीं होती है। एक साथ आने वाले दो समान अनुरोधों में से केवल एक ही विशिष्टता बाधा को पार कर सकता है और राइट्स को पूरा कर सकता है। एक नियतात्मक डोमेन अस्वीकृति जैसे कि एक समाप्त हो चुका कूपन लेनदेन के अंदर एक स्थिर त्रुटि रिस्पॉन्स के रूप में संग्रहीत किया जा सकता है। एक गैर-कमिटेड विफलता जैसे कि खोया हुआ डेटाबेस कनेक्शन संग्रहीत नहीं किया जाता है।

यदि ऑर्डर निर्माण एक छोटे लेनदेन के भीतर नहीं रह सकता है, तो दो-चरण वाली स्टेट मशीन (two-phase state machine) का उपयोग करें। पहला छोटा लेनदेन IN_PROGRESS, lease_expires_at, और एक मोनोटोनिक रूप से बढ़ते हुए attempt_token को कमिट करता है। API 202 लौटाता है, या की के साथ दूसरा अनुरोध स्टेटस एंडपॉइंट को पढ़ता है। लीज समाप्त होने के बाद, एक नया कार्यकर्ता (worker) बड़े टोकन का दावा करने के लिए तुलना-और-स्वैप (compare-and-swap) का उपयोग करता है। प्रत्येक पूर्णता अपडेट उस टोकन को सत्यापित करता है, जो किसी पुराने कार्यकर्ता को फिर से शुरू होने के बाद नए परिणाम को ओवरराइट करने से रोकता है। ऑर्डर को अभी भी व्यावसायिक ऑपरेशन आईडी पर एक विशिष्टता बाधा की आवश्यकता होती है, और साइड इफ़ेक्ट अभी भी आउटबॉक्स या डाउनस्ट्रीम idempotency keys का उपयोग करते हैं। TTL वाली लेकिन बिना टेकओवर टोकन वाली स्टेटस पंक्ति पुराने और नए कार्यकर्ताओं को एक साथ चलने की अनुमति देती है; यह केवल डुप्लिकेट रेस में देरी करती है।

बाहरी भुगतान, इन्वेंट्री और अधिसूचना कॉल स्थानीय डेटाबेस लेनदेन के अंदर नहीं होने चाहिए। स्वामी ऑर्डर के साथ एक OrderCreated आउटबॉक्स पंक्ति लिखता है, और एक उपभोक्ता इसे कमिट के बाद डिलीवर करता है। उपभोक्ता इवेंट आईडी द्वारा डीडुप्लिकेट करता है। भुगतान या इन्वेंट्री API पर कॉल ऑर्डर आईडी और ऑपरेशन प्रकार से प्राप्त एक स्थिर की का उपयोग करती हैं। कम से कम एक बार संदेश वितरण (at-least-once delivery) तब कम से कम एक बार चार्जिंग (at-least-once charging) में नहीं बदलता है। यदि किसी डाउनस्ट्रीम सेवा में कोई idempotent इंटरफ़ेस नहीं है, तो स्थानीय स्टेट मशीन, समाधान क्वेरी (reconciliation query), या मैन्युअल क्षतिपूर्ति का उपयोग करें; केवल एक आउटबॉक्स बाहरी डुप्लिकेट को नहीं हटाता है।

अवधारण (retention) पुनः प्रयास अनुबंध का पालन करता है। Stripe का सार्वजनिक कार्यान्वयन एक अवधारण अवधि के बाद की (key) को हटाने की अनुमति देता है। यह दर्शाता है कि कीज़ का एक जीवनचक्र हो सकता है, लेकिन इसकी अवधि सार्वभौमिक नहीं है। सबसे लंबे मोबाइल ऑफ़लाइन कतार, गेटवे पुनः प्रयास और मैन्युअल रीप्ले विंडो को कवर करने के लिए expires_at सेट करें। सफ़ाई के बाद किसी पुरानी की का पुन: उपयोग करना एक नया ऑपरेशन है। यदि एक checkout_id को हमेशा के लिए अधिकतम एक ऑर्डर देना चाहिए, तो orders पर एक स्थायी यूनिक बाधा लगाएं ताकि idempotency रिकॉर्ड चले जाने के बाद भी यह डुप्लिकेट को रोक सके।

Redis पूर्ण परिणामों को कैश कर सकता है, लेकिन यह एकमात्र शुद्धता सीमा (correctness boundary) नहीं होना चाहिए। SET NX के सफल होने के बाद, एक प्रक्रिया ऑर्डर कमिट से पहले या बाद में क्रैश हो सकती है, और धीमे अनुरोध के समाप्त होने से पहले लॉक TTL समाप्त हो सकता है। कैश ऑर्डर और आउटबॉक्स को परमाणु रूप से (atomically) कमिट भी नहीं कर सकता है। डेटाबेस विशिष्टता बाधा को “अधिकतम एक बार बनाया गया” तय करने दें। सत्य के स्रोत (source of truth) के रूप में डेटाबेस रिकॉर्ड के साथ, केवल हॉट रीप्ले के लिए रीड्स को कम करने के लिए कैश का उपयोग करें।

परीक्षण के लिए केवल दो क्रमिक अनुरोधों से अधिक की आवश्यकता होती है। कम से कम इन मामलों को कवर करें: एक की के साथ 50 समवर्ती अनुरोध एक ऑर्डर और एक आउटबॉक्स इवेंट उत्पन्न करते हैं; विभिन्न पैरामीटर वाली समान की एक टकराव लौटाती है; कमिट के बाद लेकिन रिस्पॉन्स से पहले एक जबरन डिस्कनेक्ट अभी भी मूल ऑर्डर को रीप्ले करता है; एक लेनदेन रोलबैक के बाद समान की पुनः प्रयास कर सकती है; की समाप्ति के बाद का व्यवहार अनुबंध से मेल खाता है; समान नग्न की का उपयोग करने वाले दो टेनेंट अलग रहते हैं; और उपभोक्ता को डुप्लिकेट डिलीवरी चार्ज को डुप्लिकेट नहीं करती है। नए दावों, रीप्ले, पैरामीटर टकराव, समवर्ती प्रतीक्षा टाइमआउट, सबसे पुराने अधूरे ऑपरेशन, लेनदेन रोलबैक, आउटबॉक्स अंतराल (lag), और समाप्त-की सफ़ाई की निगरानी करें। लॉग में किसी की से जुड़े संवेदनशील अनुरोध बॉडी शामिल नहीं होने चाहिए।

मजबूत नमूना उत्तर

“मैं एक idempotency key को POST /orders पर एक टेनेंट के लिए एक निर्माण इरादे के रूप में परिभाषित करूँगा। क्लाइंट इसे पहली कॉल पर उत्पन्न करता है और इसे टाइमआउट पुनः प्रयासों के लिए रखता है। सर्वर API संस्करण के साथ मान्य ऑर्डर फ़ील्ड का फ़िंगरप्रिंट बनाता है। वही की और फ़िंगरप्रिंट रीप्ले कर सकते हैं; एक अलग फ़िंगरप्रिंट के साथ वही की एक टकराव (conflict) है।

PostgreSQL में, मैं (tenant_id, endpoint, idempotency_key) को प्राइमरी की बनाऊँगा। अनुरोध सत्यापन के बाद, प्रत्येक कॉल INSERT ... ON CONFLICT DO NOTHING के साथ स्वामित्व के लिए प्रतिस्पर्धा करती है। विजेता कमिट करने से पहले एक छोटे लेनदेन में ऑर्डर बनाता है, आउटबॉक्स पंक्ति लिखता है, और पहला स्टेटस और रिस्पॉन्स बॉडी संग्रहीत करता है। हारने वाला दूसरा ऑर्डर नहीं बना सकता। यह पहले लेनदेन की प्रतीक्षा करता है, रिकॉर्ड पढ़ता है, और मेल खाने वाले फ़िंगरप्रिंट के लिए मूल परिणाम लौटाता है। यदि आंतरिक प्रतीक्षा सीमा पूरी हो जाती है, तो यह कॉलर को बताता है कि ऑपरेशन अभी भी प्रगति पर है। इसलिए कमिट के बाद लेकिन HTTP रिस्पॉन्स से पहले क्रैश होने पर पुनः प्रयास करने पर वही ऑर्डर पुनर्प्राप्त हो जाता है।

मैं उस लेनदेन के अंदर समकालिक रूप से (synchronously) भुगतान या इन्वेंट्री को कॉल नहीं करूँगा। एक आउटबॉक्स उपभोक्ता उन क्रियाओं को डिलीवर करता है और प्रत्येक डाउनस्ट्रीम ऑपरेशन को ऑर्डर आईडी से प्राप्त एक की पास करता है। Idempotency रिकॉर्ड केवल अधिकतम पुनः प्रयास विंडो के लिए मौजूद होता है, इसलिए यदि किसी चेकआउट सत्र को कभी भी दूसरा ऑर्डर नहीं बनाना चाहिए, तो checkout_id को ऑर्डर पर एक स्थायी यूनिक बाधा भी मिलती है।

मैं समवर्तीता, डिस्कनेक्ट, और डुप्लिकेट-मैसेज दोष इंजेक्शन (fault injection) के साथ समाप्त करूँगा। मैं केवल HTTP 200 की जाँच करने के बजाय ऑर्डर, idempotency परिणाम और आउटबॉक्स इवेंट की संख्या का दावा (assert) करूँगा। उत्पादन में, मैं रीप्ले दर, समान-की पैरामीटर टकराव, समवर्ती प्रतीक्षा टाइमआउट, लेनदेन रोलबैक और आउटबॉक्स अंतराल पर नज़र रखूँगा।”

सामान्य गलतियाँ

  • एक पंक्ति की जाँच करना, फिर ऑर्डर सम्मिलित करना → दो अनुरोध दोनों कोई पंक्ति न देख सकते हैं और दोनों लिख सकते हैं → डेटाबेस विशिष्टता बाधा और परमाणु टकराव प्रबंधन को स्वामी चुनने दें।
  • केवल idempotency key खोजना → टेनेंट आपस में टकरा सकते हैं या किसी अन्य टेनेंट का रिस्पॉन्स भी प्राप्त कर सकते हैं → प्रमाणित टेनेंट और एंडपॉइंट द्वारा स्कोप करें, और रीप्ले पर फिर से अधिकृत करें।
  • मापदंडों की तुलना किए बिना पुराना परिणाम लौटाना → आकस्मिक की पुन: उपयोग एक पुराने ऑर्डर को एक नए जैसा दिखाता है → एक सामान्यीकृत अनुरोध फ़िंगरप्रिंट संग्रहीत करें और बेमेल होने पर अस्वीकार करें।
  • ऑर्डर कमिट करना, फिर idempotency परिणाम लिखना → राइट्स के बीच एक क्रैश बिना किसी डीडुप्लिकेशन रिकॉर्ड के एक ऑर्डर छोड़ देता है → ऑर्डर, परिणाम, और आउटबॉक्स को एक डेटाबेस लेनदेन में कमिट करें।
  • डेटाबेस लेनदेन के अंदर भुगतान सेवा को कॉल करना → नेटवर्क टाइमआउट लॉक अवधि को बढ़ाते हैं, और एक सफल भुगतान को स्थानीय रूप से परमाणु रूप से वापस (rollback) नहीं किया जा सकता है → लेनदेन में एक आउटबॉक्स प्रविष्टि लिखें और इसके बाहर डाउनस्ट्रीम idempotency का उपयोग करें।
  • केवल TTL वाले Redis लॉक का उपयोग करना → क्रैश या समाप्ति के बाद, यह प्रकट नहीं कर सकता कि ऑर्डर कमिट हुआ या परिणाम रीप्ले हुआ → शुद्धता के लिए डेटाबेस बाधा का उपयोग करें और केवल त्वरण (acceleration) के लिए कैश का उपयोग करें।
  • प्रत्येक त्रुटि को कैश करना → एक सही किए गए अनुरोध को अभी भी एक पुरानी सत्यापन त्रुटि प्राप्त हो सकती है, जबकि एक क्षणिक विफलता का पुनः प्रयास करना असंभव हो जाता है → स्वामित्व से पहले की सत्यापन विफलताओं को संग्रहीत न करें; स्थिर डोमेन परिणामों को गैर-कमिटेड विफलताओं से अलग करें।
  • अनुरोध रिकॉर्ड हटाने के बाद स्थायी डीडुप्लिकेशन का वादा करना → एक समाप्त हो चुकी की को एक नए ऑपरेशन के रूप में माना जाता है → अवधारण विंडो प्रकाशित करें और स्थायी अपरिवर्तनीयता के लिए एक डोमेन यूनिक की का उपयोग करें।

फॉलो-अप प्रश्न और उत्तर

फॉलो-अप 1: जब तक पहला अनुरोध कमिट नहीं हुआ है, तब तक दूसरे अनुरोध को क्या प्राप्त होना चाहिए?

शॉर्ट-ट्रांजेक्शन डिज़ाइन के साथ, यूनिक-इंडेक्स टकराव दूसरे इंसर्ट को प्रतीक्षा कराता है। डेटाबेस प्रतीक्षा और एंडपॉइंट निष्पादन दोनों को क्लाइंट टाइमआउट से नीचे सीमित करें। यदि पहला लेनदेन समय पर कमिट हो जाता है, तो दूसरा अनुरोध इसे पढ़ता है और रीप्ले करता है। सीमा पर, ऑर्डर बनाने के बजाय एक पहचानने योग्य idempotency_in_progress परिणाम और पुनः प्रयास समय लौटाएं। एक लंबा कार्य कमिट की गई IN_PROGRESS स्थिति और एक स्टेटस एंडपॉइंट का उपयोग करता है ताकि HTTP कनेक्शन व्यस्त न रहे।

फॉलो-अप 2: क्या होता है यदि सेवा कमिट के बाद लेकिन HTTP रिस्पॉन्स लिखने से पहले क्रैश हो जाती है?

इसलिए परिणाम संग्रहीत किया जाता है। ऑर्डर, आउटबॉक्स इवेंट और रिस्पॉन्स स्नैपशॉट एक साथ कमिट हुए हैं। पुनः प्रयास यूनिक की पर टकराता है, फ़िंगरप्रिंट को सत्यापित करता है, और संग्रहीत स्थिति और बॉडी लौटाता है। केवल एक “processed” बूलियन सर्वर को यह नहीं बताएगा कि कौन सा ऑर्डर वापस करना है, और कॉलर सफलता को अज्ञात परिणाम से अलग नहीं कर सकता है।

फॉलो-अप 3: क्या होगा यदि समान idempotency key एक अलग राशि (amount) ले जाती है?

409 idempotency_key_reused लौटाएं, एक टकराव मीट्रिक रिकॉर्ड करें, और ऑर्डर निर्माण निष्पादित न करें। पुराने रिकॉर्ड को नए पैरामीटर के साथ कभी अधिलेखित न करें या चुपचाप पुराना ऑर्डर न लौटाएं। फ़िंगरप्रिंट में राशि, मुद्रा, आइटम, शिपिंग-संदर्भ फ़ील्ड और व्यावसायिक परिणाम को बदलने वाले हर दूसरे फ़ील्ड के साथ-साथ API या कैनोनिकलाइजेशन संस्करण शामिल होना चाहिए।

फॉलो-अप 4: लंबे समय तक चलने वाला एक कार्यकर्ता रुक जाता है, दूसरा कार्यकर्ता उसकी समाप्त हो चुकी लीज पर कब्जा कर लेता है, और पुराना कार्यकर्ता फिर से शुरू हो जाता है। बासी राइट्स (stale writes) को क्या रोकता है?

प्रत्येक टेकओवर पर attempt_token बढ़ाएँ। ऑर्डर-स्टेट परिवर्तन, पूर्णता राइट्स और नियंत्रणीय डाउनस्ट्रीम कार्य उस टोकन को ले जाते हैं और केवल तभी अपडेट होते हैं जब यह वर्तमान मान से मेल खाता है। पुराने कार्यकर्ता के फिर से शुरू होने के बाद उसका राइट विफल हो जाता है, इसलिए वह नई स्थिति को पढ़ता है और बाहर निकल जाता है। यदि कोई डाउनस्ट्रीम सिस्टम टोकन को सत्यापित नहीं कर सकता है, तो उसे अभी भी एक स्थिर व्यावसायिक ऑपरेशन आईडी या समाधान और क्षतिपूर्ति प्रक्रिया द्वारा की (key) किए गए एक idempotent API की आवश्यकता होती है। लीज अकेले पहले से भेजे गए बाहरी साइड इफ़ेक्ट को वापस नहीं ले सकती है।

फॉलो-अप 5: केवल orders.checkout_id पर एक विशिष्टता बाधा का उपयोग क्यों न करें?

वह बाधा दूसरे ऑर्डर को रोकती है और एक उत्कृष्ट स्थायी बैकस्टॉप है। यह परिभाषित नहीं करता है कि क्या एक idempotency key के तहत विभिन्न पैरामीटर टकराते हैं, पहले अनुरोध ने क्या स्थिति लौटाई, समवर्ती कार्य के दौरान कैसे रिस्पॉन्स दिया जाए, या checkout_id के बिना कॉल करने वालों के लिए क्या किया जाए। Idempotency रिकॉर्ड एक अनुरोध-स्तरीय प्रोटोकॉल और परिणाम रीप्ले प्रदान करता है; व्यावसायिक यूनिक की डोमेन अपरिवर्तनीयता की रक्षा करती है। वे अलग-अलग स्तरों पर काम करते हैं।

सार्वजनिक स्रोत

संबंधित प्रश्न