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

কনকারেন্সি-নিরাপদ ওয়ার্কফ্লো জন্য PostgreSQL অ্যাডভাইজরি লক

প্র্যাকটিক্যাল প্যাটার্ন, SQL স্নিপেট এবং সহজ চেক সহ জানতে পারবেন কিভাবে PostgreSQL অ্যাডভাইজরি লক ব্যবহার করে approvals, billing, এবং schedulers-এ দ্বিগুণ প্রসেসিং বন্ধ করবেন।

কনকারেন্সি-নিরাপদ ওয়ার্কফ্লো জন্য PostgreSQL অ্যাডভাইজরি লক

প্রকৃত সমস্যা: একাধিক প্রসেস একই কাজ করে ফেলা

দ্বিগুণ-প্রসেসিং তখন ঘটে যখন একই আইটেম দুইবার হ্যান্ডেল হয় কারণ দুইটি ভিন্ন অ্যাক্টরই নিজেদের দায়িত্বশীল মনে করে। বাস্তব অ্যাপে এটি দেখা যায়—একজন কাস্টমারকে দুইবার চার্জ করা, একটি অনুমোদন দুইবার প্রয়োগ হওয়া, বা “ইনভয়েস প্রস্তুত” ইমেইল দুবার পাঠানো। টেস্টে সব কিছু ঠিক থাকতে পারে, কিন্তু বাস্তব ট্র্যাফিকে ভেঙে পড়ে।

এটি সাধারণত তখনই হয় যখন টাইমিং টাইট হয়ে যায় এবং একের বেশি জিনিস এক্সিকিউট করতে পারে:

  • দুইটি ওয়ার্কার একই সময়ে একই জব তুলে নেয়।
  • নেটওয়ার্ক কল ধীর হলে একটি রিট্রাই ফায়ার করে কিন্তু প্রথম চেষ্টা এখনও চলছে।
  • ইউজার UI হ্যাং হওয়ায় Approve-এ ডাবল-ক্লিক করে।
  • ডেপ্লয় বা ক্লক ড্রিফটের পরে দুইটি শিডিউলার ওভারল্যাপ করে।
  • একটি মোবাইল অ্যাপে টাইমআউটের পরে রিকোয়েস্ট পুনরায় পাঠালে এক ট্যাপও দুই রিকোয়েস্টে পরিণত হতে পারে।

বেদনাদায়ক অংশটি হলো প্রতিটি অ্যাক্টর নিজেই “যুক্তিসংগত” আচরণ করছে। বাগটি তাদের মধ্যে থাকা ফাঁক: কেউ জানে না অন্যটি ইতিমধ্যেই একই রেকর্ড প্রক্রিয়াজাত করছে।

লক্ষ্য সহজ: কোনো নির্দিষ্ট আইটেম (একটি অর্ডার, একটি অনুমোদন অনুরোধ, একটি ইনভয়েস) জন্য এক সময়ে কেবল এক অ্যাক্টরই ক্রিটিকাল কাজটি করতে পারবে। বাকি সবাই সংক্ষেপে অপেক্ষা করবে অথবা ব্যাক-অফ করে পুনরায় চেষ্টা করবে।

PostgreSQL অ্যাডভাইজরি লক সাহায্য করতে পারে। এগুলো আপনাকে হালকা ওজনের উপায় দেয় বলতে “আমি আইটেম X নিয়ে কাজ করছি”—এবং আপনি যে ডাটাবেসেই কনসিস্টেন্সি ভরসা করেন সেটাই ব্যবহার করে।

কিন্তু প্রত্যাশা নির্ধারণ করুন। একটি লক পূর্ণ সারি সিস্টেম নয়। এটি আপনার জন্য জব শিডিউল করবে না, অর্ডারিং গ্যারান্টি দেবে না, বা মেসেজ সংরক্ষণ করবে না। এটি সেই ওয়ার্কফ্লো ধাপের চারপাশে একটি সেফটি গেট যা কখনও দুইবার চলা উচিত নয়।

PostgreSQL অ্যাডভাইজরি লক কী (এবং কী নয়)

PostgreSQL অ্যাডভাইজরি লক একটি উপায় যাতে নিশ্চিত করা যায় যে একসময় কেবল এক ওয়ার্কারই কোন কাজটি করবে। আপনি একটি লক কী বেছে নেন (যেমন “invoice 123”), ডাটাবেসকে সেটি লক করতে বলেন, কাজটি করেন, তারপর রিলিজ দেন।

"Advisory" শব্দটি গুরুত্বপূর্ণ। Postgres আপনার কী-র অর্থ জানে না এবং এটি কিছু স্বয়ংক্রিয়ভাবে রক্ষা করবে না। এটি কেবল একটি বিষয় ট্র্যাক করে: এই কী লক করা আছে কি না। আপনার কোডের মধ্যে কী ফরম্যাট নিয়ে Everyone-কে সম্মত হতে হবে এবং ঝুঁকিপূর্ণ অংশ চলানোর আগে লক নিতে হবে।

