🗄️ शाळेच्या पद्धतीने डेटाबेस शिका

शाळा आपले सत्य कुठे ठेवते, ते रेकॉर्ड रूम च्या रूपात शिकवलेले: नोंदवह्या, ओळींचे क्रमांक, पेन्सिलने लिहिलेले खातेवही, कार्ड कॅटलॉग, आगीपासून सुरक्षित प्रत. प्रत्येक धडा म्हणजे क्रमांकित आकृती असलेली शाळेची गोष्ट — आणि रेकॉर्ड रूम रेपोच्या आतच आहे: खरा SQLite डेटाबेस (Python चे अंगभूत sqlite3, शून्य dependencies) ज्यावर प्रत्येक धडा प्रश्न विचारतो, तो मोडतो आणि दुरुस्त करतो.

📇 tables आणि keys🔍 SQL✏️ transactions🗂️ indexes🔒 isolation🏗️ migrations🧯 backups🏘️ NoSQL🛡️ सुरक्षा🏆 window functions📡 replication🏛️ OLAP

📇 भाग 1 — रेकॉर्ड रूम (1–6)

  • चिकट चिठ्ठ्यांपेक्षा रेकॉर्ड रूम का बरा 🗄️
  • नोंदवह्या, ओळींचे क्रमांक, keys 📇
  • दप्तरदाराला विचारणे: SQL, JOINs 🔍
  • पेन्सिलचे खातेवही, एक तथ्य एक जागा, कॅटलॉग ✏️🧩🗂️

🏃 भाग 2 — चालवणे (7–12)

  • दोन कारकून, एक नोंदवही 🔒
  • शाळा चालू असतानाच नूतनीकरण 🏗️
  • आगीपासून सुरक्षित प्रत, अधिक कारकून आणि खोल्या 🧯📈
  • इतर खोल्या, आणि on-call चेकलिस्ट 🏘️🩺

🔬 भाग 3 — अधिक खोलात (13–18)

  • रजिस्टरला कुलूप: injection, पास, encryption 🛡️
  • क्रमवारी आणि reports: CTEs, window functions 🏆
  • engine च्या आत, खोलीच्या प्रती 📼📡
  • app ची बाजू, आणि शेजारचे संग्रहालय 🧑‍💻🏛️
# the 60-second wow — every lesson's queries, live:
git clone https://github.com/BaluRaut/learn-database-school.git && cd learn-database-school
python3 db/demo.py                 # builds school.db and runs reads, joins, a rollback, an index race, a lock, a restore…
python3 db/migrate.py              # renovates the room: three migrations, once each

तुम्हाला काय दिसायला हवे (छाटलेले — यश असे दिसते):

🗄️  school.db built from schema.sql + seed.sql

═══ reads ═══
── SELECT … WHERE … ORDER BY … LIMIT
    ('Aishwarya', '3A-01')
    ('Dipika', '3A-03')

═══ join ═══
── JOIN — cross-referencing three registers
    ('Aishwarya', '3A', 'maths', 'A')
    ('Meera', '3B', 'maths', 'A')
    ('Katrina', '3A', 'maths', 'A+')
    ('Rohan', '3B', 'maths', 'B')
    ('Dipika', '3A', 'maths', 'B+')

── GROUP BY — one row per class
    ('3A', 3)
    ('3B', 2)

═══ joins ═══
── INNER JOIN — only pupils WITH a card (matches on both sides)
    ('Aishwarya', 101)
    ('Katrina', 102)
    ('Meera', 103)

── LEFT JOIN — every pupil; no card → NULL
    ('Aishwarya', 101)
    ('Katrina', 102)
    ('Dipika', None)
    ('Meera', 103)
    ('Rohan', None)

── RIGHT JOIN — every card; a card with no pupil → NULL name
    ('Aishwarya', 101)
    ('Katrina', 102)
    ('Meera', 103)
    (None, 104)

── FULL OUTER JOIN — everyone from both sides
    ('Aishwarya', 101)
    ('Katrina', 102)
    ('Dipika', None)
    ('Meera', 103)
    ('Rohan', None)
    (None, 104)

── anti-join (LEFT JOIN … WHERE card IS NULL) — pupils with NO card
    ('Dipika',)
    ('Rohan',)

── CROSS JOIN — every class × every subject (the timetable grid)
    ('3A', 'maths')
    ('3A', 'science')
    ('3B', 'maths')
    ('3B', 'science')

── self join — two pupils in the same class (study buddies)
    ('Aishwarya', 'Katrina')
    ('Aishwarya', 'Dipika')
    ('Katrina', 'Dipika')
    ('Meera', 'Rohan')

═══ txn ═══
── before the transfer
    ('Aishwarya', 1)
    ('Meera', 2)

   💥 second update failed: FOREIGN KEY constraint failed → ROLLBACK

── after — the pencil was erased, BOTH changes gone
    ('Aishwarya', 1)
    ('Meera', 2)

═══ model ═══
── one fact, one place — the class name lives in ONE row
    (1, '3A')
    (2, '3B')

   💥 duplicate roll_no refused by the schema: UNIQUE constraint failed: students.roll_no 

   💥 impossible grade refused by CHECK: CHECK constraint failed: grade IN ('A+','A','B+','B','C') 

═══ index ═══
── without an index: SCAN attendance → 48 rows in 2.5 ms
── with the card catalogue: SEARCH attendance USING COVERING INDEX idx_attendance_student (student_id=?) → 48 rows in 0.0 ms

═══ locks ═══
── clerk 1 reads while clerk 2's change is uncommitted (isolation)
    (2, 'Katrina')

   ⏳ clerk 1 waited, then: database is locked — the lock protects the register

═══ backup ═══
── after the accident — Rohan is gone
    (1, 'Aishwarya')
    (2, 'Katrina')
    (3, 'Dipika')
    (4, 'Meera')

── after the restore — from the copy we tested
    (1, 'Aishwarya')
    (2, 'Katrina')
    (3, 'Dipika')
    (4, 'Meera')
    (5, 'Rohan')

═══ nplus1 ═══
── N+1: 6 queries, 0.11 ms · one JOIN: 1 query, 0.04 ms — same answer [('Aishwarya', 2), ('Katrina', 2)]…

═══ inject ═══
── the glued query the database actually received:
    SELECT name FROM students WHERE roll_no = 'x' OR '1'='1'
── ❌ string-glued login — every student leaks
    ('Aishwarya',)
    ('Katrina',)
    ('Dipika',)
    ('Meera',)
    ('Rohan',)

── ✅ parameterised (?) — the input is only ever a value
    (no rows)

── the reporting app's read-only connection reads: 5 students
   🔒 …and cannot write: attempt to write a readonly database 

═══ window ═══
── CTE + window: RANK() and a running total inside each class
    ('3A', 'Katrina', 20, 1, 20)
    ('3A', 'Aishwarya', 17, 2, 37)
    ('3A', 'Dipika', 15, 3, 52)
    ('3B', 'Meera', 18, 1, 18)
    ('3B', 'Rohan', 13, 2, 31)

── subquery: above the school average (16.6)
    ('Katrina', 20)
    ('Meera', 18)
    ('Aishwarya', 17)

── VIEW report_card — a saved question, used like a table
    ('Katrina', 2, 20)
    ('Meera', 2, 18)
    ('Aishwarya', 2, 17)

═══ procs ═══
── SQLite has no stored procedures (it runs inside your app); db/postgres/procedures.sql is the Postgres version.
   The closest SQLite pieces: a function the SQL can call, and a rule the room enforces for every app.

── points(grade) — a function used inside SQL (Postgres: CREATE FUNCTION points)
    ('Katrina', 20)
    ('Meera', 18)
    ('Aishwarya', 17)

   ⛔ Meera → 3A: class is full (3 pupils) — refused by the room itself, whichever app asked
   ✅ Aishwarya → 3B: moved

═══ wal ═══
── journal mode before: delete → after: wal
   ✍️  writer committed 'Aishwarya S' while the reader was mid-transaction — no 'database is locked'
   👀 reader, same transaction, still sees its snapshot: 'Aishwarya' (was 'Aishwarya')
   👀 reader, new transaction: 'Aishwarya S'
   📼 school.db-wal holds the new pages first: 4,152 bytes
   🧹 checkpoint copies them into school.db: -wal now 0 bytes
── journal mode back to: delete 

═══ replica ═══
── (simulated: school.db is the primary, an in-memory copy is the replica, a Python list is the shipped log)
── primary: 6 students · replica, before the log arrives: 5 — replication lag
   ↳ a parent who just enrolled Diya and reloads from the replica sees NO Diya: read your own writes from the primary
── log shipped → replica: 6 students
   💥 the primary dies before shipping Ishaan's line → promote the replica (failover)
── new primary has 6 students: Ishaan is LOST — async replication can lose the last writes (RPO > 0);
   synchronous replication waits for the replica's 'got it' before COMMIT returns, so nothing is lost — each commit pays a round trip

═══ appcode ═══
── 2,000 inserts: commit per row 459 ms · one transaction 1 ms — every COMMIT is a trip to the disk
   ⏳ attempt 1: database is locked → retry in 50 ms (backoff)
   ⏳ attempt 2: database is locked → retry in 100 ms (backoff)
   ✅ attempt 3: done

═══ olap ═══
── report on the live 200,000-line table: 43.6 ms · on the nightly summary (12 rows): 0.04 ms
   same answer: True → [('2026-01', 49.7), ('2026-02', 50.0), ('2026-03', 50.5)]
── CDC: a trigger records every grade change for the warehouse to pick up
    (9, 'B', 'A')

✅ done — the record room survived every lesson
🎒 धडा 01 आधी: तुम्हाला Python 3 लागतो — त्याचे अंगभूत sqlite3 हीच संपूर्ण रेकॉर्ड रूम; server नाही, Docker नाही, क्लाउड नाही. चांगले शेजारी: API शाळा (या खोलीसमोरचे काउंटर), AWS शाळेचा RDS धडा (खोली भाड्याने) आणि VectorDB शाळा (शेजारचा अर्थाचा हॉल). हे काय नाही: Postgres प्रशासनाचे पुस्तक — कल्पना Postgres, MySQL आणि SQLite मध्ये सारख्याच; कुठे फरक आहे ते धडे सांगतात.

🗺️ संपूर्ण चित्र — एक आकृती, पूर्ण कोर्स

संपूर्ण कोर्स एका कॅनव्हासवर. 4K आवृत्तीसाठी क्लिक करा.

The big picture: the record room (tables, keys, SQL, transactions, modelling, indexes), running it (concurrency, migrations, backups, scaling, NoSQL, operations) and going deeper (security, advanced SQL, WAL and MVCC, replication, app code, OLTP vs OLAP)

📇 भाग 1 — रेकॉर्ड रूम (धडे 1–6)

एक git ब्रँच = एक कल्पना; ब्रँच 04 मध्ये धडे 01–04 आहेत. लॅब साध्या Python 3 वर चालतात — डेटाबेस server स्थापायची गरज नाही.

1

🗄️ डेटाबेस का

रेकॉर्ड रूम विरुद्ध चिकट चिठ्ठ्या — टिकाऊ, सामायिक, आणि प्रश्नाला उत्तर देणारी.lesson-01-why-databasesधडा वाचा →आकृती पहा ↗
2

📇 Tables, rows & keys

क्रमांकित ओळी असलेल्या नोंदवह्या — primary keys, foreign keys, वर्ग → विद्यार्थी → श्रेणी हा आकार.lesson-02-tables-rows-keysधडा वाचा →आकृती पहा ↗
3

🔍 SQL वाचन

