ArchitectMind
  1. Home
  2. Tutorials
  3. Book 6 · Databases, APIs & Integration
  4. Volume I
  5. Why Databases Exist
Book 6 · Volume I — Database Fundamentals & Relational Data Modeling · Topic 1.1

Why Databases Exist

Persistent data, structured data, data sharing, concurrent access and durability

12 min readStage 3 · Systems8 interview questions
Chapter
Chapter 1 — Database Fundamentals
Prerequisites
Basic Java and SQL familiarity
You will be able to
Explain the five needs databases satisfy, contrast files with databases, relate each need to the DBMS mechanism that provides it (schema, constraints, transactions, MVCC/locking, WAL), and judge when application code should — and should not — re-implement database guarantees
Covers
Persistent data; Structured data; Data sharing; Concurrent access; Durability
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally — From COMMIT to Disk
  5. 4A. Deep Dive — What Applications Should Not Re-implement
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

A database is an organised collection of data managed by a database management system (DBMS) that provides persistent storage, a declared structure, shared access for many users and applications, safe concurrent updates and durability of committed changes.

Five needs that databases satisfy
Figure 1 — Five needs that databases satisfy
NeedDBMS mechanism
Persistent dataData files on durable storage, independent of application processes
Structured dataSchema: tables, types, constraints
Data sharingServer accessed by many clients through SQL and access control
Concurrent accessTransactions, locking, MVCC, isolation levels
DurabilityWrite-ahead logging, fsync, crash recovery, replication, backups
Table 1 — Needs and mechanisms

2Why It Exists

Before DBMSs, each program stored data in its own files with its own format. Sharing data meant copying files; two programs updating the same file corrupted it; a crash mid-write left files half-written; answering a new question meant writing a new program. Hierarchical (IMS, 1966) and network (CODASYL) databases centralised data but tied programs to physical access paths. Codd's relational model (1970) and SQL separated what data is needed from how it is retrieved, and transaction theory (Gray, 1970s) formalised the ACID guarantees that business systems rely on.

Files vs database
Figure 2 — Files vs database

3Core Concepts

3.1 Persistent data

Persistence means data outlives the process that created it — across restarts, deployments and hardware changes. In Java, objects vanish when the JVM exits; the database is where state lives between requests and releases.

Persistence concernDatabase answer
Survive application restartsData in database files, not heap
Survive redeployments and scalingStateless app instances share one database
Long retention (years)Archiving, partitioning, backups
Independent of application languageSQL and data outlive the Java codebase
Table 2 — Persistence

3.2 Structured data

A schema declares tables, column types and constraints. The database rejects data that violates it — no matter which application writes.

SQL
CREATE TABLE customer (
    customer_id  BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email        VARCHAR(254) NOT NULL UNIQUE,
    full_name    VARCHAR(200) NOT NULL,
    pan          CHAR(10) CHECK (pan ~ '^[A-Z]{5}[0-9]{4}[A-Z]$'),
    credit_limit NUMERIC(12,2) NOT NULL DEFAULT 0 CHECK (credit_limit >= 0),
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);
Benefit of structureExample
ValidityNegative credit limits impossible
Shared understandingEvery consumer sees the same types
Efficient storage & indexingFixed types allow compact pages and B-trees
QueryabilityStructure enables SQL joins, filters, aggregates
Table 3 — Why structure matters

Schema-less stores move structure into application code ("schema-on-read"); the structure still exists — it is just not enforced centrally.

3.3 Data sharing

One database commonly serves many consumers: web and mobile back ends, batch jobs, reporting tools, integrations and admin consoles.

Sharing mechanismPurpose
SQL as a common languageAny client can query
Roles and privileges (GRANT)Control who reads/writes what
ViewsStable, restricted interfaces over tables
ReplicationShare load with read replicas and other regions
Table 4 — Sharing mechanisms

Architecture note: sharing one database across services creates tight coupling (Book 5, Volume VII, 8.3). Sharing within one application or through read-only reporting replicas is normal; sharing writable tables across independently deployed services is an anti-pattern.

3.4 Concurrent access

Concurrent access
Figure 3 — Concurrent access

Many clients read and write simultaneously. The DBMS ensures each transaction sees a consistent view and that concurrent updates don't corrupt data.

