Chapter 1: What is a Database and Why Do We Need One?

ℹ️ Prerequisites
None. We start from absolute zero.

The Problem: Storing Data That Outlives Your Program

As a C programmer, you know that when your program exits, all its variables disappear. The memory is freed. Everything in RAM is gone.

// This data dies when the program exits
struct User {
    int id;
    char name[64];
    char email[128];
};

struct User users[1000];  // Gone when process terminates

So you write to a file:

FILE* f = fopen("users.dat", "wb");
fwrite(users, sizeof(struct User), 1000, f);
fclose(f);

This works for simple cases. But what happens when:

These are the problems a database solves.

Definition

A database is an organized collection of data stored and accessed electronically. A Database Management System (DBMS) is the software that manages that data — handling storage, retrieval, updates, concurrency, crash recovery, and security.

┌─────────────────────────────────────────────────────┐ │ Your Application │ │ (C program, Python service, Java app, etc.) │ └──────────────────────┬──────────────────────────────┘ │ SQL queries / API calls ▼ ┌─────────────────────────────────────────────────────┐ │ Database Management System │ │ (PostgreSQL, MySQL, etc.) │ │ │ │ ┌──────────┐ ┌───────────┐ ┌────────────────────┐ │ │ │ Query │ │Transaction│ │ Storage Engine │ │ │ │ Parser │ │ Manager │ │ (reads/writes │ │ │ │& Planner │ │ (ACID) │ │ actual files) │ │ │ └──────────┘ └───────────┘ └────────────────────┘ │ └──────────────────────┬──────────────────────────────┘ │ read()/write() syscalls ▼ ┌─────────────────────────────────────────────────────┐ │ Operating System / Filesystem │ │ (ext4, XFS, etc.) │ └──────────────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ Disk (SSD / HDD) │ │ The actual persistent storage │ └─────────────────────────────────────────────────────┘

What a Database Gives You (That Files Don't)

ProblemFile ApproachDatabase Approach
Find one record in millionsScan entire file: O(n)Index lookup: O(log n)
Concurrent accessFile locks (coarse, slow)Row-level locks, MVCC
Crash mid-writeCorrupted fileWrite-ahead log → auto-recovery
Partial updateRewrite entire fileUpdate in-place or append
Atomic multi-step opsManual (error-prone)Transactions (all-or-nothing)
Query flexibilityWrite custom code per querySQL: declarative queries
Data integrityManual validationConstraints, foreign keys

The Core Guarantees: ACID (Preview)

Every serious database provides these four guarantees (we'll deep-dive in Chapter 17):

💡 Telecom Analogy

Think of ACID like signaling in telecom. When a UE does a Registration, the AMF must either complete the entire procedure (update context, notify SMF, etc.) or roll back completely. You can't have a half-registered UE. That's atomicity. A database gives you this guarantee automatically for any operation you wrap in a transaction.

A Taste of SQL

Instead of writing C code to search through arrays, you write declarative queries:

-- Find a user by email (database figures out HOW to find it efficiently)
SELECT id, name, email
FROM users
WHERE email = 'alice@example.com';

-- Transfer money atomically
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- If anything fails between BEGIN and COMMIT, BOTH updates are rolled back

Compare to the C equivalent:

// Without a database, YOU must handle all of this:
int transfer(int from, int to, int amount) {
    lock_file(accounts_file);           // concurrency
    read_all_accounts(accounts_file);   // load data
    
    if (accounts[from].balance < amount)
        return -1;                       // validation
    
    accounts[from].balance -= amount;
    accounts[to].balance += amount;
    
    // What if we crash HERE? File is half-written!
    write_all_accounts(accounts_file);  // persist
    unlock_file(accounts_file);          // release
    return 0;
}

Client-Server Architecture

Most databases run as a separate process (a server/daemon). Your application connects to it over a network protocol:

┌──────────────┐ TCP connection ┌──────────────────┐ │ Your App │ ◄─────────────────────► │ Database Server │ │ (client) │ SQL over protocol │ (PostgreSQL) │ │ │ (port 5432) │ │ │ libpq / ODBC │ │ Manages: │ │ / driver │ │ • Storage │ └──────────────┘ │ • Indexes │ │ • Transactions │ ┌──────────────┐ │ • Concurrency │ │ Another App │ ◄─────────────────────► │ • Recovery │ │ (client 2) │ │ • Security │ └──────────────┘ └──────────────────┘

This separation means:

Embedded vs Client-Server

Not all databases are client-server. Some are embedded — they run inside your process:

TypeExamplesUse Case
Client-ServerPostgreSQL, MySQL, OracleMulti-user apps, web services
EmbeddedSQLite, RocksDB, LevelDBMobile apps, single-process, local storage
ℹ️ SQLite

SQLite is the most deployed database in the world (billions of instances — every phone, every browser). It's a single C file you compile into your application. No server, no configuration. Perfect for learning SQL and for embedded use cases.

The Big Picture: Where Databases Fit

User → Browser/App → Web Server → Application Logic → DATABASE ↕ Disk Storage In microservices: ┌─────────┐ ┌─────────┐ ┌─────────┐ │Service A │ │Service B │ │Service C │ │ (AMF) │ │ (SMF) │ │ (UDM) │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ DB A │ │ DB B │ │ DB C │ │(sessions)│ │(policies)│ │(subscr.) │ └─────────┘ └─────────┘ └─────────┘ Each microservice owns its own database (database-per-service pattern).

Summary

Key Takeaways