दप्तरदाराला विचारणे — SELECT, WHERE, ORDER BY, LIMIT, आणि प्रत्येक JOIN: inner, left, right, full outer, anti, cross आणि self.lesson-03-sql-readsधडा वाचा →आकृती पहा ↗
4

✏️ लिहिणे & transactions

पेन्सिलचे खातेवही — INSERT, UPDATE, DELETE, आणि ACID खरे करणारा खोडरबर (ROLLBACK).lesson-04-transactionsधडा वाचा →आकृती पहा ↗
5

🧩 Data modelling

एक तथ्य, एक जागा — normalisation, constraints, आणि हा नियम मुद्दाम कधी मोडायचा.lesson-05-data-modellingधडा वाचा →आकृती पहा ↗
6

🗂️ Indexes

कार्ड कॅटलॉग — B-trees, EXPLAIN, आणि कॅटलॉग तुम्हाला कधी हळू करतो.lesson-06-indexesधडा वाचा →आकृती पहा ↗

🏃 भाग 2 — रेकॉर्ड रूम खऱ्या जगात चालवणे (धडे 7–12)

production डेटाबेसला लागणारे सगळे, प्रत्येक गोष्ट त्याच छोट्या शाळेच्या डेटाबेसवर दाखवलेली.

7

🔒 Concurrency & isolation

दोन कारकून, एक नोंदवही — locks, isolation levels, आणि deadlock चा हस्तांदोलन.lesson-07-concurrencyधडा वाचा →आकृती पहा ↗
8

🏗️ Migrations

शाळा चालू असतानाच खोलीचे नूतनीकरण — आवृत्ती असलेल्या scripts, आधी expand मग contract.lesson-08-migrationsधडा वाचा →आकृती पहा ↗
9

🧯 Backups & recovery

आगीपासून सुरक्षित प्रत — RPO, RTO, point-in-time, आणि तुम्ही खरोखर करता ती restore ची रंगीत तालीम.lesson-09-backups-recoveryधडा वाचा →आकृती पहा ↗
10

📈 Scaling

अधिक कारकून, अधिक खोल्या — pools, replicas, caches, partitions, आणि shard कधी करू नये.lesson-10-scalingधडा वाचा →आकृती पहा ↗
11

🏘️ NoSQL & इतर खोल्या

Document, key-value, columnar, graph, vector — प्रश्नासाठी योग्य खोली निवडणे.lesson-11-nosql-other-roomsधडा वाचा →आकृती पहा ↗
12

🩺 Performance & operations

हळू queries ची तपासणी, N+1, observability — रेकॉर्ड रूम on call जाण्याआधीची चेकलिस्ट.lesson-12-performance-opsधडा वाचा →आकृती पहा ↗

🔬 भाग 3 — अधिक खोलात (धडे 13–18)

interviews आणि incidents मध्ये विचारले जाणारे विषय: सुरक्षा, advanced SQL, log आणि MVCC, replication आणि failover, app ची बाजू, आणि analytics — प्रत्येक school.db वर प्रत्यक्ष चालवलेला.

13

🛡️ सुरक्षा

कुलूपबंद रजिस्टर — SQL injection, parameters, least privilege, आणि transit मध्ये व at rest encryption.lesson-13-securityधडा वाचा →आकृती पहा ↗
14

🏆 Advanced SQL

एकही row न गमावता क्रम — subqueries, CTEs, window functions, views, आणि room मध्ये राहणारा code: functions आणि stored procedures.lesson-14-advanced-sqlधडा वाचा →आकृती पहा ↗
15

📼 engine च्या आत

log आधी येतो — WAL, crash recovery, MVCC snapshots आणि vacuum.lesson-15-inside-the-engineधडा वाचा →आकृती पहा ↗
16

📡 Replication आणि failover

खोलीच्या प्रती — log चा प्रवाह, lag, sync विरुद्ध async, failover आणि CAP trade-off.lesson-16-replication-failoverधडा वाचा →आकृती पहा ↗
17

🧑‍💻 code मधून database

app ची बाजू — pools, transactions, parameters, batching, backoff सह retries, आणि ORMs.lesson-17-app-codeधडा वाचा →आकृती पहा ↗
18

🏛️ OLTP विरुद्ध OLAP

कार्यालय आणि संग्रहालय — warehouses, रात्रीचा ETL, columnar storage, आणि trigger वापरून CDC.lesson-18-oltp-olapधडा वाचा →आकृती पहा ↗
🗣️ हे मोठ्याने समजावा — धडा 18 नंतर: (0) हाताने quotes escape केल्याने SQL injection थांबत नाही पण parameter ने का थांबते, आणि WAL मुळे commit झालेली row वीज गेल्यावरही का टिकते? मग: (1) foreign key मुळे वाईट row फक्त कमी शक्य न राहता अशक्यच का होते? (2) एक transaction दोन rows update करतो आणि दुसरा अपयशी होतो — नंतर disk वर काय असते, आणि का? (3) index जोडल्याने system कधी हळू होते? (4) दोन कारकून, एक row: isolation काय लपवते, आणि deadlock कसा दिसतो? (5) downtime शिवाय column चे नाव बदला — चार पायऱ्या सांगा. (6) RPO आणि RTO प्रत्येकी एका वाक्यात, आणि तुमच्या शेवटच्या restore सरावाची तारीख.
🎓 याच शाळेतून: API · UI · VectorDB · AWS · Kubernetes — तेच उपमांचे विश्व, तीच ब्रँच-मागून-ब्रँच पद्धत.

📐 धड्यांच्या आकृत्या — क्रमांक अनुसरा

प्रत्येक धडा एका क्रमांकित बॉक्स-आणि-बाण आकृतीत — स्वतंत्र पानावरही.

1 🗄️ डेटाबेस का

रेकॉर्ड रूम विरुद्ध चिकट चिठ्ठ्या — टिकाऊ, सामायिक, आणि प्रश्नाला उत्तर देणारी.

🧒 सोप्या शब्दांत

तीन शिक्षकांनी आपापली grades ची spreadsheet ठेवली, तर लवकरच copies जुळत नाहीत, शेवटचा save जिंकतो, आणि laptop बंद पडला की सगळे हरवते. शाळेचे record room हे सोडवते: नोंदींचा एकच संच, disk वर सुरक्षित, अनेक clerks एकाच वेळी वापरतात, आणि '3A मधील A मिळालेली सगळी मुले' असे प्रश्न विचारता येतात. computer साठी database म्हणजे हेच record room.

📖 नवे शब्दdatabase — अनेक लोक आणि apps वापरतात असा नोंदींचा एकच व्यवस्थित साठाdurable — एकदा save झाले की crash किंवा वीज गेली तरी टिकतेshared — अनेक clerks एकमेकांचे काम न पुसता एकाच नोंदींवर काम करतातSQL — record room ला प्रश्न विचारण्याची भाषा
1📝 चिकट चिठ्ठ्या आणि spreadsheets — तीन प्रती, सत्य एकही नाहीशिक्षक Agrades.xlsxशिक्षक Bgrades.xlsxकार्यालयgrades.xlsx💥✗ शेवटचा save जिंकतो💥 laptop मरतो,चिठ्ठ्या मरतात"A मिळालेला 3A चा प्रत्येक विद्यार्थी?" — कोणी विचारू शकत नाही, कोणी उत्तर देऊ शकत नाहीटिकाऊपणा नाही · वाटणे नाही · प्रश्न नाहीतप्रत्येक app ला एक फाइल, शेवटी save करणाऱ्याने पुसलेली2🗄️ रेकॉर्ड रूम — एक सत्य, तीन वचनेschool.db1 टिकाऊdisk वर लिहिलेले —crash नंतरही टिकते ✓🧑‍💼🧑‍💼🧑‍💼2 सामाईकअनेक कारकून, एक सत्यlocks, transactionsSELECT name FROM studentsWHERE grade = 'A' …3 उत्तर देणारीSQL मध्ये विचारा, catalogue ने जलद (L06)nameAishwaryaMeera3🚪 प्रत्येक counter मागे एक रेकॉर्ड रूमbrowserAPI (API शाळा)db$ python3 db/demo.py🗄️ school.db built from schema.sql + seed.sql═══ reads ═══ ═══ join ═══ ═══ txn ═══ ═══ model ══════ index ═══ ═══ locks ═══ ═══ backup ═══ ═══ nplus1 ═══इथे SQLite: एक फाइल, setup शून्य —Postgres आणि MySQL सारखेच SQLप्रत्येक धड्याची query school.db वर प्रत्यक्ष चालते — output वाचा, मग एक गोष्ट बदला
⏪ आधी

sticky notes आणि spreadsheets मध्ये grades.xlsx च्या तीन copies असतात, शेवटचा save जिंकतो, आणि laptop बंद पडला की सगळे जाते.

💡 काय

database म्हणजे record room: एकच सत्य, जे टिकाऊ आहे, अनेक clerks मध्ये shared आहे, आणि प्रश्न विचारून उत्तर देते.

⚙️ कसे

school.db disk वर लिहिले जाते, locks आणि transactions अनेक clerks ना ते share करू देतात, आणि SELECT name FROM students सारखा SQL प्रश्न विचारतो.

🎯 का

ते crash मधून वाचते, दुसऱ्याच्या save मुळे काम हरवत नाही, आणि 3A मधील A मिळालेली सर्व मुले सारख्या प्रश्नांची उत्तरे देते.

🚀 पुढे

पुढे तुम्ही registers स्वतः बनवाल: tables, rows, आणि classes, students व grades जोडणाऱ्या keys.

🧪 Try it here — चिकट चिठ्ठ्यांना विचारा, मग रेकॉर्ड रूमला विचारा

संपूर्ण धडा 01 वाचा →

2 📇 Tables, rows & keys

क्रमांकित ओळी असलेल्या नोंदवह्या — primary keys, foreign keys, वर्ग → विद्यार्थी → श्रेणी हा आकार.

🧒 सोप्या शब्दांत

शाळा तीन registers ठेवते: वर्ग, विद्यार्थी आणि grades. प्रत्येक ओळीला एक क्रमांक असतो जो पुन्हा कधी वापरला जात नाही, म्हणून student 4 म्हणजे नेहमी Meera. Meera च्या वर्गाचे नाव पुन्हा लिहिण्याऐवजी तिची ओळ फक्त सांगते 'वर्ग register, ओळ 2 पाहा'. ओळ क्रमांकाकडे बोट दाखवल्याने प्रत्येक माहिती एकाच ठिकाणी राहते, म्हणून typo मुळे एका वर्गाचे दोन वर्ग होत नाहीत.

📖 नवे शब्दtable — एक register: एकाच प्रकारच्या गोष्टींच्या rowsrow — register मधील एक ओळ, उदा. एक विद्यार्थीcolumn — प्रत्येक ओळीत असणारे एक field, उदा. name किंवा class_idprimary key — दुसऱ्या कोणत्याही ओळीला न मिळणारा आणि पुन्हा न वापरला जाणारा ओळ क्रमांकforeign key — दुसऱ्या register मधील ओळीकडे बोट दाखवणारी नोंद
1📇 क्रमांकित ओळींच्या तीन नोंदवह्या — आणि दुसऱ्या ओळींकडे बोट दाखवणाऱ्या ओळीclassesidname13A23Bstudentsidnameroll_noclass_id1Aishwarya3A-0112Katrina3A-0213Dipika3A-0314Meera3B-0125Rohan3B-022gradesidstudent_idsubjecttermgrade11maths1A21science1B+32maths1A+74maths1APK: कोणीच पुन्हा न वापरणारा ओळ क्रमांकFK: "नोंदवही X, ओळ 3 पहा"students.class_id → classes.id: Aishwarya नोंदवही 1 (3A) मध्ये · grades.student_id → students.id: grade 7 Meera चीएक वर्ग → अनेक विद्यार्थी → अनेक श्रेणी: वेगवेगळ्या नोंदवह्यांतल्या ओळी, keys ने जोडलेल्या, कधीच कॉपी न केलेल्याUNIQUE(student_id, subject, term): प्रत्येक विद्यार्थी, विषय, term ला एक grade · roll_no UNIQUE: ओळखपत्रावरचा क्रमांकdb/schema.sql नेमक्या याच तीन नोंदवह्या आखते (आणि homework) — वाचा, मग python3 db/demo.py join चालवा2🚫 कुठेच न दाखवणारी ओळ database नाकारतोINSERT INTO grades (student_id, subject, term, grade) VALUES (99, 'maths', 1, 'A');💥 FOREIGN KEY constraint failed — विद्यार्थी 99 नाहीDELETE FROM students WHERE id = 5;→ ON DELETE CASCADE: Rohan चे grades त्याच्यासोबत जातात(किंवा REFERENCES … RESTRICT: grades असेपर्यंत नकार)
⏪ आधी