এটি রো লকের সাথে তুলনাও কাজে লাগে। রো লকগুলি (যেমন SELECT ... FOR UPDATE) প্রকৃত টেবিল রো-গুলিকে রক্ষা করে। যখন কাজটি পরিষ্কারভাবে একটি রো-র সাথে মিলছে তখন সেগুলো দুর্দান্ত। অ্যাডভাইজরি লক আপনি যে কী বেছে নেন সেটিকে রক্ষা করে—এটি তখনই দরকার যখন ওয়ার্কফ্লো অনেক টেবিল স্পর্শ করে, বাহ্যিক সার্ভিস ডাকে, বা একটি রো তৈরির আগে শুরু হয়।

অ্যাডভাইজরি লক সেইসব ক্ষেত্রে দরকার যখন আপনি চান:

  • প্রতিটি এন্টিটির জন্য এক সময়ে কেবল এক-টাই অ্যাকশন (একটি রিকোয়েস্টের জন্য এক অনুমোদন, একটি ইনভয়েসের জন্য এক চার্জ)
  • আলাদা লকিং সার্ভিস না যোগ করে একাধিক অ্যাপ সার্ভারের মধ্যে সমন্বয়
  • এমন ওয়ার্কফ্লো ধাপের চারপাশে সুরক্ষা যা একটি একক রো আপডেটের চেয়ে বড়

এগুলো অন্যান্য সুরক্ষা টুলের পরিবর্তে নয়। এগুলো অপারেশনগুলোকে আইডেম্পোটেন্ট বানায় না, ব্যবসায়িক নিয়ম কঠোরভাবে বাধ্য করে না, এবং যদি কোনো কোড-পাথ লক নেওয়া ভুলে যায় তাহলে ডুপ্লিকেট থামাবে না।

এগুলো প্রায়ই “হালকা” বলা হয় কারণ আপনি স্কিমা পরিবর্তন বা অতিরিক্ত ইনফ্রা ছাড়াই ব্যবহার করতে পারেন। অনেক ক্ষেত্রে, একটি ক্রিটিকাল সেকশনের চারপাশে একটি লক কল যোগ করে আপনি দ্বিগুণ-প্রসেসিং ঠিক করে ফেলতে পারেন এবং বাকি ডিজাইন অপরিবর্তিত রাখতে পারেন।

যে লক টাইপগুলো আপনি বাস্তবে ব্যবহার করবেন

লোকেরা যখন “PostgreSQL অ্যাডভাইজরি লক” বলেন, সাধারণত কিছু ফাংশনের কথা বোঝেন। সঠিকটি বেছে নেওয়া ত্রুটি, টাইমআউট ও রিট্রাই-র সময় কি হয় তা বদলে দেয়।

সেশন বনাম ট্রানজেকশন লক

সেশন-লেভেল লক (pg_advisory_lock) ডাটাবেস কনেকশন যতক্ষণ থাকে ততক্ষণ থাকে। এটা দীর্ঘ-চলমান ওয়ার্কারদের জন্য সুবিধাজনক হতে পারে, কিন্তু অর্থ হল যদি আপনার অ্যাপ ক্র্যাশ করে এবং পুলে একটি কনেকশন ঝুলে থাকে তাহলে লক লম্বা সময় ধরে থাকতে পারে।

ট্রানজেকশন-লেভেল লক (pg_advisory_xact_lock) বর্তমান ট্রানজেকশনের সঙ্গে বাঁধা থাকে। যখন আপনি কমিট বা রোলব্যাক করবেন, PostgreSQL এটিকে স্বয়ংক্রিয়ভাবে রিলিজ করে দেয়। বেশিরভাগ রিকোয়েস্ট-রেসপন্স ওয়ার্কফ্লোর (অনুমোদন, বিলিং ক্লিক, অ্যাডমিন অ্যাকশন) জন্য এটা নিরাপদ ডিফল্ট কারণ রিলিজ ভুলে যাওয়া কঠিন।

ব্লকিং বনাম ট্রাই-লক

ব্লকিং কলগুলো লক উপলব্ধ না হওয়া পর্যন্ত অপেক্ষা করে। সরল, কিন্তু এটি একটি ওয়েব রিকোয়েস্টকে আটকে দিতে পারে যদি অন্য সেশনটি লক ধরে থাকে।

Try-lock কলগুলো সঙ্গে সঙ্গে রিটার্ন করে:

  • pg_try_advisory_lock (সেশন-লেভেল)
  • pg_try_advisory_xact_lock (ট্রানজেকশন-লেভেল)

UI অ্যাকশনের জন্য try-lock প্রায়ই ভাল। যদি লক নেওয়া থাকে, আপনি একটি পরিষ্কার বার্তা দিতে পারেন যেমন “Already processing” এবং ব্যবহারকারীকে পুনরায় চেষ্টা করতে বলুন।

শেয়ার্ড বনাম এক্সক্লুসিভ

এক্সক্লুসিভ লকগুলো "এক সময়ে একটাই"। শেয়ার্ড লক একাধিক হোল্ডারকে অনুমতি দেয় কিন্তু একটি এক্সক্লুসিভ লককে ব্লক করে। অধিকাংশ দ্বিগুণ-প্রসেসিং সমস্যায় এক্সক্লুসিভ লক ব্যবহার হয়। শেয়ার্ড লক তখনই উপকারী যখন অনেক রিডার এগিয়ে যেতে পারে, কিন্তু বিরল একটি রাইটার একা চলতে হবে।

লক কিভাবে রিলিজ হয়