SQL
-- Two sessions buy the last unit simultaneously;
-- the DBMS serialises the conflicting updates
UPDATE inventory SET available = available - 1
 WHERE sku = 'A1' AND available > 0;
-- second session waits for the first, then updates 0 rows
JAVA
// Spring: one transaction per use case; the database does the concurrency control
@Transactional
public boolean reserve(Sku sku) {
    int updated = jdbc.sql("""
            UPDATE inventory SET available = available - 1
             WHERE sku = :sku AND available > 0""")
        .param("sku", sku.value())
        .update();
    return updated == 1;
}
MechanismWhat it does
TransactionsGroup statements into atomic units
Row locksSerialize conflicting writes
MVCC (PostgreSQL, InnoDB)Readers see snapshots and don't block writers
Isolation levelsControl which anomalies are allowed (Volume II)
Constraints under concurrencyUNIQUE/FK checked atomically — no race windows
Table 5 — Concurrency control

3.5 Durability

Durability: write-ahead logging
Figure 4 — Durability: write-ahead logging

Durability (the D in ACID) guarantees that once a transaction commits, its changes survive crashes and power loss.

Layer of durabilityProtects against
WAL + fsync on commitProcess or OS crash, power loss
Crash recovery (WAL replay)Restores committed changes after restart
Synchronous replicationLoss of a single server/disk
Backups + WAL archiving (PITR)Human error, corruption, disaster
Multi-region replicasRegional outages
Table 6 — Layers of durability

4How It Works Internally — From COMMIT to Disk

1. The application sends statements within a transaction; the server modifies pages in the shared buffer pool (memory).

2. Each change generates a WAL record appended to the log buffer.

3. On COMMIT, the WAL is flushed (fsync) to disk; only then does the server acknowledge success.

4. Modified data pages are written to data files later by the background writer and checkpoints.

5. After a crash, recovery replays WAL from the last checkpoint, redoing committed changes; uncommitted changes are never visible.

Setting (PostgreSQL)EffectTrade-off
synchronous_commit = on (default)Commit waits for local WAL flushDurability vs latency
synchronous_commit = offCommit returns before flushFaster; may lose last ~few hundred ms on crash
synchronous_standby_namesWait for replica acknowledgmentSurvives server loss; higher latency
full_page_writes = onProtects against torn pagesMore WAL volume
Table 7 — Durability knobs

4ADeep Dive — What Applications Should Not Re-implement

Temptation in application codeWhy it failsUse instead
Check-then-insert for uniquenessRace between check and insertUNIQUE constraint; handle violation
In-memory locks for shared rowsDoesn't work across instancesRow locks, conditional updates, optimistic versions
Writing "before" and "after" files for safetyCrash windows, complexityTransactions
Caching as the source of truthLost on restart, inconsistentDatabase as system of record; cache as copy
Manual referential checksRaces, missed paths (batch, admin)Foreign keys
Table 8 — Guarantees to delegate to the database
JAVA
// Correct: let the UNIQUE constraint decide; translate the violation
public CustomerId register(NewCustomer c) {
    try {
        return new CustomerId(jdbc.sql("""
                INSERT INTO customer (email, full_name) VALUES (:email, :name)
                RETURNING customer_id""")
            .param("email", c.email()).param("name", c.name())
            .query(Long.class).single());
    } catch (DuplicateKeyException e) {
        throw new EmailAlreadyRegistered(c.email());
    }
}

5APIs / Features / Syntax

FeaturePurpose
CREATE TABLE … NOT NULL / UNIQUE / CHECK / REFERENCESStructure and integrity
BEGIN … COMMIT / ROLLBACKTransactions
GRANT / REVOKE, roles, row-level securityControlled sharing
JDBC Connection.setAutoCommit(false), commit()Transactions in Java
Spring @Transactional, TransactionTemplateDeclarative/programmatic transactions
Spring JdbcClient, Spring Data JPA/JDBCData access
HikariCPConnection pooling for shared access
Table 9 — Features

6Practical Examples

6.1 Replacing a JSON file store

A configuration service stored tenant settings in JSON files on a shared volume; concurrent edits overwrote each other. Moving to a tenant_setting table with optimistic locking removed lost updates and enabled auditing.

6.2 Durability misunderstanding