keys शिवाय मुलाचा वर्ग प्रत्येक ओळीवर मोकळ्या text मध्ये लिहिला जातो, आणि कोणता grade कोणाचा हे काहीच सांगत नाही.

💡 काय

table म्हणजे क्रमांक असलेल्या ओळींचे register: primary key हा कोणी पुन्हा न वापरणारा ओळ क्रमांक, foreign key दुसरीकडे बोट दाखवते.

⚙️ कसे

students.class_id → classes.id Aishwarya ला class 1 (3A) मध्ये ठेवते, आणि grades.student_id → students.id सांगते grade 7 Meera चा आहे.

🎯 का

प्रत्येक fact एकदाच साठवला जातो आणि keys ने जोडला जातो, म्हणून data वाढला तरी class → student → grade आकार बरोबर राहतो.

🚀 पुढे

जोडलेले registers तयार झाल्यावर पुढचा lesson SELECT आणि JOIN ने त्यांना प्रश्न विचारतो.

🧪 Try it here — कुठेच न दाखवणारी ओळ, किंवा आधीच असलेला क्रमांक लिहून पहा

संपूर्ण धडा 02 वाचा →

3 🔍 SQL वाचन

दप्तरदाराला विचारणे — SELECT, WHERE, ORDER BY, LIMIT, आणि प्रत्येक JOIN: inner, left, right, full outer, anti, cross आणि self.

🧒 सोप्या शब्दांत

तुम्ही archivist ला एक चिठ्ठी देता: 'वर्ग 3A मधील नावे, क्रमाने, फक्त पहिली 2'. तुम्ही काय हवे ते सांगता; ते कसे शोधायचे ते archivist ठरवतो. बऱ्याचदा उत्तर अनेक registers मध्ये विखुरलेले असते, म्हणून JOIN क्रमांक जुळवून त्यांना शेजारी लावतो. जुळणी नसेल तेव्हा कोण राहणार हे प्रत्येक प्रकारचा JOIN ठरवतो: फक्त जुळलेल्या जोड्या, एका बाजूचे सगळे, किंवा दोन्ही बाजूंचे सगळे.

📖 नवे शब्दSELECT … WHERE — कोणते columns दाखवायचे आणि कोणत्या rows ठेवायच्या ते निवडाORDER BY / LIMIT — उत्तर क्रमाने लावा, मग फक्त पहिल्या काही rows ठेवाINNER JOIN — दोन्ही बाजूंना जुळणी असलेल्या rows च ठेवतोLEFT JOIN — डाव्या बाजूची प्रत्येक row ठेवतो; उजवीकडे जुळणी नसेल तिथे रिकामेanti join — जुळणी नसलेल्या rows शोधतो, उदा. अजून grade नसलेली मुले
1🔍 एक चिठ्ठी, चार पायऱ्या — मार्ग archivist ठरवतोSELECT name, roll_noFROM students WHERE class_id = 1ORDER BY name LIMIT 2FROM students5 ओळीWHERE class_id = 13 ओळीORDER BY nameAishwarya Dipika KatrinaLIMIT 2Aishwarya, Dipikaचिठ्ठी सांगते काय;planner ठरवतो कसे(धडा 06)गाळा → लावा → पान → निवडा · SELECT name, roll_no उरलेल्यांचे फक्त दोन स्तंभ ठेवते2🔗 JOIN — तीन नोंदवह्यांचे एक रुंद उत्तरgradesstudent_idsubjectgrade1mathsAstudentsidnameclass_id1Aishwarya1classesidname13AON grades.student_id = students.idON students.class_id = classes.idnameवर्गsubjectgradeAishwarya3AmathsAMeera3BmathsAप्रत्येक grade ला एक रुंद ओळ — keys नोंदवह्या चिकटवतात, काहीच copy होत नाही3Σ GROUP BY — प्रत्येक वर्गाला एक ओळ, आणि archivist च्या कामाचा क्रमnameclass_idAishwarya1Katrina1Dipika1Meera2Rohan2वर्गCOUNT(*)3A33B2GROUP BY class_idFROMWHEREGROUP BYHAVINGSELECTORDER BYLIMITप्रत्येक गटाला COUNT, AVG, MAX, MIN · HAVING गट गाळते, WHERE ओळीpython3 db/demo.py reads join — इथली प्रत्येक query, school.db वर प्रत्यक्षLIMIT/OFFSET उत्तराची पाने करते · चुकीचा ORDER BY पाने एकमेकांवर आणतो — नेहमी unique गोष्टीने (id) लावा4🔗 JOIN कुटुंब — जोडीदार नसेल तेव्हा कोणत्या rows टिकतात?students (डावी)idname1Aishwarya2Katrina3Dipika4Meera5Rohanlibrary_cards (उजवी)cardstudent_id1011102210341049 · पाहुणाDipika आणि Rohan कडे card नाही · card 104 पाहुण्याचे आहे, विद्यार्थ्याचे नाहीON l.student_id = s.id — प्रश्न असा की ज्या rows ला जोडीदार मिळत नाही त्यांचे काय करायचे⚠️ नसलेला जोडीदार NULL म्हणून दिसतो — आणि WHERE col = NULL कधीच जुळत नाही: IS NULL वापराINNER JOINदोन्ही बाजूंनी जुळणारेnamecardAishwarya101Katrina102Meera103LEFT JOINप्रत्येक विद्यार्थी; card नाही → NULLnamecardAishwarya101Katrina102DipikaNULLMeera103RohanNULLRIGHT JOINप्रत्येक card; विद्यार्थी नाही → NULLnamecardAishwarya101Katrina102Meera103NULL104FULL OUTER JOINदोन्ही बाजूंचे सगळेnamecardAishwarya101Katrina102DipikaNULLMeera103RohanNULLNULL104anti-joincard नसलेले विद्यार्थीLEFT JOIN … WHERE l.card IS NULLnameDipikaRohanCROSS JOINप्रत्येक जोडी: 2 × 2 = 4classes × subjects — ON नाहीवर्गsubject3Amaths3Ascience3Bmaths3Bscienceself joinएकच वर्ग, a.id < b.idstudents a JOIN students ba.nameb.nameAishwaryaKatrinaAishwaryaDipikaKatrinaDipikaMeeraRohan🧭 कोणता वापरायचा?INNER — फक्त पूर्ण जोड्याLEFT — मुख्य यादी ठेवा(reports मध्ये सर्वात जास्त)RIGHT — बाजू बदललेला LEFTFULL — दोन याद्या जुळवून पाहाanti — "कोण राहिले?"CROSS — प्रत्येक combinationself — rows विरुद्ध त्यांचेच table
⏪ आधी

SQL शिवाय एका उत्तरासाठी प्रत्येक register हाताने scroll करावे लागेल आणि तीन tables मधून जुळणाऱ्या ओळी copy कराव्या लागतील.

💡 काय

SELECT म्हणजे archivist ला दिलेली चिठ्ठी: ती तुम्हाला काय हवे ते सांगते, आणि planner ते कसे आणायचे ते ठरवतो.

⚙️ कसे

WHERE class_id = 1 पाचपैकी 3 rows ठेवतो, ORDER BY name LIMIT 2 Aishwarya आणि Dipika देतो, आणि JOIN ... ON grades ला students शी जोडतो.

🎯 का

INNER फक्त जुळणारे ठेवतो, LEFT प्रत्येक विद्यार्थी ठेवतो (Dipika → NULL), FULL दोन्ही बाजू ठेवतो — report वर कोण ते join ठरवतो.

🚀 पुढे

वाचणे सुरक्षित आहे; पुढे तुम्ही transactions मध्ये INSERT, UPDATE आणि DELETE ने data बदलाल.

🧪 Try it here — चिठ्ठी बांधा आणि ओळी कमी होताना पहा
🧪 Try it here — JOIN निवडा — डावीकडे विद्यार्थी, उजवीकडे library cards; कोणत्या rows टिकतात ते पाहा

संपूर्ण धडा 03 वाचा →

4 ✏️ लिहिणे & transactions

पेन्सिलचे खातेवही — INSERT, UPDATE, DELETE, आणि ACID खरे करणारा खोडरबर (ROLLBACK).

🧒 सोप्या शब्दांत

दोन मुलांना नव्या वर्गात हलवायला दोन बदल लागतात. आधी ते pencil ने लिहा: वर्ग 99 नसल्यामुळे दुसरा बदल fail झाला, तर खोडरबर दोन्ही पुसते, म्हणून register मध्ये कधीच अर्धवट बदल दिसत नाही. सगळे ठीक असेल तर COMMIT pencil चे एकदम शाईत रूपांतर करतो, आणि वीज गेली तरी ते पुसले जात नाही. सगळे, किंवा काहीच नाही.

📖 नवे शब्दINSERT / UPDATE / DELETE — ओळ जोडा, ओळ बदला, ओळ काढाtransaction — एकत्र होणारे अनेक बदल: सगळे किंवा काहीच नाहीROLLBACK — खोडरबर: BEGIN पासूनच्या सगळ्या pencil खुणा पुसाCOMMIT — pencil चे शाईत रूपांतर, disk वर सुरक्षित saveACID — चार वचने: सगळे-किंवा-काहीच नाही, योग्य, इतरांपासून वेगळे, कायमचे
1✏️ पेन्सिलीतली वही — दोन खुणा, एक खोडरबरBEGINUPDATE students SET class_id=2 WHERE id=1✏️ पेन्सिलUPDATE students SET class_id=99 WHERE id=4💥 वर्ग 99 नाहीROLLBACK — खोडरबरदोन्ही खुणा गायबstudents — आधी / पेन्सिल / नंतरidnameclass_id1Aishwarya1 ✏️2 → 14Meera2 ✏️99 → 2वही अर्धी बदली कधीच दाखवत नाही:सगळे, किंवा काहीच नाहीpython3 db/demo.py txn — दुसरे update FK वर नापास, पहिले त्याच्यासोबत खोडले जाते2✅ COMMIT — शाई, एकाच वेळी, crash-प्रूफBEGINUPDATE … ✓ UPDATE … ✓COMMITdisk🔌 वीज गेली → तरी आहेwith conn: # Pythonconn.execute(...) # पेन्सिलconn.execute(...) # पेन्सिल# exception नाही → COMMIT · exception → ROLLBACK3🧾 ACID — वहीची चार वचने⚛️ Atomicसगळे किंवा काहीच नाही — खोडरबर✅ Consistentप्रत्येक commit नंतर constraints पाळलेले🚪 Isolatedपेन्सिलीच्या खुणांकडे डोकावणे नाही (L07)🔌 Durableशाई वीज गेल्यावरही टिकतेINSERT · UPDATE · DELETE COMMIT पर्यंत पेन्सिल — transactions छोट्या ठेवा (L07) आणि आत कधीच माणसाची वाट पाहू नका
⏪ आधी

