১২ মে, ২০২৫·6 মিনিট পড়তে

Go-তে আইডেম্পোটেন্ট এন্ডপয়েন্ট: কী, ডেডুপ টেবিল, রিট্রাই

Go-তে idempotent এন্ডপয়েন্ট ডিজাইন করুন: idempotency কী, dedup টেবিল এবং পেমেন্ট, ইম্পোর্ট ও ওয়েবহুকের জন্য রিট্রাই-সেইফ হ্যান্ডলার কিভাবে তৈরি করবেন।

Go-তে আইডেম্পোটেন্ট এন্ডপয়েন্ট: কী, ডেডুপ টেবিল, রিট্রাই

কেন রিট্রাই ডুপ্লিকেট তৈরি করে (এবং কেন আইডেম্পোটেন্সি গুরুত্বপূর্ণ)\n\nরিট্রাই ঘটে এমনকি যখন কিছু "ভুল" হয় না। ক্লায়েন্ট টাইমআউট পায় যখন সার্ভার এখনও কাজ করছে। একটি মোবাইল কানেকশন পড়ে যায় এবং অ্যাপ পুনরায় চেষ্টা করে। একটি জব রানার 502 পায় এবং স্বয়ংক্রিয়ভাবে একই অনুরোধটি আবার পাঠায়। অ্যাট-লিস্ট-ওয়ান ডেলিভারি (কিউ এবং ওয়েবহুকগুলিতে সাধারণ) থাকলে ডুপ্লিকেট স্বাভাবিক।\n\nএমনই কারণে আইডেম্পোটেন্সি গুরুত্বপূর্ণ: পুনরাবৃত্ত অনুরোধগুলো কেবল একবার কল করার মতোই চূড়ান্ত ফলাফল দেওয়া উচিত।\n\nকয়েকটি শব্দ সহজেই গুলিয়ে ফেলা যায়:\n\n- Safe: কল করলে স্টেট বদলায় না (উদাহরণ, একটি পড়া)।\n- Idempotent: একাধিকবার কল করলে একবার কল করার সমান প্রভাব রাখে।\n- At-least-once: পাঠক যতক্ষণ না “চাঙ্গা” হয় ততক্ষণ সেন্টার পুনরাবৃত্তি করে, তাই রিসিভারের কাছে ডুপ্লিকেট হ্যান্ডল করার দায় থাকে।\n\nআইডেম্পোটেন্সি না থাকলে রিট্রাই প্রকৃত ক্ষতি ঘটাতে পারে। একটি পেমেন্ট এন্ডপয়েন্ট দুটি চার্জ করতে পারে যদি প্রথম চার্জ সফল হয় কিন্তু ক্লায়েন্ট পর্যন্ত রেসপন্স পৌঁছায় না। একটি ইম্পোর্ট এন্ডপয়েন্ট কাজটি টাইমআউটের পরে পুনরায় চেষ্টা করলে ডুপ্লিকেট সারি তৈরি করতে পারে। একটি ওয়েবহুক হ্যান্ডলার একই ইভেন্ট দুইবার প্রসেস করে দুটি ইমেইল পাঠাতে পারে।\n\nমূল পয়েন্ট: আইডেম্পোটেন্সি একটি API চুক্তি, একটি গোপন বাস্তবায়ন বিবরণ নয়। ক্লায়েন্টরা জানতে হবে কী তারা রিট্রাই করতে পারে, কী পাঠাতে হবে, এবং ডুপ্লিকেট ধরা পড়লে কী রেসপন্স আশা করা যায়। আপনি যদি আচরণ নির্বিঘ্নে বদলান, তাহলে রিট্রাই লজিক ভেঙে পড়ে এবং নতুন ব্যর্থতার উপায় তৈরি হয়।\n\nআইডেম্পোটেন্সি মনিটরিং ও রিকনসিলিয়েশনের বিকল্প নয়। ডুপ্লিকেট রেট ট্র্যাক করুন, “replay” সিদ্ধান্ত লগ করুন, এবং সময়ে সময়ে বাইরের সিস্টেম (যেমন একটি পেমেন্ট প্রোভাইডার) আপনার ডাটাবেসের সাথে মিলিয়ে দেখুন।\n\n## প্রতিটি এন্ডপয়েন্টের জন্য আইডেম্পোটেন্সির স্কোপ এবং নিয়ম নির্ধারণ করুন\n\nটেবিল বা মিডলওয়্যার যোগ করার আগে সিদ্ধান্ত নিন “একই অনুরোধ” কী বোঝায় এবং ক্লায়েন্ট যখন রিট্রাই করে সার্ভার কী প্রতিশ্রুতি দেবে।\n\nবেশি সমস্যা POST-এ দেখা যায় কারণ এটি প্রায়ই কিছু তৈরি করে বা সাইড-এফেক্ট ট্রিগার করে (কার্ড চার্জ করা, একটি মেসেজ পাঠানো, একটি ইম্পোর্ট শুরু করা)। PATCH-ও আইডেম্পোটেন্সি দরকার হতে পারে যদি তা সাইড-এফেক্ট ট্রিগার করে, শুধু একটি ফিল্ড আপডেট না হয়। GET স্টেট বদলানো উচিত নয়।\n\n### স্কোপ নির্ধারণ: কোথায় একটি কী ইউনিক\n\nআপনার ব্যবসায়িক নিয়মের সঙ্গে মিল রেখে একটি স্কোপ বেছে নিন। খুব বিস্তৃত হলে বৈধ কাজ ব্লক হবে; খুব সংকীর্ণ হলে ডুপ্লিকেট ঘটবে।\n\nসাধারণ স্কোপ:\n\n- per endpoint + customer\n- per endpoint + external object (উদাহরণস্বরূপ, invoice_id বা order_id)\n- per endpoint + tenant (মাল্টি-টেন্যান্সি সিস্টেমের জন্য)\n- per endpoint + payment method + amount (শুধু যদি আপনার প্রোডাক্ট নীতিতে সেটা ঠিক থাকে)\n\nউদাহরণ: “Create payment” এন্ডপয়েন্টের জন্য কীকে প্রতিটি কাস্টমারের কাছে ইউনিক করুন। “Ingest webhook event” এর জন্য, সেটিকে payment provider-এর ইভেন্ট ID (প্রোভাইডারের গ্লোবাল ইউনিক) স্কোপ করুন।\n\n### ডুপ্লিকেটে আপনি কী ফেরত দেবেন তা নির্ধারণ করুন\n\nএকটি ডুপ্লিকেট এলে প্রথম সফল প্রচেষ্টার একই আউটকাম ফেরত দিন। বাস্তবে এর মানে হলো একই HTTP স্ট্যাটাস কোড এবং একই রেসপন্স বডি (অথবা অন্তত একই resource ID ও state) রিলেপ করা।\n\nক্লায়েন্টরা এর ওপর নির্ভর করে। যদি প্রথম চেষ্টা সফল হয় কিন্তু নেটওয়ার্ক পড়ে যায়, রিট্রাই দ্বিতীয় চার্জ বা দ্বিতীয় ইম্পোর্ট জব তৈরি করা উচিত নয়।\n\n### একটি রিটেনশন উইন্ডো বেছে নিন\n\nকীগুলো মেয়াদ শেষ হওয়া উচিত। সেগুলো পর্যাপ্ত সময় রাখুন যাতে বাস্তবসম্মত রিট্রাই এবং বিলম্বিত কাজ কভার হয়।\n\n- Payments: 24 থেকে 72 ঘন্টা সাধারণ\n- Imports: ব্যবহারকারীরা পরে আবার চেষ্টা করতে পারে, তাই এক সপ্তাহ যুক্তিযুক্ত হতে পারে\n- Webhooks: প্রোভাইডারের রিট্রাই পলিসির সাথে মিলান\n\n### “একই অনুরোধ” কীভাবে নির্ধারণ করবেন: স্পষ্ট কী বনাম বডি হ্যাশ\n\nএকটি স্পষ্ট idempotency key (হেডার বা ক্ষেত্র) সাধারণত সবচেয়ে পরিষ্কার নিয়ম।\n\nএকটি বডি হ্যাশ ব্যাকআপ হিসেবে সাহায্য করতে পারে, কিন্তু তা সহজেই ভাঙে মায়ামি পরিবর্তনের ফলে (ফিল্ড অর্ডার, হোয়াইটস্পেস, টাইমস্ট্যাম্প)। হ্যাশ ব্যবহার করলে ইনপুট নরমালাইজ করুন এবং কোন ফিল্ডগুলিকে অন্তর্ভুক্ত করা হবে তা কঠোরভাবে ঠিক রাখুন।\n\n## আইডেম্পোটেন্সি কী: বাস্তবে কিভাবে কাজ করে\n\nএকটি idempotency key ক্লায়েন্ট এবং সার্ভারের মধ্যে একটি সহজ চুক্তি: “আপনি যদি এই কী আবার দেখেন, একই অনুরোধ হিসেবে বিবেচনা করুন।” retry-safe API-এর জন্য এটা সবচেয়ে ব্যবহারযোগ্য টুলগুলোর মধ্যে একটি।\n\nকী দু’পক্ষ থেকেই আসতে পারে, কিন্তু অধিকাংশ API-র জন্য এটি ক্লায়েন্ট-জেনারেটেড হওয়া উচিত। ক্লায়েন্টই জানে কখন এটি একই অ্যাকশন পুনরায় চেষ্টা করছে, তাই এটি একই কী পুনরায় ব্যবহার করতে পারে। সার্ভার-জেনারেট কী তখনই সাহায্য করে যখন আপনি প্রথমে একটি "ড্রাফট" রিসোর্স (উদাহরণ: একটি ইম্পোর্ট জব) তৈরি করেন এবং পরে ক্লায়েন্টদের সেই জব আইডি রেফারেন্স করে পুনরায় চেষ্টা করতে দেন, কিন্তু সেগুলো প্রথম অনুরোধে সহায়ক নয়।\n\nর্যান্ডম, অনুমানযোগ্য নয় এমন স্ট্রিং ব্যবহার করুন। কমপক্ষে 128 বিট র‍্যান্ডমনেস লক্ষ্য করুন (উদাহরণ: 32 hex অক্ষর বা একটি UUID)। টাইমস্ট্যাম্প বা user IDs থেকে কী বানাবেন না।\n\nসার্ভারে, কীটি এমন প্রসঙ্গের সাথে স্টোর করুন যাতে কুড়ানো ব্যবহার ও মূল ফলাফল রিলেপ করার জন্য যথেষ্ট তথ্য থাকে:\n\n- যে কল করেছে (account বা user ID)\n- কোন এন্ডপয়েন্ট বা অপারেশনের জন্য এটি প্রযোজ্য\n- গুরুত্বপূর্ণ অনুরোধ ফিল্ডগুলোর একটি হ্যাশ\n- বর্তমান স্ট্যাটাস (in-progress, succeeded, failed)\n- রিলেপ করার জন্য রেসপন্স (স্ট্যাটাস কোড ও বডি)\n\nএকটি কী সাধারণত স্কোপড হওয়া উচিত, সাধারণত user (অথবা API token) প্লাস এন্ডপয়েন্ট। যদি একই কী ভিন্ন পে-লোড নিয়ে পুনরায় পাঠানো হয়, স্পষ্ট একটি ত্রুটি ফিরিয়ে দিন। এটি দুর্ঘটনাজনিত কোলিশন প্রতিরোধ করে যেখানে একটি বাগি ক্লায়েন্ট পুরনো কী ব্যবহার করে নতুন পেমেন্ট অ্যামাউন্ট পাঠায়।\n\nরিলেপে, প্রথম সফল প্রচেষ্টার একই ফলাফল ফেরত দিন — একই HTTP স্ট্যাটাস কোড এবং একই রেসপন্স বডি, কাঁচা রিড না যেটা সময়ের সাথে বদলাতে পারে।\n\n## PostgreSQL-এ ডেডুপ টেবিল: একটি সরল, নির্ভরযোগ্য প্যাটার্ন\n\nএকটি নিবেদিত deduplication টেবিল আইডেম্পোটেন্সি বাস্তবায়নের সবচেয়ে সহজ উপায়গুলোর মধ্যে একটি। প্রথম অনুরোধ idempotency কী-এর জন্য একটি রো তৈরি করে। প্রতিটি রিট্রাই সেই একই রো পড়ে এবং সংরক্ষিত ফলাফল ফেরত দেয়।\n\n### কী সংরক্ষণ করবেন\n\nটেবিলটি ছোট ও ফোকাসড রাখুন। একটি সাধারণ কাঠামো:\n\n- key: idempotency key (text)\n- owner: যে কীটির মালিক (user_id, account_id, বা API client ID)\n- request_hash: গুরুত্বপূর্ণ অনুরোধ ক্ষেত্রগুলোর একটি হ্যাশ\n- response: চূড়ান্ত রেসপন্স পে-লোড (প্রায়শই JSON) অথবা স্টোর করা ফলাফলের পয়েন্টার\n- created_at: কখন কী প্রথম দেখা গেছে\n\nইউনিক কনস্ট্রেইন্টই প্যাটার্নের কোর। (owner, key)-এ ইউনিকন্স ENFORCE করুন যাতে এক ক্লায়েন্ট ডুপ্লিকেট তৈরি না করে, এবং দুইটি ভিন্ন ক্লায়েন্ট কোলাইড না করে।\n\nrequest_hash-ও রাখুন যাতে কী মিসইউস শনাক্ত করেন। যদি পুনরায় আসা একই কী নিয়ে কিন্তু ভিন্ন হ্যাশ থাকে, তাতে একটি ত্রুটি ফেরত দিন যাতে দুই ভিন্ন অপারেশন এক কীর মধ্যে মিশে না যায়।\n\n### রিটেনশন এবং ইনডেক্সিং\n\nDedup রো অনন্তকালের জন্য থাকা উচিত নয়। বাস্তব রিট্রাই উইন্ডো পর্যাপ্ত সময় রাখুন, তারপর সেগুলো ক্লিনআপ করুন।\n\nলোডের নিচে দ্রুততার জন্য:\n\n- দ্রুত insert বা lookup-এর জন্য (owner, key)-এ ইউনিক ইনডেক্স\n- ক্লিনআপ সস্তা করতে created_at-এ ঐচ্ছিক ইনডেক্স\n\nযদি রেসপন্স বড় হয়, একটি পয়েন্টার (উদাহরণ: result ID) সংরক্ষণ করুন এবং পূর্ণ পে-লোড অন্যত্র রাখুন। এতে টেবিল বেলোয়িং কমে এবং retry আচরণ ধারাবাহিক থাকে।\n\n## ধাপে ধাপে: Go-তে একটি retry-safe হ্যান্ডলার ফ্লো\n\nএকটি retry-safe হ্যান্ডলারের দুটি জিনিস লাগে: “একই অনুরোধ আবার” শনাক্ত করার একটি স্থিতিশীল উপায়, এবং প্রথম আউটকাম টিকে টেকসইভাবে সংরক্ষণ করার জায়গা যাতে আপনি তা রিলেপ করতে পারেন।\n\nপেমেন্ট, ইম্পোর্ট এবং ওয়েবহুক ইনজেশন-এর জন্য একটি ব্যবহারিক ফ্লো:\n\n1) অনুরোধ ভ্যালিডেট করুন, তারপর তিনটি মান অবতারণা করুন: একটি idempotency key (হেডার বা ক্লায়েন্ট ক্ষেত্র থেকে), একটি owner (tenant বা user ID), এবং একটি request hash (গুরুত্বপূর্ণ ফিল্ডগুলোর হ্যাশ)।\n\n2) একটি ডাটাবেজ ট্রান্সঅ্যাকশন শুরু করে dedup রেকর্ড তৈরি করার চেষ্টা করুন। এটিকে (owner, key)-এ ইউনিক করুন। request_hash, স্ট্যাটাস (started, completed) এবং রেসপন্সের প্লেসহোল্ডার স্টোর করুন।\n\n3) যদি insert কনফ্লিক্ট করে, বিদ্যমান রো লোড করুন। যদি সেটা completed হয়, সংরক্ষিত রেসপন্স ফেরত দিন। যদি এটা started হয়, সংক্ষেপে অপেক্ষা করুন (সিম্পল পোলিং) অথবা 409/202 ফেরত দিন যাতে ক্লায়েন্ট পরে আবার চেষ্টা করে।\n\n4) কেবল তখনই যখন আপনি সফলভাবে dedup রো “own” করেন, বিজনেস লজিক একবার চালান। সম্ভব হলে একই ট্রান্সঅ্যাকশনের ভিতরেই সাইড-এফেক্ট লিখুন। বিজনেস রেজাল্ট এবং HTTP রেসপন্স (স্ট্যাটাস কোড ও বডি) স্থায়ী করুন।\n\n5) কমিট করুন, এবং idempotency key ও owner দিয়ে লগ করুন যাতে সাপোর্ট ডুপ্লিকেট ট্রেস করতে পারে।\n\nএকটি মিনি মাল টেবিল প্যাটার্ন:\n\nsql\ncreate table idempotency_keys (\n owner_id text not null,\n idem_key text not null,\n request_hash text not null,\n status text not null,\n response_code int,\n response_body jsonb,\n created_at timestamptz not null default now(),\n updated_at timestamptz not null default now(),\n primary key (owner_id, idem_key)\n);\n\n\nউদাহরণ: একটি “Create payout” এন্ডপয়েন্ট চার্জ করে টাইমআউট পায়। ক্লায়েন্ট একই কী নিয়ে রিট্রাই করে। আপনার হ্যান্ডলার কনফ্লিক্ট দেখে completed রেকর্ড দেখবে এবং মূল payout ID ফেরত দেবে, আবার চার্জ করবে না।\n\n## পেমেন্ট: টাইমআউটের পরও ঠিকভাবে একবারই চার্জ করা\n\nপেমেন্ট হল সেই জায়গা যেখানে আইডেম্পোটেন্সি অপশনাল নয়। নেটওয়ার্ক ব্যর্থ হয়, মোবাইল অ্যাপ রিট্রাই করে, এবং গেটওয়ে মাঝে মাঝে টাইমআউট হয়ে যায় যদিও তারা ইতিমধ্যেই চার্জ তৈরি করেছে।\n\nএকটি ব্যবহারিক নিয়ম: idempotency key চার্জ তৈরিকে রক্ষ করে, এবং payment provider ID (charge/intent ID) পরে সত্যের উৎস হয়ে ওঠে। একবার আপনি provider ID সংরক্ষণ করে নিলে একই অনুরোধের জন্য নতুন চার্জ করবেন না।\n\nএকটি প্যাটার্ন যা রিট্রাই এবং গেটওয়ে অনিশ্চয়তা হ্যান্ডল করে:\n\n- idempotency key পড়ুন ও ভ্যালিডেট করুন।\n- একটি ডাটাবেজ ট্রান্সঅ্যাকশনে (merchant_id, idempotency_key) দ্বারা কী-ভিত্তিক একটি payment রো তৈরি বা রিফেচ করুন। যদি এতে provider_id থাকে, সংরক্ষিত রেজাল্ট ফেরত দিন।\n- যদি provider_id না থাকে, গেটওয়েতে PaymentIntent/Charge তৈরি করতে কল করুন।\n- গেটওয়ে সফল হলে provider_id স্থায়ী করুন এবং payment কে “succeeded” (অথবা “requires_action”) হিসেবে মার্ক করুন।\n- যদি গেটওয়ে টাইমআউট দেয় বা অজানা রেজাল্ট দেয়, স্ট্যাটাস “pending” সংরক্ষণ করুন এবং ক্লায়েন্টকে একটি ধারাবাহিক রেসপন্স দিন যাতে পুনরায় চেষ্টা করা নিরাপদ।\n\nটাইমআউট কিভাবে ট্রিট করবেন তা হল মূল বিষয়: ফেল ধরবেন না। payment কে pending হিসেবে চিহ্নিত করুন, পরে গেটওয়ের সাথে কনফার্ম করুন (অথবা webhook মারফত) যখন আপনার কাছে provider ID থাকে।\n\nএরর রেসপন্সগুলো পূর্বানুমানযোগ্য হওয়া উচিত। ক্লায়েন্টরা আপনি যা রিটার্ন করবেন সেটার উপর ভিত্তি করে রিট্রাই লজিক বানায়, তাই স্ট্যাটাস কোড ও এরর শেপ স্থির রাখুন।\n\n## ইম্পোর্ট এবং ব্যাচ এন্ডপয়েন্ট: অগ্রগতি হারানো ছাড়াই ডেডাপ\n\nইম্পোর্টেই ডুপ্লিকেট সবচেয়ে বেশি ক্ষতি করে। একজন ব্যবহারকারী CSV আপলোড করে, আপনার সার্ভার 95% এ টাইমআউট করে, এবং তারা রিট্রাই করলে কি হবে? পরিকল্পনা না থাকলে আপনি হয় ডুপ্লিকেট সারি তৈরি করবেন অথবা তাদের আবার শুরু করতে বলবেন।\n\nব্যাচ কাজের জন্য দুই স্তরের কথা ভাবুন: ইম্পোর্ট জব এবং তার ভিতরের আইটেমগুলো। জব-লেভেল আইডেম্পোটেন্সি একই অনুরোধ থেকে একাধিক জব তৈরি হওয়া বন্ধ করে। আইটেম-লেভেল আইডেম্পোটেন্সি একই সারি দুইবার প্রয়োগ হওয়া রোধ করে।\n\nজব-লেভেলের প্যাটার্ন হলো প্রত্যেক ইম্পোর্ট রিকুয়েস্টে একটি idempotency key প্রয়োজন (অথবা একটি স্থিতিশীল request hash + user ID থেকে একটিকে ডেরাইভ করা)। এটিকে import_job রেকর্ডের সাথে সংরক্ষণ করুন এবং রিট্রাই হলে একই job ID ফেরত দিন। হ্যান্ডলার বলা উচিত, “আমি এই জবটি দেখেছি, এটাই এর বর্তমান স্টেট,” বলার পরিবর্তে “আবার শুরু করুন।”\n\nআইটেম-লেভেল ডেডাপে, ডেটাতে ইতিমধ্যে থাকা একটি ন্যাচারাল কী-র উপর নির্ভর করুন। উদাহরণস্বরূপ, প্রতিটি সারিতে সোর্স সিস্টেমের একটি external_id থাকতে পারে, বা একটি স্থিতিশীল কম্বো যেমন (account_id, email)। PostgreSQL-এ ইউনিক কনস্ট্রেইন্ট প্রয়োগ করুন এবং upsert ব্যবহার করুন যাতে রিট্রাইগুলো ডুপ্লিকেট তৈরি না করে।\n\nশিপ করার আগে সিদ্ধান্ত নিন একটি রিলেপ কি করে যখন একটি সারি ইতিমধ্যেই আছে। স্পষ্ট রাখুন: স্কিপ করবেন, নির্দিষ্ট ফিল্ড আপডেট করবেন, বা ফেল করবেন। স্পষ্ট নিয়ম না থাকলে “merge” এড়িয়ে চলুন।\n\nআংশিক সাফল্য স্বাভাবিক। সবকিছু “ok” বা “failed” না করে, জব-লেভেলে প্রতিটি সারির আউটকাম স্টোর করুন: row number, natural key, status (created, updated, skipped, error), এবং একটি এরর মেসেজ। রিট্রাই হলে আপনি নিরাপদে পুনরায় চালাতে পারবেন এবং যেসব সারি ইতিমধ্যেই শেষ হয়েছে তাদের একই ফলাফল রাখবেন।\n\nইম্পোর্টগুলি restartable করতে, চেকপয়েন্ট যোগ করুন। পেজে প্রসেস করুন (উদাহরণ: প্রতি পেজ 500 সারি), প্রতিটি পেজ কমিট করার পরে শেষ প্রসেস হওয়া কার্সর (row index বা source cursor) সংরক্ষণ করুন। প্রসেস ক্র্যাশ করলে পরবর্তী চেষ্টা শেষ চেকপয়েন্ট থেকে শুরু করবে।\n\n## ওয়েবহুক ইনজেশন: ডেডাপ, ভ্যালিডেট, তারপর নিরাপদে প্রসেস করুন\n\nওয়েবহুক প্রোভাইডাররা রিট্রাই করে। তারা আউট-অফ-অর্ডারে ইভেন্ট পাঠায়। যদি আপনার হ্যান্ডলার প্রতিটি ডেলিভারিতে স্টেট আপডেট করে, আপনি অবশ্যম্ভাবীভাবে শেষে রেকর্ড ডুবল-ক্রিয়েট, ডুবল-ইমেইল পাঠানো, বা ডুবল-চার্জ পেয়ে যাবেন।\n\nশুরুতেই সেরা ডেডাপ কী বেছে নিন। যদি প্রোভাইডার আপনাকে একটি ইউনিক ইভেন্ট ID দেয়, সেটি ব্যবহার করুন। যখন প্রোভাইডার ইভেন্ট ID না দেয়, তখন পলোডের হ্যাশ fallback হিসেবে নিন।\n\nসিকিউরিটি প্রথম: সিগনেচার যাচাই করুন আগে কী-টা গ্রহণ করা। সিগনেচার ফেল করলে অনুরোধ প্রত্যাখ্যান করুন এবং একটি dedup রেকর্ড লিখবেন না — অন্যথায় একজন অ্যাটাকার একটি ইভেন্ট ID “রিজার্ভ” করে ভবিষ্যতে আসল ইভেন্ট ব্লক করতে পারে।\n\nরিট্রাইয়ের সময় নিরাপদ ফ্লো:\n\n- সিগনেচার এবং বেসিক শেপ যাচাই করুন (প্রয়োজনীয় হেডার, event ID)\n- event ID একটি dedup টেবিলে ইউনিক কনস্ট্রেইন্টসহ insert করুন\n- যদি insert ডুপ্লিকেটে ব্যর্থ হয়, সঙ্গে সঙ্গে 200 ফেরত দিন\n- অডিট ও ডিবাগিংয়ের জন্য র.Raw পে-লোড (এবং হেডার) সংরক্ষণ করুন যখন দরকার\n- প্রসেসিং এনকিউ করুন এবং দ্রুত 200 ফেরত দিন\n\nদ্রুতভাবে acknowlege করা গুরুত্বপূর্ণ কারণ অনেক প্রোভাইডারের ছোট টাইমআউট থাকে। রিকুয়েস্টে সবচেয়ে ছোট নির্ভরযোগ্য কাজটি করুন: verify, dedup, persist। তারপর asynchronousভাবে প্রসেস করুন (ওয়ার্কার, কিউ, ব্যাকগ্রাউন্ড জব)। যদি অ্যাসিঙ্ক না করতে পারেন, একই event ID দিয়ে অভ্যন্তরীণ সাইড-এফেক্টগুলিকে কী করে কীগুলি ধরে রেখে প্রসেসিংকে আইডেম্পোটেন্ট রাখুন।\n\nআউট-অফ-অর্ডার ডেলিভারি স্বাভাবিক। ধরে নেবেন না “created” আগে আসে “updated” থেকে। এক্সটারনাল অবজেক্ট ID-তে upsert পছন্দ করুন এবং শেষ প্রক্রিয়াকৃত event timestamp বা version ট্র্যাক করুন।\n\nর কাঁচা পে-লোড সংরক্ষণ করলে গ্রাহক বলে “আমরা আপডেট পাইনি” বললে সাহায্য করে। আপনি একবার বাগ ঠিক করলে সংরক্ষিত বডি থেকে প্রসেসিং পুনরায় চালাতে পারবেন, প্রোভাইডারকে আবার রিসেন্ড করার অনুরোধ না করেই।\n\n## কনকারেন্সি: প্যারালাল অনুরোধের সময় সঠিক থাকা\n\nরিট্রাই ঝামেলা বাড়ায় যখন একই idempotency কী সহ দুটি অনুরোধই একই সময়ে আসে। যদি দুই হ্যান্ডলারই “do work” ধাপটি চালিয়ে দেয় আগে যে কেউ ফলাফল সেভ করে, তখন আপনি এখনও ডবল চার্জ, ডবল ইম্পোর্ট, বা ডবল এনকিউ পেয়ে যেতে পারেন।\n\nসরল সমন্বয়ের পয়েন্ট হল ডাটাবেজ ট্রান্সঅ্যাকশন। প্রথম ধাপটি হোক “কী ক্লেইম করা” এবং ডাটাবেজই নির্ধারণ করুক কে জয়ী। সাধারণ অপশনগুলো:\n\n- dedup টেবিলে ইউনিক insert (ডাটাবেজ এক বিজয়ী নির্ধারণ করে)\n- তৈরি (বা খুঁজে) করার পরে SELECT ... FOR UPDATE\n- idempotency key-এর হ্যাশ দিয়ে transaction-level advisory locks\n- বিজনেস রেকর্ডে ইউনিক কনস্ট্রেইন্ট একটি চূড়ান্ত ব্যাকস্টপ হিসেবে\n\nদীর্ঘ চলমান কাজের জন্য বাইরের সিস্টেম কল করার সময় রো-লক ধরেই রাখবেন না। পরিবর্তে dedup রোতে একটি ছোট state machine স্টোর করুন যাতে অন্যান্য অনুরোধ দ্রুত বের হতে পারে।\n\nপ্রায়োগিক স্টেটসমূহ:\n\n- in_progress এবং started_at\n- completed এবং cached response\n- failed এবং একটি error code (ঐচ্ছিক, আপনার রিট্রাই নীতির উপর নির্ভর করে)\n- expires_at (ক্লিনআপের জন্য)\n\nউদাহরণ: দুইটি অ্যাপ ইনস্ট্যান্স একই পেমেন্ট অনুরোধ পায়। ইনস্ট্যান্স A কী ইনসার্ট করে এবং in_progress মার্ক করে, তারপর প্রোভাইডারে কল করে। ইনস্ট্যান্স B কনফ্লিক্ট পাথ হিট করে, dedup রো পড়ে in_progress দেখে দ্রুত “still processing” রেসপন্স দেয় (অথবা সংক্ষেপে অপেক্ষা করে এবং পুনর্বিবেচনা করে)। যখন A শেষ করে, এটি রো আপডেট করে completed এবং রেসপন্স বডি সংরক্ষণ করে যাতে পরবর্তী রিট্রাই একই আউটপুট পায়।\n\n## সাধারণ ভুল যেগুলো আইডেম্পোটেন্সি ভেঙে দেয়\n\nঅধিকাংশ আইডেম্পোটেন্সি বাগ জটিল লকিংয়ের বিষয়ে নয়; এগুলো “প্রায় সঠিক” পছন্দগুলো যেগুলো রিট্রাই, টাইমআউট, বা দুই ব্যবহারকারীর একই রকম কাজ করলে ব্যর্থ হয়।\n\nএকটি কমন ফাঁদ হলো idempotency কীকে গ্লোবালি ইউনিক ধরা। যদি আপনি এটি স্কোপ না করেন (by user, account, বা endpoint), দুই ভিন্ন ক্লায়েন্ট কোলাইড করতে পারে এবং একজন অন্যজনের রেজাল্ট পেয়ে যাবে।\n\nআরও একটি সমস্যা হলো একই কীকে ভিন্ন রিকুয়েস্ট বডি নিয়ে গ্রহণ করা। যদি প্রথম কল $10 এর জন্য ছিল এবং রিলেপ $100, আপনাকে প্রথম ফলাফল নির্বিচারে ফেরত দেওয়া উচিত নয়। request hash সংরক্ষণ করুন এবং রিলেপে তুলনা করে স্পষ্ট কনফ্লিক্ট এরর ফেরত দিন।\n\nক্লায়েন্টরা আরও বিভ্রান্ত হয় যখন রিলেপ ভিন্ন রেসপন্স শেপ বা স্ট্যাটাস কোড দেয়। যদি প্রথম কল 201 এবং একটি JSON বডি ফেরত দেয়, রিলেপেও একই বডি ও সমান স্ট্যাটাস কোড ফেরত দিন। রিলেপ আচরণ বদলে দিলে ক্লায়েন্টকে অনুমান করতে হয়।\n\nপ্রচলিত ভুলগুলো যেগুলো প্রায়ই ডুপ্লিকেট করে:\n\n- কেবলই ইন-মেমরি ম্যাপ বা ক্যাশের উপর নির্ভর করা, তারপর রিস্টার্টে dedup স্টেট হারানো\n- কী ব্যবহার করা কিন্তু স্কোপ করা না (ক্রস-ইউজার বা ক্রস-এন্ডপয়েন্ট কোলিশন)\n- একই কী-র জন্য পে-লোড মিল না করা\n- প্রথমে সাইড-এফেক্ট করা (চার্জ, ইনসার্ট, publish) এবং পরে dedup রেকর্ড লেখা\n- প্রতিটি রিট্রাইতে নতুন জেনারেটেড ID ফেরত দেওয়া, মূল রেজাল্ট রিপ্লে না করা\n\nক্যাশ পড়া দ্রুত করতে পারে, কিন্তু সত্যের উৎস টেকসই হওয়া উচিত (সাধারণত PostgreSQL)। নাহলে ডিপ্লয়ের পরে রিট্রাই ডুপ্লিকেট তৈরি করতে পারে।\n\nক্লিনআপও পরিকল্পনা করুন। যদি আপনি প্রতিটি কী চিরদিন সংরক্ষণ করেন, টেবিল বাড়বে এবং ইনডেক্স ধীরে যাবে। বাস্তব রিট্রাই আচরণ অনুযায়ী রিটেনশন উইন্ডো রাখুন, পুরোনো রো মুছুন, এবং ইউনিক ইনডেক্স ছোট রাখুন।\n\n## দ্রুত চেকলিস্ট এবং পরবর্তী ধাপ\n\nআইডেম্পোটেন্সিকে আপনার API চুক্তির অংশ হিসেবে বিবেচনা করুন। প্রতিটি এন্ডপয়েন্ট যা ক্লায়েন্ট, কিউ, বা গেটওয়ে দ্বারা রিট্রাই করা হতে পারে, তার জন্য “একই অনুরোধ” কী বোঝায় এবং “একই ফলাফল” কেমন হবে তা স্পষ্ট নির্ধারণ করুন।\n\nশিপ করার আগে একটি চেকলিস্ট:\n\n- প্রতিটি রিট্রাইএবল এন্ডপয়েন্টের জন্য, আইডেম্পোটেন্সি স্কোপ নির্দিষ্ট করা হয়েছে (per user, per account, per order, per external event) এবং লিখে রাখা হয়েছে কি?\n- Dedup কি ডাটাবেজ দ্বারা এনফোর্স করা হচ্ছে (idempotency key এবং স্কোপে ইউনিক কনস্ট্রেইন্ট), কেবল কোডে চেক করা নয়?\n- রিলেপে, আপনি একই স্ট্যাটাস কোড এবং রেসপন্স বডি (অথবা ডকুমেন্টেড স্থির সাবসেট) ফেরত দিচ্ছেন, না কি একটি নতুন অবজেক্ট বা নতুন টাইমস্ট্যাম্প?\n- পেমেন্টে, সাবমিট করার পরে অজানা ফলাফলগুলো নিরাপদে হ্যান্ডল করছেন (টাইমআউট, গেটওয়ে বলে “processing”) যাতে দু’বার চার্জ না হয়?\n- লগ ও মেট্রিক্স কি সহজেই দেখায় কখন অনুরোধ প্রথম-দেখা হয়েছে বনাম রিলেপ?\n\nযদি কোনো আইটেম “হয়তো” থাকে, এখনই ঠিক করুন। বেশিরভাগ ব্যর্থতা চাপের সময়ই দেখা দেয়: প্যারালাল রিট্রাই, ধীর নেটওয়ার্ক, ও আংশিক আউটেজ।\n\nআপনি যদি AppMaster (appmaster.io) এর উপরে অভ্যন্তরীণ টুল বা কাস্টমার-ফেসিং অ্যাপ বানান, তাহলে শুরুতেই idempotency কীগুলি এবং PostgreSQL dedup টেবিল ডিজাইন করা সুবিধাযুক্ত। এভাবে, যখন প্ল্যাটফর্ম প্রয়োজন বদলালে Go ব্যাকএন্ড কোড পুনরায় জেনারেট করে, আপনার retry আচরণ ধারাবাহিক থাকবে।

