এই পর্বে যা শিখবেন
- একটি বড় কাজকে requirement, নকশা আর module-এ ভাগ করার পদ্ধতি
- মিনি key-value ডেটাবেসের পূর্ণ নকশা — protocol, header, মেমরির মালিকানা, error-এর নিয়ম
- MVP থেকে শুরু করে ধাপে ধাপে persistence, network ও concurrency যোগ করার পরিকল্পনা
- Test, sanitizer আর মাপজোখ দিয়ে নিজের সিস্টেমকে প্রমাণ করা
- AI-কে টিমমেট বানানো, কাজ GitHub-এ সাজিয়ে দেখানো, আর কোর্স শেষে পরের পথ বেছে নেওয়া
১ · প্রোগ্রাম লেখা থেকে সিস্টেম বানানো
অভিনন্দন — আপনি কোর্সের ক্যাপস্টোনে পৌঁছেছেন। গত ৫১টি পর্বে আপনি একটি একটি করে যন্ত্র হাতে পেয়েছেন: ভ্যারিয়েবল, লুপ, পয়েন্টার, ফাইল, hash table, Big-O, process, thread, socket. এবার প্রশ্ন একটাই — এই যন্ত্রগুলো একসাথে জুড়ে কি এমন কিছু বানাতে পারবেন, যা সত্যিই কেউ ব্যবহার করতে পারে?
এই কাজের নাম capstone প্রজেক্ট। ইমারতের খিলানের একদম মাথার পাথরটি সব পাথরকে একসাথে ধরে রাখে — সেখান থেকেই নামটি এসেছে। কোর্সের শেষ প্রজেক্টও ঠিক তাই করে: আলাদা আলাদা শেখা জিনিসকে একটি পূর্ণ সিস্টেমে বাঁধে।
একজন রাজমিস্ত্রি ইট গাঁথতে জানেন, একজন ইলেকট্রিশিয়ান তার টানতে জানেন। কিন্তু একটি পাঁচতলা বাড়ি বানাতে শুধু ইট গাঁথা জানলে চলে না। আগে লাগে নকশা — কয়টি ঘর, সিঁড়ি কোথায়, পানির লাইন কোন দেয়ালে। তারপর ভিত্তি, তারপর এক তলা এক তলা করে ওঠা, আর প্রতি তলায় পরীক্ষা। প্রোগ্রাম লেখা হলো ইট গাঁথা; সিস্টেম বানানো হলো পুরো বাড়ি।
পেশাদার সফটওয়্যার প্রায় সবসময় একটি চক্র মেনে তৈরি হয়। নিচের ছবিটি মনে গেঁথে রাখুন — এই পর্বের বাকি সব অংশ এই চক্রেরই একেকটি ধাপ।
একটি সিস্টেম তৈরির সাত ধাপ
From requirements to documentation
- চাহিদা
requirementsকী করবে - নকশা
designকীভাবে করবে - Module
*.hকে কোন কাজ - Build
MVP → …ছোট ধাপে - Test ও মাপ
make testপ্রমাণ - Document
README.mdঅন্যকে বোঝানো
| ধাপ | যে প্রশ্নের উত্তর দেয় | ধাপ শেষে হাতে যা থাকে |
|---|---|---|
| Requirement | ব্যবহারকারী ঠিক কী চান? কী করবে না? | এক পাতার লিখিত চাহিদা |
| নকশা (architecture) | কোন কোন অংশ লাগবে, তারা কীভাবে কথা বলবে? | একটি ব্লক-ডায়াগ্রাম |
| Module ও interface | প্রতিটি অংশের দায়িত্ব আর ফাংশনগুলোর চেহারা কী? | .h ফাইলগুলো |
| ছোট ধাপে build | সবচেয়ে ছোট কোন সংস্করণ আজই চালানো যায়? | চলে এমন MVP, git-এ সংরক্ষিত |
| Test ও মাপজোখ | এটি যে ঠিক আর যথেষ্ট দ্রুত, তার প্রমাণ কী? | make test সবুজ, গতির সংখ্যা |
| Document | অপরিচিত একজন কীভাবে চালাবেন ও বুঝবেন? | README, ডেমো |
নতুনরা সাধারণত প্রথম দিনেই কোড লেখা শুরু করেন, আর তৃতীয় দিনে আটকে যান — কারণ কী বানাচ্ছেন, সেটাই পরিষ্কার ছিল না। অভিজ্ঞরা প্রথম দিনের বড় অংশ কাটান লিখে আর এঁকে। নকশায় একটি ভুল ধরতে লাগে পাঁচ মিনিট; সেই একই ভুল ৩,০০০ লাইন কোডের ভেতরে ধরতে লাগে পাঁচ দিন।
২ · প্রজেক্ট বেছে নিন — চারটি ক্যাপস্টোন ট্র্যাক
ক্যাপস্টোনের জন্য আমরা চারটি ট্র্যাক প্রস্তাব করছি। প্রতিটিই C-এর জগতের ক্লাসিক প্রজেক্ট — বিশ্বের নামকরা বিশ্ববিদ্যালয়ের সিস্টেম কোর্সেও এগুলোই বারবার আসে। যেকোনো একটি বেছে নিন; এই পর্বে আমরা ট্র্যাক D (key-value store) শুরু থেকে শেষ পর্যন্ত বিস্তারিত দেখব, কারণ এর নকশার শিক্ষা বাকি তিনটিতেও হুবহু খাটে।
| ট্র্যাক | কী বানাবেন | কঠিনতা | যে পর্বগুলো লাগবে | মাইলস্টোন |
|---|---|---|---|---|
| A · মিনি shell | নিজের shell — কমান্ড পড়ে প্রোগ্রাম চালায় | মাঝারি | ১৯ (স্ট্রিং), ৩০, ৪৮ (fork, exec, pipe) | ১. একটি কমান্ড চালানো · ২. cd, exit · ৩. | pipe · ৪. > redirect |
| B · ক্যালকুলেটর / ছোট interpreter | 2 + 3 * (4 - 1) পড়ে ফল দেয়; পরে ভ্যারিয়েবল | মাঝারি | ১৯, ৩৪ (স্ট্যাক, postfix), ৩৫ (ট্রি) | ১. টোকেনে ভাঙা · ২. postfix-এ রূপান্তর · ৩. syntax tree · ৪. x = 5 |
| C · ছোট HTTP server | ব্রাউজার থেকে খোলা যায় এমন static ফাইল সার্ভার | কঠিন | ২৪ (ফাইল), ৩০, ৪৯, ৫০ (TCP) | ১. একটি স্থির পাতা · ২. ফাইল পাঠানো · ৩. 404 ও error · ৪. একসাথে অনেক client |
| D · মিনি ডেটাবেস (key-value store) | SET/GET/DEL বোঝে, ডেটা ফাইলে রাখে, network-এ সেবা দেয় | মাঝারি থেকে কঠিন | ২০, ২২, ২৪, ২৯, ৩০, ৩৭, ৩৯, ৪৪, ৪৮–৫০ | ১. MVP · ২. ফাইলে সংরক্ষণ · ৩. TCP · ৪. thread · ৫. test ও মাপ |
🐚 Shell বেছে নিন, যদি…
অপারেটিং সিস্টেম কীভাবে প্রোগ্রাম চালায়, সেটা হাতে-কলমে বুঝতে চান। ছোট কোডে অনেক গভীর শিক্ষা।
পরে: Operating Systems কোর্স🧮 Interpreter বেছে নিন, যদি…
Compiler আর programming language কীভাবে তৈরি হয়, সেই রহস্য খুলতে চান। স্ট্যাক আর ট্রির সবচেয়ে সুন্দর ব্যবহার।
পরে: Compiler Design কোর্স🌐 HTTP server বেছে নিন, যদি…
ওয়েব আসলে নিচে কীভাবে কাজ করে, জানতে চান। ব্রাউজারে নিজের সার্ভারের পাতা খোলার আনন্দ আলাদা।
পরে: Computer Networks কোর্স🗄️ Key-value store বেছে নিন, যদি…
ডেটা স্ট্রাকচার, ফাইল, network আর thread — সবকিছু একসাথে একটি প্রজেক্টে চান। চাকরির ইন্টারভিউতে বলার মতো গল্প।
পরে: System Design কোর্সকোন ট্র্যাক “সবচেয়ে ভালো”, সেটা নয় — কোনটি আপনি শেষ করতে পারবেন, সেটাই আসল। শেষ হওয়া একটি মাঝারি প্রজেক্ট অর্ধেক থেমে থাকা একটি বিশাল প্রজেক্টের চেয়ে অনেক বেশি মূল্যবান। প্রথমবার হলে সময় ধরুন ৩–৬ সপ্তাহ, প্রতিদিন এক-দুই ঘণ্টা।
৩ · মূল উদাহরণ: মিনি ডেটাবেস — চাহিদা আর নকশা
ধরুন আপনার এলাকার একটি কোচিং সেন্টার ছাত্রদের রোল আর নাম দ্রুত খুঁজতে চায়। কয়েকটি কম্পিউটার থেকে একসাথে তথ্য রাখা ও খোঁজা যাবে, আর বিদ্যুৎ চলে গেলেও তথ্য হারাবে না। আমরা বানাব একটি key-value store — নাম দিলাম “kvdb”. key হবে রোল (roll42), value হবে নাম (Rafi).
ধাপ ১ — চাহিদা লিখে ফেলুন
কোড লেখার আগে এক পাতায় specification লিখুন। এতে থাকবে কী করবে, কী করবে না, আর সীমা কত। “করবে না”-এর তালিকাটি সবচেয়ে গুরুত্বপূর্ণ — এটাই আপনাকে scope creep থেকে বাঁচায়।
✅ করবে
SET key value, GET key, DEL key. key সর্বোচ্চ ৩১ অক্ষর, value সর্বোচ্চ ২৫৫ অক্ষর, ফাঁকা জায়গা ছাড়া। ডেটা ফাইলে থাকবে, আবার চালু করলে ফিরে আসবে। একসাথে কয়েকটি client.
⛔ করবে না (এই সংস্করণে)
SQL নেই, পাসওয়ার্ড বা encryption নেই, একাধিক সার্ভারে ভাগ করা নেই, value-তে ফাঁকা জায়গা নেই। এগুলো “পরে হয়তো” তালিকায় থাকবে।
ধাপ ২ — Protocol ঠিক করুন
Client আর server কোন ভাষায় কথা বলবে, সেই নিয়মের নাম protocol. আমরা বেছে নিচ্ছি সবচেয়ে সহজটি — প্রতি লাইনে একটি কমান্ড, লাইনের শেষে \n. এর বড় সুবিধা: nc localhost 7000 দিয়ে হাতে লিখেই সার্ভার পরীক্ষা করা যায়। Redis আর memcached-এর মতো বিখ্যাত সিস্টেমও মানুষের পড়ার মতো text কমান্ড বোঝে।
| Client পাঠায় | Server-এর উত্তর | অর্থ |
|---|---|---|
SET roll42 Rafi | OK | রাখা হলো (আগে থাকলে নতুন মান বসল) |
GET roll42 | Rafi অথবা (nil) | মান পাওয়া গেল / এই key নেই |
DEL roll42 | OK অথবা (nil) | মুছে ফেলা হলো / আগে থেকেই নেই |
| অন্য কিছু, বা খুব লম্বা key | ERR ... | কারণসহ ভুলের বার্তা — সার্ভার কখনো crash করবে না |
ধাপ ৩ — নকশা: একটি অনুরোধের যাত্রা
এবার দেখা যাক একটি কমান্ড সিস্টেমের ভেতরে কোন কোন অংশ পার হয়। প্রতিটি অংশ আলাদা একটি module, আর প্রতিটির কাজ একটাই। “পরে” চাপুন:
- Client
ncলাইন পাঠায় - net.c
read_line()বাইট থেকে লাইন - parse.c
parse_line()লাইন থেকে struct - Hash
hash(key)কোন bucket - kv.c
kv_set()কপি করে রাখা - Reply
"OK"উত্তর ফেরত
$ nc localhost 7000 SET roll42 Rafi ← Enter চাপলেই "\n" যুক্ত হয়
recv() #1: "SET rol" recv() #2: "l42 Rafi\n" লাইন সম্পূর্ণ: "SET roll42 Rafi"
struct request {
cmd = CMD_SET
key = "roll42" /* 6 অক্ষর, সীমা 31 */
value = "Rafi" /* 4 অক্ষর, সীমা 255 */
}
hash("roll42") % 16 → bucket 11 (উদাহরণ মান)
bucket 11: [ "roll7" → "Mitu" ] ← আগে থেকেই একজন আছে
bucket 11: [ "roll42" → "Rafi" ] → [ "roll7" → "Mitu" ] store নিজের জন্য key ও value কপি করে রাখল (malloc)
$ nc localhost 7000 SET roll42 Rafi OK GET roll42 Rafi
- Client লাইন পাঠায়। একজন শিক্ষক কোচিংয়ের কম্পিউটার থেকে লিখলেন
SET roll42 Rafi. Client-এর কাছে এটি একটি লাইন; network-এর কাছে এটি কেবল কয়েকটি বাইট। - net.c বাইট জমিয়ে লাইন বানায়। পর্ব ৫০-এর শিক্ষা মনে করুন — TCP “বার্তা” চেনে না, শুধু বাইটের স্রোত দেয়। একবারের
recv()-এ অর্ধেক লাইন আসতেই পারে। তাই net module\nনা পাওয়া পর্যন্ত বাইট জমায়। এই নিয়ম না মানলে সিস্টেম ল্যাবে চলবে, আসল network-এ মাঝে মাঝে ভুল করবে। - parse.c লাইনকে struct বানায় ও যাচাই করে। কমান্ড চেনা, key আর value আলাদা করা, দৈর্ঘ্যের সীমা পরীক্ষা — সব এখানে। পর্ব ৩০-এর নিয়ম: বাইরের ইনপুট কখনো বিশ্বাস করবেন না। ভুল হলে এখান থেকেই
ERRফেরত যায়, store পর্যন্ত পৌঁছায়ই না। - Hash ঠিক করে কোন bucket. পর্ব ৩৭-এর hash table এখানে কাজ করছে। key থেকে একটি সংখ্যা হিসাব করে bucket বেছে নেওয়া হয় — গড়ে O(1) সময়ে। ছবির bucket 11 শুধু উদাহরণ; আসল নম্বর নির্ভর করে কোন hash function ব্যবহার করছেন তার ওপর। এই bucket-এ আগে থেকেই একজন আছে — একে বলে collision.
- kv.c কপি করে রাখে। Store নিজের জন্য key আর value-এর কপি বানায় (
malloc), কারণ net.c-এর buffer পরের লাইনেই বদলে যাবে। এখন থেকে এই কপির মালিক store — মুছবেও store. Collision সামলানো হয়েছে chaining দিয়ে। - উত্তর ফেরত যায়।
OK\nপাঠানো হলো। পুরো পথে খেয়াল করুন: প্রতিটি module অন্যটির ভেতরের খবর জানে না, শুধু interface জানে। তাই কাল চাইলে hash table বদলে tree বসাতে পারবেন — net.c বা parse.c-এর একটি লাইনও বদলাতে হবে না।
Module একে অপরকে চেনে শুধু header দিয়ে।
৪ · Interface আগে — header-ই হলো চুক্তি
পর্ব ২৯-এ দেখেছিলেন, header file হলো একটি module-এর বাইরের চেহারা — সে কোন কোন ফাংশন দেয়, আর কীভাবে ডাকতে হয়। বড় সিস্টেমে header আরও বড় ভূমিকা নেয় — এটি দুই দলের মধ্যে একটি লিখিত চুক্তি। আপনি, আপনার বন্ধু আর AI — তিনজন আলাদা module লিখলেও header ঠিক থাকলে শেষে সব জোড়া লাগবে। তাই নিয়ম হলো: কোড লেখার আগে header লিখুন।
/* kv.h — the store's contract. Nothing outside kv.c sees the struct. */
#ifndef KV_H
#define KV_H
#include <stddef.h>
typedef struct kv kv; /* opaque: fields hidden in kv.c */
enum kv_status { KV_OK = 0, KV_NOT_FOUND, KV_NO_MEMORY, KV_TOO_LONG };
kv *kv_open(size_t buckets); /* NULL on failure */
void kv_close(kv *db); /* frees everything, NULL is fine */
/* Copies key and value. The caller keeps ownership of its strings. */
enum kv_status kv_set(kv *db, const char *key, const char *value);
/* On success *out points INSIDE the store: read it, do not free it,
and do not keep it after the next kv_set/kv_del on the same key. */
enum kv_status kv_get(const kv *db, const char *key, const char **out);
enum kv_status kv_del(kv *db, const char *key);
size_t kv_count(const kv *db);
#endifএই কয়েকটি লাইনে অনেকগুলো নকশার সিদ্ধান্ত লুকিয়ে আছে। প্রতিটি আপনি আগের কোনো পর্বে শিখেছেন:
| সিদ্ধান্ত | কেন | কোন পর্ব |
|---|---|---|
typedef struct kv kv; — ভেতরটা লুকানো | বাইরের কেউ field-এ হাত দিতে পারবে না; ভেতরের নকশা বদলালেও বাকিদের কিছু বদলাতে হবে না | ২৯ (opaque pointer) |
enum kv_status ফেরত দেওয়া | প্রতিটি ব্যর্থতার আলাদা নাম; caller জানে ঠিক কী ভুল হলো | ৩০ (return code) |
const char * প্যারামিটার | প্রতিশ্রুতি: store আপনার string বদলাবে না | ৪৫ (const) |
| “Copies key and value” মন্তব্য | মেমরির মালিক কে, লিখিতভাবে পরিষ্কার | ২০ (dynamic memory) |
kv_close(NULL) নিরাপদ | free(NULL)-এর মতো আচরণ — cleanup কোড সহজ হয় | ৩০ |
মেমরির মালিকানার তিন নিয়ম
C-তে বড় সিস্টেমের বেশিরভাগ crash আসে একটি প্রশ্নের অস্পষ্ট উত্তর থেকে — “এই মেমরি free করবে কে?” উত্তরটি header-এ লিখে রাখুন, আর পুরো প্রজেক্টে একই নিয়ম মানুন:
- যে
mallocকরে, সে-ইfreeকরে। Store-এর ভেতরের কপি store মোছে; caller-এর string caller মোছে। - ধার করা pointer বেশিক্ষণ রাখবেন না।
kv_getযে pointer দেয়, সেটি পরেরkv_set-এর পর আর বৈধ নাও থাকতে পারে। বেশিক্ষণ লাগলে নিজে কপি করে নিন। না মানলে পাবেন use-after-free. - প্রতিটি পথে cleanup. মাঝপথে error হলেও যা নেওয়া হয়েছিল তা ফেরত দিতে হবে, নইলে memory leak. পর্ব ৩০-এর “একটি cleanup লেবেল” কৌশলটি এখানে কাজে আসে।
Thread যোগ করার পর একটি নতুন সমস্যা আসে: client ১ যখন kv_get-এর pointer পড়ছে, client ২ ঠিক তখনই kv_set দিয়ে সেই value মুছে নতুন বসাতে পারে। তাই network সংস্করণে আমরা server-এর ভেতরে lock ধরে রাখা অবস্থাতেই value-টি reply buffer-এ কপি করব, তারপর lock ছাড়ব। এই সিদ্ধান্তটিও নকশার অংশ — কোড লেখার আগেই।
MVP-র হৃদয় — ২২ লাইনে
নকশা কাগজে থাকলে চলবে না, দ্রুত চালিয়ে দেখতে হবে। নিচের প্রোগ্রামটি kvdb-র সবচেয়ে ছোট সংস্করণ: stdin থেকে কমান্ড পড়ে, ছোট একটি টেবিলে রাখে, আর protocol অনুযায়ী উত্তর দেয়। Network নেই, ফাইল নেই, hash নেই — শুধু মূল যুক্তি। চালিয়ে দেখুন:
#include <stdio.h>
#include <string.h>
#define CAP 8
char keys[CAP][16], vals[CAP][32]; int n = 0;
int find(const char *k) { /* later: hash table */
for (int i = 0; i < n; i++) if (strcmp(keys[i], k) == 0) return i;
return -1;
}
int main(void) {
char line[80], cmd[8], k[16], v[32];
while (fgets(line, sizeof line, stdin)) {
int got = sscanf(line, "%7s %15s %31s", cmd, k, v);
int i = got >= 2 ? find(k) : -1;
if (got == 3 && !strcmp(cmd, "SET") && (i >= 0 || n < CAP)) {
if (i < 0) i = n++;
strcpy(keys[i], k); strcpy(vals[i], v); puts("OK");
} else if (got == 2 && !strcmp(cmd, "GET")) puts(i >= 0 ? vals[i] : "(nil)");
else if (got == 2 && !strcmp(cmd, "DEL") && i >= 0) { keys[i][0] = 0; puts("OK"); }
else puts("ERR");
}
}fgets একটি করে লাইন পড়ে, sscanf সেটিকে তিন টুকরো করে, আর got বলে দেয় কয়টি টুকরো পাওয়া গেল। %15s-এর মতো সীমা দেওয়ায় লম্বা ইনপুট buffer ছাপিয়ে যেতে পারে না। দ্বিতীয় SET নতুন ঘর না নিয়ে পুরোনো মানের ওপর লিখেছে, তাই Mitu এসেছে। DEL শুধু key-এর প্রথম অক্ষর মুছে ঘরটিকে “খালি” চিহ্ন দিয়েছে — একে বলে tombstone. সরলতার জন্য MVP-তে না-থাকা key মুছতে চাইলে protocol-এর (nil)-এর বদলে ERR আসে — পরের মাইলস্টোনে এটিও ঠিক করবেন। find এখানে O(n) রৈখিক খোঁজ; পরের ধাপে শুধু এই একটি ফাংশন বদলে hash table বসালে বাকি প্রোগ্রাম একই থাকবে। main-এর শেষে return না থাকলেও C99 থেকে main নিজে থেকে 0 ফেরত দেয়।
৫ · ধাপে ধাপে বানানো — MVP থেকে concurrency
বড় প্রজেক্টের সবচেয়ে বড় শত্রু হলো “সবকিছু একসাথে লিখে শেষে চালাব” মানসিকতা। তখন ভুল হাজার লাইনের কোথায়, কেউ বলতে পারে না। এর বদলে আমরা বানাব MVP — সবচেয়ে ছোট সংস্করণ যা পুরোটা চলে — তারপর একটি একটি milestone যোগ করব। প্রতিটি মাইলস্টোনের শেষে প্রোগ্রাম চলবে, test পাস করবে, আর git-এ একটি commit ও tag থাকবে।
নিচের ডায়াগ্রামে দেখুন প্রজেক্টের ফোল্ডার কীভাবে বড় হচ্ছে। উজ্জ্বল চিহ্নিত ফাইলগুলো সেই ধাপে বদলেছে বা নতুন এসেছে:
$ make && ./kvdb
SET roll42 Rafi
OK
$ ./kvdb --data class.kv # SET … তারপর Ctrl+D $ ./kvdb --data class.kv GET roll42 Rafi ← তথ্য টিকে আছে
$ ./kvdb --port 7000 --data class.kv (অন্য terminal) $ nc localhost 7000 GET roll42 Rafi
client A: SET roll1 Anika → OK client B: GET roll1 → Anika client C: DEL roll1 → OK (কেউ কাউকে আটকে রাখেনি)
$ make test PASS 41, FAIL 0 $ ./bench 1000000 SET: … ops/sec GET: … ops/sec (আপনার মেশিনের সংখ্যা)
- Milestone ১ — MVP (v0.1). শুধু stdin থেকে
SET/GET/DEL, ভেতরে hash table. Network নেই, ফাইল নেই। কিন্তু পুরো পথ — পড়া, parse, store, উত্তর — শুরু থেকে শেষ পর্যন্ত চলে। প্রথম দিনেই Makefile লিখে ফেলুন, যাতেmakeচাপলেই-Wall -Wextra -gদিয়ে build হয়। git-এ tag:v0.1. - Milestone ২ — Persistence (v0.2). প্রোগ্রাম বন্ধ হলেও তথ্য থাকবে — একে বলে persistence. সহজ ও নিরাপদ উপায়: পুরো টেবিল প্রথমে একটি অস্থায়ী ফাইলে লিখুন, তারপর
rename()দিয়ে আসল নামে বদলান। এই atomic rename-এর ফলে লেখার মাঝপথে বিদ্যুৎ গেলেও পুরোনো ফাইলটি অক্ষত থাকে (পর্ব ২৪)। শুধু kv.c-এর পাশে নতুন storage module এলো, বাকি সব আগের মতো। - Milestone ৩ — Network (v0.3). stdin-এর জায়গায় TCP socket (পর্ব ৫০)। main.c এখন
accept()করে, আর net.c লাইন জোড়া লাগায়। খেয়াল করুন, parse.c আর kv.c-এর একটি লাইনও বদলায়নি — কারণ তারা কখনো জানতই না লাইনটি কোথা থেকে আসছে। ভালো interface-এর পুরস্কার এটাই। - Milestone ৪ — Concurrency (v0.4). প্রতিটি client-এর জন্য একটি thread (পর্ব ৪৯), অথবা প্রতিটির জন্য
fork()করা একটি process (পর্ব ৪৮)। Thread হলে সবাই একই hash table ভাগ করে, তাই একটি mutex দিয়ে store রক্ষা করতে হবে — নইলে race condition. Process হলে মেমরি আলাদা, তাই একজনেরSETঅন্যজন দেখবে না — kvdb-র জন্য thread-ই ঠিক পছন্দ। শুরুতে একটিমাত্র বড় lock যথেষ্ট; ধীর প্রমাণ হলে তবেই সূক্ষ্ম lock. - Milestone ৫ — Test, মাপ ও document (v1.0). Unit test, stress test, sanitizer, Valgrind, গতির benchmark আর README. গতির ঘরে ইচ্ছা করেই কোনো সংখ্যা লিখিনি — সংখ্যা আসবে আপনার নিজের মেশিনে মেপে, অনুমান থেকে নয়। এবার সিস্টেমটি অন্যকে দেখানোর মতো।
প্রতিদিনের git আর make অভ্যাস
প্রতিটি ছোট কাজ শেষে একটি commit — “কাজ করছে” এমন একটি নিরাপদ বিন্দু। কিছু ভেঙে গেলে সেখানে ফিরে যেতে পারবেন। নতুন বড় ফিচার আলাদা branch-এ বানান, আর পাস করলে তবেই মূল branch-এ আনুন। বিস্তারিত পাবেন Software Engineering ও Git কোর্সে।
git switch -c feature/persistence # নতুন কাজের জন্য আলাদা branch
make # gcc -Wall -Wextra -g ... (শূন্য warning)
make test # সব test পাস না করলে commit নয়
git add storage.c storage.h Makefile
git commit -m "storage: save table with temp file + atomic rename"
git tag v0.2Makefile-এ তিনটি target রাখুন: make (সাধারণ build), make test (সব test), make asan (sanitizer চালু করে build ও test করার জন্য)। তাহলে যেকোনো নতুন সদস্য — বা ছয় মাস পরের আপনি — একটি শব্দ লিখেই সব যাচাই করতে পারবেন। পর্ব ২৯-এর build system এখানে আসল কাজে লাগছে।
Big-O দিয়ে আগেই অনুমান
MVP-র find ছিল O(n) — ১০ লাখ key হলে প্রতিটি GET-এ গড়ে ৫ লাখ তুলনা। Hash table-এ গড় খরচ O(1), শর্ত একটাই: load factor নিয়ন্ত্রণে রাখা। টেবিল ভরে এলে বড় করে সব key নতুন করে বসানো (rehashing) একবারে O(n), কিন্তু অনেক অপারেশনের ওপর ভাগ করলে প্রতিটির গড় খরচ এখনো O(1) — পর্ব ৩৯-এর amortized হিসাব। আর ফাইলে পুরো টেবিল লেখা O(n), তাই প্রতিটি SET-এ নয়, কয়েক সেকেন্ড পরপর বা বন্ধ হওয়ার সময় লিখুন। এই ট্রেড-অফ লিখে রাখুন: “বিদ্যুৎ গেলে শেষ কয়েক সেকেন্ডের তথ্য হারাতে পারে।”
৬ · প্রমাণ করুন — test, sanitizer আর মাপজোখ
“আমার মেশিনে তো চলছে” — এটি কোনো প্রমাণ নয়। পেশাদার সিস্টেমের প্রতিটি দাবির পেছনে একটি যন্ত্র থাকে, যা প্রতিবার নিজে থেকে যাচাই করে। আপনার kvdb-র জন্য একটি test plan:
| ধরন | কী যাচাই করে | kvdb-তে উদাহরণ |
|---|---|---|
| Unit test | একটি ফাংশন একা ঠিক কাজ করে কি না | parse_line("GET a") ঠিক struct দেয়; খালি লাইনে error দেয় |
| Edge case | সীমানার ইনপুট | ঠিক ৩১ অক্ষরের key, ৩২ অক্ষরের key, খালি টেবিলে DEL |
| Regression test | পুরোনো বাগ যেন ফিরে না আসে | নিচের stepper-এর বাগটির জন্য একটি স্থায়ী test |
| Stress test | লাখো এলোমেলো কমান্ডে ফল ঠিক থাকে কি না | একই কমান্ড kvdb আর একটি সরল array-ভিত্তিক “নিশ্চিত সঠিক” সংস্করণে চালিয়ে ফল মেলানো (পর্ব ৪৪) |
| Sanitizer ও Valgrind | লুকানো মেমরি ও thread-এর ভুল | সব test -fsanitize=address,undefined দিয়ে আবার; thread-এর জন্য আলাদা TSan build |
| Benchmark | গতি দাবির প্রমাণ | ১০ লাখ SET আর GET-এ সময় মাপা |
Test লিখতে বড় কোনো library লাগে না। একটি ছোট macro আর দুটি গণনাই যথেষ্ট — একে বলে test harness. নিচের উদাহরণে আমরা MVP-র parse অংশটিকে আলাদা ফাংশনে এনে পরীক্ষা করছি। শেষ test-টি একটি আসল নকশার ভুল ধরে ফেলে — চালিয়ে দেখুন কোনটি:
#include <stdio.h>
#include <string.h>
static int pass = 0, fail = 0;
#define CHECK(cond) do { if (cond) pass++; else { fail++; \
printf("FAIL line %d: %s\n", __LINE__, #cond); } } while (0)
/* unit under test: split "CMD key [value]" */
int parse(const char *line, char *cmd, char *key, char *val) {
return sscanf(line, "%7s %15s %31s", cmd, key, val);
}
int main(void) {
char c[8], k[16], v[32];
CHECK(parse("SET roll42 Rafi", c, k, v) == 3 && strcmp(v, "Rafi") == 0);
CHECK(parse("GET roll42", c, k, v) == 2 && strcmp(k, "roll42") == 0);
CHECK(parse("", c, k, v) < 1); /* empty line */
CHECK(parse("GET abcdefghijklmnopqrstuvwxyz", c, k, v) == 2); /* long key */
printf("PASS %d, FAIL %d\n", pass, fail);
return fail != 0;
}CHECK macro শর্তটি যাচাই করে; ব্যর্থ হলে __LINE__ দিয়ে লাইন নম্বর আর #cond দিয়ে শর্তের লেখাটিই ছাপে (পর্ব ২৭-এর stringize কৌশল)। প্রথম তিনটি পাস করে, শেষটি ব্যর্থ — এটিই আসল আবিষ্কার! একটি GET লাইনে ঠিক দুটি টুকরো থাকার কথা। কিন্তু ২৬ অক্ষরের key দিলে %15s প্রথম ১৫ অক্ষর নিয়েই থেমে যায়, আর বাকি ১১ অক্ষর পরের %31s “value” হিসেবে পড়ে ফেলে — তাই sscanf ফেরত দেয় 3. অর্থাৎ লম্বা key চুপচাপ দুই টুকরো হয়ে গেল। SET-এর বেলায় এর ফল আরও খারাপ: key কাটা পড়বে, আসল value হারিয়ে যাবে, আর ব্যবহারকারী টেরও পাবেন না। Spec বলেছিল “লম্বা key-তে ERR” — test সেই প্রতিশ্রুতি ভাঙা ধরিয়ে দিল। সমাধান: আগে key-এর দৈর্ঘ্য মেপে নিন, তারপর কপি করুন। শেষে return fail != 0; — ব্যর্থ হলে exit code 1, তাই make test বা CI নিজে থেকেই থেমে যাবে।
একটি বাগ শিকারের গল্প
সব unit test পাস করছে, কিন্তু stress test-এ মাঝে মাঝে crash. এমন বাগ চোখে খুঁজে পাওয়া প্রায় অসম্ভব — যন্ত্রকে কাজে লাগাতে হয়। নিচের debugger-ধাঁচের ডায়াগ্রামে পুরো শিকারটি দেখুন:
1/* kv.c */2char *dup_str(const char *s) {3 size_t n = strlen(s);4 char *p = malloc(n);5 if (p == NULL) return NULL;6 memcpy(p, s, n + 1);7 return p;8}
| s | |
| strlen(s) | |
| malloc চাইল | |
| memcpy লিখল | |
| ASan |
- লক্ষণ। ৪০টি unit test পাস, কিন্তু এক লাখ এলোমেলো কমান্ডের stress test-এ crash — তাও প্রতিবার নয়। এমন “মাঝে মাঝে” বাগ সাধারণত মেমরির ভুল: কোনো একটি buffer-এর সীমা পেরিয়ে লেখা হচ্ছে, আর ফল নির্ভর করছে পাশে কী আছে তার ওপর। এটি undefined behavior — তাই আচরণও অনির্দিষ্ট।
- যন্ত্র চালু করুন। অনুমান করে কোড বদলানো নয় —
make asanদিয়ে-fsanitize=address,undefinedচালু করে আবার build. AddressSanitizer প্রতিটি মেমরি ব্যবহারের পাশে পাহারা বসায়, আর UBSan ধরে undefined behavior. প্রোগ্রাম একটু ধীর হয়, কিন্তু ভুলের মুহূর্তেই থামে। - ASan ঠিকানা দিল। রিপোর্টটি মন দিয়ে পড়ুন:
heap-buffer-overflow, ৫ বাইট লেখা হয়েছে,kv.c-এর লাইন ৬-এ, এমন একটি জায়গায় যা মাত্র ৪ বাইটের — আর সেই ৪ বাইট নেওয়া হয়েছিল লাইন ৪-এ। Crash হতো অনেক পরে অন্য কোথাও; ASan দেখাল ভুলের আসল জায়গা। - মূল কারণ।
strlen("Rafi")হলো 4 — শেষেরNULবাইট সে গোনে না। অথচmemcpyঠিকভাবেইNUL-সহ 5 বাইট কপি করছে। ভুলটাmalloc(n)-এ। এক বাইটের এই ক্লাসিক ভুলের নাম “off-by-one”, পর্ব ১৯ থেকে পরিচিত। পুরো ফলাফল একটি buffer overflow. - সমাধান।
malloc(n + 1). এক অক্ষরের পরিবর্তন। আবার stress test — এক লাখ অপারেশনের সবগুলো সরল reference সংস্করণের সাথে মিলেছে, ASan চুপ। কিন্তু কাজ এখনো শেষ নয়। - Regression test যোগ করুন। এই বাগের জন্য একটি স্থায়ী test লিখে test suite-এ রেখে দিন। এখন থেকে কেউ — আপনি, সহকর্মী বা AI — ভুল করে আবার
+ 1মুছে দিলেmake testসাথে সাথে লাল হবে। প্রতিটি বাগ ঠিক করার শেষ ধাপ একটি নতুন test — এটাই পেশাদার অভ্যাস।
gcc -g -fsanitize=address,undefined *.c -o kvdb_asan # মেমরি ও UB-এর ভুল
gcc -g -fsanitize=thread *.c -o kvdb_tsan -pthread # thread-এর race (ASan-এর সাথে একসাথে নয়)
valgrind --leak-check=full ./kvdb < tests/cmds.txt # sanitizer ছাড়া build-এ leak খোঁজাAddressSanitizer আর ThreadSanitizer একই build-এ একসাথে চালানো যায় না, তাই দুটি আলাদা build রাখুন। Valgrind আলাদা ধরনের যন্ত্র — নতুন করে compile লাগে না, কিন্তু প্রোগ্রাম অনেক গুণ ধীরে চলে। আর মনে রাখবেন: এগুলো শুধু সেই ভুল ধরে, যে কোড-পথ test-এ সত্যিই চলেছে। তাই ভালো test আর sanitizer — দুটো একসাথে লাগে।
৭ · AI-এর যুগে: AI আপনার টিমমেট, মালিক আপনি
আজ একজন পেশাদার প্রোগ্রামার প্রায় প্রতিদিনই AI-এর সাহায্য নেন। প্রশ্ন তাই “AI ব্যবহার করব কি না” নয়, বরং “কীভাবে করলে আমার সিস্টেম ভালো হবে, খারাপ নয়”। এই কোর্সের পুরো দর্শন এখানে এসে এক জায়গায় মেলে: নকশা, ভাগ করা, যাচাই আর দায় — এই চারটি কাজ আপনার। টাইপ করার কাজটি AI-এর সাথে ভাগ করা যায়।
| কাজ | কে করবে | কেন |
|---|---|---|
| Spec, protocol, “করবে না” তালিকা | আপনি | AI জানে না কোচিং সেন্টার আসলে কী চায় |
| Header ও মালিকানার নিয়ম | আপনি (AI-এর সাথে আলোচনা করে) | এগুলো সব module-এর চুক্তি; ভুল হলে সবখানে ছড়ায় |
| একটি module-এর প্রথম খসড়া | AI | নির্দিষ্ট interface-এর ভেতরে ছোট কাজ — AI এখানে ভালো |
| Test case-এর ধারণা | দুজনেই | AI প্রায়ই এমন edge case মনে করিয়ে দেয় যা আপনি ভাবেননি |
| Code review, sanitizer, চূড়ান্ত সিদ্ধান্ত | আপনি | কোড আপনার নামে যাবে; দায়ও আপনার |
AI-কে কাজ দেওয়ার সময় পুরো প্রজেক্ট নয়, একটি module, একটি interface, পরিষ্কার শর্ত দিন। নিচের prompt-টি একটি ভালো নমুনা — খেয়াল করুন এতে কী করতে হবে আর কী করা যাবে না, দুটোই আছে:
AI-এর কোডে লাল পতাকা
| যা দেখবেন | কেন বিপজ্জনক | কীভাবে ধরবেন |
|---|---|---|
| Header নিজে থেকে বদলে দিয়েছে | অন্য module-এর সাথে চুক্তি ভেঙে যায় | git diff kv.h — কিছু বদলালেই প্রত্যাখ্যান |
malloc-এর ফল যাচাই নেই | মেমরি ফুরালে crash | প্রতিটি malloc খুঁজে দেখা (পর্ব ২০) |
strcpy, sprintf, সীমাহীন %s | লম্বা ইনপুটে buffer overflow | Edge case test + ASan |
| Thread কোডে lock ছাড়া shared ডেটা | Race — মাঝে মাঝে ভুল ফল | TSan build |
| নেই এমন ফাংশন বা library | Compile বা link-এ ব্যর্থ | Toolchain-ই ধরবে (পর্ব ০৩) |
| দেখতে চমৎকার, কিন্তু আপনি ব্যাখ্যা করতে পারছেন না | বাগ এলে আপনি অসহায় | প্রতিটি অংশ নিজের ভাষায় বলুন — না পারলে জিজ্ঞেস করুন |
AI-এর লেখা module গ্রহণ করার আগে চেকলিস্ট
সততার প্রশ্ন। বিশ্ববিদ্যালয়ের assignment বা চাকরির পরীক্ষায় অন্যের কোড বা AI-এর লেখা কোড নিজের বলে জমা দিলে সেটি plagiarism — অনেক প্রতিষ্ঠানে এর শাস্তি কঠোর। প্রতিটি প্রতিষ্ঠানের AI-নীতি আলাদা; আগে জেনে নিন, আর README-তে সৎভাবে লিখুন কোথায় কোন সাহায্য নিয়েছেন। Open-source কোড ব্যবহার করলে তার লাইসেন্স মানতে হয় — কিছু লাইসেন্স বলে আপনার প্রজেক্টও একই শর্তে প্রকাশ করতে হবে। নিজের কাজ যেখানে সত্যিই নিজের, সেখানে বলার সাহসটাই আপনার সবচেয়ে বড় সম্পদ।
৮ · কাজটি দেখান — GitHub, README আর ডেমো
একটি ভালো প্রজেক্ট যদি শুধু আপনার ল্যাপটপে থাকে, তাহলে পৃথিবীর কাছে সেটির অস্তিত্বই নেই। চাকরি বা ইন্টার্নশিপে — বাংলাদেশে হোক বা বিদেশে — নিয়োগকারীরা প্রায়ই সার্টিফিকেটের চেয়ে বেশি দেখতে চান আপনি কী বানিয়েছেন। একটি গোছানো GitHub portfolio সেই প্রশ্নের উত্তর দেয়, আপনি ঘরে না থাকলেও।
একটি ভালো README-এর কাঠামো
অপরিচিত একজন আপনার repository খুলে প্রথম যা পড়েন, তা হলো README. প্রথম ৩০ সেকেন্ডে তাঁকে বোঝাতে হবে — এটি কী, কীভাবে চালাব, আর কেন আকর্ষণীয়। নিচের কাঠামোটি অনুসরণ করুন:
# kvdb — a tiny key-value database in C
A multi-client key-value store with a text protocol, a hash table,
crash-safe file persistence and a test suite. Built as my C capstone.
## Quick start
make && ./kvdb --port 7000 --data class.kv
nc localhost 7000 # then type: SET roll42 Rafi
## Protocol SET k v -> OK | GET k -> value or (nil) | DEL k -> OK or (nil)
## Design diagram + one paragraph per module (net, parse, kv, storage)
## Testing make test, make asan; stress test against a reference model
## Performance numbers measured on my laptop, with the exact command used
## Limits no auth, no spaces in values, last few seconds may be lost on power cut
## What I learned / Credits (libraries, tutorials, AI help - stated honestly)
🎬 দুই মিনিটের ডেমো ভিডিও
Screen record করুন: দুটি terminal-এ দুজন client, তারপর server বন্ধ করে আবার চালু — তথ্য টিকে আছে। কথা কম, কাজ বেশি। README-র শুরুতেই লিংক দিন।
📜 গোছানো commit ইতিহাস
“fix”, “update” নয় — “storage: atomic save with temp file”. ছোট ছোট অর্থপূর্ণ commit দেখায় আপনি ধাপে ধাপে ভেবে কাজ করেন।
🧠 “কী শিখলাম” অংশ
একটি কঠিন বাগ, একটি ভুল সিদ্ধান্ত যা পরে বদলেছেন, একটি ট্রেড-অফ। ইন্টারভিউতে ঠিক এই গল্পগুলোই জিজ্ঞেস করা হয়।
⚖️ সৎ সীমাবদ্ধতা
“কী পারে না” লেখা দুর্বলতা নয় — পরিপক্বতা। অভিজ্ঞ প্রকৌশলীরা এটাই প্রথমে খোঁজেন।
৯ · কোর্সের পথ — ৫২টি পর্বের যাত্রা, আর এরপর কোথায়
একবার পেছনে তাকান। প্রথম পর্বে প্রশ্ন ছিল “C কেন?” আজ আপনি এমন একটি সিস্টেমের নকশা বোঝেন, যেখানে hash table, ফাইল, socket আর thread একসাথে কাজ করে। প্রতিটি মডিউল ক্যাপস্টোনে কোথায় কাজে লাগল, দেখুন:
| মডিউল | যা শিখেছেন | ক্যাপস্টোনে কোথায় |
|---|---|---|
| ১ · ভিত্তি — কম্পিউটারের মতো ভাবা (পর্ব ১–৫) | Toolchain, সমস্যা ভাঙা, ডেটা বিটে কীভাবে থাকে | নকশাকে module-এ ভাঙা; error দেখে ধাপ চেনা |
| ২ · ভাষার মূল যন্ত্রপাতি (পর্ব ৬–১৫) | ভ্যারিয়েবল, অপারেটর, টাইপ কনভার্শন, বিটওয়াইজ, printf/scanf, সিদ্ধান্ত, লুপ, ফাংশন, স্কোপ, রিকার্শন | কমান্ড লুপ, প্রতিটি কাজের জন্য ছোট ফাংশন |
| ৩ · মেমরি ও পয়েন্টার — C-এর হৃদয় (পর্ব ১৬–২৩) | অ্যারে, পয়েন্টার, স্ট্রিং, malloc, বহুমাত্রিক অ্যারে, struct ও union, মাঝপথের CLI প্রজেক্ট | মালিকানার নিয়ম, dup_str-এর বাগ, struct request |
| ৪ · মাঝারি স্তর — বড় প্রোগ্রাম সাজানো (পর্ব ২৪–৩২) | ফাইল, কমান্ড-লাইন, স্ট্যান্ডার্ড লাইব্রেরি, ম্যাক্রো, callback, header, Makefile, error handling, ডিবাগিং, টেস্টিং | persistence, CHECK macro, kv.h, enum kv_status |
| ৫ · ডেটা স্ট্রাকচার — নিজ হাতে বানান (পর্ব ৩৩–৩৮) | লিংকড লিস্ট, স্ট্যাক, ট্রি, heap, hash table, গ্রাফ | chaining-সহ hash table; interpreter ট্র্যাকে স্ট্যাক ও ট্রি |
| ৬ · অ্যালগরিদম ও প্রবলেম সলভিং (পর্ব ৩৯–৪৪) | Big-O, সর্টিং, সার্চিং ও divide & conquer, DP, greedy ও নাম্বার থিওরি, কনটেস্ট কৌশল ও stress testing | O(1) বনাম O(n) সিদ্ধান্ত, reference-এর সাথে stress test |
| ৭ · সিস্টেম প্রোগ্রামিং (পর্ব ৪৫–৫২) | const/restrict/volatile, UB ও নিরাপদ কোডিং, আধুনিক C, process, thread, TCP socket, এমবেডেড C, ক্যাপস্টোন | network সংস্করণ, প্রতি client-এ thread, mutex |
| ৮ · পরীক্ষা ও ইন্টারভিউ প্রস্তুতি (পর্ব ৫৩–৫৬) | আউটপুট বলা, MCQ, লিখিত-মৌখিক প্রশ্ন, কোডিং ইন্টারভিউ | নিজের ক্যাপস্টোনের নকশা ইন্টারভিউতে ব্যাখ্যা করা |
এরপর কোন পথে?
C আপনাকে দিয়েছে কম্পিউটারের ভেতরটা দেখার চোখ। এখন সেই চোখ নিয়ে যেকোনো দিকে যেতে পারেন। ABCL TECH-এর এই কোর্সগুলো ঠিক এখান থেকে শুরু হয়:
Data Structures & Algorithms
ইন্টারভিউ আর কনটেস্টের জন্য আরও গভীর ডেটা স্ট্রাকচার ও অ্যালগরিদম।
Operating Systems
Process, thread, মেমরি আর ফাইল সিস্টেম — যার ওপর আপনার kvdb দাঁড়িয়ে আছে।
Computer Networks
TCP-র নিচে কী আছে, আর ইন্টারনেট আসলে কীভাবে প্যাকেট পৌঁছায়।
System Design
একটি kvdb থেকে লাখো ব্যবহারকারীর সিস্টেম — cache, replication, ভাগ করা ডেটাবেস।
C++ Programming
C-এর ওপর class, template আর নিরাপদ মেমরি ব্যবস্থাপনা।
Python for AI
AI-এর জগতে প্রবেশ — এখন জানবেন Python-এর ভেতরেও অনেকটাই C চলে।
আপনি সিস্টেম নকশা করেন, যাচাই করেন, আর তার দায় নেন।
এত দূর আসা সহজ কাজ নয় — আপনার ধৈর্য আর পরিশ্রমকে আন্তরিক অভিনন্দন। পয়েন্টার প্রথমবার বুঝতে যে কষ্ট হয়েছিল, segfault-এর সামনে যে রাতগুলো কেটেছে, সেগুলোই আজ আপনাকে অন্যদের থেকে আলাদা করেছে। এবার ক্যাপস্টোনটি বানান, GitHub-এ তুলুন, আর কাউকে দেখান। প্রশ্ন থাকলে বা ভুল পেলে আমাদের জানাবেন। শুভকামনা রইল — পরের কোর্সে আবার দেখা হবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Redis একটি ছোট প্রয়োজন থেকে শুরু হয়ে বিশ্বের অন্যতম জনপ্রিয় ডেটাবেস হলো। এর গল্প থেকে আপনার ক্যাপস্টোনের জন্য কী শেখার আছে?
Redis তৈরি করেন ইতালির প্রোগ্রামার Salvatore Sanfilippo (অনলাইনে “antirez” নামে পরিচিত), ২০০৯ সালে, C ভাষায়। তিনি তখন ওয়েবসাইটের ভিজিটর রিয়েল-টাইমে বিশ্লেষণ করার একটি সেবা নিয়ে কাজ করছিলেন, আর প্রচলিত ডেটাবেস সেই গতিতে লেখা ও পড়া সামলাতে হিমশিম খাচ্ছিল। সমাধান হিসেবে তিনি একটি ছোট in-memory key-value store বানান — মূল ধারণায় আপনার kvdb-র খুব কাছাকাছি।
কয়েকটি নকশার সিদ্ধান্ত লক্ষ্য করার মতো:
- একটি আসল সমস্যা দিয়ে শুরু: Redis কোনো “সব কাজের ডেটাবেস” হিসেবে শুরু হয়নি; একটি নির্দিষ্ট ব্যথা সারাতে বানানো হয়েছিল। পরে দেখা গেল একই ব্যথা আরও অনেকের।
- সরলতা আগে: দীর্ঘদিন Redis মূল কমান্ডগুলো একটি thread-এ পরপর চালাত। এতে lock-এর জটিলতা প্রায় নেই, আর আচরণ বোঝা সহজ। মেমরিতে থাকা ডেটায় কাজ এত দ্রুত যে প্রায়ই network-ই আসল সীমা হয়। পরে প্রয়োজন প্রমাণ হলে network I/O-র জন্য বাড়তি thread যোগ হয়েছে — আগে নয়।
- মানুষের পড়ার মতো protocol: text-ভিত্তিক কমান্ড হওয়ায় যে কেউ terminal থেকে পরীক্ষা করতে পারে, আর প্রায় যেকোনো ভাষায় client লেখা সহজ। এটাই দ্রুত জনপ্রিয়তার একটি কারণ।
- ধাপে ধাপে বৃদ্ধি: list, set, sorted set-এর মতো data type, ডিস্কে সংরক্ষণ, replication — এগুলো এসেছে ধীরে ধীরে, ব্যবহারকারীর চাহিদা দেখে।
আপনার ক্যাপস্টোনের জন্য সরাসরি শিক্ষা: প্রথমে single-thread সংস্করণ সঠিক করুন, তারপর মেপে দেখুন সত্যিই thread লাগবে কি না। অনেক ছাত্র প্রথম দিনেই জটিল lock-এর নকশা করে আটকে যান, অথচ প্রমাণিত প্রয়োজন ছাড়া জটিলতা শুধু বাগের জায়গা বাড়ায়।
মূল উপলব্ধি: বড় সিস্টেম প্রায় কখনো বড় হিসেবে জন্মায় না। একটি নির্দিষ্ট সমস্যার ছোট, সরল, ভালোভাবে করা সমাধান থেকে শুরু হয় — তারপর প্রমাণিত প্রয়োজন অনুযায়ী বড় হয়।
প্র ০২SQLite-কে কেন “test করার আদর্শ” বলা হয়? একটি ছোট দলের লেখা ডেটাবেস কীভাবে এত মানুষের আস্থা পেল?
SQLite-এর স্রষ্টা D. Richard Hipp, প্রথম প্রকাশ ২০০০ সালে, C ভাষায় লেখা। এটি আলাদা সার্ভার নয় — একটি library, যা প্রোগ্রামের ভেতরে বসে একটিমাত্র ফাইলে পুরো ডেটাবেস রাখে। আজ প্রায় প্রতিটি স্মার্টফোন, ব্রাউজার আর অসংখ্য অ্যাপের ভেতরে SQLite আছে; একে পৃথিবীর সবচেয়ে বেশি ব্যবহৃত ডেটাবেস ইঞ্জিনগুলোর একটি ধরা হয়। ভেতরে ডেটা সাজানো থাকে B-tree-তে — পর্ব ৩৫-এর ট্রির বড় ভাই।
এত জায়গায় ব্যবহারের মানে হলো একটি বাগ কোটি কোটি ডিভাইসে পৌঁছাতে পারে। তাই SQLite দল test-কে নিয়েছে প্রায় ধর্মের মতো:
- কোডের চেয়ে বহুগুণ বেশি test কোড: SQLite-এর প্রকাশিত তথ্য অনুযায়ী তাদের test কোডের পরিমাণ library-র নিজের কোডের শত গুণেরও বেশি।
- পূর্ণ branch coverage: তাদের দাবি, test চলার সময় কোডের প্রতিটি শাখা অন্তত একবার চলে — পর্ব ১১-এর branch coverage এর চূড়ান্ত রূপ।
- ব্যর্থতার অনুশীলন: ইচ্ছা করে মেমরি ফুরিয়ে দেওয়া, লেখার মাঝপথে “বিদ্যুৎ চলে যাওয়া” অনুকরণ, ভাঙা ফাইল দেওয়া — দেখা হয় ডেটাবেস নষ্ট না হয়ে ঠিকভাবে error দেয় কি না।
- Fuzzing: লাখো এলোমেলো বা অর্ধ-এলোমেলো ইনপুট দিয়ে অপ্রত্যাশিত crash খোঁজা।
আপনার kvdb-র সাথে মিল লক্ষ্য করুন: atomic rename দিয়ে নিরাপদ সংরক্ষণ, malloc ব্যর্থ হলে KV_NO_MEMORY, আর reference-এর সাথে stress test — একই চিন্তার ছোট রূপ। আপনি চাইলে একটি “malloc ব্যর্থ করো” test যোগ করতে পারেন: একটি wrapper ফাংশন যা n-তম ডাকে NULL ফেরত দেয়, তারপর দেখুন store অক্ষত থাকে কি না।
মূল উপলব্ধি: আস্থা আসে প্রমাণ থেকে, দাবি থেকে নয়। ছোট দলও বিশ্বমানের নির্ভরযোগ্যতা দিতে পারে — যদি test-কে কোডের সমান মর্যাদা দেয়।
প্র ০৩একটি প্রজেক্টে কত সময় লাগবে, কীভাবে অনুমান করবেন? প্রায় সবার অনুমান কেন কম হয়?
সফটওয়্যারে সময়ের অনুমান কুখ্যাতভাবে ভুল হয়, আর প্রায় সবসময় একই দিকে — কম। Douglas Hofstadter-এর একটি মজার “নিয়ম” আছে: কাজ সবসময় প্রত্যাশার চেয়ে বেশি সময় নেয়, এমনকি এই নিয়মটি হিসাবে ধরার পরেও। কারণগুলো বোঝা গেলে অনুমান অনেকটা ভালো করা যায়:
- আমরা শুধু “লেখা”-র সময় গুনি: অথচ আসল সময়ের বড় অংশ যায় debugging, test, ইনস্টল-সমস্যা, document আর “এটা তো ভাবিনি” মুহূর্তে।
- অজানা অজানা: TCP-তে অর্ধেক লাইন আসা, বা
strlen-এর এক বাইট — এগুলো আগে থেকে জানা থাকে না, পথে আবিষ্কার হয়। - Scope creep: “আরেকটু ফিচার” যোগ হতে থাকে, কিন্তু সময়সীমা বাড়ে না।
ব্যবহারিক পদ্ধতি: (১) কাজটিকে এত ছোট টুকরোয় ভাঙুন যে প্রতিটি আধা দিন থেকে দুই দিনের — বড় টুকরোর অনুমান প্রায় সবসময় ভুল। (২) প্রতিটির অনুমান লিখুন, তারপর মোটের সাথে একটি নিরাপত্তা-মার্জিন যোগ করুন; প্রথম প্রজেক্টে দ্বিগুণ ধরাও অযৌক্তিক নয়। (৩) প্রথম মাইলস্টোন শেষে আসল সময় আর অনুমান মিলিয়ে দেখুন — সেই অনুপাতে বাকিটা ঠিক করুন। (৪) সময় ফুরোলে সময়সীমা নয়, ফিচার কাটুন — “অবশ্যই লাগবে”, “ভালো হয়”, “পরে” — এই তিন তালিকা আগেই বানিয়ে রাখুন।
Fred Brooks তাঁর ১৯৭৫ সালের বই The Mythical Man-Month-এ আরেকটি গুরুত্বপূর্ণ কথা বলেছিলেন: দেরিতে চলা প্রজেক্টে বেশি মানুষ যোগ করলে সাধারণত আরও দেরি হয়, কারণ নতুনদের শেখাতে আর সবার মধ্যে সমন্বয় করতে সময় লাগে। দলগত ক্যাপস্টোনে এটি মনে রাখবেন।
মূল উপলব্ধি: ভালো অনুমান মানে ভবিষ্যৎ দেখা নয়, বরং কাজকে ছোট করে ভাঙা আর নিয়মিত বাস্তবের সাথে মিলিয়ে নেওয়া। MVP আর মাইলস্টোন পদ্ধতি এজন্যই এত কার্যকর।
প্র ০৪কোড অগোছালো হয়ে গেছে। পুরোটা নতুন করে লিখবেন (rewrite), নাকি ধাপে ধাপে গুছিয়ে নেবেন (refactor)?
প্রতিটি প্রোগ্রামার একসময় নিজের পুরোনো কোড দেখে ভাবেন, “পুরোটা ফেলে নতুন করে লিখি।” প্রলোভনটি স্বাভাবিক, কিন্তু প্রায়ই বিপজ্জনক। ২০০০ সালে Joel Spolsky “Things You Should Never Do” নামে একটি বিখ্যাত প্রবন্ধে Netscape-এর উদাহরণ দেন — তারা ব্রাউজারের কোড শূন্য থেকে নতুন করে লেখার সিদ্ধান্ত নিয়েছিল, আর সেই কয়েক বছর নতুন সংস্করণ বাজারে আসেনি, যখন প্রতিযোগীরা এগিয়ে যায়।
Rewrite কেন ঝুঁকিপূর্ণ? পুরোনো অগোছালো কোডের ভেতরে লুকিয়ে থাকে বছরের পর বছর ধরে ঠিক করা অসংখ্য edge case. নতুন কোড পরিষ্কার দেখায়, কিন্তু সেই শিক্ষাগুলো তাতে নেই — আবার একই বাগ ফিরে আসে। আর rewrite চলার পুরো সময়টা ব্যবহারকারী নতুন কিছু পান না।
Refactoring-এর পথ: আচরণ একই রেখে ছোট ছোট পরিবর্তন — একটি ফাংশন ভাঙা, একটি নাম ঠিক করা, একটি module আলাদা করা — আর প্রতিটির পর test চালানো। এজন্যই test এত মূল্যবান: test না থাকলে refactor করা মানে অন্ধকারে হাঁটা।
তাহলে rewrite কখন ঠিক? কোডটি ছোট হলে; মূল নকশাই ভুল প্রমাণিত হলে (যেমন single-thread ধরে লেখা সবকিছু, অথচ এখন বহু client লাগবে); ভাষা বা প্ল্যাটফর্ম বদলাতে হলে; অথবা প্রথম সংস্করণটি সচেতনভাবেই “ফেলে দেওয়ার জন্য” প্রোটোটাইপ ছিল। Fred Brooks এই শেষ কথাটিই বলেছিলেন — একটি ফেলে দেওয়ার পরিকল্পনা রাখুন, কারণ আপনি এমনিতেই ফেলবেন। আরেকটি মাঝামাঝি কৌশল হলো একটি একটি module করে নতুন অংশ বসানো, interface একই রেখে — আপনার kvdb-তে find() থেকে hash table-এ যাওয়াটা ঠিক এমনই।
মূল উপলব্ধি: ভালো interface আর ভালো test থাকলে আপনাকে প্রায় কখনো পুরো rewrite করতে হবে না — ভেতরের যেকোনো অংশ নিরাপদে বদলাতে পারবেন। Technical debt শোধ করুন কিস্তিতে, এক ধাক্কায় নয়।
প্র ০৫একটি দলে AI-এর লেখা কোডের “মালিক” হওয়া মানে আসলে কী?
কোম্পানিতে কোনো কোড মূল branch-এ যাওয়ার সময় তার পাশে একজন মানুষের নাম থাকে — যিনি লিখেছেন বা অনুমোদন করেছেন। রাত দুইটায় সার্ভার বন্ধ হলে ফোন যায় তাঁর কাছে, AI-এর কাছে নয়। মালিক হওয়া মানে এই দায়টি সচেতনভাবে নেওয়া। ব্যবহারিকভাবে এর চারটি অংশ:
- ব্যাখ্যা করতে পারা: Code review-তে সহকর্মী জিজ্ঞেস করবেন, “এখানে lock কেন?” — “AI দিয়েছে” কোনো উত্তর নয়। প্রতিটি লাইনের কারণ আপনাকে জানতে হবে।
- যাচাই করা: AI-এর আত্মবিশ্বাসী ভঙ্গি সঠিকতার প্রমাণ নয়। Test, sanitizer আর হাতে হিসাব — এই কোর্সের প্রতিটি পর্বে যা অনুশীলন করেছেন — এখানেই কাজে লাগে।
- রক্ষণাবেক্ষণ: ছয় মাস পরে বাগ এলে আপনাকেই ঠিক করতে হবে। যে কোড বোঝেন না, সেটি ঠিক করতে পারবেন না — তখন আবার AI-কে জিজ্ঞেস করা মানে অন্ধের হাতে লাঠি দেওয়া।
- আইনি ও নৈতিক দিক: AI কখনো কখনো এমন কোড দিতে পারে যা কোনো লাইসেন্সকৃত প্রজেক্টের সাথে খুব মিলে যায়। কোম্পানির নীতি মেনে চলুন, গোপন কোড বা ব্যবহারকারীর তথ্য বাইরের AI-তে পেস্ট করবেন না, আর শিক্ষাপ্রতিষ্ঠানে সাহায্যের কথা সৎভাবে জানান।
মূল উপলব্ধি: AI আপনার টাইপ করার গতি বাড়ায়, কিন্তু আপনার দায় কমায় না। যে কোডে আপনার নাম, তার প্রতিটি লাইনের জবাবদিহি আপনার — এই কোর্স আপনাকে ঠিক সেই জবাবদিহির জন্যই প্রস্তুত করেছে।
অনুশীলন
এগুলোই আপনার ক্যাপস্টোনের প্রথম সপ্তাহের কাজ — বেশিরভাগই লেখা আর ভাবা।
-
এক পাতার spec: চারটি ট্র্যাকের যেকোনো একটি বেছে নিন। লিখুন: কী করবে (৫টি পয়েন্ট), কী করবে না (৫টি পয়েন্ট), ইনপুটের সীমা, আর কোন অবস্থায় error দেবে।
মিনি shell-এর একটি নমুনা: করবে — একটি কমান্ড আর তার argument চালানো,
cdওexit, একটি|pipe,>দিয়ে ফাইলে আউটপুট, প্রোগ্রাম না পেলে স্পষ্ট বার্তা। করবে না — script ফাইল, ভ্যারিয়েবল,&&/||, job control, tab দিয়ে নাম পূরণ। সীমা — এক লাইনে সর্বোচ্চ ১০২৪ অক্ষর, ৬৪টি argument. Error — খুব লম্বা লাইন, খালি pipe-এর পাশ, অজানা কমান্ড।খেয়াল করুন, “করবে না” তালিকাটি লিখতে গিয়েই বুঝবেন কাজটি কত বড় হতে পারত। এই তালিকাই আপনাকে সময়মতো শেষ করতে সাহায্য করবে।
-
Header আগে: মিনি ক্যালকুলেটর (ট্র্যাক B)-এর জন্য দুটি header-এর ফাংশনগুলোর শুধু নাম, প্যারামিটার আর ফেরত মান লিখুন —
lexer.h(লেখাকে টোকেনে ভাঙা) আরeval.h(হিসাব করা)। মেমরির মালিক কে, মন্তব্যে লিখুন।একটি সম্ভাব্য নকশা:
lexer.h-এstruct token { enum kind k; double num; char op; };আরint lex(const char *src, struct token *out, int max);— caller নিজের array দেয়, lexer শুধু ভরে, তাইfree-এর প্রশ্নই নেই; ফেরত দেয় টোকেনের সংখ্যা, ভুল হলে −1.eval.h-এint eval(const struct token *t, int n, double *result);— ফেরত মান সফলতা বা error code (যেমন শূন্য দিয়ে ভাগ, বন্ধনী না মেলা), আর ফল যায় pointer দিয়ে। ভেতরে পর্ব ৩৪-এর দুটি স্ট্যাক বা postfix রূপান্তর — কিন্তু header-এ সেটা দেখা যায় না, আর দেখা যাওয়ার দরকারও নেই। -
Test আগে ভাবুন: kvdb-র
kv_delফাংশনের জন্য অন্তত ছয়টি test case লিখুন — শুধু বর্ণনা, কোড নয়। প্রতিটির প্রত্যাশিত ফলও লিখুন।- খালি store-এ যেকোনো key মোছা →
KV_NOT_FOUND,kv_count0-ই থাকে। - একটি key রেখে মোছা →
KV_OK, তারপরkv_getদেয়KV_NOT_FOUND. - একই key দুবার মোছা → দ্বিতীয়বার
KV_NOT_FOUND. - একই bucket-এর চেইনের মাঝের, প্রথম আর শেষ উপাদান মোছা — বাকিগুলো অক্ষত থাকে (লিংকড লিস্টের ক্লাসিক ভুলের জায়গা)।
- মুছে আবার একই key রাখা → নতুন মান পাওয়া যায়।
- পুরো test ASan ও Valgrind-এ চালালে শূন্য leak — মোছা key ও value সত্যিই
freeহয়েছে।
- খালি store-এ যেকোনো key মোছা →
-
AI-এর কোড review করুন: উপরের prompt দিয়ে যেকোনো AI থেকে
kv.cলিখিয়ে নিন। চেকলিস্ট আর লাল পতাকার টেবিল ধরে review করুন। অন্তত তিনটি পর্যবেক্ষণ লিখুন — ভালো বা খারাপ।সাধারণত যা পাওয়া যায়: (১) rehash-এর সময় নতুন টেবিলের
mallocব্যর্থ হলে পুরোনো টেবিল হারিয়ে যায় — শর্ত ৩ ভঙ্গ। (২)kv_set-এ key-এর কপি সফল কিন্তু value-এর কপি ব্যর্থ হলে key-এর কপিfreeহয় না — ছোট একটি leak. (৩) কখনো কখনো header-এরconstসরিয়ে দেয় বা নতুন ফাংশন যোগ করে। (৪) ভালো দিক — chaining-এর লিংকড লিস্ট কোড প্রায়ই পরিষ্কার হয়। প্রতিটি ভুলের জন্য একটি test লিখে ফেলুন; সেগুলো আপনার regression suite-এর অংশ হবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি সিস্টেম তৈরির ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📝 চাহিদা ও “করবে না” তালিকা লেখা
- 📐 নকশা — কোন কোন module, কীভাবে কথা বলবে
- 🧩 Header-এ interface ঠিক করা
- 🧱 MVP বানানো, তারপর এক এক মাইলস্টোন
- 🧪 Test, sanitizer আর মাপজোখ
- 📘 README ও ডেমো
একটি ক্যাপস্টোনের প্রথম দিনে কোনটি সবচেয়ে বুদ্ধিমানের কাজ?
- সব ফিচার একসাথে লিখে শেষে একবার চালানো
- এক পাতায় চাহিদা আর “করবে না” তালিকা লেখা
- শুরুতেই thread আর lock-এর নকশা করা
- আগে সুন্দর একটি README লেখা
kvdb-তে stdin-এর জায়গায় TCP যোগ করার সময় parse.c আর kv.c-এর একটি লাইনও বদলাতে হয়নি। কেন?
- ভাগ্য ভালো ছিল
- TCP আর stdin হুবহু একই জিনিস
- module-গুলো একে অপরের ভেতরের খবর না জেনে শুধু interface দিয়ে কথা বলে
- সব ডেটা global ভ্যারিয়েবলে ছিল
kv_get একটি const char * দেয় যা store-এর ভেতরে নির্দেশ করে। Caller-এর কী করা উচিত নয়?
- সেই pointer-কে
freeকরা - লেখাটি পড়ে স্ক্রিনে ছাপানো
- নিজের জন্য একটি কপি বানিয়ে রাখা
- অন্য একটি string-এর সাথে
strcmpকরা
Stress test-এ মাঝে মাঝে crash হচ্ছে, কিন্তু সব unit test পাস। পরের সবচেয়ে ভালো পদক্ষেপ কোনটি?
- সন্দেহজনক জায়গায় অনুমান করে কোড বদলানো
- Stress test বন্ধ করে দেওয়া
-O2দিয়ে compile করা-fsanitize=address,undefinedদিয়ে build করে stress test আবার চালানো
একটি বাগ ঠিক করার পর শেষ ধাপ হিসেবে কী করা উচিত?
- শুধু commit করে পরের কাজে যাওয়া
- বাগটির জন্য একটি regression test যোগ করা
- পুরো module নতুন করে লেখা
- সব warning বন্ধ করে দেওয়া
AI দিয়ে একটি module লেখানোর সবচেয়ে কার্যকর উপায় কোনটি?
- “পুরো ডেটাবেস প্রজেক্ট লিখে দাও” বলা
- AI যা দেয় তা সরাসরি মূল branch-এ রাখা
- নিজের লেখা header আর পরিষ্কার শর্ত দিয়ে একটি module চাওয়া, তারপর test ও review করা
- AI একেবারেই ব্যবহার না করা
MVP-তে find() ছিল O(n). ১০ লাখ key-তে প্রতিটি GET দ্রুত করতে কোনটি সবচেয়ে উপযুক্ত পরিবর্তন?
- একই interface রেখে ভেতরে hash table বসানো
- প্রতিটি GET-এর জন্য নতুন thread খোলা
- প্রতিবার ফাইল থেকে পড়া
- key-এর দৈর্ঘ্য ছোট করে দেওয়া
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- সিস্টেম তৈরির চক্র: চাহিদা → নকশা → interface → ছোট ধাপে build → test ও মাপ → document.
- Header আগে লিখুন — এটি module-গুলোর চুক্তি; মেমরির মালিক কে, সেটাও সেখানে লেখা থাকবে।
- আগে MVP, তারপর একটি করে মাইলস্টোন; প্রতিটির শেষে চলা প্রোগ্রাম, পাস করা test আর git tag.
- “চলছে” প্রমাণ নয় — unit, regression, stress test, sanitizer, Valgrind আর মাপা সংখ্যা হলো প্রমাণ।
- AI টিমমেট, মালিক আপনি — যে লাইন ব্যাখ্যা করতে পারেন না, সেটি আপনার কোডে থাকবে না; আর কাজটি README-সহ সবাইকে দেখান।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · আউটপুট কী হবে? — কোড পড়ার অনুশীলনপরবর্তী পর্বমডিউল ৮ শুরু — পরীক্ষা ও ইন্টারভিউর প্রস্তুতি।
- C প্রোগ্রামিং · কোর্স হোমরোডম্যাপ৫৬টি পর্বের পুরো রোডম্যাপ — ক্যাপস্টোনের সময় দরকারি পর্ব আবার খুলে দেখুন।
- Software Engineering ও Gitক্যাপস্টোনের সঙ্গীBranch, commit, code review আর দলগত কাজের পদ্ধতি।
- Software Testing ও QAগভীরে যানTest plan, automation আর মান নিশ্চিত করার পেশাদার কৌশল।
- System Designপরের ধাপএকটি kvdb থেকে লাখো ব্যবহারকারীর বড় সিস্টেম কীভাবে নকশা হয়।
- Data Structures & Algorithmsপাশের ট্র্যাকHash table, ট্রি আর গ্রাফের আরও গভীর রূপ, ইন্টারভিউয়ের প্রস্তুতি।