transactions शिवाय एखादे transfer अर्ध्यावर थांबू शकते, एक मूल हलवलेले आणि दुसरा बदल तुटलेला राहतो.

💡 काय

transaction म्हणजे pencil ने लिहिलेले ledger: BEGIN, बदल करा, मग COMMIT करून शाईने पक्के करा किंवा ROLLBACK ने सगळे खोडा.

⚙️ कसे

python3 db/demo.py txn मध्ये class 99 चा update FK वर fail होतो, म्हणून ROLLBACK पहिला update सुद्धा खोडतो.

🎯 का

ledger कधीच अर्धे transfer दाखवत नाही: सगळे किंवा काहीच नाही, आणि COMMIT झालेला बदल power cut नंतरही टिकतो.

🚀 पुढे

सुरक्षित writes ला लिहिण्यासाठी चांगला आकार लागतो, तो पुढचा data modelling चा lesson देतो.

🧪 Try it here — दोन पेन्सिलीच्या खुणा करा, मग खोडरबर की शाई ते निवडा

संपूर्ण धडा 04 वाचा →

5 🧩 Data modelling

एक तथ्य, एक जागा — normalisation, constraints, आणि हा नियम मुद्दाम कधी मोडायचा.

🧒 सोप्या शब्दांत

Ms Rao चा phone नंबर एका मोठ्या register च्या तीसही rows वर लिहिला असेल, तर नवा नंबर आला की तीस rows दुरुस्त कराव्या लागतात, आणि कोणीतरी काही विसरतोच. चांगली रचना प्रत्येक माहिती नेमकी एकाच ठिकाणी लिहिते: शिक्षक एका register मध्ये, वर्ग दुसऱ्यात, क्रमांकाने जोडलेले. constraints नावाचे नियम दारावर पहारा देतात म्हणून चुकीचा data आत येत नाही. कधी कधी वेगासाठी माहिती मुद्दाम copy करतात, पण फक्त जाणीवपूर्वक.

📖 नवे शब्दnormalisation — प्रत्येक माहिती एकदाच साठवणे, आणि keys ने तिच्याशी जोडणेanomaly — एकच माहिती अनेक rows मध्ये असल्यामुळे होणारी चूकconstraint — database पाळायला लावतो असा नियम, उदा. 'grade रिकामा नको'denormalise — वाचणे जलद व्हावे म्हणून माहिती मुद्दाम copy करणे
1❌ एक मोठी नोंदवही — तीच गोष्ट तीस वेळा लिहिलेलीविद्यार्थीवर्गशिक्षकteacher_phonegradeAishwarya3AMs Rao98201…AKatrina3AMs Rao98201…A+Dipika3AMs Rao98201…B+Meera3BMr Das99110…AMs Rao चा फोन बदलतो →30 ओळी दुरुस्त, 29 विसरल्याupdate anomaly: गोष्ट प्रत्येक ओळीत · insert anomaly: नव्या शिक्षकालाविद्यार्थी लागतो · delete anomaly: शेवटचा विद्यार्थी काढा, शिक्षक हरवतोspreadsheet म्हणजे नेहमीच एक मोठी नोंदवही — म्हणूनच कार्यालयात तीन प्रती2✅ एक गोष्ट, एक जागा — normalised, keys ने जोडलेली📇 classesid · name📇 teachersid · name · phone📇 studentsid · name · roll_no · class_id📇 gradesid · student_id · subject · grade1 → अनेक1 → अनेक1 → अनेकMs Rao चा फोन: एकच cell, teachers मध्येप्रत्येक table ला तीन प्रश्न: एक ओळ म्हणजे काय? तिला काय ओळखते? कोणत्या गोष्टी तिच्या, तिच्या pointer च्या नव्हे?3🚫 schema मूर्खपणा नाकारते — आणि नियम मुद्दाम कधी मोडायचाINSERT INTO students … roll_no='3A-02'💥 UNIQUE constraint failed: students.roll_noINSERT INTO grades … grade='Z'💥 CHECK constraint failed: grade IN (…)INSERT INTO students … class_id=99💥 FOREIGN KEY constraint failed⚖️ मुद्दाम denormalise करागरम JOIN टाळायला class_name grades मध्ये copy करा— फक्त मोजलेल्या कारणाने (L12),आणि प्रत खरी ठेवणाऱ्या नियमानेpython3 db/demo.py modelCHECK, UNIQUE, NOT NULL, FK: table नाही म्हणण्याचे चार मार्ग — कोणत्याही code review पेक्षा स्वस्त
⏪ आधी

एका मोठ्या register मध्ये Ms Rao चा phone तीस rows वर लिहिलेला असतो, म्हणून एक बदल केला की त्यातील 29 चुकीचे राहतात.

💡 काय

normalisation म्हणजे एक fact एकाच ठिकाणी: classes, teachers, students आणि grades वेगळ्या tables मध्ये, keys ने जोडलेले.

⚙️ कसे

teacher चा phone teachers मध्ये एकदाच राहतो, classes key ने त्याकडे बोट दाखवतात, आणि constraints नियम मोडणाऱ्या rows नाकारतात.

🎯 का

हे update, insert आणि delete anomalies दूर करते, आणि नियम फक्त जाणूनबुजून, स्पष्ट कारणासाठीच मोडला जातो.

🚀 पुढे

स्वच्छ model सुद्धा शोधायला हळू असू शकते, म्हणून पुढचा lesson indexes, म्हणजे card catalogue, जोडतो.

🧪 Try it here — एका मोठ्या नोंदवहीत शिक्षकाचा फोन बदला, मग normalised खोलीत

संपूर्ण धडा 05 वाचा →

6 🗂️ Indexes

कार्ड कॅटलॉग — B-trees, EXPLAIN, आणि कॅटलॉग तुम्हाला कधी हळू करतो.

🧒 सोप्या शब्दांत

catalogue नसलेल्या library मध्ये एक पुस्तक शोधायला प्रत्येक कपाटासमोरून जावे लागते. 200,000 हजेरीच्या ओळींमध्ये student 4242 शोधणे असेच आहे: सगळ्या ओळी वाचा. index म्हणजे card catalogue, ड्रॉवरच्या आत ड्रॉवर असे क्रमाने लावलेले, म्हणून database साधारण 3 पायऱ्यांत तिथे पोहोचतो. किंमत: प्रत्येक नवी ओळ catalogue मध्येही नोंदवावी लागते, म्हणून खूप जास्त indexes लिहिणे हळू करतात.

📖 नवे शब्दindex — थेट योग्य rows कडे नेणारा क्रमाने लावलेला cataloguescan — जुळणारी row शोधायला प्रत्येक row एकेक करून वाचणेB-tree — पटकन शोध कमी करण्यासाठी index वापरतो तो ड्रॉवरच्या-आत-ड्रॉवर आकारEXPLAIN — database ला विचारा की तो उत्तर कसे शोधणार आहे
1🚶 SCAN — विद्यार्थी 4242 शोधायला प्रत्येक ओळ वाचाattendance ओळ 1attendance ओळ 2attendance ओळ 3attendance ओळ …attendance ओळ 4242attendance ओळ …attendance ओळ 199,998attendance ओळ 199,999attendance ओळ 200,000← बोट चालतेसगळ्या 200,000 ओळीप्रत्येक lookup ला 2.4 msO(n) — table सोबत दुप्पट5 ओळींना ठीक; 200,000 × 1,000 lookups ला पहाटे 3 चा page2🗂️ CREATE INDEX — कार्ड catalogue, एक B-tree400080001000200030005000600070009000…410042424380ओळ 4242 ✓3 पावले, 200,000 नाही0.0 ms · O(log n)SEARCH … USING INDEX: थेट खणाकडे, मग ओळीकडे3📖 EXPLAIN QUERY PLAN — चालण्याआधी archivist ला मार्ग विचाराEXPLAIN QUERY PLAN SELECT * FROM attendance WHERE student_id=4242;SCAN attendance ← आधीCREATE INDEX idx_att_student ON attendance(student_id);SEARCH attendance USING INDEX idx_att_student (student_id=?) ← नंतरज्यावर गाळता आणि join करता त्यावर index · प्रत्येक index ला writes आणि जागा लागते · composite चा क्रम महत्त्वाचा: (class, name) ≠ (name, class)5 ओळींच्या table साठी catalogue 5 ओळी वाचण्यापेक्षा हळू — मोजा (python3 db/demo.py index), अंदाज नको
⏪ आधी

index शिवाय student 4242 शोधण्यासाठी सर्व 200,000 attendance ओळी वाचाव्या लागतात, प्रत्येक lookup ला 2.4 ms.

💡 काय

index म्हणजे card catalogue, एक B-tree जो keys drawers मध्ये लावतो म्हणजे तुम्ही थेट योग्य row वर पोहोचता.

⚙️ कसे

CREATE INDEX नंतर EXPLAIN SEARCH … USING INDEX दाखवतो: 200,000 ऐवजी 3 steps, O(n) ऐवजी O(log n).

🎯 का

scan table सोबत दुप्पट होतो, तर index वेगवान राहतो, आणि तो तुम्हाला पहाटे 3 वाजताच्या page पासून वाचवू शकतो.

🚀 पुढे

प्रत्येक index writes हळू करतो सुद्धा, आणि अनेक clerks एकाच वेळी लिहितात म्हणून पुढचा विषय concurrency आणि locks.

🧪 Try it here — attendance table वाढवा आणि बोटाची catalogue शी तुलना करा

संपूर्ण धडा 06 वाचा →

7 🔒 Concurrency & isolation

दोन कारकून, एक नोंदवही — locks, isolation levels, आणि deadlock चा हस्तांदोलन.

🧒 सोप्या शब्दांत

दोन clerks एकाच register वर काम करतात. clerk 2 Katrina चे नाव pencil ने बदलत असताना clerk 1 ला जुनेच नाव दिसते; pencil खुणांमध्ये डोकावणे नाही. clerk 1 ला तीच ओळ बदलायची असेल, तर clerk 2 commit करेपर्यंत तो lock साठी थांबतो. ही गोपनीयता किती कडक हे isolation levels ठरवतात. दोन clerks कायम एकमेकांची वाट पाहत बसले, तर तो deadlock, आणि एकाला माघार घ्यावी लागते.

📖 नवे शब्दlock — row वरची 'काम चालू' पाटी, म्हणजे एका वेळी एकच clerk बदल करतोisolation level — इतर clerks चे अपूर्ण काम तुम्हाला किती दिसू शकतेdirty read — commit होण्याआधीच कोणाची pencil खूण वाचणेdeadlock — दोन clerks एकमेकांची वाट पाहतात; database एकाला रद्द करतो
1🧑‍💼🧑‍💼 दोन कारकून, एक नोंदवही — पेन्सिलीच्या खुणा खासगीकारकून 1 (वाचतो)नोंदवहीकारकून 2 (लिहितो)BEGIN; UPDATE students SET name='Katrina K' WHERE id=2ओळ 2 locked · पेन्सिलSELECT name WHERE id=2"Katrina" — जुने नाव (डोकावणे नाही)UPDATE students … WHERE id=2⏳ lock ची वाट … "database is locked"COMMIT → lock सुटले, "Katrina K" शाईतआता कारकून 1 चे update चालतेpython3 db/demo.py locks — SQLite चे एक-लेखक lock वाट दृश्य करते; Postgres ओळ lock करतो, फाइल नाही2🎚️ isolation levelsread uncommittedपेन्सिलीचे dirty readsread committedफक्त शाई — Postgres defaultrepeatable readतेच दोनदा वाचा, तेच उत्तरserializableएका वेळी एक कारकूनसुरक्षित ↓हळू ↓कडक = सुरक्षित = हळू — प्रत्येक transaction ला निवडा, database ला नाही3🤝 deadlock चा हस्तांदोलन — प्रत्येक कारकुनाकडे दुसऱ्याला हवे ते🧑‍💼कारकून Astudents धरूनA ला हवेgrades धरून🧑‍💼कारकून BB ला हवेA B ची वाट पाहतो, B A ची — कायम, database ने एक बळी निवडून rollback केला नाही तरते टाळणारे दोन नियम:1 transactions छोट्या ठेवा — आत कधीच माणसाची वाट नको2 सगळीकडे त्याच क्रमाने lock करा (students, मग grades)बळीला पुन्हा चालवा; ते सामान्य आहे, bug नाही
⏪ आधी