A team disabled synchronous_commit to speed up an order service; a crash lost 40 committed orders. The setting was restored for orders and kept off only for a click-stream table where small loss was acceptable.

6.3 Worked example — mapping requirements to database guarantees

Scenario. A clinic chain in Pune is replacing a spreadsheet-and-phone booking process with an appointment system used by receptionists, doctors and an SMS reminder service. The product owner's requirements are written in business language; the architect's job is to turn each one into a guarantee the database must provide, rather than leaving every rule to application code.

Requirements for a clinic appointment system:

RequirementNeed (1.1)Database mechanism
Appointments must exist after server restartsPersistenceTables on durable storage
Phone numbers must be valid Indian mobilesStructureCHECK (phone ~ '^[6-9][0-9]{9}$')
Reception, doctors and SMS service use the same dataSharingOne database; roles per consumer
Two receptionists must not book the same slotConcurrencyUNIQUE (doctor_id, slot_start)
Confirmed bookings must never disappearDurabilityWAL, sync commit, sync replica, backups
Table 10 — Requirements → guarantees
SQL
CREATE TABLE appointment (
    appointment_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    doctor_id      BIGINT NOT NULL REFERENCES doctor,
    patient_id     BIGINT NOT NULL REFERENCES patient,
    slot_start     TIMESTAMPTZ NOT NULL,
    status         VARCHAR(12) NOT NULL CHECK (status IN ('BOOKED','CANCELLED','DONE')),
    CONSTRAINT one_booking_per_slot UNIQUE (doctor_id, slot_start)
);

What it shows. Each requirement maps to one of the reasons databases exist. "Appointments must survive restarts" is persistence; "phone numbers must be valid" is structure, enforced with a CHECK constraint so no client can insert a bad number; "three systems use the same data" is sharing, handled with one database and a role per consumer; "two receptionists must not book the same slot" is concurrency, made atomic with a UNIQUE constraint on doctor and slot; and "confirmed bookings must never disappear" is durability, provided by the write-ahead log, synchronous commit, a synchronous replica and backups.

Takeaway. Translate requirements into database mechanisms explicitly, one row per rule, and put each rule where it holds for every writer. Rules enforced only in one application's code are broken the first time another system or a manual fix writes to the same tables.

6.4 Worked example — proving concurrency safety with a test

Scenario. The booking service passed every unit test, yet in production two patients occasionally received the same 10:30 slot with the same doctor. The team believed their "check availability, then insert" logic was correct, because it never failed when tested one request at a time.

JAVA
@Testcontainers
@SpringBootTest
class SlotBookingConcurrencyTest {
    @Container @ServiceConnection
    static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:17");
    @Autowired BookingService booking;
 
    @Test
    void onlyOneOfTwentyConcurrentBookingsSucceeds() throws Exception {
        var results = new ConcurrentLinkedQueue<Boolean>();
        try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 20; i++) {
                exec.submit(() ->
                        results.add(booking.tryBook(DOCTOR, SLOT, newPatient())));
            }
        }
        assertThat(results).containsOnlyOnce(true);
    }
}
What the test showsWhy it matters
Exactly one booking succeedsThe UNIQUE constraint enforces the rule under concurrency
No application locks neededWorks across many app instances
Table 11 — Test outcome

What it shows. The test starts a real PostgreSQL in a container and fires twenty booking attempts for the same doctor and slot at the same moment, using virtual threads. It then asserts that exactly one succeeded. Against the old check-then-insert code, several attempts pass the availability check before any of them inserts, so the test fails. Once the UNIQUE (doctor_id, slot_start) constraint is added and the service treats a unique-violation as "slot taken", exactly one booking succeeds every time, because the database serialises the conflicting inserts.

Takeaway. Concurrency bugs do not appear in sequential tests. Prove concurrency guarantees with a concurrent test against the real database engine, not an in-memory substitute, and let constraints, not application checks, enforce the rule.

6.5 Worked example — choosing durability settings per data class

Scenario. A retail platform runs one PostgreSQL cluster for very different data: payments, user profiles, session logs and click streams. Applying the strictest durability to everything made write latency high for logging traffic, while relaxing it globally would have risked losing payments after a crash.

