expand/contract প্যাটার্নে ডাউনটাইমহীন স্কিমা পরিবর্তন
expand/contract প্যাটার্ন, নিরাপদ backfill, compatible release, যাচাই ও rollback দিয়ে ডাউনটাইম ছাড়া স্কিমা পরিবর্তনের পরিকল্পনা ও রিলিজ করুন।

কেন স্কিমা পরিবর্তনে সেবা বন্ধ হয়ে যায়
অ্যাপ্লিকেশনের সংস্করণ, background worker এবং ডেটাবেস কোন কাঠামো ও মান বৈধ, সে বিষয়ে একমত না থাকলে স্কিমা পরিবর্তনে সেবা বন্ধ হয়ে যেতে পারে। ব্যর্থতাটি স্পষ্ট হতে পারে, যেমন প্রতিটি request-এ error ফেরত আসা। আবার ধীরে ধীরেও দেখা দিতে পারে, যেমন query latency বেড়ে যাওয়া, write ব্যর্থ হওয়া, replica lag এবং পুনরায় চালাতে হবে এমন job-এর সারি জমা হওয়া।
প্রোডাকশনে ডিপ্লয়মেন্টে খুব কমই সব process একসঙ্গে বদলায়। Rolling release-এ পুরোনো ও নতুন application instance পাশাপাশি চলে। দীর্ঘসময় চলা worker ঘণ্টার পর ঘণ্টা পুরোনো build ধরে রাখতে পারে, mobile client মাসের পর মাস সক্রিয় থাকতে পারে, আর reporting বা integration job মূল অ্যাপ্লিকেশন ছাড়াই table ব্যবহার করতে পারে। সবাই একই database ভাগ করে নেয়।
সাধারণ ব্যর্থতার ধরনগুলো হলো:
- যে কলাম তৈরি করা migration শেষ হওয়ার আগেই নতুন code সেই কলামে write করে।
- পরের release যে table বা column-এর নাম বদলেছে বা মুছে ফেলেছে, পুরোনো code সেটিই read করে।
- Table rewrite, backfill বা index build এত I/O ও CPU নেয় যে স্বাভাবিক traffic ধীর হয়ে যায়।
- Schema command lock-এর জন্য অপেক্ষা করে, আর তার পেছনে request জমতে থাকে।
- নতুন constraint এমন process-এর write বাতিল করে, যেটি এখনো upgrade হয়নি।
আসল বিপদ প্রায়ই command চলার সময় নয়, lock পাওয়ার সময়। দ্রুত ALTER TABLE-ও দীর্ঘ transaction-এর পেছনে অপেক্ষা করতে পারে। অপেক্ষার সময় পরের query-গুলো মুলতুবি schema lock-এর পেছনে সারিবদ্ধ হতে পারে, ফলে ছোট migration পুরো অ্যাপ্লিকেশনকে থামিয়ে দেয়।
ডাউনটাইম এড়াতে প্রতিটি মধ্যবর্তী database state এমন হতে হবে যা এখনো চলতে থাকা প্রতিটি application version ব্যবহার করতে পারে। আগে সামঞ্জস্যপূর্ণ কাঠামো যোগ করুন, নিয়ন্ত্রিত ধাপে traffic ও data সরান, এবং শেষ ব্যবহারকারী চলে যাওয়ার পর পুরোনো পথ সরান।
লাইভ traffic, rolling deployment, কড়া availability লক্ষ্য বা ব্যয়বহুল recovery ব্যবস্থা থাকা সিস্টেমে এ কাজ যুক্তিযুক্ত। নীরব database-সহ ছোট internal tool-এর জন্য পরীক্ষিত maintenance window-ই ভালো হতে পারে। সিদ্ধান্তে outage-এর খরচ ও migration-এর operational complexity দুটোই বিবেচনায় রাখুন।
সহজ ভাষায় expand/contract
Expand/contract প্যাটার্ন একটি অসামঞ্জস্যপূর্ণ পরিবর্তনকে একাধিক সামঞ্জস্যপূর্ণ release-এ বদলে দেয়। Code ও data পুরোনো উপস্থাপনা থেকে নতুনটিতে যাওয়ার সময় database সাময়িকভাবে দুটিই সমর্থন করে।
এটির তিনটি অংশ:
- বর্তমান code-এর দরকারি কিছু না সরিয়ে column, table, index বা constraint যোগ করে expand করুন।
- Compatible code ডিপ্লয় করে, পুরোনো data সরিয়ে এবং read ও write-কে নতুন উপস্থাপনায় নিয়ে transition করুন।
- যাচাইয়ে পুরোনো অংশটি আর ব্যবহৃত হচ্ছে না প্রমাণ হলে পুরোনো code ও database object মুছে contract করুন।
ধরা যাক, একটি PostgreSQL table-এ মানুষের নাম full_name-এ রাখা হয়, কিন্তু অ্যাপ্লিকেশনের আলাদা first_name ও last_name field দরকার। Expand ধাপে full_name রেখে nullable কলাম যোগ করা হয়। Compatible release transition চলাকালে দরকারি উপস্থাপনাগুলোতে write করে। Backfill-এ বিদ্যমান মানগুলো আলাদা করা হয়, নির্ভরযোগ্যভাবে ভাগ করা যায় না এমন নামের জন্য সুস্পষ্ট নীতি রাখা হয়। নতুন field যথেষ্ট পূর্ণ হওয়ার পরেই read সরানো হয়। পরে contract ধাপে full_name সরানো হয়।
এই ক্রম rolling deployment-এর সঙ্গে মানানসই, কারণ পুরোনো build এখনো full_name পায় এবং নতুন build তিনটি কলামই পায়। এটি application rollback-এর পথও রাখে। নতুন release খারাপ আচরণ করলে আগের build চলতে পারে, কারণ তার schema dependency সরানো হয়নি।
Database rollback আর application rollback এক নয়। Data রূপান্তরের পর migration উল্টালে তথ্য হারাতে পারেন বা পুরোনো মান ফিরিয়ে আনতে পারেন। Transition চলাকালে additive database object রেখে traffic-কে পরিচিত উপস্থাপনায় ফিরিয়ে দিন। Incident স্থিতিশীল হওয়ার পর forward migration ঠিক করুন।
এ প্যাটার্নের অর্থ এই নয় যে প্রতিটি পরিবর্তনে dual-write code লাগবে। কেবল নতুন code ব্যবহার করবে এমন optional column যোগ করতে একটি additive migration ও একটি deployment-ই যথেষ্ট হতে পারে। Rename, representation change, table split ও required field পরিবর্তনে সাধারণত বেশি ধাপ লাগে, কারণ দুটি application version অন্যভাবে নিরাপদে schema ভাগ করতে পারে না।
ধাপ বাছার আগে পরিবর্তনটি শ্রেণিবদ্ধ করুন
Migration plan-এ operation-এর আসল lock, rewrite, compatibility ও data conversion ঝুঁকি থাকা দরকার। প্রতিটি ALTER TABLE-কে একই ভাবলে হয় অপ্রয়োজনীয় জটিলতা হবে, নয়তো release অনিরাপদ হবে।
Additive change সাধারণত সবচেয়ে সহজ। Nullable column, আলাদা table বা online method-এ তৈরি index অনেক সময় application code ব্যবহার শুরুর আগেই আনা যায়। Command-এর তবু lock লাগে, তাই production-এর মতো table ও transaction workload-এ এর আচরণ পরীক্ষা করুন।
Destructive change-এর মধ্যে আছে column বাদ দেওয়া বা rename করা, type ছোট করা, table প্রতিস্থাপন করা এবং কঠোর constraint যোগ করা। এসব পরিবর্তন বিদ্যমান code-এর একটি অনুমানকে অকার্যকর করে। Code reference ও বাইরের ব্যবহারকারী সরানোর পরে, এগুলো contract ধাপে রাখুন।
Data বদলানো operation আলাদা করে মূল্যায়ন করুন। Timestamp বদলানো, phone number normalise করা, record মিলিয়ে দেওয়া বা free-form text ভাগ করলে তথ্য হারাতে পারেন। Backfill শুরুর আগে invalid ও ambiguous মান কীভাবে সামলাবেন ঠিক করুন। কোনো transformation উল্টানো না গেলে business-level check পাস না করা পর্যন্ত source রেখে দিন।
একটি কার্যকর preflight review-এ পাঁচটি প্রশ্ন থাকে:
- প্রতিটি statement কোন lock চায়, এবং কতক্ষণ সেই lock-এর জন্য অপেক্ষা বা তা ধরে রাখতে পারে?
- Operation কি table rewrite করবে, ভারী WAL তৈরি করবে, বা replica lag বাড়াবে?
- কোন application, job, report ও change-data-capture consumer প্রভাবিত object ব্যবহার করে?
- বর্তমান ও প্রস্তাবিত release কি প্রতিটি transitional state-এর সঙ্গে চলতে পারে?
- কোন signal-এ operation থামবে, এবং থামার পর ঠিক কী state থাকবে?
বাস্তবসম্মত volume ও distribution-সহ data-তে সঠিক migration চালান। এক হাজার গোছানো row-সহ test table আপনাকে production table সম্পর্কে খুব কম বলে, যেখানে শত শত মিলিয়ন row, চওড়া tuple, dead row, অসম মান ও দীর্ঘ transaction থাকতে পারে।
PostgreSQL-এ নিরাপদে expand করুন
নিরাপদ PostgreSQL expansion-এ সংক্ষিপ্ত metadata change, সীমিত lock wait এবং দরকার হলে আলাদা online operation ব্যবহার করা হয়। যে code নতুন structure-এর ওপর নির্ভর করে তা ডিপ্লয় করার আগে structure যোগ করুন।
Default ছাড়া nullable column যোগ করা সাধারণত দ্রুত metadata operation:
BEGIN;
SET LOCAL lock_timeout = '2s';
SET LOCAL statement_timeout = '30s';
ALTER TABLE customers
ADD COLUMN phone_e164 text;
COMMIT;
Timeout release-কে খোলা transaction-এর পেছনে অনির্দিষ্টকাল অপেক্ষা করতে দেয় না। দ্রুত lock না পেলে migration ব্যর্থ হতে দিন, blocker দেখুন এবং নিরাপদ সময়ে আবার চেষ্টা করুন। Tight loop-এ স্বয়ংক্রিয় retry করবেন না, কারণ বারবার lock request production traffic ব্যাহত করতে পারে।
আধুনিক PostgreSQL release-এ constant default-সহ column যোগ করলে প্রতিটি বিদ্যমান row-তে সঙ্গে সঙ্গে মান লিখতে নাও হতে পারে। তাতে সব default নিরাপদ হয়ে যায় না। Volatile expression rewrite করাতে পারে, আর ALTER TABLE-এর তবু অল্প সময়ের ACCESS EXCLUSIVE lock লাগে। সাধারণ নিয়মের ওপর ভরসা না করে ব্যবহৃত PostgreSQL version ও ঠিক expression-এর আচরণ নিশ্চিত করুন।
সাধারণ CREATE INDEX write ব্লক করতে পারে। Table writable রাখতে হলে concurrent creation ব্যবহার করুন:
CREATE INDEX CONCURRENTLY idx_customers_phone_e164
ON customers (phone_e164);
CREATE INDEX CONCURRENTLY transaction block-এর ভেতরে চলতে পারে না। এতে বেশি সময় ও বাড়তি কাজ লাগে, পুরোনো transaction-এর জন্য অপেক্ষাও করতে পারে, তবে স্বাভাবিক insert, update ও delete চলতে পারে। তবু CPU, I/O ও WAL খরচ হয়, তাই চলার সময় database latency ও replica দেখুন।
Concurrent build ব্যর্থ হলে একটি invalid index থেকে যেতে পারে। Retry করার আগে index state দেখুন, তারপর invalid object ইচ্ছাকৃতভাবে সরান বা আবার তৈরি করুন। যে migration tool প্রতিটি file transaction-এ জড়িয়ে ফেলে, concurrent index operation-এর জন্য তার সমর্থিত non-transactional mode দরকার।
In-place transformation-এর চেয়ে নতুন table আনা অনেক সময় সহজ। One-to-many বা many-to-many সম্পর্কের জন্য source column রেখে target table ও তার index যোগ করুন। নতুন write, historical data, read ও downstream consumer সরার আগে source মুছবেন না।
Type change-এ বাড়তি সতর্কতা দরকার। কিছু শুধু metadata বদলায়, অন্যগুলো প্রতিটি row rewrite করে বা অনেকক্ষণ কড়া lock নেয়। ঝুঁকিপূর্ণ conversion-এর জন্য target type-সহ column যোগ করুন, batch-এ ভরুন, application access বদলান এবং পরে original বাদ দিন। এতে একটি বড় ALTER COLUMN TYPE একসঙ্গে সফল বা ব্যর্থ হওয়ার বদলে conversion failure নথিভুক্ত করার জায়গাও পাওয়া যায়।
সামঞ্জস্য বজায় রাখা code ডিপ্লয় করুন
Compatible application code অনুপস্থিত transitional value সহ্য করে এবং একই rollout-এ কখনো destructive migration বাধ্যতামূলক করে না। প্রথম application instance নতুন object ব্যবহার শুরুর আগেই database expansion শেষ হতে হবে।
দুই উপস্থাপনাই বর্তমান রাখতে হলে dual writing কাজে আসে। সম্ভব হলে একই database transaction-এ দুই write করুন। Asynchronous দ্বিতীয় write প্রথমটি সফল হওয়ার পর ব্যর্থ হতে পারে, ফলে ভিন্নতা তৈরি হয় যা পরে read-এ দেখা যায়।
Dual-write logic-এ একটিই authority দরকার। phone_e164 যদি phone থেকে আসে, দুই input দিলে কোনটি প্রাধান্য পাবে ঠিক করুন এবং API handler, worker, import ও administrative tool-এ একই normalization প্রয়োগ করুন। নইলে ঠিকঠাক দেখানো দুই code path ভিন্ন ফল সংরক্ষণ করতে পারে।
Read, write-এর পরে সরান। নতুন write দুই form পূরণ করার সময় এবং backfill পুরোনো row সামলানোর সময় established field থেকে read করুন। যাচাইয়ের পর এমন read path ডিপ্লয় করুন যা নতুন field অগ্রাধিকার দেয় এবং নির্দিষ্ট fallback নিয়মে কেবল পুরোনো মান ব্যবহার করে। Fallback-এর ব্যবহার মাপুন। নীরব fallback অসম্পূর্ণ data চিরকাল আড়াল করতে পারে।
একটি সাধারণ release sequence:
- Release 1 application behavior না বদলে নতুন database object যোগ করে।
- Release 2 established read চালু রেখে transitional representation-এ write করে।
- Release 3 backfill ও consistency check পাসের পর read বদলায়।
- Release 4 rollback-এর শর্ত শেষ হলে পুরোনো representation বজায় রাখা বন্ধ করে।
- Release 5 পুরোনো code reference সরায়, পরে database cleanup হয়।
Public API contract-কে physical schema change থেকে আলাদা রাখুন। Database column rename করলেই web, mobile বা integration response-এর field সঙ্গে সঙ্গে rename করতে হয় না। বিশেষ করে server-এর সঙ্গে upgrade করা যায় না এমন client থাকলে, নিজস্ব compatibility policy-তে সেই contract বদলান।
প্রতিটি writer-এর তালিকা করুন। HTTP handler পরিবর্তনের একমাত্র উৎস নয়। Queue consumer, scheduled job, import script, data repair tool, database trigger এবং সরাসরি administrative operation পুরোনো আকৃতির row তৈরি করে যেতে পারে। সম্ভব হলে database connection-এ application name tag করুন এবং transitional path-এর ব্যবহার log করুন, যাতে চোখ এড়িয়ে যাওয়া process ধরা পড়ে।
দীর্ঘসময় চলা process prepared statement, cached metadata বা object-relational mapping layer দিয়ে পুরোনো অনুমান ধরে রাখতে পারে। Contract-এর আগে rolling restart ও connection pool-এর আচরণ পরীক্ষা করুন। সাম্প্রতিক traffic না পাওয়া process-ও বিরল কোনো job প্রথমবার চললেই ব্যর্থ হতে পারে।
ডেটাবেসে চাপ না বাড়িয়ে data backfill করুন
নিরাপদ backfill ছোট, পুনরায় চালানো যায় এমন batch update করে এবং production health খারাপ হলে ধীর হয়। Live writer নতুন representation বজায় রাখতে পারার পরেই এটি শুরু করুন।
Universal row count নয়, elapsed time ও database impact অনুযায়ী batch বাছুন। এক হাজার সরু row millisecond-এ শেষ হতে পারে, আবার বড় value বা ব্যয়বহুল transformation-সহ এক হাজার row উল্লেখযোগ্য I/O তৈরি করতে পারে। সাবধানে শুরু করুন এবং কয়েক সেকেন্ডে শেষ হওয়া transaction লক্ষ্য করুন। Batch-এর মাঝে commit করুন, যাতে lock ও পুরোনো row version এক transaction-এ জমে না থাকে।
PostgreSQL সাধারণ UPDATE-এ সরাসরি ORDER BY ও LIMIT সমর্থন করে না। Common table expression-এ একটি batch বেছে নিয়ে তারপর সেই row-গুলো update করুন:
WITH batch AS (
SELECT id
FROM my_table
WHERE id > $1
AND new_col IS NULL
ORDER BY id
LIMIT 1000
)
UPDATE my_table AS target
SET new_col = transform_expression(target.old_col)
FROM batch
WHERE target.id = batch.id
AND target.new_col IS NULL
RETURNING target.id;
অ্যাপ্লিকেশন সবচেয়ে বড় সম্পন্ন id-কে cursor হিসেবে রাখে। Conditional update rerun-কে idempotent করে, তাই commit-এর পরে crash হলেও প্রক্রিয়াকৃত row নষ্ট হয় না। এমনভাবে progress রাখুন যেন cursor uncommitted batch পার হয়ে এগোতে না পারে।
ক্রমবর্ধমান id cursor table-এর শুরু বারবার scan করা এড়ায়, কিন্তু cursor-এর নিচে দেরিতে সংশোধিত row বা insert হওয়া row ধরতে পারে না। শেষে বাকি সব NULL value নিয়ে catch-up pass করুন। Identifier সাজানো না থাকলে বা row eligibility state-এর মধ্যে চলাচল করলে, একটি forward scan সম্পূর্ণ ধরে না নিয়ে work table বা অন্য স্পষ্ট checkpoint ব্যবহার করুন।
একাধিক worker FOR UPDATE SKIP LOCKED দিয়ে row দাবি করতে পারে, কিন্তু parallelism write pressure বাড়ায় এবং progress tracking জটিল করে। স্থায়ীভাবে এগিয়ে যাওয়া cursor-এর সঙ্গে skipped row মেলাবেন না। Parallel worker-এর জন্য claimed identifier-এর queue বা repeated eligibility scan বেশি নিরাপদ।
Query latency, active connection, lock wait, WAL generation, replica replay delay ও dead-row growth-এর মতো production measurement দেখে throttle করুন। Threshold পার হলে pause করুন, তারপর checkpoint থেকে চালান। Fixed sleep সহজ, কিন্তু database-এর feedback traffic বদলালে ভালোভাবে সাড়া দেয়।
শুধু কিছু row-তে কাজ দরকার হলে সব row বদলাবেন না। নতুন field, source state বা migration marker দিয়ে filter করুন। Transformation ব্যয়বহুল হলে, consistency অনুমতি দিলে update transaction-এর বাইরে তা হিসাব করুন, তারপর সংক্ষিপ্ত conditional write দিন। নীরবে data বানিয়ে না দিয়ে rejected value-এর count ও sample রাখুন।
প্রতিটি update-এর পর autovacuum ও replica-কে কাজটি সামলাতে হয়। Primary-তে backfill সফল হলেও replica অনেক পিছিয়ে যেতে পারে বা table bloat পরের query খারাপ করতে পারে। Rate limit-এ batch-এর তাৎক্ষণিক execution time নয়, সেই দেরিতে আসা খরচও ধরুন।
ডেটা ও production traffic যাচাই করুন
নতুন path authoritative হয়েছে, এমন data check, application telemetry ও dependency evidence একমত হলেই migration contract-এর জন্য প্রস্তুত। শুধু job counter সম্পন্ন হওয়া সঠিকতার প্রমাণ নয়।
প্রথমে completeness ও consistency দেখুন। PostgreSQL-এর IS DISTINCT FROM NULL স্পষ্টভাবে সামলে value তুলনা করে, <>-এর মতো নয়, কারণ যেকোনো পাশ NULL হলে সেটি unknown ফল দেয়:
SELECT count(*)
FROM customers
WHERE normalize_phone(phone) IS DISTINCT FROM phone_e164;
ব্যস্ত ও খুব বড় table-এ unindexed full-table count বারবার চালাবেন না। একবার নিয়ন্ত্রিত validation, সীমিত identifier range, sample বা table জুড়ে এগোনো অস্থায়ী verification process ব্যবহার করুন। ভুল হওয়ার খরচ ও database headroom অনুযায়ী সঠিক পদ্ধতি ঠিক করুন।
যাচাইয়ে অন্তর্ভুক্ত থাকা উচিত:
- নতুন field দরকার এমন row-তে অপ্রত্যাশিত কোনো missing value নেই।
- নতুন value, malformed ও empty input-সহ, সম্মত transformation-এর সঙ্গে মেলে।
- Historical pass শেষ হওয়ার পরেও নতুন row ও update সামঞ্জস্যপূর্ণ থাকে।
- Read fallback-এর ব্যবহার পরিকল্পিত সীমায় এসেছে, server-controlled traffic-এর ক্ষেত্রে সাধারণত শূন্য।
- Error rate, query latency, lock ও replica delay release limit-এর মধ্যে আছে।
Column-এর পাশাপাশি business outcome-ও তুলনা করুন। Migration যদি price, permission, account state বা identifier বদলায়, ব্যবহারকারী যে total ও invariant-এর ওপর নির্ভর করেন তা যাচাই করুন। দুটি column যান্ত্রিকভাবে মিললেও উভয়েই ভুল business rule ধারণ করতে পারে।
Cleanup-এর আগে একটি পূর্ণ operating cycle দেখুন। সঠিক সময়সীমা নির্দিষ্ট এক সপ্তাহের নিয়মে নয়, বাস্তব system behavior-এ নির্ভর করে। এতে month-end processing, বিরল billing job, দেরিতে queue retry বা পুরোনো mobile client-এর সর্বোচ্চ lifetime অন্তর্ভুক্ত হতে পারে। প্রতিটি consumer সরে গেছে, তার evidence রাখুন।
অ্যাপ্লিকেশন architecture অনুমতি দিলে read switch canary করুন। অল্প traffic নতুন read path-এ পাঠান, ফল তুলনা করুন, তারপর ধীরে ধীরে বাড়ান। Rollback সহজ রাখুন: backfill উল্টানো ছাড়াই read-কে established representation-এ ফেরান।
ডেটা প্রস্তুত হওয়ার পর constraint যোগ করুন
সব writer নিয়ম মানার পর এবং বিদ্যমান data যাচাই হওয়ার পরেই constraint কঠোর করুন। Expand চলাকালে NOT NULL, check বা foreign key কার্যকর করলে traffic ব্লক হতে পারে বা পুরোনো process-এর write বাতিল হতে পারে।
PostgreSQL-এ check constraint NOT VALID হিসেবে যোগ করা যায়, যা পুরোনো সব row সঙ্গে সঙ্গে scan না করে নতুন বা বদলানো row-তে নিয়মটি প্রয়োগ করে। Backfill-এর পরে আলাদা করে validate করুন:
ALTER TABLE customers
ADD CONSTRAINT customers_phone_e164_present
CHECK (phone_e164 IS NOT NULL) NOT VALID;
ALTER TABLE customers
VALIDATE CONSTRAINT customers_phone_e164_present;
Validation সফল হলে সমর্থিত PostgreSQL release-এ column-কে NOT NULL করতে সেই প্রমাণ ব্যবহার করা যায়, ফলে আরেকটি full table scan এড়ানো যায়। শেষ alteration-এর তবু শক্তিশালী table lock লাগে, তাই সীমিত lock timeout ও retry plan ব্যবহার করুন:
ALTER TABLE customers
ALTER COLUMN phone_e164 SET NOT NULL;
ALTER TABLE customers
DROP CONSTRAINT customers_phone_e164_present;
অস্থায়ী check-টি কাজে লাগলে রাখা যায়, তবে একই নিয়মের সমতুল্য constraint রাখলে নিয়ম না বদলে শুধু catalog clutter বাড়ে।
Foreign key-ও NOT VALID ও VALIDATE CONSTRAINT দিয়ে একই ক্রমে যোগ করা যায়। Constraint তৈরির পর নতুন write পরীক্ষা করা হয়, আর পুরোনো data পরে যাচাই হয়। Referenced relationship-এ delete বা update না হলে ব্যয়বহুল scan হতে পারে, তাই supporting index ইচ্ছাকৃতভাবে যোগ করুন।
Application validation database enforcement-এর আগে থাকা উচিত, কিন্তু সেটির বিকল্প নয়। Code পরিষ্কার user-facing error দেয়, আর database সব path দিয়ে লেখা data সুরক্ষিত রাখে। Rollout-এ constraint violation দেখুন, যাতে dependency audit বাদ দেওয়া writer ধরা পড়ে।
নিরাপদে পুরোনো path contract করুন
Contract ধাপে database object মুছার আগে application dependency সরান। Telemetry ও verification নতুন path-কে authoritative প্রমাণ করলে আলাদা release-এ cleanup করা যায়।
আগে পুরোনো field read করা ও fallback logic সরান। তারপর তার write বন্ধ করে বিরল path ধরার মতো সময় production দেখুন। পুরোনো representation উল্লেখ করা feature flag, trigger, compatibility view, repair script ও scheduled job সরান। Export করা source ও migration code-এ search করুন, পাশাপাশি মূল repository-এর বাইরের report, integration query এবং change-data-capture configuration-ও দেখুন।
নিরাপদ cleanup-এর ক্রম:
- Fallback read সরান এবং telemetry-তে তা আর দেখা যাচ্ছে না নিশ্চিত করুন।
- পুরোনো write বন্ধ করুন এবং synchronization code মুছুন।
- Deploy করা যায় এমন সব version থেকে application reference সরান।
- উপযুক্ত online method-এ অচল index ও constraint বাদ দিন।
- পরের database release-এ পুরোনো column বা table মুছুন।
PostgreSQL column drop মূলত catalog change, কিন্তু তবু ACCESS EXCLUSIVE lock লাগে। তাই সংক্ষিপ্ত statement-ও দীর্ঘ transaction-এর পেছনে অপেক্ষা করে পরের কাজ ব্লক করতে পারে। Lock timeout প্রয়োগ করুন, আগে দীর্ঘ transaction দেখুন এবং কম ঝুঁকির সময়ে চেষ্টা করুন।
Write ব্লক করা গ্রহণযোগ্য না হলে অচল index-এর জন্য DROP INDEX CONCURRENTLY ব্যবহার করুন। Concurrent creation-এর মতো এটিও transaction block-এর মধ্যে চলে না এবং migration tool-কে সামলাতে হবে এমন কিছু সীমাবদ্ধতা আছে।
এক release-এ code cleanup ও physical drop একসঙ্গে করবেন না। আলাদা রাখলে cleaned application এমন database-এ চলতে পারে যেখানে অব্যবহৃত object-টি এখনো আছে। Application problem দেখা দিলে schema আবার তৈরি বা data পুনর্গঠন ছাড়াই rollback সম্ভব থাকে।
Table drop-এর আগে sequence, view, function, grant, trigger, replication publication ও external query-এর ownership দেখুন। Production migration-এ shortcut হিসেবে CASCADE এড়িয়ে চলুন, কারণ এটি উদ্দেশ্য ছিল না এমন dependency মুছে দিতে পারে।
Rollback ও ব্যর্থ ধাপ সামলান
Rollback পরিকল্পনায় একটিমাত্র সাধারণ down migration-এর ওপর ভরসা না করে প্রতিটি phase-এর জন্য নিরাপদ কাজ নির্ধারণ করুন। Additive object, data movement, read switch ও deletion-এর recovery property আলাদা।
Expand lock পেতে ব্যর্থ হলে application অপরিবর্তিত রাখুন এবং blocking transaction সামলানোর পর আবার চেষ্টা করুন। Concurrent index build ব্যর্থ হলে invalid index রয়ে গেছে কি না দেখুন, তারপর আবার চেষ্টা করার আগে সেই নির্দিষ্ট object পরিষ্কার করুন।
Backfill load তৈরি করলে pause করুন। ইতিমধ্যে commit হওয়া idempotent batch রেখে দেওয়া যায়। Batch size বা rate কমান, ব্যয়বহুল transformation ঠিক করুন এবং checkpoint থেকে চালান। সঠিকভাবে করা মিলিয়ন update ফিরিয়ে দেওয়া সাধারণত ঝুঁকি বাড়ায়, production recovery-তে সাহায্য করে না।
নতুন read path ভুল ফল দিলে diagnosis-এর জন্য নতুন data রেখে read-কে পুরোনো representation-এ ফেরান। Dual write সঠিক জানা থাকলেই চালিয়ে যান। Writer-ই ত্রুটিপূর্ণ হলে প্রভাবিত row ঠিক করার আগে সেটি বন্ধ করুন বা application rollback করুন।
Contract-এর পর recovery-তে শুধু পুরোনো build ডিপ্লয় নয়, data restore-ও লাগতে পারে। Point of no return স্পষ্ট করে ঠিক করুন। System recovery policy অনুযায়ী দরকারি backup বা snapshot নিন, release-এর আগে restore পরীক্ষা করুন এবং storage cost অনুমতি দিলে সম্মত retention interval পর্যন্ত পুরোনো object রাখুন।
Schema command transactional হতে পারে, কিন্তু external effect সবসময় তার অন্তর্ভুক্ত নয়। Concurrent index operation, queue message, cache change ও application deployment একটি atomic transaction ভাগ করে না। প্রতিটি partial failure-এর পরে দৃশ্যমান state এবং সেখান থেকে নিরাপদে চলার command runbook-এ লিখুন।
সাধারণ migration ফাঁদ এড়িয়ে চলুন
বেশির ভাগ ব্যর্থ ডাউনটাইমহীন migration হয় নতুন state খুব তাড়াতাড়ি কার্যকর করা থেকে, নয়তো পুরোনো state-এর কোনো consumer ভুলে যাওয়ার কারণে। অনুমোদনের আগে নিচের ফাঁদগুলো স্পষ্টভাবে পর্যালোচনা করুন।
- পুরোনো application instance এখনো field বাদ দিতে পারলে
NOT NULLযোগ করা। - একটি বড় transaction-এ বিশাল backfill চালিয়ে lock ও row version খুব বেশি সময় ধরে রাখা।
- Column rename-কে additive ভাবা, যদিও পুরোনো code এখনো original name ব্যবহার করে।
- সব write path ও historical row নতুন representation পূরণ করার আগে read বদলে ফেলা।
- Deployment সফল হওয়াকে report, worker, replica ও integration compatible হওয়ার প্রমাণ ধরা।
আরেকটি সূক্ষ্ম ব্যর্থতা আসে bidirectional synchronization থেকে। একটি trigger old_col থেকে new_col কপি করে, আর application code new_col থেকে আবার old_col কপি করে। Normalization বা trigger order-এর ফারাকে loop তৈরি হতে পারে, ইচ্ছাকৃত value overwrite হতে পারে বা ownership অস্পষ্ট হতে পারে। একটিমাত্র direction বেছে নিন এবং প্রতিটি release-এ কোন representation authoritative তা নথিভুক্ত করুন।
Default writer update না হওয়া আড়াল করতে পারে। নতুন required column খালি বা সাধারণ default পেলে পুরোনো code compatible দেখায়, কিন্তু অর্থগতভাবে invalid data রাখে। অনুপস্থিতি দরকারি diagnostic তথ্য দিলে nullable transition ব্যবহার করুন, তারপর সব writer অর্থবহ value দেওয়ার পর আসল নিয়ম কার্যকর করুন।
Feature flag নিজে থেকে কোনো incompatible schema command নিরাপদ করে না। Disabled code path-ও পুরোনো process-এ loaded, prepared বা চলতে পারে। কোনো deployable বা active version আর reference না করা পর্যন্ত database object রেখে দিন।
Migration ownership-ও গুরুত্বপূর্ণ। Verification ও removal date-সহ contract পর্যন্ত transition-এর দায়িত্ব একজন ব্যক্তি বা দলকে দিন। নইলে অস্থায়ী column, flag ও synchronization job মাসের পর মাস থেকে যায় এবং পরের প্রতিটি পরিবর্তনের খরচ বাড়ায়।
ডাউনটাইম ছাড়াই phone column প্রতিস্থাপন করুন
customers.phone-কে normalized customers.phone_e164 দিয়ে প্রতিস্থাপনে additive column, নির্ধারিত conversion policy, compatible code, সীমিত backfill, read switch ও বিলম্বিত cleanup দরকার। SQL-এর আগে conversion policy ঠিক করুন, কারণ সংরক্ষিত সব value স্বয়ংক্রিয়ভাবে normalise করা যায় না।
বিদ্যমান value শ্রেণিবদ্ধ করে শুরু করুন। দরকারি country context জানা থাকলে valid number রূপান্তর করা যায়। খালি value NULL হতে পারে। Ambiguous বা malformed number অনুমান না করে exception report-এ দিন। প্রতিটি customer-এর phone number থাকা product-এর জন্য জরুরি কি না ঠিক করুন, কারণ পরে NOT NULL উপযুক্ত হবে কি না সেটিই নির্ধারণ করবে।
সংক্ষিপ্ত lock timeout-সহ column যোগ করুন:
BEGIN;
SET LOCAL lock_timeout = '2s';
ALTER TABLE customers
ADD COLUMN phone_e164 text;
COMMIT;
নতুন input normalise করে এক transaction-এ phone ও phone_e164-এ write করে এমন code ডিপ্লয় করুন। শুরুতে phone থেকেই read করুন। Account import, support tool, worker job এবং customer fixture তৈরি করা test-সহ প্রতিটি writer update করুন।
যোগ্য row-গুলো ছোট transaction-এ backfill করুন। শেষ processed identifier, রূপান্তরিত সংখ্যা, বাদ দেওয়া সংখ্যা এবং প্রতিটি failure category-এর কারণ রাখুন। Production latency ও replica delay দেখে job-এর rate limit করুন। Forward pass শেষ হলে concurrent insert বা restart-এর পরে বাদ পড়া row ধরতে eligible NULL value আবার scan করুন।
Application-এর একই normalization rule দিয়ে consistency check চালান, তারপর international prefix, extension, blank, duplicate contact record ও পুরোনো imported data হাতে sample করুন। Row count coverage দেখায়, সঠিক telephone number নয়।
এমন read path ডিপ্লয় করুন যা phone_e164 থাকলে সেটি ফেরত দেয় এবং logged exception-এ কেবল phone ব্যবহার করে। Fallback use ও normalization error পর্যবেক্ষণ করুন। Fallback-কে স্থায়ী আচরণ হতে না দিয়ে বাকি exception সমাধান করুন।
নতুন field authoritative হলে fallback সরান এবং phone-এ write বন্ধ করুন। উপযুক্ত operating cycle জুড়ে বিরল job ও integration traffic দেখুন। Product rule চাইলে তবেই validated constraint যোগ করুন।
সবশেষে phone-এর code reference সরান। এর index বা constraint আলাদা করে drop করুন, তারপর সীমিত lock wait-সহ পরের migration-এ column drop করুন। তার আগে যেকোনো সময় read switch ব্যর্থ হলে, দুই column-ই থাকা অবস্থায় application behavior rollback করুন।
এই উদাহরণটি এমন একটি domain issue-ও দেখায় যা schema mechanics দিয়ে মেটানো যায় না: মানুষের লেখা data ভাগ করা বা normalise করা সবসময় lossless নয়। Migration plan-এ exception সংরক্ষণ করতে হবে এবং তা সমাধানের জন্য একজন দায়িত্বশীল থাকতে হবে।
Release পাঠানোর আগে প্রতিটি ধাপ পরীক্ষা করুন
একটি release checklist-এ compatibility প্রমাণ, production impact সীমিত করা এবং বর্তমান phase-এর recovery action-এর নাম থাকা উচিত। Change-এর সঙ্গে evidence রাখুন, যাতে incident-এর সময় operator-কে উদ্দেশ্য নতুন করে বুঝতে না হয়।
Deployment-এর আগে নিশ্চিত করুন:
- Application versionটি এই release-এর আগে ও পরের database state-এর সঙ্গে কাজ করে।
- Traffic-এর পেছনে অপেক্ষা করতে পারে এমন schema command-এ lock ও statement timeout নির্ধারিত।
- Backfill বা validation job-এ progress, pause, resume ও rate-limit control আছে।
- Dashboard-এ error, latency, lock, database load, WAL ও replica delay দেখা যায়।
- ইতিমধ্যে সরানো object-এর ওপর নির্ভর না করে rollback action পরীক্ষা করা হয়েছে।
স্পষ্ট completion condition লিখুন। উদাহরণ: একটি পূর্ণ job cycle-এ নতুন consistency failure শূন্য, server-controlled traffic-এ fallback read শূন্য, সব জানা consumer upgrade হয়েছে এবং controlled validation query সফল হয়েছে। Backfill চলাকালে কত শতাংশ সম্পন্ন হয়েছে উপকারী, তবে 100 শতাংশ process হওয়া মানে 100 শতাংশ সঠিক হওয়া নয়।
Code review থেকে আলাদা করে migration ordering review করুন। SQL ও application change-এর একটি সঠিক সেটও deployment ভুল sequence-এ চালালে ব্যর্থ হতে পারে। কোন ধাপ অন্য ধাপ শেষ হওয়ার পরেই শুরু করা যাবে তা লিখুন।
সম্ভব হলে stop condition সংখ্যাভিত্তিক করুন। গ্রহণযোগ্য query latency, lock wait, replica delay, error rate ও batch duration ঠিক করুন। Threshold পার হলে incident-এর সময় নতুন অনুমোদন না খুঁজে operator যেন জানেন job pause করবেন, অপেক্ষমাণ statement cancel করবেন, নাকি read redirect করবেন।
নতুন representation read ও write সামলালে, historical data যাচাই পাস করলে, পুরোনো object সরালে এবং অস্থায়ী operational ব্যবস্থা চলে গেলে তবেই migration সম্পূর্ণ।
প্রক্রিয়াটি পুনর্ব্যবহারযোগ্য করুন
পুনর্ব্যবহারযোগ্য migration runbook expand/contract-কে নির্ধারিত owner ও মাপা যায় এমন gate-সহ সাধারণ release কাজ বানায়। Live deployment-এর সময় অনুসরণ করার মতো সংক্ষিপ্ত, আবার partial failure state বোঝানোর মতো নির্দিষ্ট হওয়া দরকার।
Runbook-এ পাঁচটি অংশ রাখুন:
- Expansion: সঠিক schema operation, প্রত্যাশিত lock, timeout ও transaction requirement।
- Compatibility: প্রভাবিত code, writer, reader, flag, client ও deployment order।
- Backfill: transformation policy, batching, checkpoint, throttling ও exception handling।
- Verification: SQL check, business invariant, telemetry ও completion threshold।
- Contraction: dependency removal, observation period, physical cleanup ও recovery limit।
প্রতিটি transitional object-এর জন্য owner ও প্রত্যাশিত completion date দিন। Column, index, flag, trigger ও job একই জায়গায় track করুন। Cleanup migration-এর অংশ, ঐচ্ছিক maintenance নয়।
Koder.ai দিয়ে কাজ করা দল Planning Mode ব্যবহার করে production change শুরুর আগে এসব phase ও checkpoint বিস্তারিত লিখতে পারে। Source code export-এর মাধ্যমে migration SQL ও compatibility logic-ও অন্যান্য application code-এর মতো review পায়। Koder.ai deployment, hosting, snapshot ও rollback সমর্থন করে, তবে application rollback committed data transformation উল্টে দেবে এমন ধরে নেওয়া ঠিক নয়। Database recovery plan-এর আর পুরোনো representation না লাগা পর্যন্ত schema compatibility বজায় রাখুন।
সম্ভব হলে কম traffic-এর সময়ে write-heavy কাজ নির্ধারণ করুন, কিন্তু সময় নির্ধারণকে একমাত্র safety control বানাবেন না। সীমিত transaction, feedback-ভিত্তিক throttling, দৃশ্যমান progress এবং পরীক্ষিত pause action অনলাইন migration-কে সামলানো সহজ রাখে, যখন traffic বা data প্রত্যাশার চেয়ে ভিন্ন আচরণ করে।
সাধারণ প্রশ্ন
স্কিমা পরিবর্তনে কেন সেবা বন্ধ হয়ে যেতে পারে?
পুরোনো ও নতুন অ্যাপ্লিকেশন সংস্করণ ভিন্ন ডেটাবেস কাঠামো আশা করলে স্কিমা পরিবর্তন প্রোডাকশন ভেঙে দিতে পারে। Rolling deployment চলাকালে দুই সংস্করণ একসঙ্গে চলতে পারে, তাই খুব তাড়াতাড়ি কোনো কলাম মুছে ফেললে বা নাম বদলালে read বা write ব্যর্থ হতে পারে।
expand/contract মাইগ্রেশন প্যাটার্ন কী?
Expand/contract একটি অসামঞ্জস্যপূর্ণ পরিবর্তনকে নিরাপদ ধাপে ভাগ করে। আগে নতুন কাঠামো যোগ করা হয়, তারপর কোড ও ডেটা সেখানে সরানো হয়, এবং সব ব্যবহারকারী পুরোনো কাঠামো ব্যবহার বন্ধ করার পর সেটি মুছে দেওয়া হয়।
ডাউনটাইম ছাড়াই ডেটাবেসের একটি কলামের নাম বদলাব বা প্রতিস্থাপন করব কীভাবে?
আগে নতুন কলাম যোগ করুন এবং পুরোনো কলামটি রেখে দিন। এমন কোড ডিপ্লয় করুন যা দুই ফিল্ডেই কাজ করতে পারে, ছোট batch-এ পুরোনো row ব্যাকফিল করুন, যাচাইয়ের পর read বদলান এবং পরের কোনো রিলিজে পুরোনো কলামটি মুছুন।
ট্রাফিক বন্ধ না করে কি PostgreSQL কলাম যোগ করা যায়?
বেশির ভাগ ক্ষেত্রে পারবেন। Default ছাড়া nullable কলাম PostgreSQL-এ সাধারণত দ্রুত metadata পরিবর্তন, তবে তবু টেবিল lock লাগে। অল্প সময়ের lock timeout দিন, যাতে দীর্ঘ transaction-এর পেছনে অপেক্ষা না করে migration ব্যর্থ হয়।
write ব্লক না করে index তৈরি করব কীভাবে?
টেবিলটি write করা চালু রাখতে হলে CREATE INDEX CONCURRENTLY ব্যবহার করুন। এতে বেশি সময় ও ডেটাবেসের বাড়তি লোড লাগে, এবং এটি transaction block-এর মধ্যে চালানো যায় না। চলার সময় latency, WAL ও replica delay দেখুন।
অ্যাপ্লিকেশন কখন পুরোনো ও নতুন ফিল্ডে dual write করবে?
দুই উপস্থাপনাই হালনাগাদ রাখতে হলে একই database transaction-এ দুই মান লিখুন। মান দুটি না মিললে কোন ফিল্ডটি প্রাধান্য পাবে তা ঠিক করুন এবং API, worker, import ও support tool-এ একই normalization নিয়ম ব্যবহার করুন।
বড় PostgreSQL টেবিল নিরাপদে backfill করব কীভাবে?
সংক্ষিপ্ত, পুনরায় চালানো যায় এমন batch চালান এবং প্রতিটি batch-এর পর commit করুন। একটি checkpoint রাখুন, কেবল যেসব row-তে কাজ বাকি সেগুলো update করুন, এবং query latency, lock wait, WAL-এর পরিমাণ বা replica lag বাড়লে জবটি ধীর করুন বা থামান।
একটি backfill সম্পূর্ণ ও সঠিক হয়েছে কি না জানব কীভাবে?
শুধু backfill শেষ হয়েছে বলে read বদলাবেন না। দরকারি মান আছে কি না দেখুন, পুরোনো ও নতুন উপস্থাপনা মিলিয়ে দেখুন, fallback read পর্যবেক্ষণ করুন এবং ঐতিহাসিক ডেটা প্রক্রিয়াকরণের পর নতুন write-ও সামঞ্জস্য রাখছে কি না নিশ্চিত করুন।
কখন NOT NULL, check constraint বা foreign key যোগ করব?
আগের ডেটা যাচাই পেরোনোর পরে এবং সব সক্রিয় writer বৈধ মান পাঠালে কঠোর constraint যোগ করুন। PostgreSQL-এ কিছু constraint NOT VALID হিসেবে যোগ করা যায়, যাতে নতুন row-তে নিয়মটি কার্যকর হয় এবং পুরোনো row পরে আলাদা করে যাচাই করা যায়।
পুরোনো schema path কখন সরানো নিরাপদ?
আগে fallback read সরান, তারপর পুরোনো write বন্ধ করে পুরো একটি কাজের চক্র পর্যন্ত সিস্টেম দেখুন। সব অ্যাপ, job, report, integration ও client পুরোনো object ব্যবহার বন্ধ করলে সংশ্লিষ্ট code সরান এবং পরের কোনো রিলিজে ডেটাবেসের কলাম বা টেবিলটি মুছুন।