isolation शिवाय एकाच row वर एकाच वेळी काम करणारे दोन clerks अर्धवट pencil खुणा वाचू शकतात किंवा एकमेकांचे काम पुसू शकतात.

💡 काय

locks आणि isolation levels ठरवतात की एक transaction pencil मध्ये असताना दुसरे काय पाहू आणि बदलू शकते.

⚙️ कसे

Katrina चे नाव बदलण्यासाठी clerk 2 row 2 lock करतो; clerk 1 ला जुने नावच दिसते, आणि त्याचा update COMMIT lock सोडेपर्यंत थांबतो.

🎯 का

pencil खुणा खाजगी राहतात, म्हणून वाचणाऱ्यांना फक्त शाई दिसते, आणि deadlock दोन्ही clerks ना कायम अडकवण्याऐवजी पकडला जातो.

🚀 पुढे

clerks काम करत असतानाच खऱ्या खोल्यांचा आकारही बदलतो, हे पुढचा lesson migrations ने हाताळतो.

🧪 Try it here — दोन्ही कारकून खेळा आणि पेन्सिल खासगी राहताना पहा

संपूर्ण धडा 07 वाचा →

8 🏗️ Migrations

शाळा चालू असतानाच खोलीचे नूतनीकरण — आवृत्ती असलेल्या scripts, आधी expand मग contract.

🧒 सोप्या शब्दांत

शाळेला वर्ग चालू असतानाच register मध्ये 'house' column जोडायचा आहे, आणि त्यासाठी शाळा बंद ठेवता येत नाही. म्हणून प्रत्येक बदल git मध्ये ठेवलेली क्रमांक असलेली script असते, आणि database मधली एक logbook कोणत्या scripts आधीच चालल्या ते नोंदवते, म्हणजे प्रत्येक script क्रमाने, एकदाच चालते. मोठे बदल टप्प्याटप्प्याने: आधी नवा भाग जोडा, code ला दोन्ही वापरू द्या, जुन्या rows भरा, आणि मगच जुना भाग काढा.

📖 नवे शब्दmigration — database चा आकार बदलणारी क्रमांक असलेली scriptschema — database चा आराखडा: कोणते tables आणि columns आहेतexpand / contract — आधी नवा भाग जोडा; जुना भाग कोणी वापरत नसेल तेव्हाच काढाbackfill — जुन्या rows साठी नवा column भरणे, एका वेळी एक batch
1📜 git मधल्या आवृत्ती असलेल्या scripts — कोणती चालली ते logbook सांगते001_init.sql002_add_house.sql003_index_grades_subject.sqlschool.dbschema_migrationsआवृत्तीapplied_at0012026-09-010022026-09-100032026-09-22db/migrate.py: logbook मध्ये नसलेलीप्रत्येक script, क्रमाने, एकदा— दोनदा चालवा: काहीच होत नाहीmigration म्हणजे code: review केलेला, git मध्ये, pipeline ने लावलेला — production मध्ये कधीच हाताने नाही2🏗️ expand → migrate → contract — खोली उघडी राहते1 expandADD COLUMN house DEFAULT 'red'जुना code दुर्लक्ष करतो2 shipcode जुने आणि नवे दोन्ही लिहितोदोन्ही खरे3 backfillUPDATE … SET house = …तुकड्यांत4 shipcode नवा स्तंभ वाचतोजुना न वापरलेला5 contractजुना स्तंभ DROPआता सुरक्षितएका पायरीत rename कधीच नाही: जोडा, copy करा, बदला, drop — प्रत्येक पायरी उलटवण्याजोगी किंवा प्रतीवर तालीम केलेली3🧪 तीच table, 002 आधी आणि नंतर — आणि ती लावणारे साधनstudents · आधीidnameroll_noclass_id1Aishwarya3A-011002students · नंतर (जुना code अजून चालतो)idnameroll_noclass_idhouse1Aishwarya3A-011red$ python3 db/migrate.pyapplying 002_add_house.sql … ✓applying 003_index_grades_subject.sql … ✓$ python3 db/migrate.pyup to date (3 applied)NOT NULL स्तंभ expand पायरी व्हायला DEFAULT लागतो · मोठ्या table वर index CONCURRENTLY (Postgres) बांधतात म्हणजे लेखक चालू राहतातrollback = नवी पुढची migration, खोलीच्या प्रतीवर तालीम केलेली
⏪ आधी

migrations शिवाय schema बदल production मध्ये हाताने टाइप होतात, आणि कोणत्या database मध्ये कोणते columns आहेत हे कोणाला कळत नाही.

💡 काय

migration म्हणजे git मधील versioned SQL script, जसे 002_add_house.sql, आणि एक logbook table कोणत्या चालल्या ते नोंदवते.

⚙️ कसे

db/migrate.py schema_migrations मध्ये नसलेली प्रत्येक script क्रमाने एकदाच चालवते, म्हणून दोनदा चालवले तरी काही होत नाही.

🎯 का

expand, migrate, मग contract केल्याने शाळा चालू असतानाच, downtime शिवाय, columns rename किंवा add करता येतात.

🚀 पुढे

काळजीपूर्वक केलेले बदलही चुकू शकतात, म्हणून पुढचा lesson backups आणि recovery ने fireproof copy बनवतो.

🧪 Try it here — scripts एकेक लावा — आणि साधन पुन्हा चालवून ते काहीच करत नाही ते पहा

संपूर्ण धडा 08 वाचा →

9 🧯 Backups & recovery

आगीपासून सुरक्षित प्रत — RPO, RTO, point-in-time, आणि तुम्ही खरोखर करता ती restore ची रंगीत तालीम.

🧒 सोप्या शब्दांत

शाळा महत्त्वाच्या कागदांच्या copies दुसऱ्या इमारतीत fireproof पेटीत ठेवते. database backup म्हणजे ती copy, आणि त्यानंतर झालेल्या प्रत्येक बदलाची डायरी. 10:14 ला कोणी चुकून Rohan ला delete केले, तर कालच्या रात्रीची copy restore करा, डायरी 10:13 पर्यंत पुन्हा चालवा, आणि Rohan परत येतो. restore करून पाहिल्यावरच backup खरा ठरतो.

📖 नवे शब्दbackup — data ची सुरक्षित copy, दुसरीकडे ठेवलेलीpoint-in-time recovery — copy restore करा, मग निवडलेल्या मिनिटापर्यंत बदल पुन्हा चालवाRPO — अलीकडचे किती काम हरवले तरी चालेलRTO — restore करताना किती वेळ बंद राहिले तरी चालेलrestore drill — backup चालतो हे सिद्ध करण्यासाठी restore चा प्रत्यक्ष सराव
1🧯 आगरोधक प्रत — रोज रात्री पूर्ण + सतत logschool.dbconn.backup()🏢 दुसरी इमारत.backup.dbS3 · RDS snapshot (AWS L17)📼 log (WAL)प्रत्येक बदल, क्रमाने —कोणत्याही मिनिटापर्यंत पुन्हा चालवापूर्ण dump: तासांचे काम पुन्हा · log: मिनिटे · point-in-time = dump + पुन्हा चालवलेला logpython3 db/demo.py backup: backup घ्या, Rohan delete करा, restore करा, तो परत आल्याचे सिद्ध कराएक प्रत दुसऱ्या इमारतीत ठेवा — आग disk आणि त्यावरचा backup दोन्ही घेते2💥 अपघात, ♻️ restore, ✅ पुरावा02:00backup ✓10:14DELETE FROM students WHERE id=5 💥10:20restore: प्रत परत + log 10:13 पर्यंत पुन्हा10:26SELECT name … id=5 → Rohan ✓पुरावा म्हणजे SELECT — कोणी न तपासलेला restore अजूनही अफवाच3⏱️ RPO आणि RTO — आगीआधी शाळा ठरवते ते दोन आकडेशेवटचा backupआग 💥खोली पुन्हा उघडीRPO — आपण किती गमावले? (log ची मिनिटे, dump चे तास)RTO — खोली किती वेळ बंद? (तालमीचे stopwatch)RPO 5 मिनिटे → log दर 5 मिनिटांनी पाठवा · RTO 30 मिनिटे → restore ची तालीम 30 मध्ये संपली पाहिजे, दर महिन्याला · Multi-AZ (AWS L17) आग टिकवण्यासाठी, वेगासाठी नाही
⏪ आधी

backup शिवाय एक DELETE किंवा इमारतीतील आग data आणि disk दोन्ही कायमचे घेऊन जाते.

💡 काय

backup म्हणजे दुसऱ्या इमारतीतील fireproof copy: रोज रात्रीची पूर्ण copy आणि प्रत्येक बदलाचा सतत चालणारा log (WAL).

⚙️ कसे

10:14 ला Rohan delete होतो; restore 02:00 चा backup परत copy करतो, log 10:13 पर्यंत replay करतो, आणि तो परत येतो.

🎯 का

point-in-time recovery गमावलेल्या कामाचे तास मिनिटांत बदलते, आणि RPO व RTO सांगतात किती नुकसान व downtime चालेल.

🚀 पुढे

तुम्ही खरोखर केलेल्या restore drill नंतरच backup मोजला जातो; पुढे scaling ने record room वाढतो.

🧪 Try it here — शाळेचे दोन आकडे ठरवा, मग आगीची तालीम चालवा

संपूर्ण धडा 09 वाचा →

10 📈 Scaling

अधिक कारकून, अधिक खोल्या — pools, replicas, caches, partitions, आणि shard कधी करू नये.

🧒 सोप्या शब्दांत

office ची रांग लांब झाली की तुम्ही आधी दुसरी शाळा बांधत नाही. सर्वात स्वस्त उपाय क्रमाने करून पाहता: हळू प्रश्न index ने जलद करा, connections वाटून वापरा, नेहमीची उत्तरे cache मध्ये जवळ ठेवा, मोठे machine घ्या, वाचण्यासाठी copy rooms जोडा. नोंदी अनेक वेगळ्या खोल्यांमध्ये वाटणे, म्हणजे sharding, सगळ्यात शेवटी येते, आणि बहुतेक शाळांना त्याची गरजच पडत नाही.

