পাঠ ২৯ · ৫৮-এর মধ্যে · মডিউল ৭
Home / Courses / Full-Stack Web Frameworks / মাইগ্রেশন ও স্কিমা ইভোলিউশন

মাইগ্রেশন ও স্কিমা ইভোলিউশন

Migrations and schema evolution
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • মাইগ্রেশন কেন দরকার — স্কিমা পরিবর্তনকে ট্র্যাকযোগ্য, পুনরুৎপাদনযোগ্য (reproducible) করা
  • একটি ধারাবাহিক মাইগ্রেশন তালিকা কীভাবে তৈরি ও প্রয়োগ করা হয়, প্রতিটির সাথে একটি ভার্সন নম্বর
  • প্রতিটি মাইগ্রেশনের একটি "ইনভার্স" (rollback) ফাংশন কেন থাকা উচিত
  • রোলব্যাকের পর স্কিমা আগের অবস্থার সাথে হুবহু মিলছে কি না equality চেক দিয়ে যাচাই করা

১ · মাইগ্রেশন কী ও কেন দরকার

মাইগ্রেশনDatabase Migrationএকটি ডেটাবেস স্কিমার একটি নির্দিষ্ট পরিবর্তনকে বর্ণনা করা, সংস্করণযুক্ত ও পুনরায় প্রয়োগযোগ্য একটি স্ক্রিপ্ট/ফাংশন হিসেবে। একটি অ্যাপ্লিকেশনের ডেটাবেস স্কিমা কখনোই স্থির থাকে না — নতুন ফিচারের জন্য নতুন কলাম লাগে, পুরনো কলামের নাম বদলাতে হয়, নতুন টেবিল যোগ হয়। মাইগ্রেশন প্রতিটি পরিবর্তনকে একটি ছোট্ট, ধারাবাহিক ধাপ হিসেবে রেকর্ড করে, যাতে টিমের প্রতিটি সদস্য ও প্রতিটি এনভায়রনমেন্ট (dev, staging, production) ঠিক একই ক্রমে একই পরিবর্তনগুলো প্রয়োগ করতে পারে।

ধারাবাহিক
প্রতিটি মাইগ্রেশনের একটি নির্দিষ্ট ক্রম আছে — পরের মাইগ্রেশন সবসময় আগেরটির উপর ভিত্তি করে প্রয়োগ হয়।
সংস্করণযুক্ত
একটি current_version ট্র্যাকার জানায় ডেটাবেস এখন ঠিক কোন মাইগ্রেশন পর্যন্ত আপডেট হয়েছে।
রিভার্সিবল
প্রতিটি মাইগ্রেশনের একটি ইনভার্স (rollback) থাকা উচিত, যাতে কোনো পরিবর্তন সমস্যা তৈরি করলে নিরাপদে ফিরিয়ে নেওয়া যায়।
DBMS & SQL কোর্সের সাথে সম্পর্ক

বাস্তব ডেটাবেসে একটি মাইগ্রেশনের apply ধাপ প্রকৃতপক্ষে ALTER TABLE-জাতীয় SQL স্টেটমেন্ট চালায় — সেই SQL সিনট্যাক্স Database Management Systems কোর্সে শেখানো হয়েছে। এখানে আমরা মাইগ্রেশনের গঠন ও ধারাবাহিকতার নিয়মের উপর ফোকাস করছি — নিচের কোড সেলে একটি স্কিমাকে একটি সাধারণ পাইথন dict দিয়ে উপস্থাপন করা হয়েছে, আর প্রতিটি মাইগ্রেশন সেই dict-কে বদলানো একটি সত্যিকারের ফাংশন।

২ · ধারাবাহিক মাইগ্রেশন প্রয়োগ ও রোলব্যাক

নিচের কোড সেলে তিনটি মাইগ্রেশন সংজ্ঞায়িত করা হয়েছে — প্রতিটির একটি apply ফাংশন ও একটি rollback ফাংশন। মাইগ্রেশনগুলো ক্রমানুসারে প্রয়োগ করে প্রতিবার স্কিমা প্রিন্ট করা হয়েছে, তারপর শেষ মাইগ্রেশনটি রোলব্যাক করে দেখানো হয়েছে ফলাফল ঠিক আগের রেকর্ড করা স্কিমার সাথে মিলছে কি না।

Python
import copy

