Part 01 · Backend Mindset

Why Data Lives in One Place

DatabaseSourceOfTruth

প্রতিটা 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" না, পাঠান "এক রান"। বদল যায় এক জায়গায়; বাকি সবাই সেখান থেকে পড়ে।

send the change, not your copyphone Arice, eggsphone Brice, milkserver listthe one true copyrice, eggsrice, milkeggs lost: B's copy replaced A'sadd eggsadd milkrice, eggs, milk
প্রতিটা phone list এর পুরো copy upload করে, আর দ্বিতীয় upload প্রথম phone এর বদলটা মুছে দেয়। তারপর প্রতিটা phone server এর একটা list এ শুধু নিজের বদলটা পাঠায়, আর দুটো বদলই থেকে যায়।

Analogy টা কোথায় খাটে না। Scoreboard একটাই জিনিস। আসল "single source of truth" সাধারণত একটা database, যেটা নিজেই হয়তো কয়েকটা machine এ চলে, security আর গতির জন্য copy সহ (Module 6)। "এক জায়গা" মানে প্রতিটা data র একজন logical মালিক, যার কথা সবাই মানে, সত্যি সত্যি একটা disk না।

মূল জিনিসগুলো

  1. Source of truth। প্রতিটা data র জন্য ঠিক একটা জায়গাই কর্তৃপক্ষ। বাকি যেকোনো copy (phone এ, cache এ, report এ) হলো একটা replica, যেটা পিছিয়ে থাকতে পারে, আর না মিললে source থেকে ঠিক করে নিতে হয়।
  2. Centralized data। বেশিরভাগ app এ shared data র source of truth হলো backend এর পেছনের একটা database। Shared data র মালিক client না; client সেটা চায়, আর বদলানোর request পাঠায়।
  3. Copy না, বদল পাঠাও। "ডিম যোগ করো" আর "দুধ যোগ করো" দুটো একসাথে খাটে। "এই নাও আমার পুরো list" শুধু আগে যা ছিল তার জায়গা নিতে পারে। Operation (যোগ, বাদ, বাড়ানো, paid হিসেবে চিহ্ন দেওয়া) একটার পর একটা বসানো যায়; পুরো snapshot আগেরটা মুছে দেয়। Lesson 6 এর atomic update এর শিক্ষাও এটাই।
  4. পড়া পুরনো হতে পারে; লেখা যায় source এ। দ্রুত দেখানো বা offline কাজের জন্য phone শেষবার যা দেখেছিল তা রেখে দিতে পারে। কিন্তু বদল যেতে হবে source of truth এ, যেটা বদলগুলো একটা ক্রমে বসায় আর নতুন উত্তর হয়ে যায়।
  5. 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

Phone APhone BServer

ধাপ 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 এর তথ্য মিলবে না।

চেষ্টা করো: আসল তথ্য কোথায়?

প্রতিটা case চালিয়ে দেখো

একটা source of truth, নাকি অনেক copy?

app: 12,400 taka (10:02 এ cache করা)
কাজ করে: একটা replica। Bank এর খাতা (ledger) হলো source of truth। App একটা snapshot দেখায়, আর প্রতিটা payment ledger এ পাঠানো একটা বদল, যেটা আসল balance check করে।

গোড়া থেকে 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)
}

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 র ঠিক একটাই আসল ঘর।

যখন ভাঙে

প্রতিটা case চালিয়ে দেখো

একটা failure বাছো

PUT /profile {name, bio, photo}
laptop এ edit করা bio phone আবার আগের মতো করে দিলো
ভাঙে: Phone নিজের পুরো, পুরনো profile save করেছে। শুধু বদলানো field গুলো পাঠাও (PATCH), বা একটা version number দাও আর পুরনো version থেকে বানানো save ফিরিয়ে দাও।

পড়ার আগে ভাবো

একটা 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 যোগ করো। তারপর এই ঘটনাটা চালাও:

  1. List হলো rice, eggs, milk।
  2. 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 পাও →