2.10
Schema Evolution ও Compatibility
পুরোনো reader বা writer না ভেঙে message-এর format বদলানো: backward, forward আর full compatibility।
40 মিনিট পড়া + Animation
টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।
এই Lesson শেষে তুমি পারবে
- বোঝাতে, Distributed System-এ একটা Message বা Record-এর Format বদলানো কেন কঠিন: Rolling Deploy, আলাদা আলাদা Team, Rollback, আর বছরের পর বছর টিকে থাকা Data।
- Backward, Forward আর Full Compatibility (আর এদের Transitive রূপ) Writer Schema আর Reader Schema-র চোখ দিয়ে একদম নিখুঁতভাবে বলতে।
- প্রতিটা Compatibility মোডে কে আগে Deploy হবে ঠিক করতে, আর ক্রম ভুল হলে সামলে উঠতে।
- Protobuf (Field Number,
reserved, Type বদল, Enum), Avro (Default, Alias, Union, Promotion), JSON (Tolerant Reader, JSON Schema) আর Thrift-এর Evolution-এর নিয়ম খাটাতে। - Expand–migrate–contract দিয়ে কোনো Outage ছাড়াই Field-এর নাম বদলাতে, Type বদলাতে বা Field বাদ দিতে।
- Schema Registry কীভাবে Compatibility চেক করে আর প্রতিটা Record-এর সামনের ৫ Byte-এর মানে কী, আর Event-sourced System কীভাবে Upcaster দিয়ে Event-এর Version সামলায়, তা বলতে।
- Compatibility চেক, Breaking-change Linter আর Consumer-driven Contract Test দিয়ে CI-তেই ভাঙা পরিবর্তন ধরতে।
#এই Lesson কোথায় বসে
Lesson ২.৯-এ দেখেছি Data কীভাবে Byte হয়: JSON আর XML-এ প্রতিটা Record-এ Field-এর নাম থাকে, Protobuf আর Thrift-এ থাকে Field Number, আর Avro-তে শুধু মান, পড়তে লাগে Writer Schema। এই Lesson-এর বিষয় হলো সময়: পুরোনো Byte আর পুরোনো Program যখন এখনো আছে, তখন Format বদলালে কী হয়।
দুটো Program-এর মাঝের Format একটা চুক্তি, ঠিক যেমন Client আর Server-এর মাঝে Request আর Response-এর Format একটা চুক্তি (Lesson ১.১)। চুক্তির এক পাশ বদলানো সহজ। অন্য পাশ চালু রেখেই, কিছু বন্ধ না করে বদলানো: এটাই পুরো Lesson-এর বিষয়। Lesson ১.২-এ Rolling Deploy দেখেছি (Server আসলে কী), আর Lesson ১.১৩-এ তার পেছনের Load Balancer (Web আর App Server): ঠিক এই Deploy-এর সময়েই পুরোনো আর নতুন Format-এর দেখা হয়।
Schema Evolution মানে Schema বদলাতে থাকলেও Data পড়া-লেখা চালিয়ে যেতে পারা, System বন্ধ না করে, আর আগে থেকে থাকা সব Data নতুন করে না লিখে। Schema হলো Data-র নকশা: কোন কোন Field আছে, তাদের Type কী, কোনগুলো Optional, আর Default কী।
#Schema কেন বদলায়, আর বদলানো কেন কঠিন
System প্রতি সপ্তাহে বদলায়। আজ একটা Table-এ চারটা Field, কাল পাঁচটা হবে, বা একটা কমবে। একটা Event-এ currency যোগ হয়, API Response-এ নতুন একটা Nested Object আসে, একটা ID সংখ্যা থেকে String হয়ে যায়। একটা মেশিনে হলে তুমি Code আর Data একসাথে বদলে দিতে। Distributed System-এ সেটা পারবে না, চারটা কারণে:
| পুরোনো আর নতুনের দেখা কোথায় | দুই Version একসাথে কেন থাকে | কতদিন |
|---|---|---|
| Rolling Deploy | Instance কয়েকটা করে বদলায়, তাই v1 আর v2 পাশাপাশি Traffic সামলায়, একে অন্যের লেখা পড়ে | কয়েক মিনিট থেকে কয়েক ঘণ্টা |
| আলাদা Team | একটা Event-এর Producer আর তার পাঁচটা Consumer আলাদা Team-এর, Release-এর দিনও আলাদা | কয়েক দিন থেকে কয়েক মাস |
| জমা Data | Database-এর Row, লম্বা Retention-এর Kafka Topic, Data Lake-এর File, Cache-এর Entry, Queue-তে অপেক্ষা করা Message: সব পুরোনো Code লিখেছে | কয়েক সেকেন্ড (Queue) থেকে চিরকাল (Event Store, Compacted Topic) |
| যে Client তোমার হাতে নেই | Mobile App বছরের পর বছর পুরোনো Version-এ থাকে; Partner একবার Code লিখে তোমার Public API ডাকতেই থাকে | কয়েক বছর |
আর একটা জিনিস Team-রা ভুলে যায়: Rollback। v2 যদি Data লিখে ফেলে, তারপর তুমি v1-এ ফেরো, তাহলে পুরোনো Code-কে নতুন Data পড়তে হবে।
Rolling Deploy: v1 আর v2 একই Data লেখে আর পড়ে
Deploy চলার সময়, আর যতদিন পুরোনো Data থাকে, দুই Version-এর দেখা হয়। প্রতিটা পরিবর্তন চালাও, দেখো কে এখনো কী পড়তে পারে।
পরিস্থিতি
v2 একটা Default সহ currency যোগ করে। Topic-এ পুরোনো আর নতুন Event মিশে যায়, আর সব Reader সামলে নেয়।
Rolling Deploy কয়েকটা করে Instance বদলায়, তাই কয়েক মিনিট (বড় Fleet-এ কয়েক ঘণ্টা) v1 আর v2 পাশাপাশি Traffic সামলায়। সব একসাথে বদলে যাওয়ার কোনো মুহূর্ত নেই।
Animation থেকে তিনটা শিক্ষা পুরো বিষয়জুড়ে কাজে লাগবে:
- যোগ করা পরিবর্তনই নিরাপদ ধরনের: যুক্তিসঙ্গত Default-সহ একটা নতুন Optional Field।
- যেসব Format-এ নামটাই চুক্তি (JSON, Avro), সেখানে নাম বদল মানে একটা মোছা আর একটা যোগ। এক ধাপে করলে কেউ না কেউ ভাঙবেই।
- যে Reader Data আবার লিখে রাখে, তাকে না-বোঝা Field-ও রেখে দিতে হবে; না হলে একটা Rollback, বা মিশ্র Fleet-এর একটা v1 Instance, চুপচাপ Data মুছে দেয়।
#Backward, Forward আর Full Compatibility
প্রতিটা Compatibility প্রশ্নে দুটো Schema থাকে: যেটা দিয়ে Data Encode হয়েছিল সেই Writer Schema, আর Code যেটা দিয়ে Decode করে সেই Reader Schema। নামগুলো নতুন Schema-র দিক থেকে দেখা:
Backward compatibility : new code (new schema) can read OLD data
Forward compatibility : old code (old schema) can read NEW data
Full compatibility : both backward and forward
- Backward Compatible পরিবর্তন: নতুন Schema-র Reader পুরোনো Schema-য় লেখা Data পড়তে পারে। সাধারণ উপায়: নতুন Field Optional রাখো বা Default দাও, যাতে সেই Field ছাড়া পুরোনো Record-ও Decode হয়।
- Forward Compatible পরিবর্তন: পুরোনো Schema-র Reader নতুন Schema-য় লেখা Data পড়তে পারে। এটা চলে কারণ পুরোনো Reader অচেনা Field উপেক্ষা করে। এখানে বিপদের চাল হলো Field মুছে ফেলা: যে পুরোনো Reader-এর ওই Field এখনো লাগে সে ভাঙে, যদি না তার Schema-য় ওই Field-এর Default থাকে।
- Full Compatible পরিবর্তন: দুটোই। কার্যত শুধু Default আছে এমন Optional Field যোগ বা বাদ দেওয়া।
- Breaking পরিবর্তন: কোনোটাই না, যেমন একটা Field-এর Type সংখ্যা থেকে String করা।
Schema Registry এগুলোকে মোড বানিয়ে প্রতিটা নতুন Version-এর ওপর চাপিয়ে দেয়:
| মোড | নতুন Schema-কে… | কার সাথে মেলানো হয় | কে আগে Deploy |
|---|---|---|---|
| BACKWARD (Confluent-এর ডিফল্ট) | আগের Schema-য় লেখা Data পড়তে পারতে হবে | শেষ Version | Consumer |
| BACKWARD_TRANSITIVE | আগের সব Schema-য় লেখা Data পড়তে পারতে হবে | সব Version | Consumer |
| FORWARD | এমন Data লিখতে হবে যা আগের Schema পড়তে পারে | শেষ Version | Producer |
| FORWARD_TRANSITIVE | এমন Data লিখতে হবে যা আগের সব Schema পড়তে পারে | সব Version | Producer |
| FULL | আগের Schema-র সাথে দুটোই | শেষ Version | যে কেউ |
| FULL_TRANSITIVE | আগের সব Schema-র সাথে দুটোই | সব Version | যে কেউ |
| NONE | যা খুশি, কোনো চেক নেই | কিছুই না | হাতে হাতে সমন্বয় |
প্রতিটা মোড ঠিক কী মানে তা Format-এর ওপর নির্ভর করে (Default ছাড়া Avro Field আর Protobuf Field আলাদা আচরণ করে)। তবে Avro-র জন্য মোটা দাগের নিয়ম: BACKWARD-এ চলে Field মোছা আর Default-সহ Field যোগ; FORWARD-এ চলে Field যোগ আর Default ছিল এমন Field মোছা; FULL-এ চলে শুধু Default-সহ Field যোগ বা মোছা।
Backward, Forward, Full: কোন Version কোন Data পড়ে
নামগুলো নতুন Schema-র চোখ দিয়ে দেখা। Backward: নতুন Schema পুরোনো Schema-য় লেখা Data পড়ে। Forward: পুরোনো Schema নতুন Schema-য় লেখা Data পড়ে।
পরিস্থিতি
v2 Default BDT সহ currency যোগ করে। গত বছর v1 যা লিখেছে, v2 কি তা পড়তে পারে?
গত বছর Writer v1 ব্যবহার করত। সেই Record-গুলো এখনো জমা আছে।
#Transitive: শুধু শেষটা না, প্রতিটা Version-এর সাথে Compatible
সাধারণ মোডগুলো নতুন Schema-কে শুধু শেষ Version-এর সাথে মেলায়। এতে Topic-এর মাথার কাছ থেকে পড়া Consumer রক্ষা পায়। কিন্তু যে পুরোনো Data পড়ে, সে রক্ষা পায় না: Infinite Retention-এর Topic শুরু থেকে আবার পড়া নতুন Consumer, প্রতিটা Key-র সর্বশেষ Record চিরকাল রাখা Compacted Topic, Event Store, বা Data Lake-এর Avro File। Compatibility Transitive না: v3, v2-এর সাথে Compatible, আর v2, v1-এর সাথে, অথচ v3 আর v1 মেলে না, এমন হতেই পারে।
Transitive Compatibility: নতুন Consumer কয়েক বছরের Event আবার পড়ে
v1 (২০২৪): amount একটা double। v2 (২০২৫): amount বাদ। v3 (২০২৬): amount ফিরে আসে String হয়ে, Default "0"। প্রতিটা ধাপ তার ঠিক আগের ধাপের পাশে ঠিকই দেখাচ্ছিল।
পরিস্থিতি
Registry v3-কে শুধু v2-এর সাথে মেলায়।
v3 Reader v2 Data পড়ছে: amount নেই, Default "0" বসে যায়। শেষ Version-এর সাথে Backward Compatible, তাই v3 Register হলো।
যে Data জমা থাকে আর আবার পড়া হয়, তার জন্য _TRANSITIVE মোড নাও। সাধারণ মোড শুধু সেখানে, যেখানে Data তাড়াতাড়ি পড়া হয়ে যায় আর আর কখনো Replay হয় না।
#কে আগে Deploy হবে
Compatibility মোড আসলে এক দিকের প্রতিশ্রুতি। Deploy-এর ক্রমও সেটা মেনে চলতে হবে, কারণ Registry চেক করে Schema, তোমার Deploy-এর ক্রম না:
| মোড | নিরাপদ ক্রম | কেন | উল্টো করলে কী ভাঙে |
|---|---|---|---|
| BACKWARD | আগে Consumer, তারপর Producer | নতুন Reader পুরোনো আর নতুন দুটোই পড়ে; পুরোনো Reader নতুন Data পড়বে, এমন কোনো কথা নেই | Producer আগে গেলে এমন Data লেখে যা পুরোনো Consumer পড়তে পারে না: Deserialize Error, আটকে থাকা Consumer |
| FORWARD | আগে Producer, তারপর Consumer | পুরোনো Reader নতুন Data পড়ে; নতুন Reader পুরোনো Data পড়বে, এমন কথা নেই | Consumer আগে গেলে, বা পুরোনো Data-য় ফিরে গেলে, সেটা পড়তে পারে না |
| FULL | যে কেউ | দুই দিকই টেকে | কিছুই না; এজন্যই ভাগ করে ব্যবহার করা Topic-এ FULL জনপ্রিয় |
কে আগে Deploy হবে: Compatibility মোড ঠিক করে
v2 couponCode বাদ দেয়, অথচ v1 Reader Schema-র সেটা লাগে (Default নেই)। Registry BACKWARD মোডে, তাই v2 গ্রহণ করে: v2 Reader v1 Data পড়তে পারে। কিন্তু v1 Reader v2 Data পড়বে, এমন কোনো কথা নেই।
পরিস্থিতি
আগে Shipping Upgrade হয়, তারপর Producer।
BACKWARD-এ নতুন Reader-ই দুই আকার সামলানোর নিশ্চয়তা পায়, তাই সে আগে যায়।
দুটো ফল Engineer-রা ঠেকে শেখে:
- Rollback-এর জন্য Forward Compatibility লাগে। ধরো Producer-এর পরিবর্তনটা শুধু Backward Compatible। Producer Ship হলো, Data লিখল, তারপর ফেরাতে হলো। পুরোনো Producer Code এখন নতুন Data-র Reader, আর BACKWARD সেটার কোনো প্রতিশ্রুতি দেয়নি। বিনা ঝামেলার Rollback চাইলে পরিবর্তনটা দুই দিকেই Compatible (FULL) হতে হবে, নয়তো দুই Release-এ Ship করো: আগে সব Reader-কে নতুন আকার শেখাও, তারপর সেই আকারে লেখা শুরু করো।
- Producer ফেরালেও সে যা লিখেছে তা মুছে যায় না। খারাপ Record Topic-এই থেকে যায়। বেরোনোর পথ সাধারণত সামনে: এমন Reader Ship করো যে এগুলো সামলাতে পারে, অথবা জেনেবুঝে এড়িয়ে যাও (Dead-letter Queue আছে Chapter 30-এ)।
নিজেকে যাচাই করোপ্রশ্ন
তোমার Registry Subject FORWARD মোডে, আর একটা নতুন Schema Version এইমাত্র গৃহীত হলো। কোন Deploy-এর ক্রম নিরাপদ?
#নিয়মগুলো, Format ধরে ধরে
নিয়ম আলাদা কারণ Format-গুলো Field চেনে আলাদা ভাবে (Lesson ২.৯): Protobuf আর Thrift চেনে নম্বর দিয়ে, Avro চেনে নাম দিয়ে, হাতে Writer Schema রেখে, আর JSON চেনে নাম দিয়ে, কোনো Schema ছাড়াই।
#Protobuf: Field Number-এ কখনো হাত দিও না
Wire-এ প্রতিটা Protobuf Field হলো (field number, wire type, value); নাম কখনো যায় না। দুই পাশ নিজের নিজের কপির .proto Compile করে, তাই তারা একমত শুধু নম্বরে। নিয়মগুলো এখান থেকেই আসে:
- কোনো Field-এর নম্বর কখনো বদলাবে না, আর কোনো নম্বর আবার ব্যবহার করবে না। পুরোনো অর্থে লেখা Data নতুন অর্থে পড়া হবে। পুরো Message নষ্ট হয় না, কিন্তু যে Field-এ লাগে সেটা নিঃশব্দে ভুল পড়া হয় বা হারায়; কিছু ব্যর্থ হয় না বলে প্রায়ই সেটা আরও খারাপ।
- Field যোগ করা দুই দিকেই নিরাপদ। পুরোনো Reader অচেনা Field পার হয়ে যায় (Wire Type বলে দেয় কত Byte পার হতে হবে), আর Protobuf 3.5 থেকে proto3 অচেনা Field রেখেও দেয়, Message আবার Serialize করলে লিখেও দেয়। পুরোনো Data-য় Field না থাকলে নতুন Reader Default দেখে (শূন্য, খালি String, false)।
- Reserve করলে Field মোছা নিরাপদ। Field সরাও, আর তার নম্বর আর নাম দুটোই reserved করো, যাতে কেউ নতুন অর্থে ফিরিয়ে আনতে না পারে:
message OrderPlaced {
reserved 3; // ছিল coupon_code, ২০২৬-০৩-এ সরানো
reserved "coupon_code";
string order_id = 1;
int64 amount_paisa = 2; // টাকা রাখো পূর্ণসংখ্যায় (পয়সা), কখনো double না
string currency = 4; // v2-তে যোগ; খালি মানে BDT
optional string gift_note = 5; // optional: “পাঠায়নি” আর “খালি” আলাদা
}
- নাম বদল Binary Wire-এ নিরাপদ (নাম তো যায় না), কিন্তু নাম ব্যবহার করে এমন সবকিছু ভাঙে: Protobuf-এর JSON Mapping, Text Format, Field Mask, আর Code-এর যে লাইনে পুরোনো নাম আছে। JSON Client থাকলে নাম বদলকে API পরিবর্তন হিসেবেই দেখো।
- Type বদল বেশিরভাগ ক্ষেত্রেই অনিরাপদ। কয়েকটা দলের Wire Encoding এক, সাবধানে অদলবদল চলে:
int32,uint32,int64,uint64আরbool(C-এর Cast-এর মতো মান ছেঁটে যেতে পারে);sint32আরsint64;stringআরbytes(bytes যদি বৈধ UTF-8 হয়);fixed32আরsfixed32;fixed64আরsfixed64। এর বাইরে, যেমনint64থেকেstring, Wire Type মেলে না: বেশিরভাগ Library তখন মানটাকে অচেনা Field ধরে, তাই সেটা নিঃশব্দে খালি পড়ে। - Enum আসলে সংখ্যা। proto3-এ Enum খোলা: পুরোনো Reader নতুন মান (ধরো 4) পেলে নাম না জানলেও সংখ্যাটা রেখে দেয়। Wire টিকে যায়, কিন্তু default শাখা ছাড়া
switchথাকা Code টেকে না। সবসময় অচেনা মান সামলাও, আর 0 মানটা..._UNSPECIFIEDরাখো, যাতে না-আসা মানকে কখনো আসল মান ভাবা না হয়। requiredআর নেই। proto2-এরrequiredপরে যেকোনো মোছাকে Breaking বানিয়ে দিত, তাই proto3 এটা বাদ দিয়েছে (Protobuf Editions শুধু পুরোনো File-এর জন্য একটা Legacy সুবিধা হিসেবে রেখেছে)। থাকা-না-থাকা Code-এ যাচাই করো। “Set করা হয়নি” আর Default আলাদা করতে হলে proto3-এoptionalদাও (3.15 থেকে চলে)।oneofআর একক Field-কেrepeatedকরার ব্যাপারে সাবধান। পুরোনো Fieldoneof-এর ভেতরে বা বাইরে নেওয়া, বা সংখ্যা-ধরন (Cardinality) বদলানো কিছু ভাষায় Data হারায়; নতুন Field যোগ করাই ভালো।
Protobuf: Field Number-ই চুক্তি
Protobuf প্রতিটা Field পাঠায় (Field Number, Wire Type, মান) হিসেবে; নাম কখনো যায় না। Reader আর Writer নিজের নিজের কপির .proto Compile করে, তাই তারা একমত শুধু নম্বরে।
পরিস্থিতি
নতুন Writer string gift_note = 5 যোগ করে। Reader-এর কাছে এখনো পুরোনো .proto।
প্রতিটা Field-এর আগে একটা Tag Byte, তাতে তার নম্বর আর Wire Type। Field 5-এর Wire Type বলে “দৈর্ঘ্যসহ”, তারপর দৈর্ঘ্য।
#Avro: Default আর নাম
Avro Byte-এ নাম নেই, নম্বরও নেই, শুধু Writer Schema-র ক্রমে মান। পড়তে Reader-এর লাগে হুবহু Writer Schema (Avro Container File-এর Header থেকে, বা Schema Registry থেকে), তারপর নিজের Reader Schema-র সাথে Field ধরে ধরে নাম মিলিয়ে Resolve করে:
- Writer-এ আছে, Reader-এ নেই এমন Field: পার হয়ে যায়।
- Reader-এ আছে, Writer-এ নেই এমন Field: Reader-এর Default বসে; Default না থাকলে পড়া ব্যর্থ।
- দুটোতেই আছে এমন Field: Type মিলতে হবে, বা বৈধ Promotion হতে হবে:
int→long,float,double;long→float,double;float→double;string↔bytes।
তাই Avro-র নিয়ম:
- যোগ করা প্রতিটা Field-এর Default চাই (Backward-এর জন্য: নতুন Reader, পুরোনো Data)।
- শুধু সেই Field মোছো, যার Default ছিল (Forward-এর জন্য: Field উধাও হলে পুরোনো Reader Default বসায়)।
- নাম বদলাও Reader Schema-র
aliasesদিয়ে। Alias নাম বদলকে শুধু Backward Compatible করে: পুরোনো Reader এখনো পুরোনো নাম খোঁজে। কোনো Writer-এর আগে সব Reader Upgrade করো। - Optional মানে null-সহ Union, আর Default-কে Union-এর প্রথম Type-এর সাথে মিলতে হবে, তাই
"null"আগে দাও:{"name": "note", "type": ["null", "string"], "default": null}। - Enum: নতুন Symbol যোগ করলে পুরোনো Reader ভাঙে, যদি না তার Enum-এ একটা
defaultSymbol থাকে (Avro 1.9 থেকে চলে)।
নোটের উদাহরণের Schema, একটা সংশোধনসহ:
{
"type": "record",
"name": "OrderCreated",
"fields": [
{ "name": "orderId", "type": "string" },
{ "name": "amount", "type": { "type": "bytes", "logicalType": "decimal", "precision": 12, "scale": 2 } },
{ "name": "currency", "type": "string", "default": "BDT" }
]
}
currency-র Default-ই নতুন Reader-কে currency ছাড়া পুরোনো Event পড়তে দেয়। নোটে amount-এর Type ছিল double: টাকা কখনো Binary Floating Point-এ রেখো না (0.1 + 0.2 ঠিক 0.3 হয় না)। decimal Logical Type নাও, বা সবচেয়ে ছোট এককের (পয়সা, সেন্ট) পূর্ণসংখ্যা।
Avro: Reader, Writer-এর Schema-কে Field-এর নাম ধরে মেলায়
Avro Byte-এ নাম বা নম্বর কিছুই থাকে না, শুধু Writer Schema-র ক্রমে মান। Decode করতে Reader-এর লাগে হুবহু Writer Schema, তারপর নিজের Schema-র সাথে নাম ধরে Field মেলায়।
পরিস্থিতি
Writer v1 {orderId, amount}; Reader v2 Default BDT সহ currency যোগ করে (নোটের Schema)।
Writer পরপর দুটো মান লেখে। Byte-এর কোথাও লেখা নেই কোনটা কোন Field।
#JSON: Tolerant Reader আর দুর্বল Schema
JSON-এর Wire-এ কোনো Schema নেই, তাই চুক্তি হলো Field-এর নাম আর তাদের মানের Type, আর Compatibility পুরোটাই নির্ভর করে Reader কীভাবে লেখা তার ওপর। নিয়মটার নাম Tolerant Reader: শুধু দরকারি Field পড়ো, বাকি সব উপেক্ষা করো, আর না-থাকা Optional Field-কে Default ধরো।
| Library | অচেনা Field ডিফল্টে | মন্তব্য |
|---|---|---|
Java Jackson ObjectMapper | ব্যর্থ হয় (FAIL_ON_UNKNOWN_PROPERTIES true) | Spring Boot-এর নিজে সাজানো Mapper এটা বন্ধ রাখে; খালি Jackson রাখে না |
Go encoding/json | উপেক্ষা করে | Decoder.DisallowUnknownFields() দিলে কড়া হয় |
| Python Pydantic v2 | উপেক্ষা করে (extra='ignore') | extra='forbid' দিলে কড়া হয় |
TypeScript JSON.parse + একটা Type | উপেক্ষা করে (চলার সময় কোনো চেকই নেই) | Type যা-ই বলুক, না-থাকা Field চলার সময় undefined |
JSON-এর চেনা ফাঁদ: নাম বদল (amount → total) সব Reader-এর কাছে মোছার মতো দেখায়; সংখ্যা String হয়ে গেলে (নোটের "amount": "FIFTEEN HUNDRED") Typed Reader ভাঙে; null, না-থাকা Key আর খালি String একেক ভাষায় একেক মানে; আর ৬৪ bit ID JavaScript-এর সংখ্যায় নির্ভুলতা হারায় (Lesson ২.৯), এজন্যই অনেক API প্রথম দিন থেকেই ID পাঠায় String হিসেবে।
JSON Schema JSON-কে বর্ণনা আর যাচাই করে, কিন্তু এটা বানানো হয়েছিল যাচাইয়ের জন্য, Evolution-এর জন্য না; তাই এর Registry Compatibility চেক দুর্বল, আর সহজেই ভুল বোঝা যায়। আসল সুইচ হলো additionalProperties:
- বন্ধ (
"additionalProperties": false): নতুন যেকোনো Field পুরোনো Reader-এর যাচাইয়ে ব্যর্থ, তাই প্রতিটা যোগই Forward Incompatible। - খোলা (ডিফল্ট): যোগ পাস করে, কিন্তু নাম বদলও “পাস” করে, কারণ পুরোনো নামটা স্রেফ একটা অনুমোদিত বাড়তি Property হয়ে যায়। একটা জরুরি অর্থ যে উধাও হলো, Checker সেটা দেখতে পায় না।
অনেক Team Public API-তে JSON রাখে (পড়া সহজ, সব জায়গায় চলে), আর ভেতরের Event-এ Registry-সহ Avro বা Protobuf নেয়, যেখানে কড়া নিয়মের দাম উঠে আসে।
#Thrift
Thrift Field চেনে সংখ্যার ID দিয়ে, Protobuf-এর মতোই, তাই নিয়মও একই: ID কখনো বদলাবে না বা আবার ব্যবহার করবে না, নতুন Field যোগ করো নতুন ID-তে optional হিসেবে, আর অবসরে যাওয়া ID-গুলোর তালিকা Comment-এ রেখে দাও যাতে কেউ আবার না নেয়। পুরোনো Reader অচেনা Field পার হয়ে যায় (আর সাধারণত রেখে দেয় না)। required এড়িয়ে চলো: Thrift-ব্যবহারকারীদের নিয়মই হলো “required is forever”, কারণ পরে সরালে প্রতিটা পুরোনো Reader ভাঙে।
#পরিবর্তন ধরে ধরে এক নজরে
| পরিবর্তন | Protobuf | Avro | JSON (Tolerant Reader) |
|---|---|---|---|
| Optional Field যোগ | ✅ দুই দিকেই | ✅ দুই দিকেই, যদি Default থাকে | ✅ দুই দিকেই (কড়া Reader-এ ❌) |
| Default ছাড়া Field যোগ | প্রযোজ্য না (proto3-এর সব Field-এর Default আছে) | ❌ Backward (পুরোনো Data-য় নতুন Reader ব্যর্থ) | ⚠️ না থাকলেও Reader-কে সামলাতে হবে |
| Field মোছা | ✅ যদি নম্বর আর নাম reserved হয় | ✅ Backward; Forward শুধু Default থাকলে | ❌ যে Reader-এর এখনো লাগে তার জন্য |
| Field-এর নাম বদল | ✅ Binary, ❌ JSON Mapping আর Code | ⚠️ শুধু Backward, aliases দিয়ে | ❌ (একটা মোছা আর একটা যোগ) |
| Type বদল | ❌ Compatible দল ছাড়া | ❌ Promotion ছাড়া (int → long…) | ❌ |
| Field Number বা নাম আবার ব্যবহার | ❌ নিঃশব্দ গোলমাল | ❌ পুরোনো Data নতুন অর্থ পায় | ❌ পুরোনো Data নতুন অর্থ পায় |
| Enum-এ নতুন মান | ⚠️ Wire ঠিক, Code-কে অচেনা সামলাতে হবে | ❌ Enum default ছাড়া পুরোনো Reader-এ | ⚠️ Code-কে অচেনা String সামলাতে হবে |
#Schema Registry, ওপর থেকে এক নজরে
যখন কয়েক ডজন Service একই Event ভাগ করে, তখন কাউকে Schema-গুলো ধরে রাখতে হয় আর নিয়ম চাপাতে হয়। Schema Registry হলো একটা কেন্দ্রীয় Service, যেটা প্রতিটা Schema-র প্রতিটা Version জমা রাখে, প্রতিটাকে একটা ID দেয়, আর প্রতিটা নতুন Version-কে Subject-এর Compatibility মোডের সাথে মিলিয়ে দেখে। Kafka-র জগতে এর গুরুত্ব সবচেয়ে বেশি। এখানে শুধু কাঠামোটা দেখব; পুরো Registry আছে Chapter 32-এ।
নোটের উদাহরণটা দিয়ে শুরু করি: Order Service পাঠায় {"orderId": "ORD-101", "amount": 1500}, আর Inventory Service সেটা নেয়। "currency": "BDT" যোগ করলে Inventory-র ক্ষতি হতেও পারে, না-ও পারে, সেটা নির্ভর করে সে কীভাবে পড়ে তার ওপর; আর {"orderId": 101, "amount": "FIFTEEN HUNDRED"} করলে দুটো Type-ই বদলায়, পুরোনো Consumer ভাঙবেই। Registry এই “হতেও পারে, না-ও পারে”-কে একটা চেকে বদলে দেয়, যেটা পরিবর্তন Ship হওয়ার আগেই চলে।
এটা কীভাবে কাজ করে (Confluent Schema Registry, সবচেয়ে প্রচলিতটা):
- CI-তে Register। Pipeline একটা Subject-এর জন্য নতুন Schema জমা দেয় (ডিফল্টে নাম Topic থেকে, যেমন
orders-value)। Registry সেটাকে Subject-এর মোড (ডিফল্ট BACKWARD) দিয়ে মেলায়, তারপর হয় একটা ID দেয়, নয়তো409 Conflictদিয়ে ফিরিয়ে দেয়। - Produce। Producer-এর Serializer Schema-র ID একবার খুঁজে নিয়ে Cache করে। প্রতিটা Record লেখা হয় এভাবে: Magic Byte
0+ ৪ Byte-এর Schema ID + Encode করা মূল অংশ (Protobuf Record-এ ID-র পরে Message Index-ও থাকে)। প্রতিটা Message-এ পুরো Schema-র বদলে মাত্র ৫ Byte। - Consume। Consumer-এর Deserializer ID পড়ে, সেই Schema একবার এনে (
GET /schemas/ids/{id}) Cache করে, তারপর Writer Schema দিয়ে Decode করে নিজের Reader Schema-য় মেলায়।
Data কখনো Registry দিয়ে যায় না। এটা পাশে বসে থাকা একটা খোঁজার Service, Client কোনো অচেনা (Cache-এ নেই এমন) Schema পেলে তখনই কাজে লাগে। নোটের Diagram-এ Registry-কে Producer আর Topic-এর মাঝখানে বসানো হয়েছে; ওপরেরটাই শুদ্ধ ছবি।
Schema Registry: Schema আলাদা পথে, প্রতিটা Record-এ ৫ Byte-এর Prefix
Confluent-ধাঁচের প্রবাহের সারসংক্ষেপ; Chapter 32-এ আরও গভীরে যাব। Record কখনো Registry দিয়ে যায় না: Client সেখান থেকে Schema এনে Cache করে রাখে।
পরিস্থিতি
Schema v2 দিয়ে প্রথম Record, তারপর স্বাভাবিক অবস্থা।
Serializer Registry-কে তার Schema-র ID জিজ্ঞেস করে, Subject ধরে (ডিফল্টে “topic-value”)। Production-এ Schema আগেই CI Register করে রেখেছে, App না।
Registry কী দেয়: Producer আর Consumer-এর মাঝে একটাই চুক্তি, কম Type অমিল, Version-এর ইতিহাস, নিরাপদ Deploy, আর আগেভাগে ধরা পড়া Breaking পরিবর্তন। কী নেয়: আরও একটা সবচেয়ে জরুরি Service (এটা বন্ধ থাকলে নতুন Pod চালু হতে পারে না), এমন ID যার মানে শুধু একটা Registry-র ভেতরে (Cluster-এর মধ্যে Topic কপি করলে Schema-ও কপি করতে হয়), আর শৃঙ্খলা: Production-এ Auto-registration বন্ধ রাখো (auto.register.schemas=false), যাতে শুধু Review হওয়া Pipeline-ই Schema যোগ করতে পারে।
অন্য Registry-গুলোও একই ধারণায়, নিজের নিজের খুঁটিনাটি নিয়ে: Apicurio Registry (Confluent-এর API-র সাথে মেলে), AWS Glue Schema Registry (BACKWARD_ALL-এর মতো মোড, আর ৫ Byte-এর Prefix-এর বদলে Schema Version-এর UUID-সহ নিজস্ব Header), আর Protobuf Module-এর জন্য Buf Schema Registry।
#Event-এর Version আর Upcasting
Event Sourcing প্রতিটা পরিবর্তনকে একটা অপরিবর্তনীয় Event হিসেবে জমা রাখে, আর সেগুলো আবার চালিয়ে State বানায় (Chapter 32)। তাই এখানে Evolution সবচেয়ে কড়া: Event-ই আসল সত্য, কখনো Edit হয় না, আর একটা Replay বছর পরে এ যাবৎ লেখা প্রতিটা Version পড়ে। সাধারণ হাতিয়ার:
- আগে Compatible Evolution। Default-সহ Field যোগ করো; কোনো Field-এর অর্থ কখনো বদলাবে না। বেশিরভাগ পরিবর্তনে এর বেশি কিছু লাগার কথা না।
- Event-এর ভেতরেই Version: একটা স্পষ্ট
schemaVersion(বা Type হিসেবেOrderPlaced.v2), যাতে Reader জানে হাতে কী আছে। - Upcasting: আকার বদলাতেই হলে ছোট্ট একটা Pure Function লেখো, যেটা পড়ার সময় v1 Event-কে v2 Event বানায়। Handler শুধু সর্বশেষ Version দেখে; জমা Event-এ কেউ হাত দেয় না। Upcaster শিকলের মতো জোড়া যায় (v1 → v2 → v3)।
- Copy and Transform: কদাচিৎ, প্রতিটা Event বদলে নিয়ে নতুন একটা Stream লেখো, তারপর সেটায় সরে যাও। খরচ বেশি, তবে পুরোনো Upcaster ফেলে দেওয়া যায়।
- Replay-এর Test: প্রতিটা পুরোনো Event Version-এর একটা নমুনা রেখে CI-তে Replay করো। হারানো Upcaster ধরা পড়ে ঠিক সেদিন, যেদিন তুমি একটা Projection আবার বানাচ্ছ বা দুর্যোগ থেকে ফিরছ।
Event Sourcing: পড়ার সময় পুরোনো Event-কে Upcast করা
Event-sourced System-এ Event-ই আসল সত্য, আর সেগুলো কখনো Edit হয় না। Order Event-এর আকার বদলালেও পুরোনো Event আবার চালাতে পারতে হবে। এখানে সারসংক্ষেপ; Event Sourcing আছে Chapter 32-এ।
পরিস্থিতি
v1 amount রাখত 1500.0 টাকা হিসেবে; v2 রাখে পূর্ণসংখ্যায় amountPaisa, সাথে currency।
একটা Projection প্রথম Event থেকে আবার বানানো হচ্ছে। সবচেয়ে পুরোনো Event-গুলো v1 আকারে।
#API Versioning: সীমানাতেও একই নিয়ম
REST বা gRPC API-ও একটা Schema, যার Reader অন্যদের Code, তাই একই যুক্তি খাটে (API Design আর Versioning-এর গভীরে যাব Chapter 26-এ):
- যোগ করা পরিবর্তনে নতুন Version লাগে না: নতুন Optional Request Field, নতুন Response Field, নতুন Endpoint। তবে এটা চলে শুধু Client Tolerant Reader হলে, আর সেটা API Documentation-এ লিখে রাখা ভালো।
- Breaking পরিবর্তন পায় নতুন Version: একটা Path (
/v2/orders), একটা Header বা একটা Media Type। কিছু Provider তারিখ দিয়ে Version করে, আর Server-এ পুরোনো Request-কে নতুন আকারে বদলে নেয়; এটা আসলে API-র Upcasting। - পুরোনো Version অবসরে পাঠাও জোরে ঘোষণা দিয়ে: একটা তারিখ জানাও,
DeprecationHeader (RFC 9745) আরSunsetHeader (RFC 8594) পাঠাও, মেপে দেখো এখনো কারা পুরোনো Version ডাকে, তারপরই সরাও। - Mobile Client হলো লম্বা লেজ: আজ Install হওয়া একটা App Version কয়েক বছর তোমার API ডাকতে পারে, তাই Public Response Format-এ FULL-এর মতো যত্ন লাগে।
#Database Schema: একই বুদ্ধি, Expand আর Contract
Database Table-ও একটা Schema, আর Rolling Deploy মানে পুরোনো আর নতুন Application Code একই সময়ে সেটায় Query চালায়। নিরাপদ ধাঁচ হলো Expand → Migrate → Contract, যেটা নাম বদলের Scenario-তে আগেই দেখেছ:
- Expand: নতুন Column যোগ করো (Nullable বা Default-সহ)। পুরোনো Code এটা উপেক্ষা করে।
- Migrate: এমন Code Deploy করো যা দুটো Column-এই লেখে, পুরোনো Row-গুলো Batch-এ Backfill করো, তারপর Read-গুলো নতুন Column-এ সরাও।
- Contract: চালু কোনো Code আর পুরোনো Column ব্যবহার না করলে সেটা Drop করো।
প্রতিটা ধাপ পাশের চালু Code-এর সাথে Backward আর Forward দুই দিকেই Compatible। Downtime ছাড়া Migration-এর বিস্তারিত আছে Chapter 46-এ।
#Production-এর আগেই ভাঙা পরিবর্তন ধরা
যে নিয়ম কেউ চেক করে না, সেটা নিছক পরামর্শ। এগুলো CI-তে বসাও:
- Schema Compatibility চেক: Merge করার আগে Registry-কে জিজ্ঞেস করো (
POST /compatibility/subjects/{subject}/versions/latest), বা একই কাজ করা Build Plugin ব্যবহার করো। - Breaking-change Linter:
buf breaking.protoFile-গুলোকে Main Branch-এর সাথে মেলায়, আর আবার ব্যবহার করা নম্বর,reservedছাড়া মোছা Field, Type বদল আর আরও অনেক কিছুতে ব্যর্থ হয় (শুধু Wire, Wire আর JSON, বা পুরো Source Compatibility: আলাদা আলাদা নিয়মের সেট)। - Golden নমুনা: প্রতিটা পুরোনো Version-এর Encode করা উদাহরণ রেখে দাও, আর যাচাই করো নতুন Code সেগুলো এখনো পড়তে পারে।
- Consumer-driven Contract Test (সবচেয়ে পরিচিত Tool হলো Pact): প্রতিটা Consumer জানায় সে আসলে কী ব্যবহার করে, Provider-এর Build তার আসল Message-কে প্রতিটা Consumer-এর চুক্তির সাথে যাচাই করে, আর
can-i-deployচেক এমন Release আটকে দেয় যা Production-এ চলা কোনো Consumer-কে ভাঙবে। Schema চেক Type জানে; Contract Test জানে প্রতিটা Consumer কীসের ওপর নির্ভর করে।
Consumer-driven Contract Test: Deploy-এর আগেই ভাঙা পরিবর্তন ধরো
Schema চেক Type জানে; Contract Test জানে প্রতিটা Consumer আসলে কী ব্যবহার করে। দুটোই CI-তে চলে।
পরিস্থিতি
Order Event থেকে Billing-এর কী লাগে, সে লিখে রাখে।
Billing-এর নিজের Test থেকে একটা চুক্তি তৈরি হয়: সে যে Field আর Type পড়ে, তার বেশি কিছু না। Billing-এর Version-সহ এটা প্রকাশ হয়।
#System Design-এ Schema Evolution
মোড বেছে নেওয়া:
| Data | যা নেবে | কেন |
|---|---|---|
| অনেক Team-এর পড়া, Replay হওয়া বা Compacted Kafka Topic | FULL_TRANSITIVE (বা BACKWARD_TRANSITIVE) | যেকোনো Deploy-ক্রম, যেকোনো বয়সের যেকোনো Reader |
| কয়েক মিনিটে পড়া হয়ে যাওয়া, কখনো Replay না হওয়া Queue | BACKWARD বা FULL | শুধু সাম্প্রতিক Version-ই পড়া হয় |
| Event Store (Event Sourcing) | Transitive আর Upcaster | প্রতিটা Version চিরকাল Replay হয় |
| Service-এর মধ্যে ভেতরের gRPC | Protobuf-এর নিয়ম আর buf breaking | কিছু জমা থাকে না; Rolling Deploy-এ দুই দিকই লাগে |
| Public REST API | শুধু যোগ, ভাঙলে নতুন Version, Deprecation Header | Client তোমার হাতে নেই |
| Development Topic | NONE | গতি, তবে ভাগ করা Data-য় কখনোই না |
যে লক্ষণগুলো চিনে রাখা দরকার:
| লক্ষণ | সম্ভাব্য কারণ |
|---|---|
| Consumer একটা Offset-এ আটকে, Deserialize Error, Lag বাড়ছে | Producer এমন পরিবর্তন Deploy করেছে যা তার পুরোনো Consumer পড়তে পারে না (Deploy-এর ভুল ক্রম) |
| একটা Field হঠাৎ খালি বা শূন্য, কোনো Error নেই | নরম Parser একটা নাম বদল পড়েছে, বা Protobuf-এ Wire Type অমিল |
| পুরোনো Record-এর একটা Field-এ আজগুবি মান | Field Number বা নাম নতুন অর্থে আবার ব্যবহার হয়েছে |
| নতুন Consumer বা Replay ব্যর্থ, চালু Consumer ঠিক | Non-transitive Compatibility; Topic-এ পুরোনো Data |
| নতুন Pod চালু হয় না, পুরোনোগুলো ঠিক | Schema Registry-তে পৌঁছানো যাচ্ছে না, Cache ঠান্ডা |
| Rollback-এর পরে Data থেকে একটা Field হারিয়ে গেছে | পুরোনো Code অচেনা Field না রেখেই Record আবার লিখেছে |
Interview-এর প্রশ্ন: “বারোটা Team ব্যবহার করে এমন একটা Event-এ Field-এর নাম কীভাবে বদলাবে?” (Expand–migrate–contract, কখনো এক ধাপে না)। “Backward আর Forward Compatibility-র পার্থক্য কী, আর কে আগে Deploy হয়?” “Protobuf-এ reserved কেন আছে?” “Event-sourced System-এ Event কীভাবে বদলাবে?” (Compatible পরিবর্তন, Version, Upcaster)। “প্রতিটা Kafka Record-এ Schema ID কেন থাকে, আর Registry বন্ধ হলে কী হয়?” ভালো উত্তর সবসময় বলে দেয় কে পড়ে, কে লেখে, আর কোন ক্রমে তারা Deploy হয়।
#নিজে করে দেখো
Buf দিয়ে একটা Protobuf পরিবর্তন Main Branch-এর সাথে মিলিয়ে দেখো (আবার ব্যবহার করা নম্বর বা reserved না করে মোছা Field থাকলে ব্যর্থ হবে):
buf breaking --against '.git#branch=main'
# output similar to:
# order.proto:12:3:Field "3" on message "OrderPlaced" changed name from "coupon_code" to "shipping_note".
একটা Schema Register না করেই Registry-কে জিজ্ঞেস করো সেটা গৃহীত হবে কি না (Confluent API, Local Test Registry):
curl -s -X POST -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
--data '{"schema": "{\"type\":\"record\",\"name\":\"OrderCreated\",\"fields\":[{\"name\":\"orderId\",\"type\":\"string\"},{\"name\":\"amount\",\"type\":\"string\"}]}"}' \
'http://localhost:8081/compatibility/subjects/orders-value/versions/latest?verbose=true'
# {"is_compatible":false,"messages":["... কারণ, amount Field আর দুটো Type-এর নামসহ ..."]}
curl -s http://localhost:8081/config/orders-value # Subject-এর মোড
curl -s -X PUT -H 'Content-Type: application/vnd.schemaregistry.v1+json' \
--data '{"compatibility": "FULL_TRANSITIVE"}' http://localhost:8081/config/orders-value
Python-এ fastavro দিয়ে Avro-র Schema Resolution নিজের চোখে দেখো: v1 দিয়ে লেখো, v2 দিয়ে পড়ো।
import io
from fastavro import parse_schema, schemaless_writer, schemaless_reader
v1 = parse_schema({"type": "record", "name": "Order", "fields": [
{"name": "orderId", "type": "string"},
{"name": "amount", "type": "long"}]})
v2 = parse_schema({"type": "record", "name": "Order", "fields": [
{"name": "orderId", "type": "string"},
{"name": "amount", "type": "long"},
{"name": "currency", "type": "string", "default": "BDT"}]})
buf = io.BytesIO()
schemaless_writer(buf, v1, {"orderId": "o-101", "amount": 150000})
buf.seek(0)
print(schemaless_reader(buf, v1, v2))
# {'orderId': 'o-101', 'amount': 150000, 'currency': 'BDT'} Default বসে গেছে
এবার v2 থেকে "default": "BDT" সরিয়ে আবার চালাও: পড়া ব্যর্থ হবে, কারণ নতুন Reader-এর হাতে currency-তে বসানোর মতো কিছু নেই। Registry ঠিক এই Backward চেকটাই তোমার হয়ে চালাত।
#সারসংক্ষেপ
মূল শিক্ষা
- Schema Evolution = এমন অবস্থায় Data Format বদলানো, যখন পুরোনো আর নতুন Code, পুরোনো আর নতুন Data একসাথে আছে। Rolling Deploy, আলাদা Team, Rollback, জমা Data আর পুরোনো Client-এর কারণে এটাই স্বাভাবিক অবস্থা, ব্যতিক্রম না।
- Backward: নতুন Reader পুরোনো Data পড়ে (আগে Consumer Deploy)। Forward: পুরোনো Reader নতুন Data পড়ে (আগে Producer Deploy)। Full: দুটোই (যেকোনো ক্রম)। Transitive: আগের প্রতিটা Version-এর সাথে; Replay বা জমা থাকা সবকিছুর জন্য লাগে।
- Default-সহ যোগ করা পরিবর্তনই নিরাপদ পথ। নাম বদল মানে একটা মোছা আর একটা যোগ; করো Expand → Migrate → Contract দিয়ে। Type বদল মানে ভাঙা; বদলে নতুন Field যোগ করো।
- Protobuf: Field Number কখনো বদলাবে না বা আবার ব্যবহার করবে না, মোছা নম্বর আর নাম
reserved, Field যোগ নিরাপদ, অচেনা Field রক্ষা পায়, অল্প কিছু Type বদলই নিরাপদ, অচেনা Enum মান সামলাও,requiredনেই। - Avro: Field মেলে নাম ধরে; যোগ করা Field-এর Default চাই, মোছা Field-এর Default থাকা উচিত ছিল; নাম বদল Alias দিয়ে (শুধু Backward); Optional =
nullআগে রেখে Union; শুধু তালিকার Type Promotion। - JSON: Tolerant Reader হও; কড়া Deserializer আর বন্ধ JSON Schema-র দিকে চোখ রাখো; টাকা কখনো Float-এ না।
- Schema Registry প্রতিটা নতুন Version CI-তে চেক করে, আর প্রতিটা Record-এ বসায় Magic Byte + ৪ Byte-এর Schema ID; Data কখনো এর ভেতর দিয়ে যায় না। Event Store যোগ করে Version আর Upcaster; API যোগ করে Version আর Deprecation Header; Database চলে Expand আর Contract-এ; শুধু Type যা ধরতে পারে না, Contract Test তা ধরে।
আগে নিজে উত্তর দাও, তারপর ব্যাখ্যাটা পড়ো। ঠিক হলেও পড়ো, কারণ ভুল Option-গুলো কেন ভুল, সেটাও ওখানে আছে।
নিজেকে যাচাই করোপ্রশ্ন
এই Lesson অনুযায়ী Schema Evolution-এর কোন বর্ণনাটা ঠিক?
নিজেকে যাচাই করোপ্রশ্ন
একটা Schema পরিবর্তনকে Backward Compatible বলা হলো। ঠিক কী নিশ্চিত হলো?
নিজেকে যাচাই করোপ্রশ্ন
পুরোনো Billing Code এমন একটা Event পেল যার একটা Field-এর নাম সে কখনো শোনেনি; সে Field-টা পার হয়ে বাকিটা Process করল। এটা কোন গুণ?
নিজেকে যাচাই করোপ্রশ্ন
একটা Program-এর চেয়ে Distributed System-এ Message Format বদলানো কঠিন কেন?
নিজেকে যাচাই করোপ্রশ্ন
Order Service-এর v2 নতুন currency Field-সহ Order লিখেছে। একটা Bug-এর জন্য v1-এ Rollback হলো; v1 সেই Order পড়ে, Status বদলে আবার Save করে। ওই Row-গুলো থেকে currency হারিয়ে গেল। কী ছিল না?
নিজেকে যাচাই করোপ্রশ্ন
একটা Subject BACKWARD মোডে, আর একটা Field মুছে দেওয়া নতুন Version এইমাত্র গৃহীত হলো। কোন Deploy-এর ক্রম নিরাপদ?
নিজেকে যাচাই করোপ্রশ্ন
সোমবার Order Team এমন Schema Ship করল যাতে couponCode নেই (BACKWARD সেটা নিয়েছে)। Shipping এখনো v1-এ, যার Schema-য় couponCode লাগে, Default নেই। Shipping এখন একটা Offset-এ আটকে, Deserialize Error দিচ্ছে। ঠিক উদ্ধারের পথ কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
একজন Developer .proto থেকে coupon_code = 3 মুছেছে, reserved করেনি। এক বছর পরে আরেকজন যোগ করল string shipping_note = 3। লম্বা Retention-এর Topic-এর পুরোনো Event নতুন Code দিয়ে Replay হচ্ছে। কী ঘটে?
নিজেকে যাচাই করোপ্রশ্ন
বহুল ব্যবহৃত একটা Protobuf Message থেকে একটা Field সরাতে হবে। ঠিক উপায় কোনটা?
নিজেকে যাচাই করোপ্রশ্ন
একটা Team Protobuf Field-এর নাম amount থেকে total করল, নম্বর 2 রেখেই। Binary gRPC Client ঠিক আছে। তবুও কী ভাঙতে পারে?
নিজেকে যাচাই করোপ্রশ্ন
কোন দুটো Protobuf পরিবর্তন জমা Data আর চালু Reader-এর জন্য নিরাপদ?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
Avro-তে v2 Default ছাড়া currency যোগ করল, Subject BACKWARD মোডে। Registry কী করে, আর কেন?
নিজেকে যাচাই করোপ্রশ্ন
একটা Avro Field-এর Type [“string”, “null”], Default null। কী ঘটে?
নিজেকে যাচাই করোপ্রশ্ন
v2-এর Avro Reader Schema amount-এর নাম total করেছে, আর aliases [amount] যোগ করেছে। কোন কথাটা ঠিক?
নিজেকে যাচাই করোপ্রশ্ন
পুরোনো Type-এ লেখা Data থেকে কোন দুটো Type বদল Avro Reader নিজে থেকেই মেলাতে পারে?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা Java Consumer-এর খালি Jackson Object Mapper, Producer একটা নিরীহ Optional Field যোগ করতেই Exception ছুড়তে শুরু করল। কেন?
নিজেকে যাচাই করোপ্রশ্ন
একটা Event-এর JSON Schema-য় additionalProperties false। Producer একটা Optional Field যোগ করল। পুরোনো Schema দিয়ে যাচাই করা Consumer-দের কী হয়?
নিজেকে যাচাই করোপ্রশ্ন
v1-এ amount double, v2-তে amount বাদ, v3-এ amount ফিরে এল String হয়ে, Default “0”। Registry BACKWARD (Transitive না) মোডে। v3-ওয়ালা নতুন একটা Consumer Topic-টা Offset 0 থেকে Replay করছে। কী ঘটে?
নিজেকে যাচাই করোপ্রশ্ন
কোন দুই ধরনের Data-র সবচেয়ে বেশি Transitive Compatibility মোড লাগে?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
Confluent Serializer দিয়ে Produce করা একটা Kafka Record শুরু হয় 00 00 00 00 2A Byte দিয়ে। এর মানে কী?
নিজেকে যাচাই করোপ্রশ্ন
নোটে আঁকা আছে Producer → Schema Registry → Validate → Kafka → Consumer → Schema Registry। ছবিটায় ভুল কী?
নিজেকে যাচাই করোপ্রশ্ন
Schema Registry দশ মিনিট পৌঁছানো যাচ্ছে না। কী প্রভাব আশা করবে?
নিজেকে যাচাই করোপ্রশ্ন
একটা Consumer ID 42-এর জন্য “Schema পাওয়া যায়নি” (Error 40403) পাচ্ছে, অথচ Producer ঠিকঠাক চলছে। সবচেয়ে সম্ভাব্য কারণ কী?
নিজেকে যাচাই করোপ্রশ্ন
Event-sourced System-এ Upcaster কী?
নিজেকে যাচাই করোপ্রশ্ন
একটা proto3 Producer Payment Method Enum-এ নতুন মান 4 = bkash যোগ করল। পুরোনো Consumer মান 4 পেল। আসল ঝুঁকিটা কী?
নিজেকে যাচাই করোপ্রশ্ন
Registry প্রতিটা Schema পরিবর্তন মেনে নিচ্ছে, অথচ ব্যবহার করা একটা Field-এর নাম বদলাতেই Billing ভাঙল। কোন Tool সবচেয়ে সরাসরি Deploy-এর আগে এটা ধরতে পারত?
নিজেকে যাচাই করোপ্রশ্ন
নোটের Avro উদাহরণে amount রাখা double হিসেবে। এতে সমস্যা কী, আর কী ব্যবহার করা উচিত?
নিজেকে যাচাই করোপ্রশ্ন
ব্যস্ত একটা Table-এ amount Column-এর নাম total করতে হবে, আর Rolling Deploy-এর কারণে পুরোনো আর নতুন Code দুটোই চলছে। কোন পরিকল্পনা নিরাপদ?
নিজেকে যাচাই করোপ্রশ্ন
“শুধু একটা Field যোগ হয়েছে”, তবুও কোন দুটো অবস্থা ভাঙে?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
একটা Public REST API-কে Response-এর একটা Field-এর Type বদলাতে হবে। কোন পদ্ধতি ভালো চর্চা মেনে চলে?
নিজেকে যাচাই করোপ্রশ্ন
Avro Schema-র জন্য FULL Compatibility কোন দুটো পরিবর্তন মেনে নেয়?
বেছে নাও ২
নিজেকে যাচাই করোপ্রশ্ন
তোমার Mobile App লাখ লাখ Install হয়েছে, আর কিছু ব্যবহারকারী কখনো Update করে না। এর API Response নিয়ে কোন কথাটা ঠিক?