Part 01 · Backend Mindset
Why Data Lives in One Place
প্রতিটা device এ নিজের copy না রেখে app কেন একটা single source of truth রাখে।
⏱ 1 ঘণ্টা · Station 7 / 153
ডিমটা কে মুছলো?
তুমি আর তোমার roommate একটা shopping list app share করো। তুমি অফিসে বসে ডিম যোগ করলে। ঠিক একই সময়ে বাজারে দাঁড়িয়ে তোমার roommate যোগ করলো দুধ। সন্ধ্যায় list এ দেখা গেলো: চাল, দুধ। ডিম নেই।
কেউ মোছেনি। দুটো phone ই ঠিক তাই করেছে যা করার কথা: list এর একটা copy রাখা, সেটা বদলানো, আবার save করা। দ্বিতীয় save টা শুধু প্রথমটার জায়গা নিয়ে নিয়েছে।
যে app এ একাধিক device একই তথ্যে হাত দেয়, সবাই এই সমস্যায় পড়ে। এই lesson এ engineer দের খুঁজে পাওয়া সবচেয়ে সহজ আর সবচেয়ে জরুরি উত্তরটা দেখবো: একটা copy রাখো যেটাকে সবাই আসল বলে মানে, আর সেখানে পুরো copy না পাঠিয়ে শুধু বদলটা পাঠাও।
সমস্যা: copy গুলো আলাদা হয়ে যায়
প্রতিটা device যখন shared data র নিজের একটা copy রাখে, তিনটা জিনিস গোলমাল হয়।
| সমস্যা | User কী দেখে |
|---|---|
| Copy গুলো মেলে না | Phone বলছে 5টা item, laptop বলছে 4টা। কোনটা ঠিক? |
| এক save আরেক save কে মুছে দেয় | দুজন একসাথে edit করলো, একজনের বদল চুপচাপ উধাও |
| কেউ সিদ্ধান্ত নিতে পারে না | "একজন একটাই vote" বা "10টার বেশি বেচা যাবে না" এর মতো rule এর জন্য এমন একটা জায়গা লাগে যেটা প্রতিটা request দেখে। Copy অনেকগুলো হলে কোনো device ই সব দেখে না |
Lesson 5 আর 6 এ এর কিছু অংশ আগেই দেখেছি: server এর copy গুলোর মধ্যে state ভাগ হয়ে যাওয়া, আর একটা seat এর জন্য দুটো request এর race। দুটোর গোড়া একই: একাধিক জায়গা ভেবেছিল আসল তথ্য তার কাছে।
ক্রিকেটের scoreboard এর কথা ভাবো
একটা ক্রিকেট ম্যাচে হাজার হাজার মানুষ score মনে রাখে মাথায়, phone এ, কাগজে। সবসময় মেলে না: কেউ একটা wide মিস করেছে, কেউ একটা চার দুবার গুনেছে।
তাতে কিছু যায় আসে না, কারণ একজন official scorer আছেন। রান হলে umpire ইশারা করেন, scorer লিখে রাখেন, বড় scoreboard এ দেখায়। তোমার হিসাব না মিললে তুমিই ভুল; board দেখে নিজেকে ঠিক করে নাও।
খেয়াল করো umpire scorer কে কী পাঠান: "score এখন 147" না, পাঠান "এক রান"। বদল যায় এক জায়গায়; বাকি সবাই সেখান থেকে পড়ে।
Analogy টা কোথায় খাটে না। Scoreboard একটাই জিনিস। আসল "single source of truth" সাধারণত একটা database, যেটা নিজেই হয়তো কয়েকটা machine এ চলে, security আর গতির জন্য copy সহ (Module 6)। "এক জায়গা" মানে প্রতিটা data র একজন logical মালিক, যার কথা সবাই মানে, সত্যি সত্যি একটা disk না।
মূল জিনিসগুলো
- Source of truth। প্রতিটা data র জন্য ঠিক একটা জায়গাই কর্তৃপক্ষ। বাকি যেকোনো copy (phone এ, cache এ, report এ) হলো একটা replica, যেটা পিছিয়ে থাকতে পারে, আর না মিললে source থেকে ঠিক করে নিতে হয়।
- Centralized data। বেশিরভাগ app এ shared data র source of truth হলো backend এর পেছনের একটা database। Shared data র মালিক client না; client সেটা চায়, আর বদলানোর request পাঠায়।
- Copy না, বদল পাঠাও। "ডিম যোগ করো" আর "দুধ যোগ করো" দুটো একসাথে খাটে। "এই নাও আমার পুরো list" শুধু আগে যা ছিল তার জায়গা নিতে পারে। Operation (যোগ, বাদ, বাড়ানো, paid হিসেবে চিহ্ন দেওয়া) একটার পর একটা বসানো যায়; পুরো snapshot আগেরটা মুছে দেয়। Lesson 6 এর atomic update এর শিক্ষাও এটাই।
- পড়া পুরনো হতে পারে; লেখা যায় source এ। দ্রুত দেখানো বা offline কাজের জন্য phone শেষবার যা দেখেছিল তা রেখে দিতে পারে। কিন্তু বদল যেতে হবে source of truth এ, যেটা বদলগুলো একটা ক্রমে বসায় আর নতুন উত্তর হয়ে যায়।
- Rule check করে source। সব বদল যেহেতু এর ভেতর দিয়ে যায়, rule (lesson 3 এর decide ধাপ) আর uniqueness (lesson 6) এর স্বাভাবিক জায়গা এটাই। অনেকগুলো copy তে আলাদা আলাদা check করা rule আসলে কোথাও check হয় না।
দুটো phone এর প্রত্যেকটায় list এর copy [rice]। Phone A ডিম যোগ করলো, phone B দুধ, আর দুজনই নিজের পুরো list server এ upload করলো, আগে A, তারপর B। Server এ শেষে কী থাকবে?
ভেতরে কী হয়: ডিম হারানোর গল্প
দুটো copy, একটা list
ধাপ 1/5 দুটো phone ই list load করে
Server এ আছে [rice]। প্রতিটা phone এটা download করে নিজের একটা copy রাখে, যাতে app সাথে সাথে দেখাতে পারে।
- server
- rice
- A copy
- rice
- B copy
- rice
মাথায় রাখার ছবি
একটা তারা আঁকো: মাঝখানে একটা source of truth, চারপাশে সব device।
- ভেতরের দিকের তীর হলো বদল ("add eggs", "pay order 41")। ছোট, কী করতে চায় সেটা বলে, আর কোনটা আগে কোনটা পরে সেটা মাঝের জন ঠিক করে।
- বাইরের দিকের তীর হলো পড়া। Device যা পড়ে তা রেখে দিতে পারে, কিন্তু সেটাকে একটা snapshot হিসেবে দেখে যেটা পুরনো হতে পারে।
- Device থেকে device এ কোনো তীর নেই। Device রা কখনো নিজেদের মধ্যে সরাসরি sync করে না; করলে আর একটা উত্তর থাকতো না।
কোনো feature design করার সময় জিজ্ঞেস করো: এই data কোন এক জায়গায় সত্যি? আঙুল দিয়ে দেখাতে না পারলে, একদিন না একদিন দুজন user এর তথ্য মিলবে না।
চেষ্টা করো: আসল তথ্য কোথায়?
একটা source of truth, নাকি অনেক copy?
app: 12,400 taka (10:02 এ cache করা)
গোড়া থেকে code
Shared shopping list টা, দুবার।
- অনেক copy: প্রতিটা phone নিজের list রাখে আর পুরোটা upload করে, অনেক সাধারণ app যেমন করে।
- একটা source of truth: phone শুধু বদলটা পাঠায়;
addচলে server এ, list এর মালিক server।
package main
import (
"fmt"
"slices"
"strings"
)
func show(step string, a, b, server []string) {
fmt.Printf("%-26s %-18s %-18s %s\n", step, strings.Join(a, ", "), strings.Join(b, ", "), strings.Join(server, ", "))
}
func main() {
fmt.Printf("%-26s %-18s %-18s %s\n", "step", "phone A", "phone B", "server")
// Many copies: each phone keeps its own list and uploads the whole thing.
server := []string{"rice"}
a := slices.Clone(server)
b := slices.Clone(server)
show("start", a, b, server)
a = append(a, "eggs")
show("A adds eggs on the phone", a, b, server)
b = append(b, "milk")
show("B adds milk on the phone", a, b, server)
server = slices.Clone(a) // upload: replace the server's list with mine
show("A uploads its list", a, b, server)
server = slices.Clone(b)
show("B uploads its list", a, b, server)
// One place: phones send the change; only the server holds the list.
fmt.Println("--- one source of truth: send the change, not the copy ---")
server = []string{"rice"}
add := func(item string) { server = append(server, item) } // runs on the server
add("eggs")
show("A sends: add eggs", nil, nil, server)
add("milk")
show("B sends: add milk", nil, nil, server)
show("A and B read the list", server, server, server)
}const show = (step, a, b, server) =>
console.log(`${step.padEnd(26)} ${a.join(", ").padEnd(18)} ${b.join(", ").padEnd(18)} ${server.join(", ")}`);
console.log(`${"step".padEnd(26)} ${"phone A".padEnd(18)} ${"phone B".padEnd(18)} server`);
// Many copies: each phone keeps its own list and uploads the whole thing.
let server = ["rice"];
const a = [...server];
const b = [...server];
show("start", a, b, server);
a.push("eggs");
show("A adds eggs on the phone", a, b, server);
b.push("milk");
show("B adds milk on the phone", a, b, server);
server = [...a]; // upload: replace the server's list with mine
show("A uploads its list", a, b, server);
server = [...b];
show("B uploads its list", a, b, server);
// One place: phones send the change; only the server holds the list.
console.log("--- one source of truth: send the change, not the copy ---");
server = ["rice"];
const add = (item) => server.push(item); // runs on the server
add("eggs");
show("A sends: add eggs", [], [], server);
add("milk");
show("B sends: add milk", [], [], server);
show("A and B read the list", server, server, server);fn show(step: &str, a: &[&str], b: &[&str], server: &[&str]) {
println!("{:<26} {:<18} {:<18} {}", step, a.join(", "), b.join(", "), server.join(", "));
}
// Runs on the server: the only place the list is changed.
fn add<'a>(server: &mut Vec<&'a str>, item: &'a str) {
server.push(item);
}
fn main() {
println!("{:<26} {:<18} {:<18} server", "step", "phone A", "phone B");
// Many copies: each phone keeps its own list and uploads the whole thing.
let mut server = vec!["rice"];
let mut a = server.clone();
let mut b = server.clone();
show("start", &a, &b, &server);
a.push("eggs");
show("A adds eggs on the phone", &a, &b, &server);
b.push("milk");
show("B adds milk on the phone", &a, &b, &server);
server = a.clone(); // upload: replace the server's list with mine
show("A uploads its list", &a, &b, &server);
server = b.clone();
show("B uploads its list", &a, &b, &server);
// One place: phones send the change; only the server holds the list.
println!("--- one source of truth: send the change, not the copy ---");
let mut server = vec!["rice"];
add(&mut server, "eggs");
show("A sends: add eggs", &[], &[], &server);
add(&mut server, "milk");
show("B sends: add milk", &[], &[], &server);
show("A and B read the list", &server, &server, &server);
}def show(step, a, b, server):
print(f"{step:<26} {', '.join(a):<18} {', '.join(b):<18} {', '.join(server)}")
print(f"{'step':<26} {'phone A':<18} {'phone B':<18} server")
# Many copies: each phone keeps its own list and uploads the whole thing.
server = ["rice"]
a = list(server)
b = list(server)
show("start", a, b, server)
a.append("eggs")
show("A adds eggs on the phone", a, b, server)
b.append("milk")
show("B adds milk on the phone", a, b, server)
server = list(a) # upload: replace the server's list with mine
show("A uploads its list", a, b, server)
server = list(b)
show("B uploads its list", a, b, server)
# One place: phones send the change; only the server holds the list.
print("--- one source of truth: send the change, not the copy ---")
server = ["rice"]
def add(item): # runs on the server
server.append(item)
add("eggs")
show("A sends: add eggs", [], [], server)
add("milk")
show("B sends: add milk", [], [], server)
show("A and B read the list", server, server, server)import java.util.ArrayList;
import java.util.List;
public class Main {
static void show(String step, List<String> a, List<String> b, List<String> server) {
System.out.printf("%-26s %-18s %-18s %s%n", step, String.join(", ", a), String.join(", ", b), String.join(", ", server));
}
// Runs on the server: the only place the list is changed.
static void add(List<String> server, String item) {
server.add(item);
}
public static void main(String[] args) {
System.out.printf("%-26s %-18s %-18s %s%n", "step", "phone A", "phone B", "server");
// Many copies: each phone keeps its own list and uploads the whole thing.
List<String> server = new ArrayList<>(List.of("rice"));
List<String> a = new ArrayList<>(server);
List<String> b = new ArrayList<>(server);
show("start", a, b, server);
a.add("eggs");
show("A adds eggs on the phone", a, b, server);
b.add("milk");
show("B adds milk on the phone", a, b, server);
server = new ArrayList<>(a); // upload: replace the server's list with mine
show("A uploads its list", a, b, server);
server = new ArrayList<>(b);
show("B uploads its list", a, b, server);
// One place: phones send the change; only the server holds the list.
System.out.println("--- one source of truth: send the change, not the copy ---");
server = new ArrayList<>(List.of("rice"));
add(server, "eggs");
show("A sends: add eggs", List.of(), List.of(), server);
add(server, "milk");
show("B sends: add milk", List.of(), List.of(), server);
show("A and B read the list", server, server, server);
}
}Output (পাঁচটা language এই একই):
step phone A phone B server
start rice rice rice
A adds eggs on the phone rice, eggs rice rice
B adds milk on the phone rice, eggs rice, milk rice
A uploads its list rice, eggs rice, milk rice, eggs
B uploads its list rice, eggs rice, milk rice, milk
--- one source of truth: send the change, not the copy ---
A sends: add eggs rice, eggs
B sends: add milk rice, eggs, milk
A and B read the list rice, eggs, milk rice, eggs, milk rice, eggs, milk
Output টা পড়ি:
- A adds eggs / B adds milk: প্রতিটা phone এর copy বদলায়, server এর টা না। দুজন user ই এমন list দেখে যেটা তাদের কাছে ঠিকই মনে হয়।
- A uploads, তারপর B uploads: server এর list প্রথমে হয়
rice, eggs, তারপরrice, milk। B এর upload ডিম "মোছেনি"; list টা এমন একটা copy দিয়ে বদলে দিয়েছে যেখানে ডিম কখনো ছিলই না। - একটা source of truth: server একই list এ আগে
add eggsতারপরadd milkবসায়, তাই দুটোই থাকে:rice, eggs, milk। - A and B read the list: দুটো phone এখন একই জিনিস দেখায়, কারণ দুজনই সেই এক জায়গা থেকে পড়ছে যেখানে তথ্যটা সত্যি।
দুই অংশের মধ্যে পার্থক্য শুধু একটাই: phone কী পাঠায়, নিজের পুরো copy, নাকি user কী করলো।
আসল engineering
- সাধারণত database ই source of truth। Backend stateless (lesson 5) যাতে scale করতে পারে; shared database সত্যটা ধরে রাখে আর constraint check করে। (Module 6।)
- API বানাও বদল ঘিরে।
POST /lists/7/items {name: eggs}আরDELETE /lists/7/items/31একটার পর একটা খাটে;PUT /lists/7 [পুরো list]আগেরটা মুছে দেয়। পুরো document নিতেই হলে version number রাখো, যাতে পুরনো copy save না হয়ে ফেরত যায় (optimistic concurrency, lesson 6)। - বাকি সব copy একেকটা cache। Mobile app এর storage, CDN, Redis, search index আর analytics table সবই replica। প্রত্যেকটার জন্য ঠিক করতে হয় কতটা পুরনো হলে চলবে আর কীভাবে refresh হবে। (Module 6, lesson 17–18।)
- Offline-first app এরও source আছে। যে app offline এ চলে, সে user এর বদলগুলো জমিয়ে রাখে আর connection ফিরলে পাঠায়; server সেগুলো ক্রমে বসায়। একদম peer-to-peer sync এর জন্য বিশেষ data type (CRDT) লাগে, ওটা advanced topic।
- Logical source একটা, machine অনেক হতে পারে। security আর দ্রুত পড়ার জন্য database replicate হয় (Module 6, lesson 15), আর খুব বড় database data কে অনেক machine এ ভাগ করে (lesson 16)। তবুও প্রতিটা data র ঠিক একটাই আসল ঘর।
যখন ভাঙে
একটা failure বাছো
PUT /profile {name, bio, photo}
laptop এ edit করা bio phone আবার আগের মতো করে দিলোপড়ার আগে ভাবো
একটা source of truth রাখা এতই জরুরি হলে প্রায় সব mobile app তবুও data র একটা local copy রাখে কেন?
উত্তর দেখো
গতি আর offline এ চালানোর জন্য। Phone থেকে পড়া সাথে সাথে হয়, network ছাড়া বাসেও চলে। আসল কৌশলটা হলো local copy কে cache হিসেবে দেখা: user শেষবার যা জানতো সেটা দেখায়, আর user যা বদলায় তা operation হিসেবে server এ যায়, যেটা সিদ্ধান্ত নেওয়ার একমাত্র জায়গা। Server এর উত্তর এলে local copy ঠিক করে নেওয়া হয়। Local copy সমস্যা না; সমস্যা হলো local কর্তৃত্ব।
নিজেকে check করো
1. "Single source of truth" মানে কী?
উত্তর
প্রতিটা data র জন্য একটা জায়গাই কর্তৃপক্ষ। বাকি প্রতিটা copy হলো replica, যেটা পুরনো হতে পারে আর সেই source থেকে ঠিক করে নেওয়া হয়।
2. "add milk" কেন "add eggs" এর পাশাপাশি টিকে থাকে, কিন্তু "এই নাও আমার list" টেকে না?
উত্তর
Operation বর্তমান data র ওপর একটার পর একটা বসানো যায়, তাই দুটোই কাজ করে। পুরো copy শুধু যা আছে তার জায়গা নিতে পারে, তাই শেষ upload জেতে আর আগের বদলগুলো হারায়।
3. "একজন customer সর্বোচ্চ 10টা" এর মতো rule কেন source of truth এ check করা উচিত?
উত্তর
কারণ শুধু source ই প্রতিটা device এর প্রতিটা বদল দেখে। একটা copy তে check করলে আরেকটা copy তে কী বদল হলো সেটা হিসাবে আসে না।
4. Backend সাধারণত ব্যবহার করে এমন দুই রকম replica র নাম বলো।
উত্তর
যেমন: cache (Redis, in-memory copy), CDN, database এর read replica, search index, বা mobile app এ রাখা data।
তোমার challenge: কিছু না হারিয়ে বাদ দাও
Program এর source-of-truth অংশে একটা remove operation যোগ করো। তারপর এই ঘটনাটা চালাও:
- List হলো
rice, eggs, milk। - Phone A
eggsবাদ দিলো। একই সময়ে phone B, যেটা এখনো পুরনো list দেখাচ্ছে,eggsকে12 eggsবানাতে পাঠালোremove eggsআরadd 12 eggs।
চালানোর আগে শেষ list আন্দাজ করো। দুজন user যা চেয়েছিল এটা কি তাই? কোনো user যেন অবাক না হয়, সেজন্য "নাম দিয়ে remove" এর বদলে কী পাঠানো উচিত ছিল?
সফল হয়েছো যখন তোমার program শেষ list print করে, আর তুমি বুঝিয়ে বলতে পারো কেন প্রতিটা item কে একটা id দিয়ে remove item 31 বা rename item 31 to "12 eggs" পাঠালে ফলাফল বোঝা সহজ হয়, নাম দিয়ে বাদ দেওয়ার চেয়ে।
Mental model
Shared data র এমন একটা জায়গা লাগে যেখানে সেটা সত্যি। দ্রুত হওয়ার জন্য device আর server copy রাখতে পারে, কিন্তু সেই copy গুলো cache, যেটা পুরনো হতে পারে। বদল source এ যায় operation হিসেবে ("add eggs"), বদলে দেওয়ার মতো পুরো copy হিসেবে না ("আমার list হলো..."), তাই বদলগুলো একটার ওপর আরেকটা মুছে না দিয়ে একসাথে খাটে। Source of truth বদলগুলো সাজায়, rule check করে, আর জরুরি প্রতিটা পড়ার উত্তর দেয়।
Station stamp
Chapter শেষ করো
পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।
Pro নিয়ে XP পাও →