DataLoss toleranceSettings
Payments, ordersNonesynchronous_commit=on + synchronous standby, PITR backups
User profilesNoneSame as above
Session/activity logsSeconds acceptableSET LOCAL synchronous_commit = off in those transactions
Metrics/click streamsMinutes acceptableSeparate database or unlogged/partitioned tables
Table 12 — Durability by data class

What it shows. The team classified data by how much loss the business can tolerate and chose settings per class. Payments, orders and profiles keep synchronous_commit on with a synchronous standby and point-in-time recovery, so a committed transaction is never lost. Session and activity logs, where losing a few seconds is acceptable, use SET LOCAL synchronous_commit = off inside those transactions only, which removes the wait for the disk flush. Metrics and click streams, where minutes of loss are tolerable, move to a separate database or to unlogged or partitioned tables designed for high write volume.

Takeaway. Durability is a business decision per data class, not one global switch. Make the loss tolerance explicit with data owners, apply weaker settings only to the transactions that can afford them, and keep money and orders on the strongest guarantees.

7Common Problems & Mistakes

  • Using files or caches as a system of record for shared business data.
  • Application-level uniqueness checks without database constraints.
  • Disabling durability globally for performance.
  • Sharing writable tables across services.
  • Assuming the ORM provides concurrency safety without transactions and constraints.
  • No tested backups — durability ends at the last restorable backup.

8Performance & Production Considerations

AspectNote
Commit latencyDominated by WAL fsync (~sub-ms on NVMe, more on network disks)
Connection countEach client needs connections; use pools (1.3)
Concurrency hotspotsRows updated by many transactions (counters) serialise writes
BackupsTest restores regularly; measure RTO
Replication lagRead replicas are eventually consistent
Table 13 — Production view

9Architecture & Design Considerations

  • Treat the database as the system of record and use its guarantees deliberately.
  • Enforce invariants in the schema (constraints) and translate violations in the application.
  • Choose durability levels per data class explicitly.
  • Keep one owner per database in service architectures; share via APIs, events or read replicas.

10Interview Questions & Answers

Q1Basic

Why use a database instead of files?

Files store bytes; a database manages data. With files, every application must invent its own format, parse it, handle partial writes after a crash, and coordinate concurrent writers, usually badly. A database provides structured storage with a schema and constraints, safe concurrent access through locking or MVCC, transactions that apply several changes atomically, crash recovery through a write-ahead log, and a declarative query language so you ask for data instead of writing loops. Many applications and users can share the same data safely. Files still suit large immutable objects such as images or exports, usually stored next to a database rather than instead of one.

Q2Basic

What does durability mean?

Durability is the D in ACID: once the database reports that a transaction has committed, its changes must survive any later crash, power failure or restart. If a customer sees "order confirmed", that order must still exist after the server reboots. Durability is not the same as availability or backup: a durable database can still be unreachable during an outage, and it does not protect against someone deleting data on purpose. In practice, durability also depends on the hardware honouring flushes and, for protection against losing a whole server, on replication to another machine before commit is acknowledged.

Q3Intermediate

How does a database guarantee durability?

Before acknowledging a commit, the database writes a description of the changes to its write-ahead log and forces that log to durable storage with an fsync. The actual table and index pages can be written later, because after a crash the database replays the log from the last checkpoint and reapplies every committed change, while discarding uncommitted ones. PostgreSQL calls this the WAL, Oracle the redo log. Synchronous replication extends durability beyond one machine by also waiting for a standby to receive the log. Settings such as synchronous_commit control how strictly this happens, trading latency against possible data loss.

Q4Intermediate

Why is an application-level "check then insert" unsafe for uniqueness?

Check-then-insert runs as two separate steps: first a SELECT to see whether the value exists, then an INSERT if it does not. When two requests arrive at nearly the same moment, both SELECTs can run before either INSERT, both see no existing row, and both insert, creating a duplicate. Wrapping the steps in a transaction does not help at the default READ COMMITTED isolation level. A UNIQUE constraint makes the rule atomic inside the database: the second insert fails with a unique-violation error, which the application can turn into a clear message such as "this slot is already booked".

Q5Senior Engineer

What is MVCC and why does it help concurrency?