📖 नवे शब्दconnection pool — काही उघडी connections, जी अनेक requests आळीपाळीने वापरतातcache — नेहमीच्या उत्तराची जलद copy, कदाचित थोडी जुनीread replica — फक्त वाचण्यासाठी वापरली जाणारी database ची copy, थोडी मागेpartition — एका मोठ्या table चे भाग करणे, उदा. प्रत्येक वर्षासाठी एक खोलीshard — data वेगवेगळ्या database servers मध्ये वाटणे; शेवटचा उपाय
1📈 टीम खरोखर पाळतात तो क्रम — आधी स्वस्त, शेवटी shard1🔧 queries + indexes दुरुस्तL06 · L122🚰 poolpgbouncer · HikariCP3🧊 cacheRedis, थोडक्यात शिळे4💪 मोठे machineएक click5📖 read replicasवाचक, एक ठोका मागे6🏢 partitionsप्रत्येक वर्षाला खोली7🔪 shardsशेवटचा उपायबहुतेक शाळा ही रेषा कधीच ओलांडत नाहीतwrites scale करणे सगळ्यात कठीण: एक primary, अनेक replicas · पायरी 5 नंतर JOINs आणि transactions त्रस्त — प्रत्येक पायरीआधी मोजाindex ने दुरुस्त झालेली हळू query उजवीकडच्या प्रत्येक box ला हरवते2📖 primary + replicas — एक लेखक, अनेक वाचकॲपwritesprimaryreplica 1replica 2readslag: एक ठोकामागे — स्वतःचेwrite primaryवरून वाचाMulti-AZ standby = आग टिकवण्यासाठी · replica = reads वाटण्यासाठी3🧊 counter वरचे cache — गरम उत्तरे, थोडक्यात शिळीॲपRedis"3A roster" → …hit ✓missdbभरा, TTL सह (60 s)मुद्दाम शिळे, थोडक्यात · write वर invalidate करा किंवा expire होऊ द्याhit rate हा पाहायचा आकडा — 95% म्हणजे खोलीला 20 पैकी 1 दिसतो
⏪ आधी

योजनेशिवाय teams थेट sharding कडे उडी घेतात, जेव्हा हळू query ला फक्त index हवा होता, आणि कायम complexity ची किंमत भरतात.

💡 काय

scaling म्हणजे क्रमाने क्षमता वाढवणे, स्वस्त आधी: queries सुधारा, pool, cache, मोठे machine, replicas, partitions, shards.

⚙️ कसे

एक primary सर्व writes घेतो तर read replicas थोडे मागे राहून readers ना सेवा देतात, आणि pgbouncer connections pool करतो.

🎯 का

index ने सुधारलेली हळू query उजवीकडील प्रत्येक box पेक्षा चांगली आहे, आणि बहुतेक शाळांना shards कधीच लागत नाहीत.

🚀 पुढे

काही प्रश्न rows मध्ये बसतच नाहीत, म्हणून पुढचा lesson NoSQL आणि इतर खोल्यांना भेट देतो.

🧪 Try it here — लक्षण निवडा — शिडी सांगते पुढची पायरी कोणती

संपूर्ण धडा 10 वाचा →

11 🏘️ NoSQL & इतर खोल्या

Document, key-value, columnar, graph, vector — प्रश्नासाठी योग्य खोली निवडणे.

🧒 सोप्या शब्दांत

ओळी आणि columns असलेले register शाळेच्या बहुतेक प्रश्नांना चालते, पण सगळ्यांना नाही. पानांच्या आत पाने असलेली मुलाची पूर्ण file folder मध्ये बसते; 'locker 42 मध्ये काय आहे?' key box मध्ये; अब्जावधी rows ची बेरीज column store मध्ये; 'मित्रांचे मित्र' graph मध्ये; 'सारखा अर्थ असलेल्या गोष्टी' vector store मध्ये. नेहमी register वापरा, आणि दुसरी खोली फक्त तिच्या खास प्रश्नासाठी निवडा.

📖 नवे शब्दrelational — keys, JOINs आणि transactions असलेले tables; नेहमीची निवडdocument store — प्रत्येक record एका आत-आत असलेल्या file सारखा ठेवतो, उदा. मुलाचा folderkey-value — एका key ला एक उत्तर, खूप जलद, उदा. sessions साठीgraph — कोण कोणाशी जोडलेला ते साठवतो, मित्रांचे मित्र सारख्या उड्यांसाठीvector — अर्थाने सर्वात जवळच्या गोष्टी शोधतो
1🏘️ पाच इतर खोल्या — प्रत्येक नोंदवही वाईट उत्तर देते त्या प्रश्नासाठी घडवलेली📄 documentMongoDB🔑 key-valueRedis · DynamoDB📊 columnarBigQuery · ClickHouse🕸️ graphNeo4j🗺️ vectorpgvector · Pinecone{ "name": "Katrina", "grades": [ {…}, {…} ] }nested records, थोडे JOINssess:42{uid: 7}एक key, एक उत्तर, µscaches · sessions · countersgradetermवर्गΣ एक अब्ज ओळीanalytics, प्रत्येक ओळ बदलणे नव्हेकोण-कोणाला-ओळखतो hopsमित्रांचे मित्रअर्थाने जवळचेVectorDB शाळाrelational (Postgres · MySQL · SQLite) ही default खोली: constraints, JOINs, transactions — ती वाईट उत्तर देते त्या प्रश्नासाठीच दुसरी निवडा2🧭 खोली प्रश्नानुसार ठरतेखोल nested, आकार सतत बदलतोdocumentएक key, microseconds, प्रचंड volumekey-valueअब्जावधींची बेरीज / सरासरीcolumnarगोष्टींमधले मार्गgraph"याच्यासारखे"vectorबाकी सगळेrelational ✓बऱ्याच शाळा Postgres + Redis + एक analytics खोली चालवतात · मोजलेल्या कारणाशिवाय "scale साठी NoSQL" ही नेहमीची पश्चात्तापाची गोष्ट
⏪ आधी

प्रत्येक प्रश्न rows मध्ये कोंबल्याने nested records, microsecond lookups आणि अब्जावधी rows चे analytics हळू किंवा अवघड होतात.

💡 काय

NoSQL म्हणजे प्रत्येकी एका प्रश्नासाठी घडवलेल्या इतर खोल्या: document, key-value, columnar, graph आणि vector stores.

⚙️ कसे

MongoDB nested records ठेवतो, Redis एका key चे उत्तर µs मध्ये देतो, BigQuery अब्ज rows जोडतो, Neo4j friends of friends शोधतो.

🎯 का

constraints, JOINs आणि transactions साठी relational हीच default खोली राहते; ती वाईट उत्तर देते त्या प्रश्नासाठीच दुसरी निवडा.

🚀 पुढे

vector खोली VectorDB school कडे नेते, आणि पुढे तुम्ही कोणतीही खोली operations मध्ये वेगवान कशी ठेवायची ते शिकाल.

🧪 Try it here — प्रश्न सांगा — खोली मिळवा

संपूर्ण धडा 11 वाचा →

12 🩺 Performance & operations

हळू queries ची तपासणी, N+1, observability — रेकॉर्ड रूम on call जाण्याआधीची चेकलिस्ट.

🧒 सोप्या शब्दांत

कल्पना करा, एक शिक्षक वर्गाच्या यादीसाठी एकदा office मध्ये जातात, मग प्रत्येक मुलाच्या grades साठी पुन्हा एकदा: 5 मुलांसाठी 6 फेऱ्या, 500 साठी 501. JOIN ने एकदाच विचारले की सगळे एका फेरीत मिळते. database एक slow-query log सुद्धा ठेवू शकतो, म्हणजे खूप वेळ घेतलेल्या प्रत्येक प्रश्नाची यादी. ती दर आठवड्याला वाचा, सर्वात हळू दहा दुरुस्त करा, आणि मग रात्री अडचणी क्वचितच उठवतात.

📖 नवे शब्दN+1 — यादीसाठी एक query, मग प्रत्येक item साठी आणखी एक: अनेक हळू फेऱ्याslow-query log — ठरलेल्या वेळेपेक्षा जास्त वेळ घेतलेल्या प्रत्येक query ची यादीquery plan — database निवडतो तो मार्ग: सगळे वाचणे, की index वापरणेobservability — database आत्ता कसा चालला आहे ते दाखवणारे मीटर आणि logs
1🐢 N+1 — पाच विद्यार्थ्यांसाठी सहा फेऱ्याॲपdb1 SELECT * FROM students+5 SELECT … grades WHERE student_id = ?6 फेऱ्या · 500 विद्यार्थी → 5011 LEFT JOIN grades … GROUP BY student🐇 एक JOIN: 1 फेरी, तेच उत्तरpython3 db/demo.py nplus1 फेऱ्या मोजते · ORMs हे default करतात — log पहा2🩺 slow-query log — N ms पेक्षा जास्त प्रत्येक queryduration: 812 ms SELECT * FROM students WHERE lower(name) = 'katrina'plan: SCAN students ← स्तंभावर functionfix: WHERE name = 'Katrina' + CREATE INDEX ON students(name)duration: 0.3 ms SEARCH students USING INDEXदर आठवड्याला वाचा · सगळ्यात हळू 10 queries हेच अख्खे कामस्तंभावरचे function ते catalogue पासून लपवते · SELECT * प्रत्येक स्तंभ ओढतेp95 latency, सरासरी नाही — हळू शेपूट विद्यार्थ्यांना जाणवते3📋 रेकॉर्ड रूम on-call जाण्याआधीची checklist✓connections मर्यादेजवळ?✓replication lag?✓disk + WAL वाढ?✓lock waits + deadlocks?✓या आठवड्यातल्या सगळ्यात हळू 10 queries?✓शेवटच्या restore तालमीची तारीख?dashboardsQPS70p9540errors10cache hit90%Kubernetes शाळेची Grafana सवय, database कडे वळवलेली · alerts checklist वर, CPU वर नाहीकळस-प्रकल्प: N+1 शोधा, index ने घालवा, EXPLAIN आणि log ने सिद्ध करा
⏪ आधी

triage शिवाय app गुपचूप 500 मुलांसाठी 501 trips करते, आणि database कोसळेपर्यंत हळू query लपून राहते.

💡 काय

operations म्हणजे slow-query log आणि plans पाहणे, आणि record room on call जाण्याआधी N+1 सुधारणे.

⚙️ कसे

WHERE lower(name) = 'katrina' 812 ms मध्ये scan करतो; WHERE name = 'Katrina' आणि CREATE INDEX ON students(name) ला 0.3 ms लागतात.

🎯 का

एक JOIN सहा round trips ची जागा घेतो, आणि दर आठवड्याला सर्वात हळू 10 queries सुधारणे हेच बहुतेक काम आहे.

🚀 पुढे

खऱ्या teams on call असताना हीच checklist वापरतात, आणि slow-query log व metrics रोज पाहिले जातात.

🧪 Try it here — शाळा वाढते तशा फेऱ्या मोजा — मग दुरुस्त करा

संपूर्ण धडा 12 वाचा →

13 🛡️ सुरक्षा

कुलूपबंद रजिस्टर — SQL injection, parameters, least privilege, आणि transit मध्ये व at rest encryption.

🧒 सोप्या शब्दांत

login box तुमचा roll number विचारतो. तुम्ही टाइप केलेले app थेट आपल्या SQL मध्ये चिकटवत असेल, तर एखादा लबाड x' OR '1'='1 टाइप करतो, database त्याला command समजून पाळतो आणि सगळ्या मुलांची माहिती देतो. Parameters टाइप केलेला मजकूर बंद पाकिटात पाठवतात: नेहमी फक्त value, कधीच command नाही. प्रत्येक app ला फक्त गरजेच्या चाव्या मिळतात, आणि data प्रवासात व साठवताना कुलूपबंद असतो.