রিলিজ টাইপের উপর নির্ভর করে:

  • সেশন লক: ডিসকানেক্টে রিলিজ হয়, বা স্পষ্টভাবে pg_advisory_unlock দিয়ে
  • ট্রানজেকশন লক: ট্রানজেকশন শেষ হলে স্বয়ংক্রিয়ভাবে রিলিজ হয়

সঠিক লক কী নির্বাচন

একটি অ্যাডভাইজরি লক তখনই কাজ করে যখন প্রতিটি ওয়ার্কার একই স্পেসিফিক কীটা লক করতে চেষ্টা করে। যদি একটি কোড-পাথ "invoice 123" লক করে এবং অন্যটি "customer 45" লক করে, তখন আপনি এখনও ডুপ্লিকেট পাবেন।

প্রথমে সেই “বস্তু” নামকরণ করুন যা আপনি রক্ষা করতে চান। এটাকে কংক্রিট বানান: একটি ইনভয়েস, একটি অনুমোদন অনুরোধ, একটি শিডিউলড টাস্ক রান, অথবা একটি কাস্টমারের মাসিক বিলিং সাইকেল। ঐ পছন্দই নির্ধারণ করে আপনি কতটা কনকারেন্সি অনুমোদন করবেন।

ঝুঁকির সাথে মিলে এমন স্কোপ বেছে নিন

অধিকাংশ দল শেষ পর্যন্ত এইগুলোর একটিতে পরে:

  • প্রতি রেকর্ড: অনুমোদন এবং ইনভয়েসের জন্য সবচেয়ে নিরাপদ (lock by invoice_id বা request_id)
  • প্রতি কাস্টমার/অ্যাকাউন্ট: যখন একই কাস্টমারের জন্য ক্রিয়াগুলো সিরিয়ালাইজ করা দরকার (বিলিং, ক্রেডিট পরিবর্তন)
  • প্রতি ওয়ার্কফ্লো ধাপ: যখন বিভিন্ন ধাপ এক সঙ্গেই চলতে পারে, কিন্তু প্রতিটি ধাপ এক-টাই হওয়া দরকার

স্কোপকে একটি প্রোডাক্ট সিদ্ধান্ত হিসেবে বিবেচনা করুন, ডাটাবেস বিবরণ নয়। “Per record” ডাবল-ক্লিক থেকে দুবার চার্জ হওয়া প্রতিরোধ করে। “Per customer” দুইটি ব্যাকগ্রাউন্ড জব থেকে ওভারল্যাপিং স্টেটমেন্ট তৈরির সম্ভাবনা বন্ধ করে।

একটি স্থিতিশীল কী কৌশল বেছে নিন

সাধারণত আপনার দুটো অপশন আছে: দুইটি 32-bit ইন্ট (প্রায়ই namespace + id হিসেবে) বা একটি 64-bit ইন্ট (bigint), কখনো কখনো স্ট্রিং আইডি থেকে হ্যাশ করে তৈরি করা।

দুই-ইন্ট কী স্ট্যান্ডার্ড করা সহজ: প্রতিটি ওয়ার্কফ্লোর জন্য একটি নির্ধারিত namespace সংখ্যা (উদাহরণস্বরূপ approvals বনাম billing) এবং রেকর্ড আইডি হিসাবে দ্বিতীয় মানটি ব্যবহার করুন।

হ্যাশিং সুবিধাজনক হতে পারে যখন আপনার আইডেন্টিফায়ার UUID, কিন্তু আপনাকে ছোট একটি কলোশন ঝুঁকি মেনে নিতে হবে এবং সর্বত্র কনসিস্টেন্ট থাকতে হবে।

যা না-ই হোক, ফরম্যাট লিখে রাখুন এবং কেন্দ্রীয়ভাবে ব্যবহার করুন। “প্রায় একই কী” দুই জায়গায় থাকা সাধারণভাবে ডুপ্লিকেট পুনরায় প্রবর্তনের কারণ।

ধাপে ধাপে: এক-এক করে প্রসেসিংয়ের জন্য একটি নিরাপদ প্যাটার্ন

বিলিং রিট্রাই নিরাপদ করুন
Stripe বা অন্যান্য পেমেন্টের আগে একবারে একটিই করে invoice প্রসেসিং যোগ করুন।
বিল্ড শুরু করুন

একটি ভালো অ্যাডভাইজরি-লক ওয়ার্কফ্লো সাদাসিধে: লক নিন, যাচাই করুন, কাজ করুন, রেকর্ড রাখুন, কমিট করুন। লক নিজে_business rule নয়। এটি একটি গার্ডরেইল যা নিশ্চিত করে যে দুই ওয়ার্কার একই রেকর্ডে একই সময়ে আঘাত করবে না।

একটি বাস্তবসম্মত প্যাটার্ন:

  1. যেখানে আউটকাম এটমিক হওয়া উচিত সেখানে একটি ট্রানজেকশন খুলুন।
  2. নির্দিষ্ট ইউনিট অফ ওয়ার্কের জন্য লক অর্জন করুন। ট্রানজেকশন-স্কোপড লক (pg_advisory_xact_lock) পছন্দ করুন যাতে এটি স্বয়ংক্রিয়ভাবে রিলিজ হয়।
  3. ডাটাবেসে স্টেট আবার চেক করুন। ধরে নিবেন না আপনি প্রথম। নিশ্চিত করুন রেকর্ড এখনও উপযুক্ত।
  4. কাজটি করুন এবং ডাটাবেসে একটি টেকসই “ডান” মার্কার লিখুন (স্ট্যাটাস আপডেট, লেজার এন্ট্রি, অডিট রো)।
  5. কমিট করুন এবং লক ছেড়ে দিন। যদি আপনি সেশন-লেভেল লক ব্যবহার করে থাকেন, কনেকশন পুলে ফিরানোর আগে আনলক করুন।

