মাল্টি-স্টেপ ওয়ার্কফ্লোর জন্য Postgres ট্রানজেকশন শিখুন: কিভাবে আপডেটগুলো নিরাপদে গ্রুপ করবেন, আংশিক রাইট প্রতিরোধ করবেন, রিট্রাই হ্যান্ডেল করবেন এবং ডেটা কনসিস্টেন্ট রাখবেন।

বেশিরভাগ বাস্তব ফিচার একক ডাটাবেস আপডেট নয়। এগুলো সাধারণত একটি ছোট চেইন: একটি সারি ইনসার্ট করা, ব্যালান্স আপডেট করা, স্ট্যাটাস চিহ্নিত করা, একটি অডিট রেকর্ড লেখা, হয়তো একটি জব এনকিউ করা। আংশিক রাইট তখন ঘটে যখন উক্ত ধাপগুলোর মাত্র কিছু অংশই ডাটাবেসে পৌঁছে।
এটি তখন দেখা দেয় যখন কোন কিছু চেইনকে বাধা দেয়: সার্ভার এরর, আপনার অ্যাপ ও Postgres-এর মধ্যে টাইমআউট, ধাপ 2-এর পরে ক্র্যাশ, বা এমন একটি রিট্রাই যা ধাপ 1 আবার চালায়। প্রতিটি স্টেটমেন্ট নিজে থেকে ঠিক থাকে। সমস্যা তখন আসে যখন ওয়ার্কফ্লো মাঝপথে থেমে যায়।
সাধারণত আপনি এটি দ্রুত ধরতে পারবেন:
একটি স্পষ্ট উদাহরণ: একটি প্ল্যান আপগ্রেড গ্রাহকের প্ল্যান আপডেট করে, একটি পেমেন্ট রেকর্ড যোগ করে এবং উপলব্ধ ক্রেডিট বাড়ায়। যদি অ্যাপ পেমেন্ট সেভ করার পরে ক্র্যাশ করে কিন্তু ক্রেডিট যোগ করার আগে থেমে যায়, সাপোর্ট টেবিলগুলোর একটিতে "paid" দেখবে এবং অন্যটিতে "no credits"। ক্লায়েন্ট যদি রিট্রাই করে, আপনি এমনকি পেমেন্ট দু'বার রেকর্ডও করতে পারেন।
লক্ষ্য সহজ: ওয়ার্কফ্লোটাকে একটি একক সুইচ মনে করুন। সব ধাপ সফল হবে, অথবা কোনটিই হবে না, যাতে আপনি অর্ধ-সম্পন্ন কাজ কখনই সেভ না করেন।
ট্রানজেকশন হলো ডাটাবেসের উপায় বলার: এই ধাপগুলোকে একক ইউনিট হিসেবে বিবেচনা করো। বা সব পরিবর্তন ঘটবে, বা কিছুই হবে না। যখন আপনার ওয়ার্কফ্লো একাধিক আপডেট প্রয়োজন, যেমন একটি সারি তৈরি করা, ব্যালান্স আপডেট করা এবং একটি অডিট রেকর্ড লেখা—তখন এটি গুরুত্বপূর্ণ।
ধরা যাক দুই অ্যাকাউন্টের মধ্যে টাকা স্থানান্তর করা হচ্ছে। আপনাকে অ্যাকাউন্ট A থেকে প্লাস করে অ্যাকাউন্ট B-তে যোগ করতে হবে। যদি অ্যাপ প্রথম ধাপের পরে ক্র্যাশ করে, আপনি শুধু কেটে ফেলা অংশই সিস্টেমে "মনে রাখে" এটা চান না।
যখন আপনি commit করেন, আপনি Postgres-কে বলছেন: এই ট্রানজেকশনের সবকিছু রাখো। সব পরিবর্তন স্থায়ী এবং অন্য সেশনগুলোর কাছে দৃশ্যমান হয়ে যায়।
যখন আপনি rollback করেন, আপনি Postgres-কে বলছেন: এই ট্রানজেকশনের সবকিছু ভুলে যাও। Postgres ঐ পরিবর্তনগুলোকে এমনভাবে উল্টে দেয় যেন ট্রানজেকশনটি কখনোই ঘটেনি।
একটি ট্রানজেকশনের ভিতরে, Postgres গ্যারান্টি দেয় আপনি কমিট করার আগে অন্য সেশনগুলোর কাছে অর্ধসম্পন্ন ফলাফল দেখাবেন না। যদি কিছু ব্যর্থ হয় এবং আপনি রোলব্যাক করেন, ডাটাবেস ঐ ট্রানজেকশনের লিখাগুলো পরিষ্কার করে।
ট্রানজেকশন খারাপ ওয়ার্কফ্লো ডিজাইনকে ঠিক করে না। যদি আপনি ভুল পরিমাণ কেটে ফেলেন, ভুল ইউজার আইডি ব্যবহার করেন, বা কোন প্রয়োজনীয় চেক এড়িয়ে যান, Postgres বিশ্বস্তভাবে ভুল ফলাফল কমিট করবে। ট্রানজেকশন স্বয়ংক্রিয়ভাবে প্রতিটি ব্যবসায়িক স্তরের সংঘাত (যেমন অতিরিক্ত বিক্রি প্রতিরোধ) আটকায় না—এটি সঠিক কনস্ট্রেইন্ট, লক, বা আইসোলেশন লেভেলের সাথে জোড়া না করলে।
যখনই আপনি একটি বাস্তব-জগত ক্রিয়া শেষ করতে একাধিক টেবিল (বা একাধিক সারি) আপডেট করেন, তখন আপনি একটি ট্রানজেকশনের প্রার্থী পান। মূল নীতি একই: সবকিছু করা হবে, অথবা কিছুই হবে না।
অর্ডার ফ্লো ক্লাসিক কেস। আপনি একটি অর্ডার সারি তৈরি করতে পারেন, স্টক রিজার্ভ করতে পারেন, পেমেন্ট নিতে পারেন, তারপর অর্ডারকে paid হিসেবে চিহ্নিত করতে পারেন। যদি পেমেন্ট সফল হয় কিন্তু স্ট্যাটাস আপডেট ব্যর্থ হয়, আপনি এমন পরিস্থিতি পাবেন যেখানে টাকা নেওয়া হয়েছে কিন্তু অর্ডার এখনও unpaid হিসাবে আছে। যদি অর্ডার সারি তৈরি হয় কিন্তু স্টক রিজার্ভ না হয়, আপনি তাদের বিক্রি করতে পারেন যে আসলে আপনার কাছে নেই।
ইউজার অনবোর্ডিং একইভাবে চুপিচুপি ভেঙে যায়। ইউজার তৈরি করা, প্রোফাইল রেকর্ড ইনসার্ট করা, রোল অ্যাসাইন করা এবং একটি ওয়েলকাম ইমেল পাঠানোর নির্দেশ সেভ করা—সব মিলিয়ে একটি যৌক্তিক ক্রিয়া। গ্রুপ না করলে আপনি এমন একজন ইউজার পেতে পারেন যিনি সাইন ইন করতে পারবেন কিন্তু কোন অনুমতি নেই, বা একটি প্রোফাইল আছে কিন্তু কোন ইউজার নেই।
ব্যাক-অফিস অ্যাকশনগুলো প্রায়ই সখত "পেপার ট্রেইল + স্টেট চেইনজ" আচরণ চায়। একটি অনুরোধ অনুমোদন করা, একটি অডিট এন্ট্রি লেখা, এবং ব্যালান্স আপডেট করা একসাথে সফল হওয়া উচিত। যদি ব্যালান্স বদলায় কিন্তু অডিট লগ মিসিং থাকে, আপনি হারান কে কি এবং কেন পরিবর্তন করেছে সেই প্রমাণ।
ব্যাকগ্রাউন্ড জবগুলোও উপকৃত হয়, বিশেষ করে যখন আপনি অনেক ধাপে একটি ওয়ার্ক আইটেম প্রসেস করেন: আইটেম ক্লেইম করা যাতে দুই কর্মী এক্টি না করে, ব্যবসায়িক আপডেট প্রয়োগ করা, রিপোর্টিং ও রিট্রাইয়ের জন্য ফলাফল রেকর্ড করা, তারপর আইটেমকে ডোন (বা নির্দিষ্ট কারণেFailed) হিসেবে চিহ্নিত করা। যদি এই ধাপগুলো বিচ্ছিন্ন হয়, রিট্রাই ও কনকারেন্সি বিশৃঙ্খলা সৃষ্টি করবে।
মাল্টি-স্টেপ ফিচারগুলো ভাঙে যখন আপনি সেগুলোকে স্বাধীন আপডেটের গুচ্ছ হিসেবে আচরণ করেন। ডাটাবেস ক্লায়েন্ট খুলবার আগে, ওয়ার্কফ্লোটিকে একটি ছোট গল্প হিসেবে লিখুন এক স্পষ্ট ফিনিশ লাইনের সাথে: ব্যবহারকারীর জন্য ঠিক কোনটা "ডান" হিসেবে গণ্য হবে?
প্রথমে ধাপগুলো সাধারণ ভাষায় তালিকাভুক্ত করুন, তারপর একটি একক সাফল্য শর্ত নির্ধারণ করুন। উদাহরণ: "অর্ডার তৈরি হয়েছে, ইনভেন্টরী রিজার্ভ করা হয়েছে, এবং ব্যবহারকারী একটি অর্ডার কনফার্মেশন নম্বর দেখেছে।" এই কন্ডিশনের কম নয় এমন কিছুই সফল নয়।
এরপর, ডাটাবেস কাজ এবং বাইরের কাজের মধ্যে একটি কঠিন রেখা আঁকুন। ডাটাবেস ধাপগুলোই সেগুলো যেগুলো ট্রানজেকশন দিয়ে সুরক্ষিত করা যায়। কার্ড পেমেন্ট, ইমেল পাঠানো, বা তৃতীয়-পক্ষ API কলের মতো বাইরের কলগুলো ধীর, অনিয়মিতভাবে ব্যর্থ হতে পারে এবং সাধারণত আপনি সেগুলো রোলব্যাক করতে পারবেন না।
একটি সহজ পরিকল্পনা পদ্ধতি: ধাপগুলোকে (1) অবশ্যই সব-বা-কিছুই হওয়া উচিত, (2) কমিটের পরে ঘটতে পারে—এভাবে আলাদা করে ফেলুন।
ট্রানজেকশনের ভিতরে কেবল সেই ধাপগুলো রাখুন যেগুলো একসাথে সঙ্গতিপূর্ণ থাকা জরুরি:
সাইড-ইফেক্টগুলো বাইরে সরান। উদাহরণ: প্রথমে অর্ডার কমিট করুন, তারপর আউটবক্স রেকর্ডের উপর ভিত্তি করে কনফার্মেশন ইমেল পাঠান।
প্রতিটি ধাপের জন্য লিখুন পরবর্তী ধাপ ব্যর্থ হলে কি হওয়া উচিত। "রোলব্যাক" মানে ডাটাবেস রোলব্যাক হতে পারে, অথবা এক ধরণের প্রতিকারী অ্যাকশন।
উদাহরণ: পেমেন্ট সফল কিন্তু ইনভেন্টরি রিজার্ভ ব্যর্থ হলে, আগে থেকেই সিদ্ধান্ত নিন আপনি কি দ্রুত রিফান্ড দেবেন, না অর্ডারকে "পেমেন্ট ক্যাপচারড, স্টক অপেক্ষায়" হিসেবে চিহ্নিত করে অ্যাসিঙ্ক্রোনাসভাবে হ্যান্ডেল করবেন।
ট্রানজেকশন Postgres-কে বলে: এই ধাপগুলোকে এক ইউনিট হিসেবে বিবেচনা করো। সবগুলো হবে, বা কিছুই হবে না। এটা আংশিক রাইট প্রতিরোধ করার সবচেয়ে সরল উপায়।
শুরু থেকে শেষ পর্যন্ত একটি ডাটাবেস কানেকশন (একই সেশন) ব্যবহার করুন। যদি আপনি বিভিন্ন কানেকশনে ধাপগুলো ছড়িয়ে দেন, Postgres সব-অথবা-কিছুই ফলাফল গ্যারান্টি করতে পারবে না।
ক্রমটি সরল: begin, প্রয়োজনীয় রিড ও রাইট করুন, সবকিছু সফল হলে commit, নতুবা rollback করুন এবং একটি স্পষ্ট এরর ফেরত দিন।
নীচে SQL-এ একটি ন্যূনতম উদাহরণ:
BEGIN;
-- reads that inform your decision
SELECT balance FROM accounts WHERE id = 42 FOR UPDATE;
-- writes that must stay together
UPDATE accounts SET balance = balance - 50 WHERE id = 42;
INSERT INTO ledger(account_id, amount, note) VALUES (42, -50, 'Purchase');
COMMIT;
-- on error (in code), run:
-- ROLLBACK;
ট্রানজেকশন চলাকালীন লক ধরে রাখে। আপনি যতক্ষণ এগুলো খোলা রাখবেন, অন্য কাজগুলো তত বেশি ব্লক হবে এবং টাইমআউট বা ডেডলকের সম্ভাবনা বাড়বে। ট্রানজেকশনের ভিতরে জরুরি কাজগুলোই রাখুন, এবং ধীর কাজগুলো (ইমেল পাঠানো, পেমেন্ট প্রোভাইডার কল, PDF জেনারেট করা) বাইরে সরান।
কিছু ব্যর্থ হলে পুনরুৎপাদন করতে পর্যাপ্ত কনটেক্সট লগ করুন, তবে সংবেদনশীল ডেটা ফাঁস করবেন না: ওয়ার্কফ্লো নাম, order_id বা user_id, মূল প্যারামিটার (amount, currency), এবং Postgres এরর কোড। সম্পূর্ণ পে-লোড, কার্ড ডেটা বা ব্যক্তিগত বিবরণ লগ করা উচিত নয়।
কনকারেন্সি হলো কেবল একই সময়ে দুইটি জিনিস ঘটা। দুই গ্রাহক শো দেখছেন একটাই টিকিট বাকি। উভয় স্ক্রিনে "1 left" দেখায়, উভয় ক্লিক করে Pay, এখন আপনার অ্যাপকে সিদ্ধান্ত নিতে হবে কে পাবে।
প্রতিরক্ষা অব্যবহারে, দুটি রিকোয়েস্ট একই পুরনো ভ্যালু পড়তে পারে এবং উভয়ই আপডেট লিখতে পারে। এভাবেই নেতিবাচক ইনভেন্টরি, ডুপ্লিকেট রিজার্ভেশন, বা অর্ডার ছাড়া পেমেন্ট দেখা যায়।
রো লকগুলো সবচেয়ে সহজ গার্ডরেইল। আপনি যে নির্দিষ্ট সারিটি বদলাতে যাচ্ছেন সেটি লক করেন, চেক করেন, তারপর আপডেট করেন। একই সারি স্পর্শ করা অন্য ট্রানজেকশনগুলোকে আপনার কমিট বা রোলব্যাক পর্যন্ত অপেক্ষা করতে হয়, যা ডাবল আপডেট আটকায়।
একটি প্রচলিত প্যাটার্ন: একটি ট্রানজেকশন শুরু করুন, FOR UPDATE দিয়ে ইনভেন্টরি সারি সিলেক্ট করুন, স্টক আছে কি ভেরিফাই করুন, এটি হ্রাস করুন, তারপর অর্ডার ইনসার্ট করুন। এটাই দরজা ধরে রাখে যতক্ষণ আপনি জরুরি ধাপগুলো শেষ করেন।
আইসোলেশন লেভেলগুলো নিয়ন্ত্রণ করে কনকারেন্ট ট্রানজেকশনগুলো থেকে কতটা অদ্ভুত ফল আপনি অনুমোদন করেন। ট্রেড-অফ সাধারণত সেফটি বনাম গতি:
লকগুলো সংক্ষিপ্ত রাখুন। যদি একটি ট্রানজেকশন খোলা বসে থাকে যখন আপনি একটি বহির্গত API কল করেন বা ব্যবহারকারীর একশন অপেক্ষা করেন, আপনি দীর্ঘ অপেক্ষা ও টাইমআউট তৈরি করবেন। একটি পরিষ্কার ফেলিওর পথ পছন্দ করুন: একটি লক টাইমআউট সেট করুন, এরর ধরা হলে "কৃপয়া রিট্রাই করুন" ফেরত দিন, অনির্দিষ্টভাবে অনুরোধ লটকে রাখার বদলে।
বহির্গত কাজ (যেমন কার্ড চার্জ করা) দরকার হলে ওয়ার্কফ্লো ভাগ করুন: দ্রুত রিজার্ভ করুন, কমিট করুন, তারপর ধীর অংশটি করুন, এবং আরেকটি সংক্ষিপ্ত ট্রানজেকশনে ফাইনালাইজ করুন।
রিট্রাইসমূহ Postgres-ভিত্তিক অ্যাপগুলিতে স্বাভাবিক। একটি অনুরোধ ব্যর্থ হতে পারে এমনকি আপনার কোড সঠিক থাকলেও: ডেডলক, স্টেটমেন্ট টাইমআউট, সংক্ষিপ্ত নেটওয়ার্ক ড্রপ, বা উচ্চ আইসোলেশন লেভেলে সেরিয়ালাইজেশন এরর। যদি আপনি কেবল একই হ্যান্ডলার আবার চালান, আপনি দ্বিতীয় অর্ডার তৈরি করা, দুবার চার্জ করা, বা ডুপ্লিকেট "ইভেন্ট" সারি ইনসার্ট করার ঝুঁকি নেবেন।
ফিক্স হলো আইডেম্পোটেন্সি: একই ইনপুটে অপারেশন দু'বার চালানো নিরাপদ হওয়া উচিত। ডাটাবেস জানতে পারবে "এটি একই রিকুয়েস্ট" এবং ধারাবাহিকভাবে প্রতিক্রিয়া দেবে।
একটি ব্যবহারিক প্যাটার্ন হলো প্রতিটি মাল্টি-স্টেপ ওয়ার্কফ্লোর সাথে একটি idempotency কী (সাধারণত ক্লায়েন্ট-জেনারেটেড request_id) যুক্ত করা এবং এটিকে মেইন রেকর্ডে স্টোর করা, পরে ঐ কী-তে ইউনিক কনস্ট্রেইন্ট যোগ করা।
উদাহরণ: চেকআউটে, ব্যবহারকারী Pay এ ক্লিক করলে request_id জেনারেট করুন, তারপর ঐ request_id সহ অর্ডার ইনসার্ট করুন। যদি রিট্রাই ঘটে, দ্বিতীয় চেষ্টা ইউনিক কনস্ট্রেইন্টে পড়ে এবং আপনি বিদ্যমান অর্ডার ফেরত দেবেন নতুনটি তৈরির বদলে।
সাধারণভাবে যা গুরুত্বপূর্ণ:
রিট্রাই লুপকে ট্রানজেকশনের বাইরে রাখুন। প্রতিটি চেষ্টা একটি নতুন ট্রানজেকশন শুরু করা উচিৎ এবং উপরে থেকে পুরো ইউনিট অফ ওয়ার্ক আবার চালানো উচিৎ। একটি ব্যর্থ ট্রানজেকশনের ভিতরে রিট্রাই করা সাহায্য করে না কারণ Postgres সেটিকে aborted হিসেবে চিহ্নিত করে।
একটি ছোট উদাহরণ: আপনার অ্যাপ অর্ডার তৈরি করে এবং ইনভেন্টরি রিজার্ভ করার চেষ্টা করে, কিন্তু COMMIT-এর ঠিক পরে টাইমআউট হয়। ক্লায়েন্ট রিট্রাই করে। একটি idempotency কী থাকলে দ্বিতীয় অনুরোধ ইতোমধ্যেই তৈরি অর্ডার ফেরত দেবে এবং দ্বিতীয় রিজার্ভ প্রতিরোধ করবে, কাজ দ্বিগুণ করবে না।
ট্রানজেকশনগুলো মাল্টি-স্টেপ ওয়ার্কফ্লো একসাথে রাখে, কিন্তু তারা স্বয়ংক্রিয়ভাবে ডেটা সঠিক করে না। আংশিক-রাইট ফলাফল এড়াতে একটি শক্ত উপায় হলো ডাটাবেসে "ভুল" স্টেটগুলি কঠিন বা অসম্ভব করে তোলা, এমনকি যদি অ্যাপ কোডে বাগ চলে আসে।
বেসিক সেফটি রেল দিয়ে শুরু করুন। ফোরেন কী নিশ্চিত করে রেফারেন্সগুলো বাস্তব (একটি order line নাও করে একটি অনুপস্থিত order কে)। NOT NULL অর্ধ-পূরণ সারিকে থামায়। CHECK কনস্ট্রেইন্ট দিয়ে এমন মান আটকান যা মানে পাঠায় না (উদাহরণ: quantity > 0, total_cents >= 0)। এই নিয়মগুলো প্রতিটি লিখায় চলবে, কোনো সার্ভিস বা স্ক্রিপ্ট ডাটাবেস স্পর্শ করুক না কেন।
দীর্ঘ ওয়ার্কফ্লো জন্য, স্টেট চেইঞ্জগুলো স্পষ্টভাবে মডেল করুন। অনেক বুলিয়ান ফ্ল্যাগের পরিবর্তে একটি একক status কলাম ব্যবহার করুন (pending, paid, shipped, canceled) এবং কেবল বৈধ ট্রানজিশনগুলোই অনুমোদন করুন। আপনি কনস্ট্রেইন্ট বা ট্রিগার দিয়ে এটি জোর করতে পারেন যাতে ডাটাবেস অবৈধ জাম্পগুলো (যেমন shipped -> pending) গ্রহণ না করে।
ইউনিকনেস আরেকটি ধরনের সঠিকতা। যেখানে ডুপ্লিকেট আপনার ওয়ার্কফ্লো ভেঙে দেবে সেখানে ইউনিক কনস্ট্রেইন্ট যোগ করুন: order_number, invoice_number, বা রিট্রাই কিলের জন্য ব্যবহার করা idempotency_key। এরপর, যদি আপনার অ্যাপ একই রিকুয়েস্ট রিট্রাই করে, Postgres দ্বিতীয় ইনসার্ট ব্লক করবে এবং আপনি নিরাপদে "already processed" ফিরিয়ে দিতে পারবেন বরং দ্বিতীয় অর্ডার তৈরি করার বদলে।
ট্রেসেবিলিটির প্রয়োজন হলে, এটি স্পষ্টভাবে স্টোর করুন। একটি অডিট টেবিল (বা হিস্টোরি টেবিল) যারা কি পরিবর্তন করেছে ও কখন তা রেকর্ড করলে "রহস্যময় আপডেট"গুলিকে ইনসিডেন্টে কোয়েরি করার মত বাস্তব ইভেন্টে পরিণত করে।
বেশিরভাগ আংশিক রাইট "খারাপ SQL" থেকে আসে না। এগুলো আসে ওয়ার্কফ্লো সিদ্ধান্ত থেকে যা এটি সহজ করে দেয় যে আপনি গল্পের শুধু অর্ধেকটাই কমিট করবেন।
accounts তারপর orders আপডেট করে, কিন্তু অন্যটি orders তারপর accounts করে, লোডের সময় ডেডলক হওয়ার সম্ভাবনা বাড়ে।একটি বাস্তব উদাহরণ: চেকআউটে, আপনি ইনভেন্টরি রিজার্ভ করেন, অর্ডার তৈরি করেন, তারপর কার্ড চার্জ করেন। যদি আপনি চার্জটিকে একই ট্রানজেকশনের ভিতরে করেন, আপনি নেটওয়ার্ক অপেক্ষায় ইনভেন্টরি লক ধরে রাখতে পারেন। যদি চার্জ সফল হয় কিন্তু পরে আপনার ট্রানজেকশন রোলব্যাক হয়, আপনি গ্রাহককে চার্জ করে ফেলেছেন কিন্তু অর্ডার নেই।
একটি নিরাপদ প্যাটার্ন হলো: ট্রানজেকশনকে ডাটাবেস স্টেটে ফোকাসেড রাখুন (রিজার্ভ ইনভেন্টরি, অর্ডার তৈরি, পেমেন্ট pending রেকর্ড করা), কমিট করুন, তারপর বাইরের এপিআই কল করুন, এবং নতুন একটি সংক্ষিপ্ত ট্রানজেকশনে ফলাফল ফেরত লিখুন। অনেক দল এটি পেন্ডিং স্ট্যাটাস ও একটি ব্যাকগ্রাউন্ড জব দিয়ে বাস্তবায়ন করে।
যখন একটি ওয়ার্কফ্লোতে একাধিক ধাপ থাকে (ইনসার্ট, আপডেট, চার্জ, পাঠানো), লক্ষ্য সহজ: বা সবকিছু রেকর্ড হবে, বা কিছুই না।
প্রয়োজনীয় প্রতিটি ডাটাবেস রাইট এক ট্রানজেকশনের ভিতরে রাখুন। যদি একটি ধাপ ব্যর্থ হয়, রোলব্যাক করুন এবং ডেটা ঠিক আগের মত রাখুন।
সাফল্যের শর্ত স্পষ্ট করুন। উদাহরণ: "অর্ডার তৈরি হয়েছে, স্টক রিজার্ভ হয়েছে, এবং পেমেন্ট স্টেটাস রেকর্ড করা হয়েছে।" এর চেয়ে কম কিছুই সাফল্য নয়।
BEGIN ... COMMIT ব্লকের ভিতরে ঘটে।ROLLBACK যায়, এবং কলার স্পষ্ট ব্যর্থ ফল পায়।একই রিকুয়েস্ট রিট্রাই হতে পারে ধরে নিন। ডাটাবেসকে কেবল-একবার নিয়মগুলো প্রয়োগ করতে সাহায্য করুন।
ট্রানজেকশনের ভিতরে সর্বনিম্ন কাজ করুন, এবং লক ধরে রেখে নেটওয়ার্ক কল এড়িয়ে চলুন।
আপনি কোথায় ভেঙেছে তা দেখতে না পারলে, আপনি অনুমান করতে থাকবেন।
একটি চেকআউটে কয়েকটি ধাপ আছে যা একসাথে চলে: অর্ডার ক্রিয়েট করা, ইনভেন্টরি রিজার্ভ করা, পেমেন্ট চেষ্টা রেকর্ড করা, তারপর অর্ডারের স্ট্যাটাস চিহ্নিত করা।
ধরুন ব্যবহারকারী 1টি আইটেম কেনার জন্য Buy ক্লিক করেছে।
একটি ট্রানজেকশনের ভিতরে কেবল ডাটাবেস পরিবর্তনগুলো করুন:
orders টেবিলে একটি সারি ইনসার্ট করুন স্ট্যাটাস pending_payment সহ।inventory.available হ্রাস করা বা একটি reservations সারি তৈরি করা)।idempotency_key সহ একটি payment_intents সারি ইনসার্ট করুন (যা ইউনিক)।outbox সারি ইনসার্ট করুন যেমন "order_created"।যদি কোন স্টেটমেন্ট ব্যর্থ হয় (স্টক শেষ, কনস্ট্রেইন্ট এরর, ক্র্যাশ), Postgres পুরো ট্রানজেকশন রোলব্যাক করে দেবে। আপনি এমন কিছুও পাবেন না যেমন অর্ডার আছে কিন্তু রিজার্ভ নেই, বা রিজার্ভ আছে কিন্তু অর্ডার নেই।
পেমেন্ট প্রোভাইডার আপনার ডাটাবেসের বাইরে, তাই এটাকে আলাদা ধাপ হিসেবে বিবেচনা করুন।
প্রোভাইডারের কল যদি আপনি কমিট করার আগে ব্যর্থ করে, ট্রানজেকশন বাতিল করুন এবং কিছুই লেখা হবে না। যদি প্রোভাইডারের কল কমিটের পরে ব্যর্থ হয়, একটি নতুন ট্রানজেকশন চালান যা পেমেন্ট চেষ্টাকে failed হিসেবে চিহ্নিত করে, রিজার্ভ মুক্ত করে, এবং অর্ডার স্ট্যাটাস canceled করে।
ক্লায়েন্ট প্রতি চেকআউট চেষ্টা একটি idempotency_key পাঠাক। এটি payment_intents(idempotency_key) এ একটি ইউনিক ইনডেক্স দিয়ে জোর করুন (অথবা আপনি চাইলে orders-এ)। রিট্রাইতে আপনার কোড বিদ্যমান সারিগুলো দেখে এবং নতুন ইনসার্ট না করে চলতে থাকবে।
ট্রানজেকশনের ভিতরে ইমেল পাঠাবেন না। একই ট্রানজেকশনে একটি আউটবক্স রেকর্ড লিখুন, তারপর কমিটের পরে একটি ব্যাকগ্রাউন্ড ওয়ার্কার ইমেল পাঠাক। এভাবে আপনি কখনই রোলব্যাক হওয়া অর্ডারের জন্য ইমেল পাঠাবেন না।
একটি ওয়ার্কফ্লো বাছুন যা একাধিক টেবিল ছোঁয়: সাইনআপ + ওয়েলকাম ইমেল এনকিউ, চেকআউট + ইনভেন্টরি, ইনভয়েস + লেজার এন্ট্রি, বা প্রজেক্ট তৈরি + ডিফল্ট সেটিংস।
ধাপগুলো আগে লিখুন, তারপর যেগুলো সবসময় সত্য থাকা দরকার সেগুলো (আপনার ইনভারিয়েন্টগুলো) নির্ধারণ করুন। উদাহরণ: "একটি অর্ডার হয় সম্পূর্ণরূপে পেইড এবং রিজার্ভ করা, অথবা পেইড নয় এবং রিজার্ভ নয়। কখনই অর্ধ-রিজার্ভেড নয়।" ঐ নিয়মগুলোকে একটি সব-বা-কিছুই ইউনিটে রূপান্তর করুন।
একটি সহজ পরিকল্পনা:
তারপর রুক্ষ কেসগুলি ইচ্ছাকৃতভাবে টেস্ট করুন। ধাপ 2-এর পরে ক্র্যাশ সিমুলেট করুন, কমিটের ঠিক আগে টাইমআউট সিমুলেট করুন, এবং UI থেকে ডাবল-সাবমিট সিমুলেট করুন। লক্ষ্য হওয়া উচিত বোরিং আউটকাম: কোন孤立 সারি নেই, দুবার চার্জ নেই, চিরস্থায়ী পেন্ডিং নয়।
আপনি যদি দ্রুত প্রোটোটাইপ করছেন, তাহলে হ্যান্ডলার ও স্কিমা জেনারেট করার আগে প্ল্যানিং-ফার্স্ট টুলে ওয়ার্কফ্লো স্কেচ করা সাহায্য করে। উদাহরণ হিসেবে, Koder.ai (koder.ai) এর একটি Planning Mode আছে যা স্ন্যাপশট ও রোলব্যাক সাপোর্ট করে, এবং ট্রানজেকশন বাউন্ডারি ও কনস্ট্রেইন্টে ইটারেট করার সময় সহায়ক হতে পারে।
এই সপ্তাহে এক ওয়ার্কফ্লো করুন। দ্বিতীয়টি অনেক দ্রুত হবে।