📖 नवे शब्दSQL injection — टाइप केलेला मजकूर SQL म्हणून चालवायला app ला फसवणेparameter (?) — जागा राखणारे चिन्ह; टाइप केलेली value वेगळी जाते, कधीच code म्हणून नाहीleast privilege — प्रत्येक app ला फक्त गरजेच्या permissions, जास्त काहीच नाहीencryption — data गुंडाळून टाकणे म्हणजे फक्त key असणारेच वाचू शकतात, प्रवासात आणि साठवताना
1❌ चिकटवलेले SQL — input च code बनतो🔐 loginx' OR '1'='1SELECT nameFROM studentsWHERE roll_no = 'x' OR '1'='1''1'='1' नेहमीच खरे असतेnameAishwaryaKatrinaDipikaMeeraRohan💥 प्रत्येक विद्यार्थ्याची माहिती फुटते —हल्लेखोराने SQL टाइप केले,database ने आज्ञा पाळली2✅ parameters — input फक्त एक value असतोSELECT name FROM studentsWHERE roll_no = ?x' OR '1'='1वेगळा पाठवला, मोहोरबंदname(एकही row नाही)कोणत्याही विद्यार्थ्याचा roll_no"x' OR '1'='1" नाही → काहीच नाहीconn.execute("… WHERE roll_no = ?", (typed,)) — प्रत्येक भाषेत हे आहे; ORMs तुमच्यासाठी हे करतात3🪪 least privilege — प्रत्येक app ला फक्त आवश्यक तेवढाच पास🧑‍💻 school-apiSELECT, INSERT, UPDATEgrades, students वर📊 reportsफक्त SELECTफक्त-वाचनीय connection🏗️ migrationsALTER, CREATECI चालवते, app नाही👑 superuserसर्व काहीकोणाचेही रोजचे login नाहीGRANT SELECT ON grades TO reports; -- Postgres/MySQL · SQLite: फाइल mode=ro ने उघडाtransit मध्ये TLS (sslmode=require) · at rest encryption (RDS/disk) · passwords secret store मधून, कधीच git मध्ये नाही
⏪ आधी

Apps टाइप केलेला मजकूर SQL string मध्ये जोडायचे, त्यामुळे login box मधूनच पूर्ण tables वाचता किंवा delete करता यायचे.

💡 काय

Database security: parameters SQL injection थांबवतात, प्रत्येक app ला फक्त गरजेचा pass मिळतो, आणि data encrypt असतो.

⚙️ कसे

Input ? ने पाठवा, string मध्ये जोडू नका; reports app ला read-only pass द्या; TLS आणि encryption at rest वापरा.

🎯 का

Demo मध्ये जोडलेल्या login ने सर्व 5 विद्यार्थी उघड केले आणि parameter ने एकही नाही — एका सवयीने पूर्ण हल्ला बंद.

🚀 पुढे

पुढे advanced SQL — आणि आता लिहिलेली प्रत्येक query parameters वापरते, app code मध्येही (L17).

🧪 Try it here — roll number ने login करा — मग त्याऐवजी SQL टाइप करा आणि प्रत्येक query काय करते ते पहा

संपूर्ण धडा 13 वाचा →

14 🏆 Advanced SQL

एकही row न गमावता क्रम — subqueries, CTEs, window functions, views, आणि room मध्ये राहणारा code: functions आणि stored procedures.

🧒 सोप्या शब्दांत

शिक्षकांना प्रत्येक वर्गात क्रमवारी हवी आहे: 3A मध्ये Katrina पहिली, 3B मध्ये Meera पहिली. GROUP BY प्रत्येक वर्ग दाबून एका ओळीत करेल, पण window function प्रत्येक row ठेवून प्रत्येक मुलाला क्रमांक देते, आणि प्रत्येक वर्गासाठी पुन्हा 1 पासून सुरू करते. CTE म्हणजे नाव दिलेले कच्चे काम, जे आधी करून मग वापरता. Functions आणि stored procedures म्हणजे record room मध्येच साठवलेल्या कृती.

📖 नवे शब्दsubquery — मोठ्या प्रश्नाच्या आत एक छोटा प्रश्नCTE (WITH) — नाव दिलेला कच्च्या कामाचा निकाल, आधी बनवून मग त्यावर querywindow function — rows एकत्र न करता त्यांच्यावर क्रमांक किंवा चालू बेरीज देतेview — साठवलेला प्रश्न, ज्यावर table सारखी query करता येतेstored procedure — database मध्येच साठवलेला SQL steps चा संच, नावाने चालवतात
1🏆 CTE + window functions — प्रत्येक वर्गात क्रमांकWITH points AS ( SELECT student_id, SUM(points) AS pts … GROUP BY student_id)A+=10 · A=9 · B+=8 · B=7 · C=6वर्गnameptsRANK()चालू बेरीज3AKatrina201203AAishwarya172373ADipika153523BMeera181183BRohan13231── नवा वर्ग: क्रमांक पुन्हा सुरू होतोRANK() OVER (PARTITION BY class ORDER BY pts DESC) — प्रत्येक row ठेवणारा GROUP BY;SUM(pts) OVER (…) चालू बेरीज देतो · ROW_NUMBER, LAG, LEAD, NTILE याच प्रकारे काम करतात2🔍 प्रश्नाच्या आत एक subqueryKatrina20Meera18Aishwarya17Dipika15Rohan13AVG = 16.6WHERE pts > (SELECT AVG(pts) FROM points)→ Katrina, Meera, Aishwarya3📋 VIEW — table सारखा वापरायचा जतन केलेला प्रश्नCREATE VIEW report_card AS SELECT s.name, COUNT(*) AS subjects, SUM(points) AS pts FROM grades g JOIN students s … GROUP BY s.idSELECT * FROM report_cardnamesubjectsptsKatrina220Meera218Aishwarya217एक व्याख्या, प्रत्येक app तोचप्रश्न त्याच प्रकारे विचारतेview प्रश्न साठवतो, उत्तरनाही — materialized view उत्तर साठवतोआणि त्याला refresh करावे लागते4🧑‍🍳 room मध्ये राहणारा code — stored procedure (PostgreSQL)CREATE PROCEDURE transfer_pupil( p_student int, p_class int)LANGUAGE plpgsql AS $$BEGIN IF (SELECT count(*) FROM students WHERE class_id = p_class) >= 3 THEN RAISE EXCEPTION 'class % is full (3 pupils)', p_class; END IF; UPDATE students SET class_id = p_class WHERE id = p_student; INSERT INTO audit_log (what) VALUES (…);END $$;db/postgres/procedures.sql — Postgres च्या parser ने तपासलेलेCALL transfer_pupil(4, 1);ERROR: class 1 is full (3 pupils)CALL transfer_pupil(1, 2);CALL (+ 1 row in audit_log)प्रत्येक app — API, script,report tool — यांना तोच नियम मिळतो,data च्या शेजारी तपासलेला, एकाचround trip मध्येप्रकारकसा चालवताकाय परत देतोVIEW (वर)SELECT … FROM itrowsFUNCTIONinside SQL: points(g)एक valuePROCEDURECALL name(…)काही नाही / OUTTRIGGER (L18)स्वतःहून, बदल झाल्यावर—✓ प्रत्येक app साठी एक नियम · कमी trips✗ logic app code पासून लपलेले, test, versionआणि deploy करणे कठीण — ते लहान ठेवाSQLite मध्ये नाही: db/demo.py procs त्याऐवजीtrigger + Python function वापरते
⏪ आधी

वर्गानुसार विद्यार्थ्यांचा क्रम लावायला rows code मध्ये आणून loop करावे लागायचे, किंवा GROUP BY जे rows दाबून टाकते.

💡 काय

Advanced SQL: subqueries, CTEs (WITH), RANK() आणि running totals सारखी window functions, आणि जतन केलेले प्रश्न (views).

⚙️ कसे

RANK() OVER (PARTITION BY class ORDER BY pts DESC) rank चा column जोडतो आणि प्रत्येक वर्गासाठी पुन्हा सुरू करतो, सर्व rows ठेवून.

🎯 का

Reports, leaderboards आणि 'सरासरीपेक्षा जास्त' याद्या code च्या loops ऐवजी room मधली एक query बनतात.

🚀 पुढे

पुढची पायरी: room मध्ये राहणारा code — SQL मधले function, आणि CALL केली जाणारी stored procedure, जेणेकरून प्रत्येक app एकच नियम पाळतो.

🧪 Try it here — एक window function आणि partition निवडा — अतिरिक्त column दिसताना पहा, प्रत्येक row कायम
🧪 Try it here — विद्यार्थ्याला हलवा — "एका वर्गात जास्तीत जास्त 3" हा नियम कुठे राहतो आणि कोण बोलावतो ते ठरवा

संपूर्ण धडा 14 वाचा →

15 📼 engine च्या आत

log आधी येतो — WAL, crash recovery, MVCC snapshots आणि vacuum.

🧒 सोप्या शब्दांत

काळजीपूर्वक काम करणारा clerk प्रत्येक बदल आधी डायरीत लिहितो, आणि मोठे register नंतर बदलतो. दिवे गेले तर शेवटच्या checkpoint पासून डायरी पुन्हा वाचली जाते, म्हणून ज्याला 'OK' सांगितले ते काहीच हरवत नाही. वाचणाऱ्यांना ते सुरू झाले तेव्हाचा register चा फोटो मिळतो, म्हणून लिहिणाऱ्यांना त्यांची वाट पाहावी लागत नाही. जुन्या आवृत्त्या फाडलेल्या पानांसारख्या साचतात, आणि vacuum त्या झाडून टाकतो.

📖 नवे शब्दWAL — write-ahead log: 'OK' सांगण्याआधी बदल डायरीत जातोcheckpoint — साठवलेली pages डायरीच्या बरोबरीला येतात तो क्षणMVCC — अनेक आवृत्त्या ठेवणे, म्हणजे प्रत्येक वाचणाऱ्याला स्थिर snapshot दिसतोvacuum — आता कोणालाच न लागणाऱ्या जुन्या row आवृत्त्या साफ करणे
1📼 engine च्या आत — log आधी लिहिला जातो, pages नंतर✏️ COMMIT#41#42#43#44#45#46#47WAL — फक्त शेवटी जोडणे, "OK" आधी disk वर flush (fsync)✅ app ला OK🧠 memory मधील pages(buffer cache)checkpoint, नंतरdata फाइल💥 "OK" नंतर वीज गेली?restart वर engine WAL पुन्हा चालवतोशेवटच्या checkpoint पासून → commitपरत येतो (ACID मधला D, धडा 04)2🕰️ MVCC — वाचणारे snapshot ठेवतात, लिहिणारे त्यांची वाट पाहत नाहीत👀 वाचणारा✍️ लेखकBEGIN'Aishwarya' दिसतेअजूनही 'Aishwarya' (snapshot)COMMIT'Aishwarya S'UPDATE → 'Aishwarya S', COMMIT ✓"database is locked" नाही — धडा 07 च्या उलटv1 'Aishwarya' (जुनी)कोणी वाचत असेपर्यंत ठेवली जातेv2 'Aishwarya S'प्रत्येक row च्या आवृत्त्या असू शकतात;जुन्या नंतर साफ केल्या जातात —VACUUM (Postgres) / checkpoint (SQLite)
⏪ आधी

Log नसताना लिहिताना वीज गेली तर pages अर्धवट बदललेली राहू शकत, आणि प्रत्येक reader ला प्रत्येक writer ची वाट पाहावी लागे.

💡 काय

Engine च्या आत: write-ahead log (WAL) commits टिकाऊ करतो, आणि MVCC प्रत्येक reader ला स्थिर snapshot देतो.

⚙️ कसे

Commit आधी WAL मध्ये जोडला जाऊन disk वर flush होतो, मगच OK; pages नंतर checkpoint ला data file मध्ये जातात.

🎯 का

WAL mode मध्ये writer ने 'Aishwarya S' commit केले तेव्हा reader ला अजून 'Aishwarya' दिसत होते — या वेळी 'database is locked' नाही.

🚀 पुढे