উদাহরণ: দুইটি অ্যাপ সার্ভার একই সেকেন্ডে “Approve invoice #123” পায়। দুজনই শুরু করে, কিন্তু কেবল একজনই 123-এর জন্য লক পায়। বিজয়ী সার্ভার চেক করে যে invoice #123 এখনও pending, এটিকে approved মার্ক করে, অডিট/পেমেন্ট রেকর্ড লিখে, এবং কমিট করে। দ্বিতীয় সার্ভার হয় দ্রুত ব্যর্থ হয় (try-lock) বা প্রথমটির শেষ হওয়ার পরে লক পেয়ে আবার স্ট্যাটাস দেখে এবং কিছুই পরিবর্তন না করে বেরিয়ে আসে।

যেখানে অ্যাডভাইজরি লক মানায়: অনুমোদন, বিলিং, শিডিউলার

অ্যাডভাইজরি লক তখনই সবচেয়ে ভালো যখন নিয়মটি সরল: একটি নির্দিষ্ট জিনিসের জন্য কেবল এক প্রক্রিয়া “জয়ী” কাজটি একসাথে করবে। আপনি আপনার বিদ্যমান ডাটাবেস ও অ্যাপ কোড রাখেন, কিন্তু একটি ছোট গেট যোগ করেন যা রেস কন্ডিশনগুলো ঘটানো অনেক কঠিন করে দেয়।

অনুমোদন

অনুমোদন হল ক্লাসিক কনকারেন্সি ট্র্যাপ। দুইটি রিভিউয়ার (অথবা একই ব্যক্তি ডাবল-ক্লিক করে) কয়েক মিলিসেকেন্ডের মধ্যে Approve-এ সক্ষম হতে পারে। approval id দিয়ে কী করে একটি লক নিলে কেবল একটি ট্রানজেকশন স্টেট পরিবর্তনটি করবে। বাকি সবাই দ্রুত আউটকাম জানতে পারবে এবং একটি স্পষ্ট বার্তা দেখাতে পারবে যেমন “already approved” বা “already rejected।”

এটি গ্রাহক পোর্টাল ও অ্যাডমিন প্যানেলে সাধারণ যে অনেকেই একই কিউ দেখে।

বিলিং

বিলিং সাধারণত কঠোর নিয়ম চায়: একটি ইনভয়েসের জন্য এক পেমেন্ট চেষ্টা, এমনকি যখন রিট্রাই হয়। একটি নেটওয়ার্ক টাইমআউট ইউজারকে আবার Pay ক্লিক করাতে পারে, অথবা একটি ব্যাকগ্রাউন্ড রিট্রাই প্রথম অ্যাটেম্পট চলার সময় চালাতে পারে।

invoice ID দিয়ে কী করে একটি লক নিশ্চিত করে কেবল একটি পাথ এক সময়ে পেমেন্ট প্রোভাইডারের সঙ্গে কথা বলে। দ্বিতীয় অ্যাটেম্পট “payment in progress” রিটার্ন করতে পারে অথবা সর্বশেষ পেমেন্ট স্ট্যাটাস পড়তে পারে। এতে ডুপ্লিকেট কাজ ও দ্বিগুণ চার্জের ঝুঁকি কমে।

শিডিউলার এবং ব্যাকগ্রাউন্ড ওয়ার্কার

মাল্টি-ইনস্ট্যান্স সেটআপে, শিডিউলারগুলো ভুল করে একই উইন্ডো একসাথে চালাতে পারে। জব নাম ও টাইম উইন্ডোর উপর লক (উদাহরণ: daily-settlement:2026-01-29) করে নিশ্চিত করুন কেবল একটা ইনস্ট্যান্সই এটি চালায়।

একই পদ্ধতি তাদের জন্যও কাজ করে যারা টেবিল থেকে আইটেম পুল করে: আইটেম ID-তে লক দিন যাতে কেবল এক ওয়ার্কারই সেটি প্রক্রিয়াজাত করতে পারে।

সাধারণ কী-গুলোর উদাহরণ: একটি একক অনুমোদন রিকোয়েস্ট আইডি, একটি ইনভয়েস আইডি, একটি জব নাম প্লাস টাইম উইন্ডো, একটি কাস্টমার আইডি “এক্সপোর্ট এক সময়ে” জন্য, অথবা রিট্রাই-র জন্য একটি ইউনিক idempotency কী।

একটি বাস্তব উদাহরণ: পোর্টালে ডাবল-অ্যাপ্রুভাল বন্ধ করা

ওভারল্যাপিং শিডিউলার রান প্রতিরোধ করুন
জব নেম এবং টাইম উইন্ডোতে লক করে মাল্টি-ইনস্ট্যান্স জব গুলো নিরাপদে চালান।
অ্যাপ তৈরি করুন