প্রশ্নোত্তর

Why do retries create duplicate charges or duplicate records even when my API is correct?

রিট্রাইজ স্বাভাবিক কারণ নেটওয়ার্ক বা ক্লায়েন্ট মাঝে মাঝে ব্যর্থ হয়। সার্ভারে অনুরোধ সফল হলেও ক্লায়েন্ট পর্যন্ত উত্তর পৌঁছাতে নাও পারে, ফলে ক্লায়েন্ট পুনরায় অনুরোধ করে এবং আপনি যদি সার্ভারে একক ফলাফল পুনরায় প্লে না করতে পারেন তাহলে একই কাজ দু’বার করতে হতে পারে।

What should I use as an idempotency key, and who should generate it?

একই কাজের প্রতিটি পুনরায়চেষ্টায় একই কী পাঠান। এটি ক্লায়েন্টই জেনারেট করা উচিত — একটি র্যান্ডম, অনুমানযোগ্য নয় স্ট্রিং (উদাহরণ: UUID)। একই কীকে ভিন্ন কাজের জন্য পুনরায় ব্যবহার করবেন না।

How should I scope idempotency keys so they don’t collide across users or tenants?

আপনার ব্যবসায়িক বিধির সঙ্গে মিল রেখে স্কোপ নির্ধারণ করুন — সাধারণত প্রতিটি এন্ডপয়েন্ট প্লাস কল করার পরিচয় যেমন user, account, tenant, বা API token। এতে দুই ভিন্ন গ্রাহক একই কী ব্যবহার করে একে অপরের ফলাফল পাবে না।