Multi-version concurrency control keeps several versions of a row instead of overwriting it in place. When a transaction updates a row, the database creates a new version and keeps the old one until no running transaction needs it. Each transaction reads from a consistent snapshot, seeing only versions committed before its snapshot was taken. As a result, readers never wait for writers and writers never wait for readers; only two writers changing the same row must wait for each other. PostgreSQL, Oracle and MySQL's InnoDB all use MVCC. The cost is cleanup work, such as PostgreSQL's VACUUM, to remove old row versions.

Q6Senior Engineer

When might you relax durability?

Relax durability only for data whose loss the business has explicitly accepted, such as click streams, metrics, activity logs or cache-like tables that can be rebuilt. In PostgreSQL, SET LOCAL synchronous_commit = off inside those transactions lets commits return before the log is flushed, so a crash can lose the last fraction of a second of such writes but never corrupts the database. Unlogged tables go further and lose all their data after a crash. Never relax durability for money, orders or anything with legal or customer impact, and document each relaxation with its owner's approval.

Q7Technical Architect

Why is sharing a database between services problematic?

When several services read and write the same tables, the database schema becomes a shared contract that nobody owns. Any change, such as renaming a column, can break another service, so deployments must be coordinated and the services are no longer independent. Ownership blurs: it becomes unclear which service enforces which rules, and data can be changed by code that does not understand them. Performance couples too, because one service's heavy query can slow all the others. The usual solution is a database per service, with data shared through APIs or events, and read-only replicas or warehouses for reporting.

Q8Solution Architect

How do you decide which guarantees the database should enforce?

Put in the schema every invariant that must hold no matter which application or person writes the data: primary and unique keys, foreign keys, NOT NULL, CHECK constraints for value domains, and exclusion constraints such as non-overlapping bookings. These are cheap, atomic and impossible to bypass. Keep in application code the rules that depend on workflow, user context or several aggregates, such as approval processes, pricing policies or rules that involve calls to other services. A useful test is: would corrupted data be possible if a new client or a manual SQL fix ignored this rule? If yes, the database should enforce it.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 6

Databases, APIs & Integration

Volume I · Topic 1.2

Database Management Systems

Database Fundamentals & Relational Data Modeling

Volume I · Topic 1.3

Database Architecture

Database Fundamentals & Relational Data Modeling

Volume II · Topic 1.1

Data Retrieval

SQL, Transactions, Concurrency & Database Performance

Volume II · Topic 1.2

Data Modification

SQL, Transactions, Concurrency & Database Performance

Volume II · Topic 1.3

Aggregation

SQL, Transactions, Concurrency & Database Performance

Volume III · Topic 1.1

JDBC Architecture

Java Persistence & Data Access Architecture

Volume III · Topic 1.2

PreparedStatement

Java Persistence & Data Access Architecture

Volume III · Topic 1.3

JDBC Transactions

Java Persistence & Data Access Architecture

Volume IV · Topic 1.1

Why NoSQL Exists

NoSQL, Caching & Polyglot Persistence

Volume IV · Topic 1.2

Categories

NoSQL, Caching & Polyglot Persistence

Volume IV · Topic 1.3

Trade-offs

NoSQL, Caching & Polyglot Persistence

Volume V · Topic 1.1

HTTP Architecture

API Design, HTTP & REST

Volume V · Topic 1.2

Methods

API Design, HTTP & REST

Volume V · Topic 1.3

Status Codes

API Design, HTTP & REST

Volume VI · Topic 1.1

Gateway Responsibilities

API Management, GraphQL & Distributed API Design

Volume VI · Topic 1.2

Gateway Patterns

API Management, GraphQL & Distributed API Design

Volume VI · Topic 1.3

Gateway Risks

API Management, GraphQL & Distributed API Design

Volume VII · Topic 1.1

Why Systems Integrate

Enterprise Integration & Messaging

Volume VII · Topic 1.2

Integration Styles

Enterprise Integration & Messaging

Volume VII · Topic 1.3

Point-to-Point Integration

Enterprise Integration & Messaging

Volume VIII · Topic 1.1

Integration Boundaries

Integration Architecture & Data Consistency

Volume VIII · Topic 1.2

Synchronous vs Asynchronous

Integration Architecture & Data Consistency

Volume VIII · Topic 1.3

Coupling

Integration Architecture & Data Consistency