কল্পনা করুন একটি অনুমোদন অনুরোধ একটি পোর্টালে: একটি পারচেজ অর্ডার অপেক্ষায় আছে, এবং দুই ম্যানেজার একই সেকেন্ডে Approve ক্লিক করে। সুরক্ষা না থাকলে, দুই রিকোয়েস্টই pending পড়তে পারে এবং দুইজন approved লিখে ফেলতে পারে—ডুপ্লিকেট অডিট এন্ট্রি, ডুপ্লিকেট নোটিফিকেশন, বা ডাউনস্ট্রিম ওয়ার্ক দুবার ট্রিগার হতে পারে।

PostgreSQL অ্যাডভাইজরি লক আপনাকে এই অ্যাকশনকে এক-এক করে করার সরল উপায় দেয়—প্রতি অনুমোদনের জন্য এক-টাই।

ফ্লো

API যখন approve অ্যাকশন পায়, প্রথমে approval id-ভিত্তিক একটি লক নেয় (তাই বিভিন্ন অনুমোদন প্যারালেলে প্রসেস হতে পারে)।

একটি সাধারণ প্যাটার্ন হলো: approval_id-এ লক নিন, বর্তমান স্ট্যাটাস পড়ুন, স্ট্যাটাস আপডেট করুন, তারপর একটি অডিট রেকর্ড লিখুন—সবই একটি ট্রানজেকশনের মধ্যে।

BEGIN;

-- One-at-a-time per approval_id
SELECT pg_try_advisory_xact_lock($1) AS got_lock;  -- $1 = approval_id

-- If got_lock = false, return "someone else is approving, try again".

SELECT status FROM approvals WHERE id = $1 FOR UPDATE;

-- If status != 'pending', return "already processed".

UPDATE approvals
SET status = 'approved', approved_by = $2, approved_at = now()
WHERE id = $1;

INSERT INTO approval_audit(approval_id, actor_id, action, created_at)
VALUES ($1, $2, 'approved', now());

COMMIT;

(উপরের SQL ব্লক অপরিবর্তিত রাখা হয়েছে।)

দ্বিতীয় ক্লিক কী অনুভব করে

দ্বিতীয় রিকোয়েস্টটি হয় লক পায় না (তাহলে দ্রুত “Already being processed” রিটার্ন করে) অথবা প্রথমটি শেষ হওয়ার পরে লক পেয়ে স্ট্যাটাসটি দেখেই বেরিয়ে আসে—কোন কিছু পরিবর্তন না করে। যে কোনোভাবে, আপনি ডুপ্লিকেট এড়িয়ে চলবেন এবং UI-ও রেসপনসিভ থাকবে।

ডিবাগ করার জন্য যথেষ্ট লগ রাখুন: রিকোয়েস্ট আইডি, approval id এবং গণিতকৃত লক কী, অ্য্যাকটার আইডি, আউটকাম (lock_busy, already_approved, approved_ok), এবং টাইমিং।

অ্যাপ ফেল হওয়া ছাড়াই ওয়েট, টাইমআউট, ও রিট্রাই হ্যান্ডেল করা

আপনার লক কী স্ট্যান্ডার্ড করুন
সব ওয়ার্কফ্লো একইভাবে কী ব্যবহার করে তা নিশ্চিত করতে ব্যাকএন্ডে একটি শেয়ার্ড লক-কী হেল্পার তৈরি করুন।
ব্যাকএন্ড তৈরি করুন

একটি লকের জন্য অপেক্ষা করা নিরীহ শোনায় যতক্ষণ না এটি একটি স্পিনিং বোতাম, একটি আটকে থাকা ওয়ার্কার, বা একটি কিউ ব্যাকলগে পরিণত হয়। যখন আপনি লক পেতে পারেন না, তখন যেখানে একজন মানুষ অপেক্ষা করছে সেখানে দ্রুত ব্যর্থ করুন এবং যেখানে অপেক্ষা নিরাপদ সেখানে অপেক্ষা করুন।

ইউজার অ্যাকশনের জন্য: try-lock এবং স্পষ্ট ভাবে উত্তর দিন

যদি কেউ Approve বা Charge ক্লিক করে, তাদের অনুরোধ সেকেন্ডের জন্য ব্লক করবেন না। try-lock ব্যবহার করুন যাতে অ্যাপ তৎক্ষণাত উত্তর দিতে পারে।

প্রায়োগিক পদ্ধতি: লক নেওয়ার চেষ্টা করুন, এবং ব্যর্থ হলে একটি পরিষ্কার “busy, try again” রেসপন্স দিন (অথবা আইটেম স্টেট রিফ্রেশ করুন)। এতে টাইমআউট কমে এবং ডাবল-ক্লিক কম হয়।

লক ধরা অংশ সংক্ষিপ্ত রাখুন: স্টেট ভেরিফাই, স্টেট চেঞ্জ প্রয়োগ, কমিট।

ব্যাকগ্রাউন্ড জবগুলোর জন্য: ব্লক করা ঠিক আছে, কিন্তু সীমা দিন

শিডিউলার ও ওয়ার্কারদের জন্য ব্লক করা ঠিক আছে কারণ সেখানে কোন মানুষ অপেক্ষা করে না। কিন্তু আপনাকে সীমা রাখতে হবে, নইলে একটি ধীর জব পুরো ফ্লিটকে স্টল করে দিতে পারে।