What should my API return when it receives a duplicate request with the same key?

প্রথম সফল প্রচেষ্টার একই ফলাফল ফিরিয়ে দিন। বাস্তবে এর মানে হলো একই HTTP স্ট্যাটাস কোড এবং একই রেসপন্স বডি (অথবা অন্তত একই resource ID ও state) রিলেপ করা যাতে ক্লায়েন্ট নিরাপদে পুনরায় চেষ্টা করতে পারে এবং দ্বিতীয় কোন পার্শ্বপ্রতিক্রিয়া না ঘটে।

What if the client accidentally reuses the same idempotency key with a different request body?

এটা ক্লায়েন্ট ভুল করে একই idempotency কী দিয়ে ভিন্ন অনুরোধ পাঠালে তা স্পষ্টভাবে কনফ্লিক্ট-স্টাইল এরর দিয়ে প্রত্যাখ্যান করুন। গুরুত্বপূর্ণ অনুরোধ ক্ষেত্রগুলোর হ্যাশ সংরক্ষণ করুন এবং কী মিললে কিন্তু পে-লোড না মিলে দ্রুত ত্রুটি দেখান, যাতে একই কী’তে দুই আলাদা অপারেশন মিশে না যায়।

How long should I retain idempotency keys in my database?

বাস্তবসম্মত রিট্রাই কভার করার মত সময় পর্যন্ত কী রাখুন, তারপর সেগুলো মুছুন। সাধারণ ডিফল্ট: পেমেন্টের জন্য 24–72 ঘন্টা, ইম্পোর্টের জন্য এক সপ্তাহ, এবং ওয়েবহুকগুলোর জন্য প্রেরকের রিট্রাই পলিসির সাথে মেলে এমন একটি উইন্ডো নির্ধারণ করুন।

