PostgreSQL-এর “cannot execute CREATE TABLE in a read-only transaction” এররটি বর্তমানে অনেক টিমের মাইগ্রেশন প্রক্রিয়ায় বাধা সৃষ্টি করছে, যার ফলে স্কিমাগুলো অর্ধেক সম্পন্ন অবস্থায় থেকে যাচ্ছে এবং মাইগ্রেশন হিস্ট্রি টেবিলগুলো লক হয়ে যাচ্ছে। সাধারণত এই সমস্যাটি তখন ঘটে যখন একটি রাইট অপারেশন (write operation) প্রাইমারির পরিবর্তে একটি রেপ্লিকার (replica) ওপর পড়ে, যা Flyway, Liquibase, Django ORM এবং এই জাতীয় যেকোনো টুলকে স্থবির করে দেয়।
কেন এই এররটি দেখা দেয়
যখন একটি ট্রানজ্যাকশনকে read-only হিসেবে চিহ্নিত করা হয়, তখন PostgreSQL সমস্ত রাইট অপারেশন, স্কিমা পরিবর্তন এবং সিকোয়েন্স আপডেট বন্ধ করে দেয়। মাইগ্রেশন এই অবস্থায় পড়ার সবচেয়ে সাধারণ কারণগুলো হলো:
- Connection poolers (যেমন, PgBouncer) যা অনিচ্ছাকৃতভাবে মাইগ্রেশন কানেকশনটিকে একটি read replica-তে পাঠিয়ে দেয়।
- Roles যেগুলোর
default_transaction_read_onlyপ্যারামিটারটি ডিফল্টভাবে on করা আছে। - Cloud endpoints যা আলাদা reader এবং writer URL প্রদান করে; reader URL ব্যবহার করলে (যা AWS RDS বা Aurora-এর ক্ষেত্রে সাধারণ) রাইট অপারেশনগুলো একটি রেপ্লিকার দিকে চলে যায়।
যখন এই শর্তগুলোর কোনোটি প্রযোজ্য হয়, তখন মাইগ্রেশন প্রক্রিয়া টেবিল তৈরি করা, কলাম যোগ করা বা সিকোয়েন্স আপডেট করার চেষ্টা করে কিন্তু শেষ পর্যন্ত বাধাগ্রস্ত হয়, যার ফলে ডাটাবেসটি আংশিক মাইগ্রেটেড অবস্থায় থেকে যায়।
ট্রানজ্যাকশনের ভেতরে তাৎক্ষণিক সমাধান
আপনি যদি ইতিমধ্যে এই এররটির সম্মুখীন হয়ে থাকেন, তবে পুরো সেশনের ওপর প্রভাব না ফেলে বর্তমান ট্রানজ্যাকশনের জন্য read-only ফ্ল্যাগটি ওভাররাইড করতে পারেন—এটি তখন অত্যন্ত গুরুত্বপূর্ণ যখন একটি কানেকশন পুল অন্যান্য কাজের জন্য একই সেশন পুনরায় ব্যবহার করে।
BEGIN;
SET LOCAL default_transaction_read_only = off;
SET TRANSACTION READ WRITE;
CREATE TABLE orders (id SERIAL PRIMARY KEY, total NUMERIC);
COMMIT;
SET LOCAL শুধুমাত্র ট্রানজ্যাকশনের মেয়াদ পর্যন্ত সেটিংস পরিবর্তন করে। সাধারণ SET ব্যবহার করলে পুরো সেশনের জন্য পরিবর্তনটি স্থায়ী হয়ে যাবে, যা অন্যান্য অপারেশনগুলোকে ব্যাহত করতে পারে যেগুলোর জন্য ডিফল্টভাবে read-only থাকা প্রয়োজন।
প্রতিরোধমূলক ব্যবস্থা
১. আপনি প্রাইমারি নোডে আছেন কিনা তা যাচাই করুন
যেকোনো মাইগ্রেশন চালানোর আগে একটি দ্রুত চেক যোগ করুন:
SELECT CASE WHEN pg_is_in_recovery() THEN 'REPLICA' ELSE 'PRIMARY' END;
যদি ফলাফল REPLICA হয়, তবে মাইগ্রেশনটি বাতিল (abort) করুন। pg_is_in_recovery() ফাংশনটি একটি স্ট্যান্ডবাই সার্ভারে true রিটার্ন করে, যা নিশ্চিত করে যে আপনি কোনো read-only কপি-তে রাইট করার চেষ্টা করছেন না।
২. ডেডিকেটেড মাইগ্রেশন রোল ব্যবহার করুন
এমন একটি রোল তৈরি করুন যার ডিফল্ট ট্রানজ্যাকশন মোড রাইট-এনাবলড (write-enabled), এবং তাকে শুধুমাত্র প্রয়োজনীয় প্রিভিলেজগুলো প্রদান করুন:
- টার্গেট ডাটাবেসে
CREATEকরার ক্ষমতা। - মাইগ্রেশন টুলকে সেশন খোলার অনুমতি দিতে
CONNECTকরার ক্ষমতা।
এই রোলটিকে default_transaction_read_only = on অ্যাট্রিবিউটটি দেবেন না, যা অনেক সময় সাধারণ ব্যবহারকারীদের জন্য সেট করা থাকে।
৩. ইনফ্রাস্ট্রাকচারকে রাইটার এন্ডপয়েন্টের দিকে নির্দেশ করুন
IaC স্ক্রিপ্টে (Terraform, CloudFormation, ইত্যাদি), মাইগ্রেশন রানারগুলোকে ক্লাস্টারের writer এন্ডপয়েন্ট ব্যবহার করার জন্য কনফিগার করুন, reader এন্ডপয়েন্ট নয়। রাইটার এন্ডপয়েন্ট প্রাইমারি নোডকে নির্দেশ করে, যেখানে reader এন্ডপয়েন্ট একটি রেপ্লিকার দিকে নির্দেশ করে যা রাইট অপারেশন প্রত্যাখ্যান করবে।
৪. একটি CI/CD গেট যোগ করুন
আপনার পাইপলাইনে (GitHub Actions, GitLab CI, ইত্যাদি) একটি শেল স্টেপ যুক্ত করুন যা pg_is_in_recovery() কুয়েরিটি চালায়। যদি এটি true রিটার্ন করে, তবে ডিপ্লয়মেন্ট দ্রুত বন্ধ করতে একটি নন-জিরো (non-zero) স্ট্যাটাস সহ জবটি এক্সিট করুন।
if psql $DATABASE_URL -c "SELECT pg_is_in_recovery()" | grep -q t; then
echo "Connected to replica – aborting migration"
exit 1
fi
৫. মাইগ্রেশন-টুলের রিট্রাই (retry) অপশন টিউন করুন
Flyway-এর মতো টুলগুলো প্রায়ই স্বয়ংক্রিয়ভাবে কানেকশন রিট্রাই করে। Flyway 9+ এর জন্য flyway.connectRetries=0 সেট করুন। এটি টুলটিকে বারবার একটি রেপ্লিকার ওপর আঘাত করা থেকে বিরত রাখবে, যা অন্যথায় রেপ্লিকেশন ল্যাগ (replication lag) বাড়িয়ে দিতে পারে এবং রিসোর্স অপচয় করতে পারে।
পরবর্তীতে যা খেয়াল রাখতে হবে
- Replication lag metrics: ক্রমবর্ধমান ল্যাগ নির্দেশ করতে পারে যে একটি মাইগ্রেশন অনিচ্ছাকৃতভাবে একটি রেপ্লিকার ওপর কাজ করার চেষ্টা করেছে, যার ফলে রাইট অপারেশনগুলো কিউতে (queue) জমা হচ্ছে।
- Connection-pooler routing rules: নিশ্চিত করুন যে পুলার কনফিগারেশনগুলো মাইগ্রেশন ট্রাফিককে স্পষ্টভাবে প্রাইমারি হোস্টের দিকে রাউট করছে।
- Role defaults after upgrades: ডাটাবেস আপগ্রেড করার সময় অনেক সময় রোল প্যারামিটারগুলো রিসেট হয়ে যায়; বড় ভার্সন পরিবর্তনের পর
default_transaction_read_onlyপুনরায় অডিট করুন।
মূল কথা হলো: read-only ট্রানজ্যাকশন এরর খুব কমই কোনো PostgreSQL বাগ; এটি মূলত ভুল নোডে ট্রাফিক পাঠানো বা ভুলভাবে কনফিগার করা রোলের একটি লক্ষণ। নোড রোল যাচাই করে, ডেডিকেটেড মাইগ্রেশন অ্যাকাউন্ট ব্যবহার করে এবং আপনার CI/CD পাইপলাইনকে আরও শক্তিশালী করার মাধ্যমে, আপনি মাইগ্রেশন প্রক্রিয়াটি মসৃণভাবে চালাতে পারেন এবং আংশিক সম্পন্ন হওয়া স্কিমা এড়াতে পারেন যা পরবর্তী ডেভেলপমেন্টের কাজে বাধা সৃষ্টি করে।