টাইমআউট ব্যবহার করুন যাতে ওয়ার্কার ছেড়ে দিয়ে অন্য জব নিয়ে যেতে পারে:

SET lock_timeout = '2s';
SET statement_timeout = '30s';
SELECT pg_advisory_lock(123456);

এছাড়া জবটির জন্য একটি সর্বোচ্চ প্রত্যাশিত রানটাইম সেট করুন। যদি বিলিং সাধারণত 10 সেকেন্ডের মধ্যে শেষ হয়, তাহলে 2 মিনিটকে একটি ইনসিডেন্ট মাপুন। শুরু সময়, জব আইডি, এবং লক কিভাবে ধরে আছে তা ট্র্যাক করুন। যদি আপনার জব রানার ক্যানসেলেশন সমর্থন করে, ম্যাচিং ক্যাপ অতিক্রম করলে টাস্কগুলো ক্যানসেল করুন যাতে সেশন শেষ হয় এবং লক রিলিজ হয়।

উদ্দেশ্যমূলকভাবে রিট্রাই পরিকল্পনা করুন। যখন লক পাওয়া যায় না, তখন সিদ্ধান্ত নিন: শীঘ্রই ব্যাকঅফ সহ পুনঃনির্ধারণ (কিছু র‍্যান্ডমনেস সহ), এই সাইকেলে বেস্ট-এফোর্ট কাজ স্কিপ করা, অথবা যদি পুনরাবৃত্ত ব্যর্থতা হয় তবে আইটেমকে কন্টেন্ড হিসেবে মার্ক করা।

সাধারণ ভুল যা স্টক করা লক বা ডুপ্লিকেট সৃষ্টি করে

সর্বাধিক সাধারণ বিস্ময় হল সেশন-লেভেল লকগুলো যা কখনো রিলিজ হয় না। কনেকশন পুলগুলো কনেকশন খোলা রাখে, তাই একটি সেশন যদি লক নিয়ে থাকা অবস্থায় পুলে ফিরে যায় এবং আনলক ভুলে যায়, লক ধরে থাকতে পারে যতক্ষণ না সেই কনেকশন রিসাইকেল হয়। অন্য ওয়ার্কাররা অপেক্ষা করবে (অথবা ব্যর্থ হবে) এবং কেন তা বোঝা কঠিন।

আরেকটি ডুপ্লিকেটের উৎস হল লক নেওয়া কিন্তু স্টেট চেক না করা। একটি লক কেবল নিশ্চিত করে যে এক সময়ে একটি কর্মী ক্রিটিকাল সেকশন চালায়; এটি গ্যারান্টি দেয় না যে রেকর্ড এখনও উপযুক্ত। সর্বদা একই ট্রানজেকশনের ভিতরে পুনরায় চেক করুন (উদাহরণ: pending নিশ্চিত করার আগে approved এ যাওয়া)।

লোক কী-ও টিমকে বিভ্রান্ত করে। যদি একটি সার্ভিস order_id-এ লক করে এবং অন্যটি একই রিয়েল-ওয়ার্ল্ড রিসোর্সের জন্য আলাদা গণিতকৃত কী তে লক করে, তখন আপনার কাছে দুইটি আলাদা লক থাকবে। দুইটি পাথই একসাথে চলতে পারে, যা একটি মিথ্যা নিরাপত্তা ভাব গ্রহণ করে।

দীর্ঘ লক হোল্ড সাধারণত নিজেই তৈরি করা হয়। আপনি যদি লক ধরে রেখে ধীর নেটওয়ার্ক কল করেন (পেমেন্ট প্রোভাইডার, ইমেল/SMS, ওয়েবহুক), একটি ছোট গার্ডরেইল ব্যাটেনকেক হয়ে ওঠে। লক করা সেকশানটাকে দ্রুত ডাটাবেস কাজের উপর কেন্দ্রীভূত রাখুন: স্টেট ভেরিফাই, নতুন স্টেট লিখুন, কি হবে পরবর্তী তা রেকর্ড করুন। তারপর সাইড-ইফেক্টগুলো ট্রানজেকশন কমিট হওয়ার পর ট্রিগার করুন।

শেষে, অ্যাডভাইজরি লক আইডেম্পোটেন্সি বা ডাটাবেস কনস্ট্রেইন্টের বদলি নয়। এগুলোকে ট্রাফিক লাইট হিসেবে বিবেচনা করুন, প্রমাণ সিস্টেম হিসেবে নয়। যেখানে মানায় সেখানে unique constraints এবং বাহ্যিক কলের জন্য idempotency কী ব্যবহার করুন।

শিপ করার আগে দ্রুত চেকলিস্ট

প্রোডাকশন-রেডি পোর্টাল শিপ করুন
AppMaster-এ একটিই প্রজেক্ট থেকে ব্যাকএন্ড, ওয়েব ও মোবাইল অ্যাপ জেনারেট করুন।
পোর্টাল তৈরি করুন

অ্যাডভাইজরি লককে একটি ছোট চুক্তি হিসেবে বিবেচনা করুন: টিমের সবাইকে জানতে হবে লক কী বোঝায়, এটার যা রক্ষা করে, এবং লক ধরে থাকার সময় কি_allowed।