हाच log L16 मध्ये replicas stream करतात आणि L18 मध्ये CDC tools वाचतात — WAL म्हणजे room ची डायरी.

🧪 Try it here — एक वाचणारा, एक लिहिणारा — दोन्ही journal modes मध्ये करून पहा

संपूर्ण धडा 15 वाचा →

16 📡 Replication आणि failover

खोलीच्या प्रती — log चा प्रवाह, lag, sync विरुद्ध async, failover आणि CAP trade-off.

🧒 सोप्या शब्दांत

मुख्य record room आपली बदलांची डायरी copy rooms ना पाठवते; त्या अद्ययावत राहतात पण थोड्या मागे, उदा. 0.2 किंवा 3 seconds. पालकांनी Diya चे नाव नोंदवून मागे पडलेल्या copy मधून वाचले, तर Diya गायब वाटते. मुख्य room बंद पडली तर एक copy काम हाती घेते. async मध्ये शेवटचे काही बदल हरवू शकतात; sync मध्ये प्रत्येक commit copy च्या 'मिळाले' ची वाट पाहतो, म्हणून काही हरवत नाही पण writes हळू होतात.

📖 नवे शब्दprimary — सगळे writes स्वीकारणारी एकमेव roomreplica — बदलांचा log मिळवणारी आणि वाचण्यासाठी वापरली जाणारी copylag — replica किती seconds मागे आहेfailover — primary बंद पडल्यावर replica ला नवा primary बनवणेsync vs async — OK सांगण्याआधी copy ची वाट पाहा, की आधी OK सांगा (जलद, थोडे हरवू शकते)
1📡 replication — primary आपला log प्रतींकडे प्रवाहित करतो🗄️ primaryसर्व writes इथे येतातWAL प्रवाहreplica 1lag 0.2 sWAL प्रवाहreplica 2lag 3 s ⚠️👪 एक पालक Diya ची नोंदणी करतातwrite → primary ✓reload → replica 2 → "Diya नाही?" 😟उपाय: स्वतःचे writesprimary मधून वाचा (किंवा replicaपकडेपर्यंत थांबा)2🚑 failover — sync की async?async (default)replica कडे पोहोचण्याआधीच COMMIT परत येतो"Ishaan" पाठवण्याआधीच primary मरतो →replica ला promote करा → Ishaan हरवलाजलद commits · RPO > 0synchronousCOMMIT replica च्या "मिळाले" ची वाट पाहतोprimary मरतो → replica कडे प्रत्येक commit आहेpromote → काहीच हरवले नाहीहळू commits · RPO = 0 · replica बंद पडल्यास writes अडू शकतात✂️ खोल्यांमधले network तुटले? निवडा: एका बाजूला writes नाकारा (consistent) किंवा दोन्हीकडे स्वीकारा आणि नंतर जुळवा (available) — CAP trade-off
⏪ आधी

एकमेव standby म्हणजे कालच्या रात्रीचा backup, त्यामुळे server गेला की तासन्तास बंद आणि एक दिवसाचे writes गमावले.

💡 काय

Replication primary चा log replicas कडे stream करते; primary मेला की failover एका replica ला primary बनवतो.

⚙️ कसे

Async: COMMIT लगेच परत येतो आणि log मागून जातो; sync: COMMIT replica च्या 'मिळाले' ची वाट पाहतो.

🎯 का

Demo मध्ये async failover मध्ये Ishaan चे admission हरवले, आणि replica वर Diya दिसली नाही — lag आणि RPO प्रत्यक्ष.

🚀 पुढे

Managed databases (AWS L17) हे तुमच्यासाठी चालवतात; तरी sync की async ते तुम्ही ठरवता आणि स्वतःचे writes primary वरून वाचता.

🧪 Try it here — एका विद्यार्थ्याची नोंदणी करा, replica मधून वाचा, मग primary मारा

संपूर्ण धडा 16 वाचा →

17 🧑‍💻 code मधून database

app ची बाजू — pools, transactions, parameters, batching, backoff सह retries, आणि ORMs.

🧒 सोप्या शब्दांत

एका वर्गात काही pens वाटून वापरतात: प्रत्येक विद्यार्थी एक घेतो, लिहितो, आणि परत देतो. app database connections असेच वाटून वापरते: प्रत्येक request pool मधून एक घेते, एक छोटा transaction करते, आणि परत देते. 2,000 rows एकेक करून save करायला 513 ms लागले; एका transaction मध्ये 1 ms, कारण प्रत्येक commit म्हणजे disk ची एक फेरी. database 'locked' म्हणाला तर थोडे थांबा, पुन्हा प्रयत्न करा, आणि प्रत्येक वेळी जास्त थांबा.

📖 नवे शब्दconnection pool — सगळ्या requests मध्ये वाटलेली काही उघडी connections, घेऊन परत दिली जाणारीbatching — प्रत्येक row ला वेगळा commit न करता अनेक rows एका transaction मध्ये save करणेretry with backoff — थोडे थांबून पुन्हा प्रयत्न, प्रत्येक वेळी जास्त थांबणेORM — तुमच्या code मधील objects वरून तुमच्यासाठी SQL लिहिणारी library
1🧑‍💻 app ची बाजू — pool, transaction, parametersविनंती🚰 poolएक connectionउसने घ्याwith conn: # BEGIN conn.execute( "UPDATE … WHERE id=?", (sid,))# COMMIT, or ROLLBACK# on any exceptionएक विनंती = एक छोटा transaction · नेहमी parameters (L13)connection परत द्या — गळती झालेला connection pool रिकामा करतो2📦 batch writes — प्रत्येक COMMIT म्हणजे disk ची एक फेरी2,000 insertsप्रत्येक row ला commit513 msएक transaction1 msएकाच with-block मध्ये executemany() · आमचा laptop, SQLite —आकडे नाही, गुणोत्तर हा धडा आहे3🔁 backoff सह retry — "database is locked" बहुतेक तात्पुरते असतेप्रयत्न 1locked ⏳50 ms थांबाप्रयत्न 2locked ⏳100 ms थांबाप्रयत्न 3झाले ✅🧩 ORMobjects ↔ rowsफक्त पुन्हा करायला सुरक्षित तेच retry करा (संपूर्ण transaction, किंवा idempotent write — API शाळा L10) · प्रयत्नांना मर्यादा घाला ·ORM तुमच्यासाठी SQL आणि parameters लिहितो — तरीही N+1 साठी त्याच्या queries वर लक्ष ठेवा (धडा 12)
⏪ आधी

Apps प्रत्येक request ला नवे connection उघडायचे, प्रत्येक row commit करायचे, input SQL मध्ये जोडायचे आणि पहिल्या lock ला कोसळायचे.

💡 काय

App ची बाजू: pool मधून connection घ्या, प्रत्येक request ला एक छोटा transaction, parameters, batch writes आणि सुरक्षित retries.

⚙️ कसे

with conn: BEGIN आणि COMMIT गुंडाळतो, executemany rows एकत्र पाठवतो, आणि locked write 50, 100, 200 ms नंतर पुन्हा प्रयत्न करतो.

🎯 का

2,000 inserts ला प्रत्येक row commit केल्यावर 513 ms आणि एका transaction मध्ये 1 ms लागले — प्रत्येक COMMIT disk ची वाट पाहतो.

🚀 पुढे

ORM हे SQL तुमच्यासाठी लिहितो — तरी N+1 (L12) साठी queries ची संख्या आणि transaction च्या सीमा पाहत राहा.

🧪 Try it here — writes batch करा, मग retries वापरून locked खोलीतून तगून रहा

संपूर्ण धडा 17 वाचा →

18 🏛️ OLTP विरुद्ध OLAP

कार्यालय आणि संग्रहालय — warehouses, रात्रीचा ETL, columnar storage, आणि trigger वापरून CDC.

🧒 सोप्या शब्दांत

शाळेचे office दिवसभर अनेक छोटी कामे करते: Katrina ची हजेरी लावा, Rohan चे grades दाखवा. तिथे कोणी 'महिन्यानुसार हजेरी' असा मोठा report चालवला, तर सगळ्यांना थांबावे लागते. म्हणून दर रात्री नोंदी मोठ्या प्रश्नांसाठी बनवलेल्या archive मध्ये copy होतात; तिथे प्रत्येक column एकत्र साठवलेला असतो आणि उत्तर क्षणात मिळते. CDC म्हणजे office लिहिते ती बदलांची डायरी, म्हणजे archive प्रत्येक बदलाबरोबर राहू शकतो.

📖 नवे शब्दOLTP — चालू system: अनेक छोटे, जलद reads आणि writesOLAP — विश्लेषणाची system: अनेक rows वर काही मोठे प्रश्नETL — data बाहेर काढा, आकार बदला, आणि warehouse मध्ये भरा, उदा. दर रात्रीcolumnar — प्रत्येक column एकत्र साठवणे, एका column वरच्या बेरजेसाठी जलदCDC — प्रत्येक बदल होताक्षणी टिपणे, म्हणजे दुसरी system मागोमाग राहू शकते
1🏫 कार्यालय (OLTP) विरुद्ध 🏛️ संग्रहालय (OLAP)🗄️ चालू रेकॉर्ड रूम — OLTPदिवसभर अनेक लहान reads आणि writes"Katrina ची हजेरी लावा" · "Rohan चे grades"rows एकत्र साठवलेल्या (row store)विद्यार्थ्यांसाठी जलद राहिलीच पाहिजे⚠️ इथे जड report सगळ्यांना हळू करतो200,000 चालू ओळींवर report: 29.3 ms🌙 रात्रीचा ETLCDC: प्रत्येक बदल🏛️ warehouse — OLAPमोजके प्रचंड प्रश्न: "महिन्यानुसार हजेरी"columns एकत्र साठवलेले (columnar)BigQuery · Redshift · ClickHouse · DuckDBमहिनादर %2026-0149.72026-0250.0सारांश: 0.08 ms2🧾 trigger वापरून CDC — खोली स्वतःची बदल-डायरी लिहितेCREATE TRIGGER cdc_gradesAFTER UPDATE OF grade ON gradesBEGIN INSERT INTO grade_changes (grade_id, old, new) VALUES (OLD.id, OLD.grade, NEW.grade);END;grade_changesgrade_idoldnew9BARohan चे गणित: B → Awarehouse डायरी वाचते,चालू खोली नाही(खरा CDC WAL वाचतो:Debezium, DMS)
⏪ आधी

महिनाअखेरचे reports live database वर चालायचे, आणि कोणी dashboard उघडला की कार्यालय अडकायचे.

💡 काय

OLTP म्हणजे छोट्या reads आणि writes चे गजबजलेले कार्यालय; OLAP म्हणजे मोठ्या प्रश्नांसाठी बांधलेले archive, बहुतेक columnar.

⚙️ कसे

रात्रीचा ETL summary tables बनवतो, आणि trigger (CDC) प्रत्येक grade बदल warehouse साठी डायरीत लिहितो.

🎯 का

Attendance report ला 200,000 live ओळींवर 29.3 ms आणि 12 ओळींच्या summary वर 0.08 ms लागले — उत्तर तेच.

🚀 पुढे

खरे warehouses (BigQuery, Redshift, ClickHouse) आणि CDC tools (Debezium, DMS) WAL वाचून हेच मोठ्या प्रमाणावर करतात.

🧪 Try it here — report चालू खोलीवर चालवा, मग warehouse करतो तसा

संपूर्ण धडा 18 वाचा →

धडा 01 सुरू करा → 🗓️ अभ्यास योजना (6 आठवडे) 📐 सर्व 18 धड्यांच्या आकृत्या 🧪 प्रश्नमंजुषा (24 प्रश्न) ⏮️ आधी काय होते & फायदे-तोटे