# ---------- প্রতিটি মাইগ্রেশনের apply ও rollback ফাংশন ----------
def migration_001_add_email_apply(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["email"] = "string"
    return new_schema

def migration_001_add_email_rollback(schema):
    new_schema = copy.deepcopy(schema)
    del new_schema["columns"]["email"]
    return new_schema


def migration_002_rename_name_apply(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["full_name"] = new_schema["columns"].pop("name")
    return new_schema

def migration_002_rename_name_rollback(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["name"] = new_schema["columns"].pop("full_name")
    return new_schema


def migration_003_add_created_at_apply(schema):
    new_schema = copy.deepcopy(schema)
    new_schema["columns"]["created_at"] = "datetime"
    return new_schema

def migration_003_add_created_at_rollback(schema):
    new_schema = copy.deepcopy(schema)
    del new_schema["columns"]["created_at"]
    return new_schema


# ---------- ধারাবাহিক মাইগ্রেশন তালিকা ----------
MIGRATIONS = [
    ("0001_add_email", migration_001_add_email_apply, migration_001_add_email_rollback),
    ("0002_rename_name_to_full_name", migration_002_rename_name_apply, migration_002_rename_name_rollback),
    ("0003_add_created_at", migration_003_add_created_at_apply, migration_003_add_created_at_rollback),
]

schema = {"table": "users", "columns": {"id": "integer", "name": "string"}}
current_version = 0
history = [copy.deepcopy(schema)]   # history[i] = i-নম্বর মাইগ্রেশন প্রয়োগের পরের স্কিমা (history[0] = শুরুর স্কিমা)

print(f"ভার্সন {current_version} (শুরু): {schema}")

for name, apply_fn, rollback_fn in MIGRATIONS:
    schema = apply_fn(schema)
    current_version += 1
    history.append(copy.deepcopy(schema))
    print(f"ভার্সন {current_version} ({name}): {schema}")

# ---------- শেষ মাইগ্রেশন রোলব্যাক করা ----------
last_name, last_apply, last_rollback = MIGRATIONS[-1]
schema_before_rollback = schema
prior_recorded_schema = history[-2]      # রোলব্যাকের আগেই রেকর্ড করা "আগের" স্কিমা

rolled_back_schema = last_rollback(schema_before_rollback)
current_version -= 1

print(f"\n'{last_name}' রোলব্যাক করার আগের স্কিমা: {schema_before_rollback}")
print(f"'{last_name}' রোলব্যাক করার পরের স্কিমা:  {rolled_back_schema}")
print(f"পূর্বে রেকর্ড করা স্কিমা (ভার্সন {current_version}):     {prior_recorded_schema}")

matches = rolled_back_schema == prior_recorded_schema
print(f"\nরোলব্যাক করা স্কিমা পূর্বের রেকর্ড করা স্কিমার সাথে হুবহু মিলছে: {matches}")
assert matches

print(f"রোলব্যাকের পর current_version: {current_version}")

    
লক্ষ্য করুন migration_002_rename_name_apply-এ .pop("name") দিয়ে পুরনো কী সরিয়ে "full_name" নামে নতুন কী যোগ করা হয়েছে — এটিই একটি রিনেমের সবচেয়ে সহজ উপস্থাপন। প্রতিটি apply/rollback জোড়া copy.deepcopy(schema) দিয়ে শুরু হয়, মূল schema dict-কে সরাসরি না বদলিয়ে — এভাবে history লিস্টে রাখা আগের প্রতিটি ভার্সনের স্ন্যাপশট অক্ষত থাকে, যা রোলব্যাক যাচাইয়ের জন্য জরুরি।
মূল কথা · Key takeaway

মাইগ্রেশন স্কিমা পরিবর্তনকে ছোট্ট, ধারাবাহিক, সংস্করণযুক্ত ধাপে ভাঙে — প্রতিটি ধাপের একটি apply ও একটি rollback থাকা উচিত। উপরের উদাহরণে দেখা গেছে রোলব্যাক করা স্কিমা ঠিক আগের রেকর্ড করা স্কিমার সাথে হুবহু মিলে যায় — এই সমতাই প্রমাণ করে মাইগ্রেশন সিস্টেমটি নির্ভরযোগ্য। পরের পাঠে (L30) আমরা L28-এর Model ক্লাস বিস্তৃত করব রিলেশনশিপ মডেল করতে — এবং সেই মডেলগুলোর কলাম এই মাইগ্রেশন প্র্যাকটিসের মাধ্যমেই সময়ের সাথে বদলায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ প্রতিটি মাইগ্রেশন ফাংশন কেন মূল schema dict-কে সরাসরি না বদলিয়ে copy.deepcopy দিয়ে একটি নতুন কপি তৈরি করে বদলায়?

যদি মূল schema সরাসরি বদলানো হতো, তাহলে history লিস্টে রাখা আগের স্ন্যাপশটগুলোও (যদি সেগুলো একই অবজেক্টের রেফারেন্স হতো) নিঃশব্দে বদলে যেত — কারণ পাইথনে dict মিউটেবল, এবং একই অবজেক্টের একাধিক রেফারেন্স থাকলে একটি বদলালে সবগুলো বদলে যায়। deepcopy নিশ্চিত করে প্রতিটি ভার্সনের স্ন্যাপশট সম্পূর্ণ স্বাধীন থাকে, তাই রোলব্যাক যাচাইয়ের সময় history[-2] নির্ভরযোগ্যভাবে "আগের" অবস্থা প্রতিনিধিত্ব করে।

প্র ০২ migration_002_rename_name_rollback কেন new_schema["columns"]["name"] = new_schema["columns"].pop("full_name") লেখে, শুধু del ব্যবহার করে না কেন?

একটি রিনেম অপারেশনের ইনভার্সও একটি রিনেম — full_name-কে আবার name-এ ফিরিয়ে আনতে হবে, শুধু কলামটি মুছে ফেললে (del) সেই ডেটা-টাইপ তথ্যসহ কলামটিই হারিয়ে যেত, যা আসল রোলব্যাক নয়। .pop("full_name") সেই কলামের মান (এখানে "string") বের করে আনে এবং সেটিকে নতুন কী "name"-এ বসায় — এভাবে কলামটি টিকে থাকে, শুধু নামই বদলায়।

প্র ০৩ রোলব্যাক করার পর current_version ২-এ নেমে আসে কেন, ১ বা ৩ নয়?

কারণ তিনটি মাইগ্রেশন প্রয়োগের পর current_version ছিল ৩, এবং আমরা শুধু সবচেয়ে শেষ মাইগ্রেশনটি (0003_add_created_at, যা ভার্সন ৩-এ নিয়ে গিয়েছিল) রোলব্যাক করেছি। একটি মাইগ্রেশন রোলব্যাক মানে ঠিক এক ধাপ পিছিয়ে যাওয়া — তাই ভার্সন ৩ থেকে ২-এ নামে, যা 0002_rename_name_to_full_name প্রয়োগের ঠিক পরের অবস্থা।

অনুশীলন

  1. চিন্তা করুন: যদি migration_003_add_created_at_apply-এর rollback ফাংশনটি ভুলবশত del new_schema["columns"]["full_name"] লিখত (ভুল কলাম মুছত), তাহলে রোলব্যাকের সমতা যাচাই (rolled_back_schema == prior_recorded_schema) কী ফলাফল দিত এবং কেন এটি একটি গুরুত্বপূর্ণ সেফটি-নেট?

    সমতা যাচাই False ফেরত দিত এবং assert matches লাইনে AssertionError raise করত — কারণ ভুল রোলব্যাক created_at কলাম রেখে full_name কলাম মুছে ফেলত, যা পূর্বের রেকর্ড করা স্কিমার (যেখানে full_name আছে, created_at নেই) সাথে মেলে না। এই equality চেকটিই একটি স্বয়ংক্রিয় সেফটি-নেট — এটি ছাড়া একটি ভুল রোলব্যাক নিঃশব্দে স্কিমা নষ্ট করে দিতে পারত, বাগ ধরা পড়ত অনেক পরে।

  2. পরীক্ষা করুন: উপরের কোড সেলে একটি চতুর্থ মাইগ্রেশন যোগ করুন, 0004_add_is_active, যা "is_active": "boolean" কলাম যোগ করে (ও তার rollback), MIGRATIONS তালিকায় যোগ করুন, এবং পুরো সেল আবার চালিয়ে দেখুন ভার্সন ৪ পর্যন্ত স্কিমা কেমন দেখায় ও রোলব্যাক এখনো সঠিকভাবে কাজ করে কি না।

    নতুন মাইগ্রেশন যোগ করার পর ভার্সন ৪-এর স্কিমায় id, email, full_name, created_at, ও is_active — পাঁচটি কলাম থাকবে। যেহেতু কোডে সবসময় MIGRATIONS[-1] (তালিকার সর্বশেষ মাইগ্রেশন) রোলব্যাক করা হয়, নতুন মাইগ্রেশন যোগ করার পর এটি স্বয়ংক্রিয়ভাবে 0004_add_is_active-কেই রোলব্যাক করবে এবং history[-2]-এর সাথে মিলিয়ে দেখাবে — যা প্রমাণ করে এই প্যাটার্নটি যেকোনো সংখ্যক মাইগ্রেশনের জন্য কাজ করে, শুধু তিনটির জন্য নয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • পরবর্তী পাঠ L30 রিলেশনশিপ মডেলিং — L28-এর Model ক্লাস ওয়ান-টু-মেনি ও মেনি-টু-মেনি সম্পর্ক পর্যন্ত বিস্তৃত করা দেখুন।
  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
  • Database Management Systems কোর্স সহোদর কোর্স বাস্তব ALTER TABLE-জাতীয় SQL স্টেটমেন্ট, যা প্রকৃত মাইগ্রেশন টুলের ভেতরে চলে, সেই কোর্সে শেখানো হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।
আগের পাঠ
ORM প্যাটার্ন — অবজেক্টকে টেবিলে ম্যাপ করা