চেকলিস্ট যা বেশিরভাগ সমস্যা ধরবে:

  • প্রতিটি রিসোর্সের জন্য এক পরিষ্কার লক কী, লিখে রাখুন এবং সর্বত্র পুনরায় ব্যবহার করুন
  • কিছু অপরিবর্তনীয় করার আগে লক অর্জন করুন (পেমেন্ট, ইমেইল, বাহ্যিক API কল)
  • লক ধরে নেওয়ার পর ও পরিবর্তন লেখার আগে স্টেট আবার চেক করুন
  • লক করা সেকশনটাকে ছোট ও পরিমাপযোগ্য রাখুন (লক ও এক্সিকিউশন সময় লগ করুন)
  • প্রতিটি পাথের জন্য "lock busy" মানে কি সিদ্ধান্ত নিন (UI বার্তা, ব্যাকঅফ সহ রিট্রাই, স্কিপ)

পরবর্তী ধাপ: প্যাটার্ন প্রয়োগ করুন এবং রক্ষণযোগ্য রাখুন

একটি জায়গা বেছে নিন যেখানে ডুপ্লিকেট সবচেয়ে বেশি ক্ষতি করে এবং সেখান থেকেই শুরু করুন। ভালো প্রথম টার্গেট হচ্ছে এমন অ্যাকশন যা অর্থ খরচ করে বা স্থায়ীভাবে স্টেট পরিবর্তন করে—যেমন “charge invoice” বা “approve request।” কেবল ঐ ক্রিটিকাল সেকশনটিকে একটি অ্যাডভাইজরি লক দিয়ে র‌্যাপ করুন, তারপর আচরণ ট্রাস্ট করা হলে পার্শ্ববর্তী ধাপগুলোতে প্রসারিত করুন।

শুরুতেই মৌলিক অবজার্ভেবিলিটি যোগ করুন। যখন কোনো ওয়ার্কার লক পায় না তা লগ করুন, এবং লক ধরে থাকা কাজ কতক্ষণ নেয় তা ট্র্যাক করুন। যদি লক ওয়েট spike করে, সাধারণত এর মানে ক্রিটিকাল সেকশনটি খুব বড় বা ভিতরে একটি ধীর কুয়েরি লুকানো আছে।

লোকগুলো ডাটা সেফটির উপর ভিত্তি করে সবচেয়ে ভালোভাবে কাজ করে, তার বদলে নয়। স্পষ্ট স্ট্যাটাস ফিল্ড রাখুন (pending, processing, done, failed) এবং যেখানে সম্ভব কনস্ট্রেইন্ট দিয়ে সেগুলো ব্যাক করুন। সবচেয়ে খারাপ মুহূর্তে যদি রিট্রাই ঘটে, একটি unique constraint বা idempotency কীই দ্বিতীয় লাইন অফ ডিফেন্স হতে পারে।

যদি আপনি AppMaster (appmaster.io) এ ওয়ার্কফ্লো নির্মাণ করেন, একই প্যাটার্ন প্রযোজ্য: ক্রিটিকাল স্টেট চেঞ্জকে এক ট্রানজেকশনের ভিতরে রাখুন এবং “finalize” ধাপের আগে একটি ট্রানজেকশন-লেভেল অ্যাডভাইজরি লক নেওয়ার জন্য একটি ছোট SQL স্টেপ যোগ করুন।

অ্যাডভাইজরি লকগুলো ভাল কাজ করে যতক্ষণ না আপনি সত্যিই কিউ ফিচারের দরকার পড়ে (প্রায়োরিটি, ডিলেড জব, ডেড-লেটার), আপনার কাছে ভারী কনটেনশন আছে এবং স্মার্ট প্যারালেলিজম দরকার, বেশ কয়েকটি ডাটাবেস জুড়ে সমন্বয় প্রয়োজন যেখানে কোনো শেয়ার করা Postgres নেই, অথবা আপনি কঠোর আইসোলেশন নিয়ম চান। লক্ষ্য হলো বিরক্তিকরভাবে নির্ভরযোগ্যতা: প্যাটার্নটাকে ছোট, কনসিস্টেন্ট, লগে দৃশ্যমান এবং কনস্ট্রেইন্ট দিয়ে ব্যাক করুন।

প্রশ্নোত্তর

When should I use PostgreSQL advisory locks instead of just trusting my app logic?

যখন কোনো নির্দিষ্ট ইউনিট অফ ওয়ার্কের জন্য “এক সময়ে কেবল এক জন” দরকার হয় — যেমন একটি রিকোয়েস্ট অনুমোদন, একটি ইনভয়েস চার্জ করা, বা একটি নির্ধারিত উইন্ডো চালানো — তখন অ্যাডভাইজরি লক ব্যবহার করুন। বিশেষভাবে সহায়ক যখন একাধিক অ্যাপ ইনস্ট্যান্স একই আইটেমে হস্তক্ষেপ করতে পারে এবং আপনি আলাদা লকিং সার্ভিস যোগ করতে চান না।

How are advisory locks different from SELECT ... FOR UPDATE row locks?

Row locks (SELECT ... FOR UPDATE) বাস্তবে যে রো-গুলোর উপর কাজ করছেন সেগুলোকে সুরক্ষা দেয় এবং যখন পুরো অপারেশন একটি একক রো-আপডেটের সাথে মানানসই তখন তা দুর্দান্ত। অ্যাডভাইজরি লক আপনি যে কী চান তা রক্ষা করে, তাই যখন ওয়ার্কফ্লো অনেক টেবিল স্পর্শ করে, বাহ্যিক সার্ভিস ডাকে, বা শেষ রো তৈরির আগেই শুরু হয় তখন এটা কাজ করে।

