এই পর্বে যা শিখবেন
- Query-র আকৃতি দেখে key-value, cache, search, time-series, warehouse বা data lake বেছে নেওয়া
- Access pattern থেকে partition key সাজানো, আর hot partition ধরা ও সারানো
- RCU/WCU ধরনের capacity হিসাব, আর provisioned বনাম on-demand-এর break-even
- Cache-এর hit ratio, গড় latency, eviction, node নষ্ট হওয়া আর cold cache সামলানো
- Columnar storage আর pay-per-scan বুঝে warehouse-এর খরচ কমানো
- একাধিক data store চালানোর আসল খরচ মেপে সিদ্ধান্ত নেওয়া
১ · এক database দিয়ে সব কাজ কেন চলে না
পর্ব ২২-এ আমরা managed relational database দেখেছি। সেটি দারুণ সাধারণ একটি হাতিয়ার, আর বেশিরভাগ app-এর শুরু সেখান থেকেই হওয়া উচিত। কিন্তু একটি কাল্পনিক অনলাইন বাজার অ্যাপ বড় হলে একই database-এ চারটি খুব আলাদা অনুরোধ আসে। কেউ নিজের cart খুলছেন। কেউ “লাল শাড়ি” লিখে খুঁজছেন। Home page-এর একই তালিকা প্রতি সেকেন্ডে হাজারবার পড়া হচ্ছে। আর হিসাব বিভাগ জানতে চাইছে, গত দুই বছরে কোন জেলায় কত বিক্রি হলো।
পাড়ার বড় মুদি দোকানের কথা ভাবুন। বাকির খাতায় নাম দেখে সরাসরি পাতা খোলা হয়; এটি key দিয়ে খোঁজা। সবচেয়ে বেশি বিক্রি হওয়া চিনি আর লবণ ক্যাশ কাউন্টারের পাশেই থাকে; এটি cache. “কোন বিস্কুটে নারকেল আছে” জানতে প্যাকেটের লেখা মিলিয়ে দেখতে হয়; এটি search. আর বছর শেষে সব রসিদ গুদামে নিয়ে যোগ করা হয়; এটি warehouse. এক খাতায় চারটি কাজই করা যায়, কিন্তু ভিড়ের সময় দোকান থেমে যাবে।
তাই প্রশ্নটি “কোন database সেরা” নয়। প্রশ্নটি হলো আপনার query দেখতে কেমন। Query কয়টি row ছোঁয়, কোন পথে খোঁজে, উত্তর কত দ্রুত চাই, আর data কত পুরোনো হলে চলবে। এই হিসাবকে আমরা বলব query shape. SQL আর NoSQL-এর তাত্ত্বিক তুলনা আছে System Design-এর SQL বনাম NoSQL পর্বে আর DBMS কোর্সের NoSQL পর্বে। এখানে আমরা দেখব, cloud-এ এগুলো কিনে চালাতে গেলে কী হিসাব করতে হয়।
Query-র আকৃতি থেকে service-এর ধরন
From query shape to service type
তিন provider-এ একই ধারণা আলাদা নামে পাওয়া যায়। নিচের তালিকাটি লেখার সময়ের উদাহরণ। Service-এর নাম, ধরন আর সুবিধা বদলায়, তাই সিদ্ধান্তের আগে provider-এর docs দেখুন।
| ধরন | AWS | Azure | Google Cloud |
|---|---|---|---|
| Key-value / document | DynamoDB | Cosmos DB | Firestore, Bigtable (wide-column) |
| Managed cache | ElastiCache | Azure Managed Redis (পুরোনো Azure Cache for Redis অবসরের পথে) | Memorystore |
| Search | OpenSearch Service | Azure AI Search | Vertex AI Search, Marketplace-এর Elastic |
| Time-series | Timestream for InfluxDB | Azure Data Explorer | Bigtable-এর ওপর নিজের নকশা |
| Data warehouse | Redshift | Synapse, Fabric | BigQuery |
| Lake-এর ওপর SQL | Athena | Synapse serverless, Fabric | BigQuery external table |
২ · Key-value আর document service — আগে প্রশ্ন, পরে নকশা
Key-value store-এ একটি key দিলে তার value ফেরত আসে। Document database-এ value হলো JSON-এর মতো গঠনের একটি document, আর তার ভেতরের field ধরেও খোঁজা যায়। DynamoDB, Cosmos DB আর Firestore এই ঘরানার managed উদাহরণ। আপনি server, patch বা disk দেখেন না। আপনি দেখেন table বা collection, আর প্রতিটি অনুরোধের বিল।
এর বিনিময়ে একটি শর্ত আছে। Relational নকশায় আগে data সাজানো হয়, প্রশ্ন আসে পরে। এখানে উল্টো। আগে access pattern-এর তালিকা, অর্থাৎ app ঠিক কোন কোন প্রশ্ন করবে তার তালিকা। তারপর key সাজানো, যাতে প্রতিটি প্রশ্ন একবারেই সঠিক জায়গায় পৌঁছায়। রান্নাঘরের রেসিপি খাতার সূচিপত্র রান্না শুরুর আগেই লেখা হয়; পরে নতুন ধরনের সূচি চাইলে পুরো খাতা নতুন করে সাজাতে হয়।
আমাদের কাল্পনিক অনলাইন বাজার অ্যাপের অর্ডার ধরা যাক। একটি item-এ দুই অংশের key থাকে। Partition key ঠিক করে item কোন ভাগে যাবে, আর sort key সেই ভাগের ভেতরে ক্রম ঠিক করে।
{
"pk": "CUST#123",
"sk": "ORDER#2026-10-30#9012",
"shop_id": "SHOP#45",
"status": "PAID",
"total": 1850,
"items": [{ "sku": "RICE-5KG", "qty": 2 }]
}| App-এর প্রশ্ন | যেভাবে উত্তর আসবে | মন্তব্য |
|---|---|---|
| একজন গ্রাহকের অর্ডার, নতুনটি আগে | pk = CUST#123, sk উল্টো ক্রমে | একটি partition, দ্রুত |
| নির্দিষ্ট মাসের অর্ডার | sk begins_with ORDER#2026-10 | Sort key-তে তারিখ থাকায় সম্ভব |
| একটি দোকানের আজকের অর্ডার | Secondary index: SHOP#45#2026-10-30 | Index-এর লেখাও আলাদা খরচ |
| গত দুই বছরে জেলাভিত্তিক বিক্রি | এখানে নয়, warehouse-এ | পুরো table পড়তে হতো |
শেষ সারিটি সবচেয়ে গুরুত্বপূর্ণ। Query মানে একটি partition-এর ভেতরে পড়া। Scan মানে পুরো table পড়া, আর প্রতিটি পড়া item-এর দাম দেওয়া, filter করে ফেলে দিলেও। ধরুন ১০ GB-র table, প্রতি ৪ KB-তে ০.৫ capacity unit. তাহলে একটি Scan-এ লাগে 10,000,000 KB ÷ 4 × 0.5 = 1,250,000 ইউনিট। সেকেন্ডে ১,০০০ ইউনিট বরাদ্দ থাকলে ১,২৫০ সেকেন্ড, অর্থাৎ প্রায় ২১ মিনিট। ততক্ষণ বাকি app-এর ভাগে টান পড়ে।
Consistency-ও service-ভেদে আলাদা। লেখার সময়ে DynamoDB-তে পড়া সাধারণত eventually consistent, আর অনুরোধে strongly consistent পড়া যায়, দ্বিগুণ দামে। Cosmos DB পাঁচটি স্তর দেয়, যার default সাধারণত Session. Firestore-এর পড়া strongly consistent বলে নথিভুক্ত। আসল কথা, “সদ্য লেখা জিনিস এখনই দেখা চাই কি না” একটি ব্যবসায়িক প্রশ্ন, এবং এর দাম আছে। তত্ত্ব আছে CAP আর consistency পর্বে।
৩ · Partition key আর hot partition
Table আসলে একটি জিনিস নয়। Provider data ভাগ করে অনেক partition-এ রাখে, আর partition key-র hash থেকে ঠিক হয় কোন item কোথায় যাবে। Sharding-এর তত্ত্ব আছে System Design-এর partitioning পর্বে। Cloud-এ এর সবচেয়ে বড় ব্যবহারিক ফল হলো, capacity পুরো table-এর হলেও সীমা বসে প্রতিটি partition-এ।
ঈদের আগে বাস টার্মিনালে দশটি কাউন্টার। প্রতিটি ঘণ্টায় ১০০ টিকিট দিতে পারে, তাই মোট ক্ষমতা ১,০০০। কিন্তু সবাই রাজশাহীর টিকিট চান, আর রাজশাহীর জন্য খোলা শুধু ৪ নম্বর কাউন্টার। সেখানে ৫০০ জনের লাইন, বাকি নয়টি কাউন্টার ফাঁকা। মোট ক্ষমতা যথেষ্ট, তবু মানুষ ফিরে যাচ্ছেন। এটাই hot partition.
লেখার সময়ে DynamoDB-র একটি partition সাধারণত প্রতি সেকেন্ডে প্রায় ৩,০০০ RCU বা ১,০০০ WCU পর্যন্ত নিতে পারে বলে নথিভুক্ত। অন্য service-এ সংখ্যা আলাদা, ধারণা একই। নিচের simulator-এ table-এর ৪টি partition, মোট ৪,০০০ WCU, আর traffic মাত্র ৩,০০০ লেখা/সেকেন্ড। তিন রকম key-তে কী হয় দেখুন।
// 4 partitions, 1000 WCU/s each (4000 total). Traffic: 3000 writes/s for 60 s.
let seed = 23;
function rand() { // mulberry32: small seeded PRNG
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
function hash(s) { // FNV-1a, stands in for the provider's hash
let h = 0x811C9DC5;
for (const c of s) h = Math.imul(h ^ c.charCodeAt(0), 16777619);
return h >>> 0;
}
const PARTS = 4, LIMIT = 1000, RATE = 3000, SECONDS = 60;
function run(name, pickKey) {
const done = Array(PARTS).fill(0), throttled = Array(PARTS).fill(0);
for (let s = 0; s < SECONDS; s++) {
const used = Array(PARTS).fill(0); // the budget resets each second
for (let i = 0; i < RATE; i++) {
const p = hash(pickKey()) % PARTS;
if (used[p] < LIMIT) { used[p]++; done[p]++; } else throttled[p]++;
}
}
const lost = throttled.reduce((a, b) => a + b, 0);
console.log(name);
console.log(" writes/s per partition: " +
done.map((d, p) => ((d + throttled[p]) / SECONDS).toFixed(0)).join(" "));
console.log(" throttled: " + lost + " of " + RATE * SECONDS +
" (" + (100 * lost / (RATE * SECONDS)).toFixed(1) + "%)");
}
const anyUser = () => "user#" + Math.floor(rand() * 100000);
run("A. many keys", anyUser);
run("B. 50% on one key", () => rand() < 0.5 ? "product#777" : anyUser());
run("C. hot key split into 20", () => rand() < 0.5 ?
"product#777#" + Math.floor(rand() * 20) : anyUser());A-তে writes/s per partition: 747 746 754 752, আর throttled: 0 of 180000 (0.0%). B-তে অর্ধেক লেখা product#777-এ গেল। মোট traffic একই, অথচ 1874 378 374 374 আর throttled: 52446 of 180000 (29.1%). এক partition-এ সীমার প্রায় দ্বিগুণ চাহিদা, অন্য তিনটি প্রায় ফাঁকা। Throttling হলো যদিও পুরো table-এর ৪,০০০ WCU-র বেশিরভাগ অব্যবহৃত। C-তে একই key-কে ২০টি নকল key-তে ভাগ করায় 826 675 678 822 আর throttle শূন্য। সরল মডেলে burst বা স্বয়ংক্রিয় ভাগ করা ধরা হয়নি।
C-র কৌশলকে বলে write sharding. দাম আছে: পণ্যটির সব লেখা পড়তে এখন ২০টি key-তে প্রশ্ন পাঠাতে হবে। তাই এটি উপযুক্ত গণনা বা log-এর জন্য, যেখানে পড়া কম। আধুনিক service-ও অনেকটা সাহায্য করে। Adaptive capacity ব্যস্ত partition-কে অন্যের অব্যবহৃত ভাগ ধার দেয়, আর partition ভেঙেও আলাদা করতে পারে। কিন্তু একটিমাত্র item-এ ঢালা ভার কোনো ভাগাভাগিতে ছড়ায় না। এটাকেই বলে hot key.
| Partition key | সমস্যা | ভালো বিকল্প |
|---|---|---|
status | মান মাত্র কয়েকটি, প্রায় সব PAID | order_id বা customer_id |
date | আজকের সব লেখা এক জায়গায় | date#bucket, যেখানে bucket ০–১৯ |
country | একটি দেশেই প্রায় সব গ্রাহক | customer_id |
seller_id | একজন জনপ্রিয় বিক্রেতার ভিড় | পড়ায় cache, লেখায় queue আর sharding |
Throttle হলে SDK সাধারণত নিজে থেকে exponential backoff দিয়ে আবার চেষ্টা করে। ফলে error দেখা যায় না, শুধু latency বাড়ে। তাই dashboard-এ গড় নয়, throttled অনুরোধের metric আর p99 দেখুন। DynamoDB-তে ThrottledRequests, আর Cosmos DB-তে HTTP 429 খুঁজুন। কোন key গরম, তা জানাতে provider-ভেদে আলাদা হাতিয়ার আছে, যেমন DynamoDB-র Contributor Insights.
৪ · Capacity-র হিসাব — RCU, WCU আর দুই রকম বিল
Managed NoSQL-এ CPU বা RAM কেনা হয় না। কেনা হয় পড়া ও লেখার ইউনিট। নিচের সংখ্যাগুলো DynamoDB-র ধরনে, লেখার সময়ের। অন্য provider-এ মূল ধারণা এক, মাপ আলাদা। যেমন Cosmos DB-তে সব কাজ একটি একক Request Unit-এ মাপা হয়, যেখানে ১ KB item-এর point read প্রায় ১ RU, আর Firestore-এ বিল হয় পড়া ও লেখা document-এর সংখ্যায়।
| কাজ | প্রতি এককের আকার | খরচ |
|---|---|---|
| Strongly consistent পড়া | ৪ KB পর্যন্ত | ১ RCU |
| Eventually consistent পড়া | ৪ KB পর্যন্ত | ০.৫ RCU |
| Transactional পড়া | ৪ KB পর্যন্ত | ২ RCU |
| সাধারণ লেখা | ১ KB পর্যন্ত | ১ WCU |
| Transactional লেখা | ১ KB পর্যন্ত | ২ WCU |
পূর্ণ সমাধান: প্রতি সেকেন্ডে ২০০টি ৬ KB item পড়তে হবে, strongly consistent. ৬ ÷ ৪ = ১.৫, যা ওপরে গোল হয়ে ২ RCU. মোট ২০০ × ২ = ৪০০ RCU. Eventually consistent হলে ২০০ RCU. প্রতি সেকেন্ডে ৫০টি ২.৪ KB লেখা: ২.৪ ওপরে গোল হয়ে ৩ WCU, মোট ১৫০ WCU. একটি secondary index সব attribute রাখলে সেখানেও ১৫০, তাই মোট ৩০০ WCU.
নিচের stepper-এ একটি অর্ডার লেখার পুরো খরচ দেখুন। এখানে table-এ একটি secondary index আছে, যাতে item-এর সব attribute কপি হয়।
| কাজ | |
| Table-এর খরচ | |
| Index-এর খরচ | |
| এই লেখায় মোট | |
| এই সেকেন্ডে p2-র বাকি |
- অনুরোধ এলো। App
CUST#123key-তে ২.৪ KB-র একটি অর্ডার লিখতে চায়। p2 এই সেকেন্ডে এখনো পুরো ১,০০০ WCU-র বাজেট হাতে রেখেছে। - পথ ঠিক হলো। Provider-এর router partition key-র hash করে দেখল item-টি p2-এর। Item-এর আকার বা table-এর মোট capacity এখানে কোনো ভূমিকা রাখে না।
- আকার গোল হলো। ২.৪ KB ওপরে গোল হয়ে ৩ KB, তাই ৩ WCU. ১.১ KB-র item-ও ২ WCU নেয়। Attribute-এর নাম ছোট করলে item ছোট হয়, তাই বিশাল table-এ এটিও সঞ্চয়।
- বাজেট থেকে কাটা হলো। p2-এর এই সেকেন্ডের বাকি ১,০০০ − ৩ = ৯৯৭। Leader একাধিক zone-এর replica-য় লেখা পাঠায়, আর সংখ্যাগরিষ্ঠের নিশ্চিতি পেলে উত্তর দেয়। এই প্রতিলিপির জন্য আলাদা WCU লাগে না।
- Index আলাদা দাম নেয়। Secondary index আসলে আরেকটি table. ২.৪ KB সেখানেও লিখতে ৩ WCU, যা index-এর নিজের capacity থেকে কাটে। তাই একটি অর্ডারের মোট ৬ WCU, আর ৩০০ অর্ডার/সেকেন্ডে ১,৮০০ WCU. Index সাধারণত eventually consistent.
- এক byte বদলালেও পুরো item-এর দাম। পরের সেকেন্ডের কথা নয়, একই সেকেন্ডে আরেকটি লেখা: শুধু একটি attribute বদল। লেখার খরচ বদলানো অংশ নয়, পুরো item-এর আকারে হয়। তাই আবার ৩ + ৩ = ৬। p2-তে ৯৯৭ − ৩ = ৯৯৪। ঘনঘন বদলানো ছোট counter-কে বড় item-এর ভেতরে রাখা তাই দামি নকশা।
- Index-এর key বদলালে দ্বিগুণ।
statusযদি index-এর key হয়, পুরোনো entry মুছে নতুনটি লিখতে হয়, অর্থাৎ index-এ ২টি লেখা। মোট ৩ + ৬ = ৯ WCU. p2-তে ৯৯৪ − ৩ = ৯৯১। - Transaction-এ দ্বিগুণ।
TransactWriteItemstable-এ প্রস্তুতি আর সম্পাদন, দুই ধাপে কাজ করে, তাই ৩ × ২ = ৬ WCU. p2-তে ৯৯১ − ৬ = ৯৮৫। Index-এর হিসাব আলাদা, docs দেখে মেলান।
অনুমান না করে খরচ দেখার উপায়ও আছে। বেশিরভাগ SDK আর CLI একটি অনুরোধে কত ইউনিট গেল তা জানাতে পারে। নিচেরটি উদাহরণ, চালানোর জন্য নয়; আউটপুটটি নমুনা, version-ভেদে আলাদা হতে পারে।
aws dynamodb put-item --table-name orders \
--item file://order_item.dynamodb.json \
--return-consumed-capacity INDEXES
# নমুনা আউটপুট
# { "ConsumedCapacity": { "TableName": "orders", "CapacityUnits": 6.0,
# "Table": { "CapacityUnits": 3.0 },
# "GlobalSecondaryIndexes": { "shop-date": { "CapacityUnits": 3.0 } } } }Provisioned নাকি on-demand
Provisioned capacity মানে আগেই বলে দেওয়া, “সেকেন্ডে এত ইউনিট রাখুন”, আর ব্যবহার হোক বা না হোক, ঘণ্টা ধরে দাম দেওয়া। On-demand capacity মানে প্রতিটি অনুরোধের দাম দেওয়া। প্রথমটি মাসিক CNG চুক্তি, দ্বিতীয়টি প্রতিবার আলাদা ভাড়া। রোজ একই পথে গেলে চুক্তি সস্তা, মাঝেমধ্যে গেলে আলাদা ভাড়া।
ধরে নিই ১ WCU-মাসের দাম $০.৫০, আর on-demand-এ প্রতি ১০ লাখ লেখায় $১। সংখ্যাগুলো হিসাব বোঝার জন্য; আসল দাম provider-এর pricing page-এ দেখুন। ৩০ দিনে ২৫,৯২,০০০ সেকেন্ড। Provisioned-এর লক্ষ্য ৭০% ব্যবহার ধরা হলো, যাতে headroom থাকে।
| Traffic | মাসে লেখা | On-demand | Provisioned |
|---|---|---|---|
| স্থির ১০০/সেকেন্ড | ২৫.৯২ কোটি | $২৫৯.২০ | ১৪৩ WCU = $৭১.৫০ |
| দিনে ২ ঘণ্টা ১,০০০/সেকেন্ড, বাকি সময় ১০ | ২৩.৯৮ কোটি | $২৩৯.৭৬ | সারাক্ষণ ১,৪২৯ WCU = $৭১৪.৫০ |
| নতুন feature: গড় ০.৫, মাঝে ৫০ পর্যন্ত | ১২.৯৬ লাখ | $১.৩০ | ৭২ WCU = $৩৬ |
Break-even: ১ WCU সারা মাস পুরো চললে ২৫.৯২ লাখ লেখা হয়, যার on-demand দাম $২.৫৯২। Provisioned-এর দাম $০.৫০। তাই ০.৫০ ÷ ২.৫৯২ ≈ ১৯.৩%. গড় ব্যবহার এর বেশি হলে এই অনুমিত দামে provisioned সস্তা, কম হলে on-demand. দ্বিতীয় সারিতে autoscaling ঠিক সময়ে বাড়তে পারলে গড় প্রায় ১৩২ WCU, অর্থাৎ প্রায় $৬৬ হতো। কিন্তু ১০ থেকে ১,০০০-এ হঠাৎ লাফে autoscaling পিছিয়ে পড়ে, আর সেই ফাঁকে throttle হয়।
On-demand মানে সীমাহীন নয়। লেখার সময়ে নতুন table-এর শুরুর ক্ষমতা আর হঠাৎ ভিড়ে বাড়ার গতিতে সীমা আছে, আর partition-এর সীমা তখনও থাকে। বিলেও শুধু ইউনিট নয়; storage, backup, region-পার replication আর data transfer আলাদা। শুরুতে on-demand-এ চালিয়ে আসল traffic মেপে নেওয়া একটি নিরাপদ পথ। আর budget alert আগেই বসান, কারণ on-demand-এ ভুল loop মানে সরাসরি বিল।
৫ · Managed cache — hit ratio-র হিসাব
Cache হলো দ্রুত মেমরিতে রাখা data-র সাময়িক কপি। সবচেয়ে চেনা নকশা cache-aside: app আগে cache-এ খোঁজে, না পেলে database থেকে এনে cache-এ রেখে দেয়। Write-through-সহ বিভিন্ন কৌশলের বিস্তারিত আছে Caching কৌশল পর্বে, আর Redis-এর গঠন DBMS কোর্সের Redis পর্বে।
ElastiCache, Azure Managed Redis আর Memorystore-এ node চালানো, patch, replica আর failover provider সামলায়। আপনার দায়িত্বে থাকে node-এর আকার, TTL, eviction নীতি, network নিয়ম, আর সবচেয়ে বড় কথা, cache না থাকলে কী হবে তার পরিকল্পনা। ধরে নিন cache আপনার VPC-র ভেতরে থাকবে, internet-এ খোলা নয়।
Cache-এর দক্ষতার মাপ cache hit ratio: যত অনুরোধ cache-এই উত্তর পেল তার ভাগ। ধরুন h হলো hit ratio, cache-এর সময় Tc, database-এর সময় Tdb. Cache-aside-এ miss হলেও আগে cache-এ খোঁজা হয়, তাই:
DB-র ভার = (1 − h) × মোট অনুরোধ
পূর্ণ সমাধান: ১০,০০০ অনুরোধ/সেকেন্ড, Tc = ১ ms, Tdb = ২০ ms. h = ০.৯ হলে গড় ১ + ০.১ × ২০ = ৩ ms আর database-এ ১,০০০ query/সেকেন্ড। h = ০.৯৫ হলে ২ ms আর ৫০০। h = ০.৯৯ হলে ১.২ ms আর ১০০। লক্ষ করুন, ৯৫% থেকে ৯৯%-এ যেতে latency তেমন কমে না, কিন্তু database-এর ভার পাঁচ ভাগের এক ভাগ হয়। তাই hit ratio-র আসল মূল্য প্রায়ই latency-তে নয়, database বাঁচানোয়।
Hit ratio আসে কোথা থেকে? বাস্তবে জনপ্রিয়তা বাঁকা: অল্প কিছু key-তেই বেশিরভাগ অনুরোধ। নিচের simulator-এ ১০,০০০ key, Zipf ধরনের বাঁকা জনপ্রিয়তা, আর LRU cache. Cache বড় করলে hit ratio, গড় latency আর database-এর ভার কীভাবে বদলায় দেখুন। Database-এর সীমা ২,০০০ query/সেকেন্ড ধরা হয়েছে।
// LRU cache in front of a database. 10,000 keys, skewed (Zipf) popularity.
let seed = 7;
function rand() { // mulberry32: small seeded PRNG
seed = (seed + 0x6D2B79F5) | 0;
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
const KEYS = 10000, REQUESTS = 300000, WARMUP = 50000;
const CACHE_MS = 1, DB_MS = 20, TRAFFIC = 10000, DB_LIMIT = 2000;
const cdf = []; let sum = 0;
for (let k = 1; k <= KEYS; k++) { sum += 1 / k; cdf.push(sum); }
function pick() { // binary search on the Zipf CDF
const x = rand() * sum; let lo = 0, hi = KEYS - 1;
while (lo < hi) { const m = (lo + hi) >> 1; if (cdf[m] < x) lo = m + 1; else hi = m; }
return lo;
}
function hitRatio(size) {
const lru = new Map(); let hits = 0, total = 0;
for (let i = 0; i < REQUESTS + WARMUP; i++) {
const k = pick(), hit = lru.delete(k); // re-inserting puts a key last
if (!hit && lru.size >= size) lru.delete(lru.keys().next().value); // evict oldest
lru.set(k, 1);
if (i >= WARMUP) { total++; if (hit) hits++; }
}
return hits / total;
}
console.log("cache size hit% avg ms DB q/s DB load");
for (const size of [100, 500, 1000, 2000, 5000]) {
const h = hitRatio(size);
const ms = CACHE_MS + (1 - h) * DB_MS, db = (1 - h) * TRAFFIC;
console.log(String(size).padStart(10) + (100 * h).toFixed(1).padStart(6) +
ms.toFixed(1).padStart(10) + db.toFixed(0).padStart(8) +
(100 * db / DB_LIMIT).toFixed(0).padStart(8) + "%");
}১০০টি key রাখলে অর্থাৎ মাত্র ১% জায়গায় 39.0% hit আসে, এটাই বাঁকা জনপ্রিয়তার শক্তি। কিন্তু তখন database-এ 6102 query, সীমার 305%. ২,০০০ key-তেও 116%, আর ৫,০০০ key-তে গিয়ে 89.8% hit, 3.0 ms আর 51% ভার। অর্থাৎ cache-এর আকার ঠিক হয় গড় গতি দেখে নয়, database নিরাপদ সীমায় থাকছে কি না দেখে। প্রতিটি নতুন GB-র লাভ ক্রমশ কমে, তাই আপনার নিজের key-বণ্টনে মেপে নিন।
চলমান system-এ hit ratio বের করা সহজ, কিন্তু একটি ফাঁদ আছে। Redis-ধরনের engine-এর INFO stats-এর counter শেষ restart থেকে জমতে থাকে। পুরোনো সংখ্যা এত বড় যে নতুন সমস্যা চোখে পড়ে না। তাই দুই সময়ের পার্থক্য নিতে হয়। নিচের block নমুনা লেখা থেকে হিসাব করে; কোনো আসল cache-এ যুক্ত হয় না।
set -euo pipefail
# Sample "INFO stats" text from one cache node, taken five minutes apart.
cat > before.txt <<'SAMPLE'
keyspace_hits:91000000
keyspace_misses:9000000
evicted_keys:120000
SAMPLE
cat > after.txt <<'SAMPLE'
keyspace_hits:91200000
keyspace_misses:9100000
evicted_keys:138000
SAMPLE
get() { awk -F: -v k="$2" '$1 == k { print $2 }' "$1"; }
for f in before.txt after.txt; do
h=$(get "$f" keyspace_hits); m=$(get "$f" keyspace_misses)
awk -v f="$f" -v h="$h" -v m="$m" \
'BEGIN { printf "%-11s since restart: %.2f%%\n", f, 100 * h / (h + m) }'
done
dh=$(( $(get after.txt keyspace_hits) - $(get before.txt keyspace_hits) ))
dm=$(( $(get after.txt keyspace_misses) - $(get before.txt keyspace_misses) ))
de=$(( $(get after.txt evicted_keys) - $(get before.txt evicted_keys) ))
awk -v h="$dh" -v m="$dm" -v e="$de" 'BEGIN {
printf "last 5 min : hits=%d misses=%d ratio=%.2f%%\n", h, m, 100 * h / (h + m)
printf "evictions : %d (%.0f per second)\n", e, e / 300
}'Restart থেকে মোট হিসাবে 91.00% থেকে 90.93%, মনে হয় কিছুই হয়নি। অথচ শেষ পাঁচ মিনিটে hits=200000 misses=100000 ratio=66.67%, যা বড় বিপদের ইঙ্গিত। সাথে evictions : 18000 (60 per second). তাই dashboard-এ সবসময় হার (rate) দেখুন। Managed service-এর metric-ও এই হিসাব করে দেয়, যেমন ElastiCache-এর CacheHitRate, তবে নাম provider-ভেদে আলাদা।
Eviction — জায়গা ফুরালে কে বের হবে
মেমরি ভরলে কাউকে সরাতে হয়। এটাই eviction. Redis-এ maxmemory-policy নীতি ঠিক করে। Managed service-এ এটি সাধারণত CONFIG SET দিয়ে নয়, parameter group বা সমতুল্য settings দিয়ে বদলাতে হয়। Default-ও এক নয়: open source Redis-এ সাধারণত noeviction, আর কোনো কোনো managed service-এ অন্য কিছু। আগে দেখে নিন।
| নীতি | কী করে | কখন মানায় |
|---|---|---|
noeviction | কিছু সরায় না, নতুন লেখায় error | Cache নয়, data রাখার কাজে |
allkeys-lru | দীর্ঘদিন না ছোঁয়া key সরায় | সাধারণ cache-এর ভালো শুরু |
allkeys-lfu | কম ব্যবহৃত key সরায় | স্থায়ী জনপ্রিয় key থাকলে |
volatile-lru | শুধু TTL-ওয়ালা key থেকে সরায় | Cache আর স্থায়ী data মিশে থাকলে |
volatile-ttl | যার TTL সবচেয়ে কম বাকি, তাকে সরায় | মেয়াদ দিয়ে অগ্রাধিকার বোঝালে |
TTL আর invalidation-এর গভীরতর আলোচনা আছে Eviction আর invalidation পর্বে। evictions বাড়তে থাকা আর hit ratio পড়তে থাকা একসাথে দেখলে বুঝবেন cache ছোট পড়েছে।
৬ · Cache পড়ে গেলে কী হয় — আর Redis-এর লাইসেন্স
Cache বসানোর আগে সে ছিল “গতি বাড়ানোর জিনিস”। কিছুদিন পরে সে হয়ে যায় ভার বহনের দেয়াল। ওপরের হিসাবে database ২,০০০ query নিতে পারে, অথচ ১০,০০০ অনুরোধ আসছে। অর্থাৎ cache ছাড়া database আর টিকতে পারে না। নিচে ৪টি shard-এর একটি cache-এ একটি node মারা গেল। প্রতিটি shard ২৫% key ধরে রাখে, আর এটির কোনো replica নেই।
| সময় | |
| Shard-3 | |
| মোট hit ratio | |
| DB-তে query/s | |
| DB-র অবস্থা | |
| ব্যর্থ query (মোট) |
- স্বাভাবিক অবস্থা। ৯৫% hit. ৯,৫০০ অনুরোধ cache-এ শেষ, ৫০০ query database-এ, সীমার ২৫%. সব dashboard সবুজ।
- Shard-3 মারা গেল। এর ২৫% key এখন সবসময় miss. নতুন hit ratio ০.৯৫ × ০.৭৫ = ০.৭১২৫। Database-এ হঠাৎ (১ − ০.৭১২৫) × ১০,০০০ = ২,৮৭৫ query, পাঁচ গুণেরও বেশি।
- সীমা পার। প্রতি সেকেন্ডে ৮৭৫টি query বাড়তি। ১০ সেকেন্ডে ৮,৭৫০টি timeout বা error. বাস্তবে আরও খারাপ, কারণ client আবার চেষ্টা করে, আর সেই retry ভার বাড়ায়। আমাদের মডেলে retry ধরা হয়নি।
- নতুন node এলো, কিন্তু খালি। ৬০ সেকেন্ড পর managed service একটি নতুন node দিল। ৮৭৫ × ৬০ = ৫২,৫০০ ব্যর্থ। এই খালি node-ই cold cache: প্রতিটি key প্রথমবার miss. তাই hit ratio এখনই ফেরে না।
- ভরতে সময় লাগে। ধরে নিই shard-এর hit ratio প্রতি সেকেন্ডে ১ শতাংশ-বিন্দু বাড়ে। t = ৯৫-এ সেটি ৩৫%, মোট ০.৭১২৫ + ০.০৮৭৫ = ০.৮০, database ঠিক ২,০০০-এ। ৬০ থেকে ৯৫ সেকেন্ডে বাড়তি ভার ৮৭৫ থেকে ০-তে নামে, গড়ে ৪৩৭.৫ × ৩৫ ≈ ১৫,৩১২ ব্যর্থ। মোট প্রায় ৬৭,৮১২।
- সীমার নিচে। t = ১২০-এ shard-এর hit ৬০%, মোট ০.৭১২৫ + ০.১৫ = ৮৬.২৫%, database-এ ১,৩৭৫ query. আর ব্যর্থতা নেই।
- পুরো সুস্থ। t = ১৫৫-এ shard ৯৫%-এ ফিরল। Node মরার পর আড়াই মিনিটের বেশি কাটল, যদিও node বদলাতে লেগেছে এক মিনিট। বাকি সময়টি cold cache-এর দাম।
- Replica থাকলে কী হতো। অন্য zone-এ replica থাকলে ধরুন ৩০ সেকেন্ডে সে primary হতো। তার কাছে data আছে, তাই hit ratio সাথে সাথে ৯৫%. ব্যর্থ ৮৭৫ × ৩০ = ২৬,২৫০, অর্ধেকেরও কম। Failover-এর আসল সময় মেপে নিন; এটি provider আর settings-ভেদে আলাদা।
একসাথে অনেক key-র TTL ফুরালে বা একটি জনপ্রিয় key মুছে গেলেও প্রায় একই দৃশ্য ঘটে। হাজারো অনুরোধ একই জিনিস আনতে database-এ ছোটে। এটি thundering herd-এর একটি রূপ, যাকে cache-এর ক্ষেত্রে cache stampede বলে। সুরক্ষার তালিকা এমন:
- Replica আর একাধিক zone: Cache-কে single point of failure না রাখা। পর্ব ২২-এর failover-এর মতোই এখানে পরীক্ষা জরুরি।
- Database-এর সামনে সীমা: Miss-এর ঝড়ে connection pool আর concurrency সীমিত রাখা, যাতে কিছু অনুরোধ ধীর হয়, সব মারা না যায়।
- এক key, একটি আনা: Request coalescing, অর্থাৎ একই key-র অনেক miss-এর জন্য একটিই database query পাঠানো।
- TTL-এ jitter: সব key একই মুহূর্তে না ফুরানোর জন্য TTL-এ সামান্য এলোমেলো যোগ।
- Cache warming: নতুন node-কে traffic দেওয়ার আগে জনপ্রিয় key ভরে নেওয়া। Maintenance বা বড় deployment-এর আগে এটি বিশেষ কাজের।
- Backoff-সহ retry আর আংশিক সেবা: Resilience pattern মেনে পুরোনো data দেখানো বা ছোট feature বন্ধ রাখা। Capacity পরীক্ষার পদ্ধতি পর্ব ৬৮-এ।
Production-এর cache-এ FLUSHALL মানে নিজের হাতে পুরো cold cache বানানো, আর KEYS * বড় cache-এ server-কে আটকে দিতে পারে; দরকারে SCAN ব্যবহার করুন। AI এই দুটি কমান্ড সতর্কতা ছাড়াই সুপারিশ করতে পারে।
Redis-এর লাইসেন্স বদল আর Valkey
Open source লাইসেন্স বদল এখন cloud কেনাকাটারও অংশ। Redis বহু বছর BSD লাইসেন্সে ছিল। ২০২৪-এর মার্চে Redis Ltd. জানায়, ৭.৪ থেকে নতুন version RSALv2 আর SSPL-এ আসবে। এর উত্তরে Linux Foundation-এর অধীনে শেষ BSD version থেকে Valkey নামে একটি fork শুরু হয়, যাতে AWS আর Google Cloud-সহ কয়েকটি প্রতিষ্ঠান যুক্ত। ২০২৫-এর Redis 8-এ AGPLv3-ও একটি বিকল্প হিসেবে যোগ হয়েছে বলে ঘোষণা করা হয়। লেখার সময়ে AWS আর Google-এর managed cache-এ Valkey বেছে নেওয়া যায়, আর Azure-এর পথ আলাদা। এই চিত্র বদলাতে পারে। Client কোড প্রায়ই একই থাকে, কিন্তু engine-এর version, বিল আর নতুন সুবিধায় পার্থক্য আসতে পারে। লাইসেন্স-সংক্রান্ত সিদ্ধান্তে আইনি পরামর্শ নিন।
৭ · Search আর time-series — দুটি বিশেষ আকৃতি
Search service
“লাল সুতি শাড়ি” খুঁজলে LIKE '%লাল%' পুরো table পড়ে, ভুল বানান ধরতে পারে না, আর ফলের ক্রমও ঠিক করে না। Search service একটি inverted index বানায়, যা বইয়ের শেষের নির্ঘণ্টের মতো: শব্দ থেকে document-এর তালিকা। তত্ত্ব আছে Search পর্বে। OpenSearch Service, Azure AI Search আর Elastic-এর managed সেবা এই ধরনের উদাহরণ। OpenSearch এসেছে ২০২১-এ Elasticsearch-এর লাইসেন্স বদলের পর একটি fork হিসেবে; Elastic ২০২৪-এ AGPLv3 বিকল্প যোগ করার ঘোষণা দেয়।
পরিচালনার দিক থেকে তিনটি কথা মনে রাখুন। এক, search index সাধারণত মূল data নয়, মূল database থেকে তৈরি একটি কপি। তাই সে কিছুটা পিছিয়ে থাকে, আর নষ্ট হলে আবার বানানো যায়। দুই, shard-এর সংখ্যা আর আকার পরে বদলানো কঠিন হতে পারে, আর shard খুব বেশি হলে cluster ভারী হয়। তিন, মেমরি আর disk-এই আসল খরচ, তাই পুরোনো log রাখার জন্য স্তরভিত্তিক storage আর মেয়াদের নিয়ম লাগে। Log-এর দিকটি পর্ব ৬৪-এ আসবে।
Time-series service
Server-এর CPU, সেন্সরের তাপমাত্রা, শেয়ারের দাম: প্রতিটি লেখায় একটি সময় থাকে, data প্রায় শুধু যোগ হয়, আর প্রশ্ন আসে “গত ১ ঘণ্টায় গড় কত”। Time-series database সময় ধরে data ভাগ করে, পাশাপাশি মান খুব ঘন করে চেপে রাখে, আর পুরোনো data-কে downsampling করে। Amazon Timestream, Azure Data Explorer, InfluxDB আর TimescaleDB এর উদাহরণ; ধরনগুলো হুবহু এক নয়। DBMS কোর্সে এর গঠন আছে, আর Prometheus পর্ব ৬৩-এ।
ছোট হিসাব: ১,০০০টি সেন্সর, প্রতিটি ১০ সেকেন্ডে একটি মান। অর্থাৎ ১০০ মান/সেকেন্ড, ৩০ দিনে ২৫.৯২ কোটি মান। চাপার পর প্রতিটি ১৬ byte ধরলে ৪.১৫ GB. ৫ মিনিটের গড়ে নামালে মানের সংখ্যা ৩০ ভাগের এক ভাগ, প্রায় ০.১৪ GB. তাই সাধারণ নিয়ম হলো: নতুন data সূক্ষ্ম, পুরোনো data মোটা দাগে। Cardinality, অর্থাৎ আলাদা sensor বা label-এর সংখ্যা, হঠাৎ বেড়ে গেলে কিন্তু খরচও লাফিয়ে বাড়ে।
৮ · Data warehouse আর data lake — পড়া byte-এর দাম
দোকানের ক্যাশ কাউন্টারে প্রতিটি বিক্রি একটি ছোট, দ্রুত কাজ: এটি OLTP. বছর শেষে হিসাবরক্ষক গুদামে বসে লাখো রসিদ যোগ করেন: এটি OLAP. দুটি কাজ এক ডেস্কে হলে কাউন্টারের লাইন আটকে যায়। Data warehouse হলো সেই আলাদা গুদাম। Star schema আর ETL-এর তত্ত্ব আছে DBMS কোর্সের Data warehouse পর্বে।
| দিক | OLTP database | Data warehouse |
|---|---|---|
| একটি query ছোঁয় | অল্প কিছু row | কোটি row, অল্প column |
| সময় | মিলিসেকেন্ড | সেকেন্ড থেকে মিনিট |
| Storage | Row ধরে | Column ধরে |
| লেখা | একটি একটি করে, অনবরত | দলে দলে load |
| ব্যবহারকারী | App, হাজারো একসাথে | Analyst, dashboard |
Row storage
এক row-এর সব মান disk-এ পাশাপাশি। একটি পুরো অর্ডার আনা সহজ। কিন্তু শুধু total যোগ করতে গেলেও ঠিকানা, পণ্যের তালিকা সব পড়তে হয়।
Columnar storage
এক column-এর সব মান পাশাপাশি। total যোগ করতে শুধু সেই column পড়লেই হয়। একই ধরনের মান পাশাপাশি থাকায় চাপও ভালো হয়।
Columnar storage-এর ফল সরাসরি বিলে আসে। BigQuery-র on-demand ধরনে, Athena-তে আর Synapse serverless-এ বিল হয় মূলত কত byte পড়া হলো তাতে। একে বলব pay-per-scan. অন্যদিকে Redshift-এর node, Synapse-এর dedicated pool, Fabric-এর capacity বা BigQuery-র slot reservation-এ বিল হয় বরাদ্দ করা ক্ষমতায়। ধরে নিই ১ TB পড়তে $৫; আসল দাম আলাদা এবং বদলায়। Table: ১,২০০ GB, ২৪ মাসের partition, সাতটি column.
SELECT *
FROM shop.orders;
SELECT *
FROM shop.orders
LIMIT 10;
SELECT region, SUM(total) FROM shop.orders WHERE order_date >= '2026-09-01' AND order_date < '2026-10-01' GROUP BY region; -- table এখনো partition করা নয়
SELECT region, SUM(total) FROM shop.orders WHERE order_date >= '2026-09-01' AND order_date < '2026-10-01' GROUP BY region;
SELECT SUM(total)
FROM shop.orders
WHERE order_date >= '2026-09-01'
AND order_date < '2026-10-01'
AND region = 'Rajshahi';
SELECT region, SUM(total)
FROM shop.orders
WHERE FORMAT_DATE('%Y-%m', order_date) = '2026-09'
GROUP BY region;
| Column পড়া হলো | |
| মাস পড়া হলো | |
| পড়া data | |
| এই query-র দাম |
- সব পড়া। ১,২০০ GB = ১.২ TB, তাই ১.২ × ৫ = $৬। Analyst শুধু table-টি দেখতে চেয়েছিলেন, আর সেই এক ক্লিকে বিল উঠল।
- LIMIT বাঁচায় না। Scan-ভিত্তিক বিলে
LIMIT 10সাধারণত পড়া byte কমায় না; ফল ছোট হয়, পড়া নয়। Data দেখার জন্য table preview বা নমুনা ব্যবহার করুন। সব engine-এ আচরণ এক নয়, dry run দিয়ে মিলিয়ে নিন। - Column pruning. তিনটি column-এর আকার ৩০ + ৩০ + ১২০ = ১৮০ GB. বিশাল
items_jsonআরaddressছোঁয়া হলো না। দাম ০.১৮ × ৫ = $০.৯০। Table partition করা নয়, তাই তারিখের filter থাকলেও পড়া কমেনি। - Partition pruning. এবার table মাস ধরে ভাগ করা। Engine ২৪টি ভাগের ২৩টি না খুলেই বাদ দিল। ১৮০ ÷ ২৪ = ৭.৫ GB, দাম $০.০৩৭৫। মাসে ৬,০০০ query-তে $৫,৪০০ থেকে $২২৫।
- Clustering. Partition-এর ভেতরে data region ধরে সাজানো। ধরে নিই ৫টি সমান region, তাই প্রায় এক-পঞ্চমাংশ block. প্রায় ১.৫ GB, দাম প্রায় $০.০০৭৫। এটি অনুমান; আসল ফল data-র বণ্টনে নির্ভর করে, আর BigQuery-তে dry run clustering-এর সঞ্চয় আগে থেকে পুরো দেখায় না।
- ফাঁদ। Partition column-কে function-এ মোড়ানো হলো। ফলাফল একই, কিন্তু engine অনেক ক্ষেত্রে আর ভাগ বাদ দিতে পারে না। পড়া আবার ১৮০ GB, দাম ২৪ গুণ। তাই filter লিখুন column-এর ওপর সরাসরি, range দিয়ে।
একই হিসাব এবার কোডে। Column-এর আকার ইচ্ছামতো বদলে দেখুন। মাসে ৬,০০০ query আর একটি অনুমিত স্থির capacity fee-র সাথে তুলনাও আছে।
// Columnar table: 24 monthly partitions, 1200 GB in total. Assumed $5 per TB.
const COLUMNS = { order_id: 60, order_date: 30, customer_id: 60, region: 30,
total: 120, items_json: 600, address: 300 }; // GB per column
const MONTHS = 24, PRICE_PER_TB = 5, QUERIES_PER_MONTH = 6000, FLAT_FEE = 2000;
function scanned(q) {
const cols = q.cols === "*" ? Object.keys(COLUMNS) : q.cols;
// Column pruning: read only named columns. Partition pruning: only these months.
const gb = cols.reduce((a, c) => a + COLUMNS[c], 0);
return gb * (q.months / MONTHS) * (q.blockShare ?? 1);
}
const three = ["order_date", "region", "total"];
const queries = [
{ name: "SELECT * (everything)", cols: "*", months: 24 },
{ name: "3 columns, all months", cols: three, months: 24 },
{ name: "3 columns, 1 month", cols: three, months: 1 },
{ name: "3 cols, 1 month, 1/5 blocks", cols: three, months: 1, blockShare: 0.2 },
];
console.log("query GB scanned $/query $/month");
for (const q of queries) {
const gb = scanned(q), cost = gb / 1000 * PRICE_PER_TB;
console.log(q.name.padEnd(28) + gb.toFixed(1).padStart(11) +
cost.toFixed(4).padStart(10) + (cost * QUERIES_PER_MONTH).toFixed(0).padStart(10));
}
const perQuery = scanned(queries[1]) / 1000 * PRICE_PER_TB;
console.log("flat fee $" + FLAT_FEE + " beats 3-column full scans above " +
Math.ceil(FLAT_FEE / perQuery) + " queries/month");SELECT * (everything)-এ 1200.0 GB আর মাসে 36000 ডলার। শুধু তিনটি column-এ 5400, এক মাসের partition-এ 225, আর clustering-সহ 45. যে উত্তরটি দরকার ছিল সেটি মিললেও বিল নামে প্রায় ৮০০ ভাগের এক ভাগে। শেষ লাইন flat fee $2000 beats 3-column full scans above 2223 queries/month দেখায়, কখন স্থির capacity কেনা যুক্তিসঙ্গত হতে পারে।
টাকা খরচের আগেই পড়া byte জানার উপায় আছে। নিচে BigQuery-র উদাহরণ, চালানোর জন্য নয়; আউটপুটটি নমুনা, version-ভেদে আলাদা হতে পারে। Dry run-এ query চলে না, তাই সাধারণত বিলও হয় না। প্রতি query-তে পড়ার সর্বোচ্চ সীমা (maximum bytes billed) আর project-এর quota বসিয়ে রাখা আরও নিরাপদ।
bq query --use_legacy_sql=false --dry_run \
"SELECT region, SUM(total) FROM shop.orders
WHERE order_date >= '2026-09-01' AND order_date < '2026-10-01'
GROUP BY region"
# নমুনা আউটপুট
# Query successfully validated. Assuming the tables are not modified,
# running this query will process 7500000000 bytes of data.Data lake — ধারণাটুকু
Data lake হলো সস্তা object storage-এ সব কাঁচা data ফেলে রাখা: log, JSON, ছবি, database-এর export. প্রশ্ন আগে ঠিক না হলেও চলে; গঠন ঠিক হয় পড়ার সময়। Object storage পর্ব ২০ আর ২১-এ বিস্তারিত আছে। সাধারণত Parquet-এর মতো columnar file-এ রাখলে Athena বা BigQuery-র external table সরাসরি query করতে পারে। ওপরের column আর partition-এর হিসাব এখানেও খাটে: folder-এর ভাগ যেন filter-এর সাথে মেলে।
নিয়ম না থাকলে lake হয়ে যায় “data swamp”, যেখানে কেউ জানে না কোন file ঠিক। Iceberg, Delta Lake আর Hudi-র মতো table format object storage-এর ওপর transaction আর schema-র ব্যবস্থা যোগ করে। Warehouse আর lake-এর এই মিলনকে অনেকে lakehouse বলেন, যদিও সীমারেখা নিয়ে মতভেদ আছে। Data ঢোকানোর batch আর stream পথ দেখতে পারেন Batch আর stream পর্বে।
৯ · কোনটি বাছবেন — আর বহু store-এর আসল দাম
ছবি ২৩.১ শুরুর পথ দেখায়। তারপর চারটি প্রশ্নে সিদ্ধান্ত পাকা করুন।
| প্রশ্ন | কী মাপবেন | উত্তরের প্রভাব |
|---|---|---|
| Query shape | Key, range, join, full-text, aggregate; প্রতিটি কতবার | Service-এর ধরন ঠিক হয় |
| Consistency | সদ্য লেখা কত দ্রুত দেখা চাই; টাকা নাকি like-এর সংখ্যা | Relational, strong read, নাকি পিছিয়ে থাকা কপি চলবে |
| Scale | Peak-এ সেকেন্ডে পড়া ও লেখা, data-র আকার, হটস্পট | এক node যথেষ্ট, নাকি partition লাগবে |
| Cost | গড় নয়, মাসের পুরো বিল: ইউনিট, storage, replication, backup, মানুষের সময় | On-demand, provisioned, scan, নাকি capacity |
একই app-এ একাধিক store ব্যবহারের নাম polyglot persistence. শব্দটি ২০১১-র দিকে Martin Fowler-এর লেখায় ছড়িয়ে পড়ে। আমাদের কাল্পনিক অ্যাপ বড় হলে এমন হতে পারে: অর্ডার ও টাকা relational-এ, session আর cart key-value-তে, home page-এর তালিকা cache-এ, পণ্য খোঁজা search-এ, ব্যবসার প্রশ্ন warehouse-এ। প্রতিটি যুক্তিসঙ্গত। কিন্তু প্রতিটি নতুন store-এর সাথে আসে নিচের কাজের তালিকা, যাকে আমরা বলি operational burden:
- Backup আর restore-এর মহড়া (পর্ব ২৪)
- IAM, secret আর network নিয়ম (পর্ব ২৭, ৩১, ৩৩)
- আলাদা metric, alert, runbook আর on-call জ্ঞান (পর্ব ৬৩, ৬৬)
- Version আপগ্রেড আর API বদল সামলানো
- একটি আলাদা বিলের দাগ, যা কেউ না কেউ বুঝবেন (পর্ব ৭২, ৭৩)
- অন্য store-এর সাথে data মেলানোর পথ
শেষ কাজটিই সবচেয়ে কঠিন। App যদি database-এ লিখে তারপর search-এ লেখে, আর মাঝে crash করে, তবে দুটি store আলাদা কথা বলবে। এই ভুলকে বলে dual write সমস্যা। ভালো পথ হলো একটি store-কে সত্যের উৎস ধরা, আর তার পরিবর্তন change data capture বা queue (পর্ব ৭০) দিয়ে অন্যদের কাছে পাঠানো। ৫টি store-এ জোড়ার সম্ভাব্য সংখ্যা ৫ × ৪ ÷ ২ = ১০; সব জোড়া লাগে না, তবু জটিলতা store-এর সংখ্যার চেয়ে দ্রুত বাড়ে। তাই সব পথ একটি উৎস থেকে বের হওয়া ভালো।
নতুন store যোগের আগে জিজ্ঞেস করুন, বর্তমান database কি এটি যথেষ্ট ভালোভাবে পারে? PostgreSQL-এর JSONB, full-text search আর partition ছোট দলের অনেক প্রয়োজন মেটায়; কিছু লেখা আছে JSON আর full-text পর্বে। ছোট দলে পাঁচটি store মানে পাঁচ রকম রাতের alert. মাপা সমস্যা পেলে তবেই নতুন store. Cloud database পর্ব দেখলে পুরো ছবিটা পাবেন। Lock-in-এর আলোচনা পর্ব ৭৪-এ।
হাতে-কলমে চাইলে (ঐচ্ছিক): Cloud-এর আসল ব্যবহারে টাকা লাগতে পারে; free tier-এর শর্ত provider-ভেদে আলাদা এবং বদলায়। পরীক্ষা করলে on-demand-এ একটি ছোট table বানান, budget alert বসান, আর কাজ শেষে table, index, backup ও cache cluster মুছে দিন। Cache node ব্যবহার না হলেও ঘণ্টা ধরে বিল চলতে থাকে।
আরও গভীরে: Amazon-এর Dynamo paper (DeCandia ও সহকর্মীরা, SOSP ২০০৭) leaderless ধারণা জনপ্রিয় করে, যদিও ২০১২-র DynamoDB সার্ভিসটি নকশায় তা থেকে আলাদা। ২০২২ সালের USENIX ATC paper-এ DynamoDB-র প্রকৌশলীরা partition, multi-Paxos আর admission control-এর বিবর্তন বর্ণনা করেছেন। Columnar ধারণার গুরুত্বপূর্ণ ধাপ C-Store (২০০৫) আর Google-এর Dremel paper (২০১০)। Facebook-এর “Scaling Memcache” (NSDI ২০১৩) cache-এর বড় মাপের failure সামলানোর ক্লাসিক লেখা।
পরীক্ষা ও ইন্টারভিউতে:
- মোট capacity থাকার পরও throttle কেন? সীমা partition-ভিত্তিক; ভার এক partition-এ জমেছে।
- ৩.২ KB item-এর একটি সাধারণ লেখায় কত WCU? ওপরে গোল করে ৪ WCU.
- গড় latency-র সূত্র? Cache-aside-এ Tc + (1 − h) × Tdb.
- Cold cache কী? খালি cache, যেখানে শুরুতে সবই miss, তাই ভার database-এ যায়।
- Warehouse-এ columnar কেন? Aggregate query অল্প column পড়ে, আর একই ধরনের মান ভালো চাপা যায়।
১০ · AI-এর যুগে managed data service
AI আপনাকে দ্রুত table definition বা cache client লিখে দেবে। কিন্তু সে আপনার traffic-এর বণ্টন জানে না, আর বিল সে দেয় না। এই পর্বের ভুলগুলো প্রায়ই প্রথম দিনে নয়, ভিড়ের দিনে ধরা পড়ে।
| AI-এর ভুল | ফল | যাচাই |
|---|---|---|
status বা date-কে partition key করা | ভিড়ে hot partition | Peak-এ key-র বণ্টন হিসাব করা |
App কোডে Scan দিয়ে filter | বিল আর latency দুটোই বাড়ে | Access pattern-এর সাথে মেলানো |
| Cache-এ TTL না রাখা, বা সব key-তে একই TTL | পুরোনো data, বা একসাথে stampede | TTL আর jitter পড়ে দেখা |
Cache বা search-এ 0.0.0.0/0 খোলা | Data ফাঁসের ঝুঁকি | শুধু app-এর security group থেকে অনুমতি |
Policy-তে dynamodb:* বা Resource: "*" | অতিরিক্ত অনুমতি | Least privilege, নির্দিষ্ট table ও action |
| Connection string-এ password বসানো | Secret Git-এ চলে যায় | Secret manager (পর্ব ৩৩) |
| বানানো service limit বা CLI flag | ভুল capacity পরিকল্পনা, error | Provider-এর docs আর --help |
SELECT * আর “LIMIT দিলে সস্তা” | Warehouse-এর বিল | আগে dry run |
সতর্কতা ছাড়া FLUSHALL বা table মোছা | Cold cache, data হারানো | আগে ফল বোঝা, পরীক্ষা পরিবেশে চালানো |
AI-কে সমাধান চাওয়ার আগে নিজের হিসাবগুলো দিন, আর তাকে অনুমান প্রকাশ করতে বলুন। তখন সে আপনাকে একটি পছন্দ নয়, তুলনা দেবে।
AI-এর পরামর্শ হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১DynamoDB-র মতো service join আর ইচ্ছামতো query সরিয়ে দিল কেন? এতে আসলে কী কেনা হলো?
গল্পের শুরু Amazon-এর ভেতরের একটি পর্যবেক্ষণ থেকে। তাদের Dynamo paper (SOSP ২০০৭) জানায়, অনেক সেবা শুধু primary key দিয়েই data পড়ত, যেমন shopping cart. Relational database-এর join আর জটিল query সেখানে অব্যবহৃত থাকলেও সেগুলোর দাম দিতে হতো: কঠিন scale আর অনিশ্চিত latency. ২০০৭-এর সেই ব্যবস্থা আর ২০১২-র DynamoDB সার্ভিস এক জিনিস নয়, কিন্তু মূল শিক্ষা থেকে গেছে।
একটি ব্যবস্থা যদি প্রতিটি অনুরোধকে একটি partition-এ পাঠাতে পারে, তাহলে latency table-এর আকারের ওপর প্রায় নির্ভর করে না। ১ GB আর ১০০ TB-তে key দিয়ে খোঁজা প্রায় সমান সময় নেয়। Join থাকলে এই নিশ্চয়তা ভাঙে, কারণ একটি query অনেক partition ছুঁতে পারে, আর সবচেয়ে ধীর partition পুরো উত্তরকে আটকায়।
তাই এখানে কেনা হলো পূর্বানুমানযোগ্যতা, দেওয়া হলো নমনীয়তা। দাম আসে পরে। নতুন একটি প্রশ্ন মানে নতুন index বা data-র পুনর্গঠন। Relational-এ যে report একটি SQL লাইনে হতো, এখানে তার জন্য warehouse-এ data পাঠাতে হয়। Single-table design-এর মতো কৌশল নিপুণ, কিন্তু নতুন সহকর্মীর পক্ষে পড়া কঠিন।
অনেক দল ভিন্ন পথেও সমাধান পেয়েছে। Spanner (২০১২ সালের paper), এবং তার পরের distributed SQL ব্যবস্থাগুলো দেখায়, SQL আর horizontal scale একসাথে সম্ভব, যদিও latency আর জটিলতার দাম দিতে হয়। অর্থাৎ “NoSQL না হলে scale হয় না” কথাটি ঠিক নয়। আবার ছোট app-এ শুরুতেই DynamoDB-র মতো কঠোর নকশা নিলে অদরকারি সীমায় বাঁধা পড়তে পারেন। যে দলের পরের মাসের প্রশ্নগুলোই এখনো অজানা, তাদের জন্য নমনীয় SQL প্রায়ই নিরাপদ শুরু।
মূল উপলব্ধি: প্রতিটি সীমা আসলে একটি নিশ্চয়তার দাম। আপনার প্রশ্নগুলো স্থির আর traffic বিশাল হলে এই বিনিময় ভালো; প্রশ্ন প্রতিদিন বদলালে নয়।
প্র ০২Provider কেন partition-প্রতি সীমা রাখে? পুরো table-এর capacity এক জায়গায় ব্যবহার করতে দিলে সমস্যা কী?
কারণ partition একটি কাল্পনিক ধারণা নয়, এটি বাস্তব যন্ত্রের একটি ভাগ। একটি partition কয়েকটি storage node-এ থাকে, আর সেই node-এ অন্য গ্রাহকের partition-ও থাকতে পারে। এক partition-কে সীমাহীন চলতে দিলে পাশের গ্রাহক ধীর হবেন। এটাই noisy neighbour সমস্যা। Multi-tenant ব্যবস্থায় সীমা ছাড়া কোনো প্রতিশ্রুতি রাখা যায় না।
২০২২ সালের USENIX ATC paper-এ DynamoDB-র প্রকৌশলীরা একটি শিক্ষণীয় ইতিহাস লিখেছেন। শুরুতে table-এর capacity সব partition-এ সমান ভাগ হতো। বাস্তবে ভার কখনো সমান নয়। ফলে গ্রাহকের table-এর বাকি অংশ ফাঁকা থাকলেও ব্যস্ত অংশ throttle হতো, আর partition ভাগ হলে প্রতিটির ভাগের capacity আরও কমে যেত। Paper-টি একে “throughput dilution”-এর মতো সমস্যা হিসেবে আলোচনা করে। পরে burst, adaptive capacity, আর শেষে table-স্তরের global admission control এসে সমস্যা অনেকটা কমায়, আর ব্যস্ততা দেখে partition ভাগ করার ব্যবস্থাও যোগ হয়।
তবু একটি সীমা ভৌত। এক partition-এর সর্বোচ্চ গতি শেষ পর্যন্ত একটি leader-এর write path-এ আটকায়। Key একটিই হলে ভাগ করার পথও থাকে না, কারণ একটি item-কে দুই ভাগ করা যায় না। তাই provider যত চালাকই হোক, একটি মাত্র hot key-এর দায় নকশাকারীর।
এর সাথে পর্ব ৪-এর statistical multiplexing-এর মিল আছে। Cloud ব্যবসা দাঁড়িয়ে আছে এই ধারণায় যে সবাই একসাথে সর্বোচ্চ ব্যবহার করবেন না। Partition-সীমা সেই ধারণার রক্ষাকবচ। তাই সীমাটিকে শাস্তি হিসেবে না দেখে ন্যায্য ভাগাভাগির নিয়ম হিসেবে দেখা ভালো। আপনার কাজ হলো ভার এমনভাবে ছড়ানো, যাতে সেই নিয়ম আপনার পথে না আসে।
মূল উপলব্ধি: “মোট capacity” একটি বিলের সংখ্যা, “partition-এর সীমা” একটি পদার্থবিদ্যার সংখ্যা। নকশা করতে হয় দ্বিতীয়টির ওপর।
প্র ০৩“Cache শুধু গতি বাড়ায়, পড়ে গেলেও system চলবে” — এই কথাটি কেন বিপজ্জনক?
কথাটি প্রথম দিনে সত্য, এক বছর পরে নয়। Traffic বাড়তে বাড়তে database-এর আকার আর বাড়ানো হয় না, কারণ cache তো ভার নিচ্ছেই। একসময় database একা পুরো ভার নেওয়ার ক্ষমতা হারায়। কেউ সেই সিদ্ধান্ত নেয়নি; এটি নিঃশব্দে ঘটে গেছে। Cache তখন আর ঐচ্ছিক গতি নয়, capacity-র অংশ।
২০১০ সালের সেপ্টেম্বরে Facebook-এর প্রায় আড়াই ঘণ্টার একটি বিভ্রাট এই বিপদের বিখ্যাত উদাহরণ। তাদের প্রকাশিত ব্যাখ্যা অনুযায়ী, একটি স্বয়ংক্রিয় configuration যাচাই ব্যবস্থা ভুল মান দেখে সেটি ঠিক করতে database cluster-এ প্রশ্ন পাঠাচ্ছিল। ভুল মান সব client-এ ছড়িয়ে পড়ায় অসংখ্য প্রশ্ন গিয়ে database-কে ডুবিয়ে দেয়। ব্যর্থ query-গুলো আবার cache-এর মান মুছে আরও প্রশ্ন ডেকে আনছিল, ফলে একটি feedback loop তৈরি হয়। শেষে সাইটে traffic বন্ধ করে ব্যবস্থাকে শান্ত হতে দিতে হয়েছিল।
২০১৩ সালের “Scaling Memcache” paper-এ Facebook-এর প্রকৌশলীরা এই ধরনের ঝুঁকির প্রতিকার লেখেন। Lease ব্যবস্থা একই key-র জন্য একসাথে অনেক database প্রশ্ন আটকায়। Gutter pool নামের অল্প কিছু বাড়তি server নষ্ট node-এর ভার সাময়িকভাবে ধরে। নতুন cluster-কে সরাসরি database-এ না পাঠিয়ে আগে থেকে উষ্ণ cluster থেকে data আনিয়ে “cold cluster warmup” করা হয়।
আমাদের জন্য ব্যবহারিক প্রশ্নটি হলো: cache পুরো খালি হলে database-এ কত query যাবে, আর সে কত নিতে পারে? দুটি সংখ্যা আলাদা হলে মাঝখানের ব্যবধানের জন্য একটি পরিকল্পনা লাগবে। Replica, সীমা, আংশিক সেবা, নিয়ন্ত্রিত warm-up, সবই সেই পরিকল্পনার অংশ। আর এটি কাগজে নয়, পরীক্ষায় যাচাই করতে হবে (পর্ব ৬৮)।
মূল উপলব্ধি: যে জিনিস সরালে system পড়ে যায়, সেটি ঐচ্ছিক নয়, যে নামেই ডাকুন। Cache-কে database-এর মতো গুরুত্ব দিয়ে নকশা ও পর্যবেক্ষণ করুন।
প্র ০৪Redis, Elasticsearch আর Terraform-এর লাইসেন্স বদল একজন managed service ক্রেতার জন্য কী অর্থ বহন করে?
টানাপোড়েনটি অর্থনৈতিক। একটি প্রতিষ্ঠান প্রকৌশলী রেখে open source প্রকল্প বানায়, আর আয় করে নিজস্ব managed সেবা থেকে। বড় cloud provider একই code নিয়ে সেবা বিক্রি করতে পারে, যা তাদের বিতরণ-ক্ষমতায় অনেক দূর পৌঁছায়। MongoDB ২০১৮-তে SSPL আনে। Elastic ২০২১-এ একই পথে যায়, আর AWS সেই code থেকে OpenSearch শুরু করে। HashiCorp ২০২৩-এ Terraform-কে BSL-এ নেয়, আর OpenTofu জন্ম নেয়। Redis ২০২৪-এ একই সিদ্ধান্ত নেয়, আর Valkey আসে। পরে Elastic আর Redis দুজনই AGPLv3 বিকল্প যোগ করেছে বলে ঘোষণা দেয়, যা দেখায় এই গল্প এখনো চলছে।
একজন managed service ক্রেতা হিসেবে আপনি সরাসরি হয়তো লাইসেন্সের বাধায় পড়বেন না, কারণ সেবাটি provider-ই চালায়। প্রভাব পড়ে অন্য জায়গায়। এক, API-র মিল চিরকাল থাকবে এমন কথা নেই। Fork আর মূল প্রকল্প সময়ের সাথে আলাদা সুবিধা পায়, তাই একটি নতুন feature-এ নির্ভর করলে ফেরার পথ কঠিন হতে পারে। দুই, “Redis-compatible” কথাটি পুরো মিল বোঝায় না; version আর module-এর তালিকা মিলিয়ে দেখতে হয়। তিন, নিজে self-host করে সেবা বিক্রি করলে শর্তগুলো আপনাকেই পড়তে হবে, আর সেখানে আইনি পরামর্শ জরুরি।
আরেকটি দিক বিল। Provider অনেক সময় নিজের সমর্থিত engine-এ কম দাম দেয়, ফলে migration একটি আর্থিক প্রশ্নও হয়ে ওঠে। তবে শুধু দামের জন্য engine বদলানোর আগে পরীক্ষা, failover আর client library-র আচরণ যাচাই করা দরকার।
ব্যবহারিক সুরক্ষা সহজ। সাধারণ, বহুল সমর্থিত command-এর মধ্যে থাকুন। Client-কে একটি পাতলা স্তরের পেছনে রাখুন। আর infrastructure-এ engine ও version স্পষ্টভাবে বেঁধে রাখুন। Vendor lock-in নিয়ে বড় আলোচনা পর্ব ৭৪-এ।
মূল উপলব্ধি: লাইসেন্স একটি dependency-র অংশ। কোড যেমন আপডেট হয়, শর্তও হয়। তাই “এখন কী চলছে” এর সাথে “বদলে গেলে ফিরব কীভাবে” প্রশ্নটিও রাখুন।
প্র ০৫Warehouse-এ storage আর compute আলাদা হলো কেন? এতে pay-per-scan আর capacity দাম নিয়ে সিদ্ধান্ত কীভাবে বদলায়?
পুরোনো warehouse-এ data আর CPU একই যন্ত্রে থাকত। Data বাড়লে compute-ও কিনতে হতো, অথচ অনেকের দরকার শুধু বেশি জায়গা। আবার বছর-শেষের ভারী রিপোর্টের জন্য বাড়তি compute কিনলে বাকি বছর তা অলস পড়ে থাকত। Object storage সস্তা ও টেকসই হওয়ার পর ধারণাটি বদলাল: data এক জায়গায় থাকুক, আর query-র সময় যত দরকার তত compute আসুক।
তাত্ত্বিক ধাপগুলো আগেই তৈরি হচ্ছিল। C-Store (২০০৫) দেখায়, column ধরে রাখা বিশ্লেষণে কত শক্তিশালী। Google-এর Dremel paper (২০১০) দেখায়, হাজারো যন্ত্রে ছড়িয়ে column পড়ে কয়েক সেকেন্ডে বিশাল data-র ওপর প্রশ্ন করা যায়; BigQuery এই ধারার বাণিজ্যিক রূপ। Snowflake-এর ২০১৬ সালের SIGMOD paper storage আর compute আলাদা করার নকশাটি স্পষ্টভাবে বর্ণনা করে। Redshift-ও পরে নিজের storage আলাদা করার পথ নেয়।
এই আলাদা হওয়া থেকেই pay-per-scan সম্ভব হলো। কম্পিউটার চালু রাখার দাম নয়, প্রতিটি প্রশ্নের দাম। সুবিধা বড়: মাসে কয়েকটি query-র জন্য কোনো cluster চালু রাখতে হয় না। ঝুঁকিও বড়: একজন analyst-এর একটি অসাবধান SELECT * বা একটি ভুল dashboard, যা প্রতি মিনিটে refresh হয়, সরাসরি বিল বাড়ায়। Capacity-তে উল্টো: খরচ অনুমান করা যায়, কিন্তু অলস সময়ের দামও দিতে হয়, আর ভিড়ে query লাইনে দাঁড়ায়।
সিদ্ধান্তটি হিসাবের। ধরুন capacity-র মাসিক দাম F, আর গড় scan-এর দাম C. তাহলে break-even হলো F ÷ C query. এর নিচে pay-per-scan, ওপরে capacity যুক্তিসঙ্গত। তবে দামের সাথে দরকার governance: query-প্রতি সীমা, দলভিত্তিক quota, tag দিয়ে বিল ভাগ করা, আর ব্যয়বহুল query-র নিয়মিত তালিকা। FinOps-এ এই কাজ বিস্তারিত দেখবেন (পর্ব ৭৩)।
মূল উপলব্ধি: বিলের কাঠামো আচরণ ঠিক করে দেয়। Scan-এ বিল হলে byte কমানো দক্ষতা; capacity-তে বিল হলে অলস সময় কমানো দক্ষতা। আগে বুঝুন আপনি কোন খেলায় আছেন।
অনুশীলন
খাতা-কলম নিয়ে বসুন। কোনো cloud account লাগবে না।
-
Capacity হিসাব: প্রতি সেকেন্ডে ৩০০টি ২.৫ KB item লেখা হয়, আর সব attribute কপি করা একটি secondary index আছে। প্রতি সেকেন্ডে ৮০০টি ৬ KB item eventually consistent পড়া হয়। DynamoDB ধরনের নিয়মে কত WCU আর RCU লাগবে? লেখা সমানভাবে ছড়ালেও table-এ কমপক্ষে কয়টি partition লাগবে (প্রতি partition ১,০০০ WCU, index-এর লেখা বাদে)?
লেখা: ২.৫ KB ওপরে গোল হয়ে ৩ WCU. Table-এ ৩০০ × ৩ = ৯০০ WCU, index-এ আরও ৯০০, মোট ১,৮০০ WCU.
পড়া: ৬ ÷ ৪ = ১.৫, গোল করে ২ ইউনিট; eventually consistent-এ অর্ধেক, ১ RCU. মোট ৮০০ × ১ = ৮০০ RCU.
Partition: table-এর ৯০০ WCU একটি partition-এই আঁটে (৯০০ ÷ ১,০০০), তাই ন্যূনতম ১টি। Index আলাদা একটি table-এর মতো, তার হিসাব আলাদা। তবে ৯০% ব্যবহারে headroom প্রায় নেই, তাই বাস্তবে আরও partition থাকা ভালো, আর key-র বণ্টন দেখতে হবে।
-
ফল আগে বলুন: একটি LRU cache-এ ৩টি key-র জায়গা আছে, আর সে cache-aside মেনে চলে। ক্রমটি হলো:
SET A,SET B,SET C,GET A,SET D,GET B,GET C. Miss হলে database থেকে এনে key-টি cache-এ বসে। শেষে cache-এ কোন key থাকবে, আর ৩টি GET-এর ফল কী?পুরোনো থেকে নতুন ক্রমে দেখি। তিনটি SET-এর পর
A B C.GET Ahit, ক্রমB C A.SET D-তে সবচেয়ে পুরোনো B বের হলো:C A D.GET Bmiss; B আসে, C বের হয়:A D B.GET Cmiss; C আসে, A বের হয়:D B C.শেষ অবস্থা: D, B, C. ফল: ১টি hit, ২টি miss. লক্ষ করুন, A এইমাত্র hit হয়েও পরে বের হলো, কারণ পরপর দুটি miss জায়গা নিয়েছে।
-
নকশা সমালোচনা: একটি কাল্পনিক টিকিট অ্যাপে AI এই নকশা দিল: table
tickets, partition keyevent_date, sort keyticket_id. “কোন গ্রাহকের কী টিকিট” দেখাতে কোডেScanআর filter. সবচেয়ে বড় ইভেন্টে টিকিট ছাড়লেই সাইট ধীর হয়। সমস্যাগুলো ধরুন আর একটি ভালো নকশা দিন।- Hot partition: বড় ইভেন্টের দিনের সব লেখা একটি partition-এ জমে। মোট capacity বাড়ালেও সমস্যা পুরো মিটবে না।
- Scan: প্রতিটি “আমার টিকিট” পুরো table পড়ে, যার বিল আর latency data-র সাথে বাড়ে।
- ভালো নকশা: মূল key
ticket_id. Secondary index-এcustomer_id(sort key তারিখ)। ইভেন্টের তালিকা দরকার হলেevent_id#bucketদিয়ে ছড়ানো। - ভিড় সামলানো: আসন ধরে রাখার মতো সংবেদনশীল কাজে শর্তযুক্ত লেখা বা transaction, ইভেন্ট-তথ্যে cache, আর চাহিদা সামলাতে queue বা অপেক্ষার ঘর। টিকিটের নিশ্চয়তা বেশি জরুরি হলে relational store-ও বিবেচনার যোগ্য।
-
Cache-এর ঝুঁকি হিসাব: ২০,০০০ অনুরোধ/সেকেন্ড, database-এর সীমা ৩,০০০ query/সেকেন্ড, cache-এ ৫টি সমান shard, hit ratio ৯৭%. (ক) নিরাপদ থাকতে ন্যূনতম hit ratio কত? (খ) replica ছাড়া একটি shard মারা গেলে কী হবে?
(ক) (১ − h) × ২০,০০০ ≤ ৩,০০০, তাই ১ − h ≤ ০.১৫, অর্থাৎ h ≥ ৮৫%.
(খ) নতুন hit ratio ০.৯৭ × ০.৮ = ০.৭৭৬, অর্থাৎ ৭৭.৬%. Database-এ ০.২২৪ × ২০,০০০ = ৪,৪৮০ query, সীমার প্রায় ১৪৯%, আর প্রতি সেকেন্ডে ১,৪৮০টি বাড়তি। একটি shard হারালেই database ডোবে। সমাধান: shard-এ replica, অথবা shard বাড়ানো। n shard-এর একটি হারালে মোট hit হবে ০.৯৭ × (n − ১) ÷ n. ০.৮৫-এর ওপরে রাখতে n ≥ ৯ লাগবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমনতুন managed data store বেছে নেওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- App-এর access pattern আর প্রতিটির peak হার লেখা
- Query shape দেখে service-এর ধরন ঠিক করা
- Consistency আর partition key-র নকশা যাচাই করা
- Peak ধরে capacity আর মাসিক বিল হিসাব করা
- Backup, alert, permission আর data sync-এর পরিকল্পনা করা
একটি table-এ ৪টি partition, প্রতিটির সীমা ১,০০০ WCU/সেকেন্ড, মোট ৪,০০০। একটি মাত্র key-তে ১,৫০০ লেখা/সেকেন্ড আসছে, আর সব মিলিয়ে traffic ২,৫০০ লেখা/সেকেন্ড। কী ঘটবে?
- কিছুই হবে না, মোট capacity যথেষ্ট
- ওই key-র partition-এ throttling হবে
- সব partition-এর সব লেখা বন্ধ হবে
- বাড়তি লেখা নিজে থেকে অন্য partition-এ চলে যাবে
DynamoDB ধরনের হিসাবে ১ WCU মানে ১ KB পর্যন্ত একটি সাধারণ লেখা। ৩.২ KB-র একটি item লিখতে কত WCU লাগবে (কোনো index নেই)?
- ১ WCU
- ৩ WCU
- ৪ WCU
- ৮ WCU
Cache-aside ব্যবস্থায় cache-এর সময় ২ ms, database-এর সময় ৩০ ms, hit ratio ৯০%. Miss-এও আগে cache দেখা হয়। গড় latency কত?
- ৫ ms
- ২ ms
- ২৭ ms
- ৩২ ms
মোট অনুরোধ একই থাকলে cache hit ratio ৯৯% থেকে ৯৮%-এ নামলে database-এর ভারের কী হয়?
- ১% বাড়ে
- অর্ধেক হয়
- প্রায় একই থাকে
- দ্বিগুণ হয়
পড়া byte-এ বিল হয় এমন একটি columnar warehouse-এ, ১,০০০ GB-র partition না করা table-এ SELECT * FROM orders LIMIT 10 চালানো হলো। সাধারণত কত data-র দাম দিতে হবে?
- শুধু ১০ row-এর
- প্রায় পুরো ১,০০০ GB-র
- একটি column-এর আকারের
- কিছুরই নয়
Valkey সম্পর্কে কোন কথাটি ঠিক?
- এটি একটি provider-এর closed-source database
- এটি একটি relational data warehouse
- এটি Redis-এর শেষ BSD version থেকে তৈরি fork
- এটি Elasticsearch-এর search fork
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের শব্দগুলো এখানে একসাথে:
মনে রাখুন
- Service বাছাই শুরু হয় query shape থেকে, জনপ্রিয়তা থেকে নয়।
- NoSQL-এ আগে access pattern, পরে key. সীমা বসে partition-এ, মোট table-এ নয়।
- গড় latency = Tc + (1 − h) × Tdb, আর DB-র ভার = (1 − h) × মোট। Cache পড়লে কী হবে, তার হিসাব রাখুন।
- Warehouse-এ column pruning আর partition pruning সরাসরি বিল কমায়;
LIMITসাধারণত কমায় না। - প্রতিটি নতুন store-এর সাথে আসে নতুন operational burden. কম store দিয়ে শুরু করুন, মেপে যোগ করুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৪ · Backup আর disaster recovery — RPO, RTO, snapshot আর চারটি কৌশলপরবর্তী পর্বData store হারালে কত data আর কত সময় হারাবেন, তার হিসাব।
- ক্লাউড কম্পিউটিং ও DevOps · কোর্স হোম৮১টি পর্বপুরো কোর্সের রোডম্যাপ।
- System Design · Distributed cacheগভীরে যানCache-এর partitioning, replication, hot key আর বহু-স্তরের নকশা।
- System Design · Partitioning আর shardingগভীরে যানHash, range, hot spot আর rebalancing-এর তত্ত্ব।
- DBMS · Data warehouseপাশের ট্র্যাকOLAP, star schema আর ETL.
- DBMS · Redisপাশের ট্র্যাকData structure, লাইসেন্স আর cache-এর নকশা।