What’s the simplest PostgreSQL schema pattern for idempotency?

একটি পৃথক dedup টেবিল খুব কার্যকর কারণ ডাটাবেজ ইউনিক কনস্ট্রেইন্ট এনফোর্স করে এবং রিস্টার্টের পরে টেকসই থাকে। মালিক স্কোপ, কী, একটি অনুরোধ হ্যাশ, স্ট্যাটাস, এবং রিপ্লে করার জন্য রেসপন্স সংরক্ষণ করুন; তারপর (owner, key)-এ ইউনিক কনস্ট্রেইন্ট করুন যাতে শুধু এক অনুরোধই “জয়ী” হয়।

How do I handle two identical requests arriving at the same time?

প্রথমে ডাটাবেজ ট্রান্সঅ্যাকশনের মধ্যে কী ক্লেইম করুন, তারপর সাইড-ইফেক্টটি কেবল তখনই চালান যদি আপনি সফলভাবে ক্লেইম করেন। অন্য যেকোনো প্যারালাল অনুরোধ ইউনিক কনফ্লিক্টে পৌঁছবে, in_progress বা completed দেখবে এবং লজিকটি দু’বার চালানো থেকে বিরত থাকবে।

How do I prevent double-charging when the payment gateway times out?

টাইমআউটকে “ফেল” ধরে নেবেন না; এটিকে "অজ্ঞাত" হিসেবে দেখুন। একটি pending স্টেট রেকর্ড করুন এবং যদি provider ID থাকে, সেটাকে সূত্র হিসেবে ব্যবহার করুন যাতে রিট্রাই কেবল একই পেমেন্ট রেজাল্ট ফিরিয়ে দেয় এবং নতুন চার্জ তৈরি না করে।

How can I make imports retry-safe without forcing users to start over or creating duplicates?

দুই স্তরে ডেডাপ করুন: job-level এবং item-level। রি-ট্রাই একই import job ID ফিরিয়ে দিক, এবং প্রতিটি সারির জন্য একটি প্রাকৃতিক কী (external ID বা (account_id, email) ইত্যাদি) ইউনিক কনস্ট্রেইন্ট বা upsert দিয়ে নিশ্চিত করুন যাতে পুনরায় প্রসেস করলে ডুপ্লিকেট না হয়।

শুরু করা সহজ
কিছু আশ্চর্যজনকতৈরি করুন

বিনামূল্যের পরিকল্পনা সহ অ্যাপমাস্টারের সাথে পরীক্ষা করুন।
আপনি যখন প্রস্তুত হবেন তখন আপনি সঠিক সদস্যতা বেছে নিতে পারেন৷

এবার শুরু করা যাক