Should I use transaction-level or session-level advisory locks?

রিকোয়েস্ট/রেসপন্স ধাঁচের অ্যাকশনের জন্য ডিফল্ট হিসেবে pg_advisory_xact_lock (ট্রানজেকশন-লেভেল) ব্যবহার করুন, কারণ এটি কমিট বা রোলব্যাক হলে স্বয়ংক্রিয়ভাবে রিলিজ হয়। pg_advisory_lock (সেশন-লেভেল) তখনই ব্যবহার করুন যখন সত্যিই চান লক একটি ট্রানজেকশনের বাইরেও টিকে থাকবে এবং আপনি নিশ্চিত যে কনেকশন পুলে ফিরানোর আগে আপনি আনলক করে দেবেন।

Is it better to block waiting for a lock or use a try-lock?

UI-চালিত অ্যাকশনের জন্য try-lock (pg_try_advisory_xact_lock) বেছে নিন যাতে রিকোয়েস্ট দ্রুত ব্যর্থ হতে পারে এবং স্পষ্ট “already processing” রেসপন্স দেওয়া যায়। ব্যাকগ্রাউন্ড ওয়ার্কারদের জন্য ব্লকিং লক ঠিক আছে, কিন্তু lock_timeout দিয়ে এটিকে সীমাবদ্ধ করুন যাতে এক চটকানো টাস্ক সবকিছুকে স্টল না করে।

What should I lock on: record ID, customer ID, or something else?

সেই ছোট জিনিসটিকে লক করুন যেটা অবশ্যই আবার চালানো যাবে না—সাধারণত “একটি ইনভয়েস” বা “একটি অনুমোদন রিকোয়েস্ট।” যদি আপনি খুব বিস্তৃতভাবে লক করেন (যেমন পুরো কাস্টমার) throughput কমে যায়; খুব সরু লক করলে (যদি বিভিন্ন পাথ একই রিয়েল-ওয়ার্ল্ড রিসোর্সকে আলাদা কী দিয়ে লক করে) আপনি এখনও ডুপ্লিকেট পেতে পারেন।

How do I choose a lock key so all services use the exact same one?

একটি স্থিতিশীল কী ফরম্যাট বেছে নিন এবং যে সকল সার্ভিস একই ক্রিটিকাল অ্যাকশন করতে পারে সেখানে একইটা ব্যবহার করুন। সাধারণ পদ্ধতি হলো দুইটি ইন্ট: একটি স্থির namespace নম্বর ও এন্টিটি আইডি, যাতে বিভিন্ন ওয়ার্কফ্লো ভুলক্রমে একে অপরকে ব্লক না করে কিন্তু সঠিকভাবে সমন্বয় করে।

Do advisory locks replace idempotency checks or unique constraints?

না। লক কেবল সমান্তরাল এক্সিকিউশনকে আটকায়; এটি পুনরাবৃত্তি নিরাপদ কিনা তা প্রমাণ করে না। আপনি এখনও ট্রানজেকশনের ভিতরে স্টেট পুনরায় যাচাই করবেন (যেমন আইটেম এখনও pending আছে কি না) এবং যেখানে মানায় সেখানে unique constraints বা idempotency ব্যবহার করবেন।

What should I do inside the locked section to avoid slowing everything down?

লক-ধরা সেকশনটি ছোট এবং ডাটাবেস-কেন্দ্রিক রাখুন: অযোগ্যতা যাচাই করুন, নতুন স্টেট লিখুন, এবং কমিট করুন। ধীর-পক্ষপ্রভাবগুলি (পেমেন্ট, ইমেল, ওয়েবহুক) কমিটের পর বা আউটবক্স-স্টাইল রেকর্ডের মাধ্যমে ট্রিগার করুন যাতে নেটওয়ার্ক দেরি সময় লক ধরে না থাকে।

Why do advisory locks sometimes seem “stuck” even after a request finishes?

সর্বাধিক সাধারণ কারণ হচ্ছে সেশন-লেভেল লক যা একটি পুলড কনেকশনে অপরিবর্তিত রয়ে যায় কারণ কোনো কোড পাথ আনলক চালায়নি। ট্রানজেকশন-লেভেল লক পছন্দ করুন; যদি সেশন লক ব্যবহার করতে হয়, নিশ্চিত করুন pg_advisory_unlock নির্ভরযোগ্যভাবে রান করে কনেকশন পুলে যাওয়ার আগে।

What should I log or monitor to know advisory locks are working?

লক কাজ করছে কিনা বুঝতে—এন্টিটি আইডি ও গণিতকৃত লক কী লগ করুন, লক পাওয়া গেছে কি না, পাওয়ার সময় কত লেগেছে, এবং ট্রানজেকশন কতক্ষণ চলেছে তা লগ করুন। এছাড়া আউটকামগুলো লগ করুন যেমন lock_busy, already_processed, বা processed_ok—এগুলো কনটেনশন আর আসল ডুপ্লিকেশন আলাদা করতে সাহায্য করে।

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

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

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