Designing Data-Intensive Applications, session 2

Data models &
query languages

The shape you give your data decides what’s easy, what’s awkward, and how you think about the problem.

→ next · ← back · N notes · F full screen

Software is data models stacked on data models.

The real worldpeople, merchants, money flows, events
Your objectsclasses, entities, APIs
General-purpose modeltables · JSON documents · graphstoday
Bytesmemory, disk, networknext session
Physicscurrents, light pulses, magnetic fields

Each layer hides the complexity below it behind a clean model.

01

Relational vs document

A fifty-year-old debate, rerun with new names.

Every challenger got hype. SQL stayed.

1970Codd proposes the relational model
’70sHierarchical (IMS) and network (CODASYL) models dominate
mid-’80sRDBMS + SQL become the default
’90sObject databases come and go
2000sXML databases: niche
2009#NoSQL starts as a meetup hashtag
TodayPolyglot persistence
25–30 yrs
of relational dominance. An eternity in computing.
Why NoSQL? More scale and write throughput, open source, specialised queries, and frustration with rigid schemas.

Lab: one profile, two shapes

A résumé is a tree: one user, many positions, many schools.
users
idnameregion_id
42Sara A.ir:21
positions
user_idtitleorg
42Backend eng.Acme Pay
42InternDataCo
education
user_idschoolfrom
42Sharif Univ.2014
contacts
user_idtypevalue
42blogsara.dev
4
queries to load the profile (or 1 messy multi-way join)
4
places on disk the data lives
Easy
turning “org” into a shared entity later

Store "ir:21", not "Greater Tehran".

Anything meaningful to humans may need to change someday. An ID never does.

Removing duplicated human-meaningful data is the whole idea of normalisation.

Consistent spelling No ambiguity between same-named cities Rename once, updated everywhere Easy to localise Smarter search (city → province)

Data grows more connected with every feature.

Companies become entities with pages and logos. Users recommend each other. Suddenly the tree has many-to-many links.

No joins in the database? Then your application code does them.

résumé: Sararésumé: Omid position → orgeducation → schoolrecommended by →position → orgeducation → school Acme Pay Sharif Univ.

We’ve had this argument before.

Hierarchical · IMS, 1968

A tree of nested records, like JSON. Great for one-to-many. No joins: duplicate data or chase references by hand.

Network · CODASYL

Records with many parents, linked by pointers. Queries walk access paths by hand. Change the paths, rewrite the code.

Relational · SQL

Everything laid out in the open. The query optimizer picks the access path. New index? Queries use it automatically.

Build the optimizer once, and every application benefits. The general solution wins in the long run.

So which one leads to simpler code?

Document wins when…

  • data is a tree of one-to-many
  • you load the whole tree at once
  • records are heterogeneous
  • you want schema flexibility

Shredding a tree into many tables makes schemas and code clumsy.

Relational wins when…

  • you need joins
  • many-to-one references are common
  • many-to-many relationships appear
  • all records share a structure

Denormalising or app-side joins push complexity into your code.

tree-shaped → documentsome links → relationalhighly connected → graph

“Schemaless” is a myth. It’s schema-on-read.

Your reading code still assumes a structure. The only question is who enforces it: the database at write time, or your code at read time. Like static vs dynamic typing.

Schema-on-read: handle old shapes in code
if (user.fullName != null && user.firstName == null) {
  // written before the split
  user.firstName = user.fullName.split(" ")[0];
}
Schema-on-write: migrate (PostgreSQL)
ALTER TABLE users ADD COLUMN first_name text;
UPDATE users
   SET first_name = split_part(full_name, ' ', 1);

Lab: which model fits?

Locality: a win only if you read the whole thing.

A document is stored as one blob. Reading one field still loads it all; growing it usually means rewriting it all.

Keep documents small.

4 tables: scattered reads 1 document: one contiguous read Spanner, Oracle clusters, and Bigtable column families offer the same idea.

The two models are converging.

JSON in SQL
PostgreSQL (since 9.3), MySQL, DB2 can store, index, and query inside documents.
Joins in NoSQL
RethinkDB joins; some MongoDB drivers resolve references client-side.
1970
Codd’s original paper already allowed nested relations as values.

A hybrid of relational and document is a good route for databases to take.

02

Query languages

Say what you want, not how to get it.

Imperative says how. Declarative says what.

Imperative
List<Payment> failed = new ArrayList<>();
for (Payment p : payments) {
  if (p.status() == FAILED) {
    failed.add(p);
  }
}
Declarative
SELECT * FROM payments
WHERE status = 'FAILED';
Concise
Easier to write and read.
Free to optimise
No promised order, so the DB can reorganise storage and pick indexes.
Parallel
A pattern, not an algorithm: easy to spread across cores and machines.

Lab: CSS is declarative too

User clicks:
Declarative: li.selected > p { background: yellow }
Imperative: loop over DOM, set style

Click “Run JS once”, then select a different page.

MapReduce: somewhere in between.

Refunded amount per month, three ways:

Moral: NoSQL systems tend to reinvent SQL, in disguise.

03

Graph-like models

For when anything can relate to anything.

Vertices and edges.

Social
people → who knows whom
Web
pages → links, ranked by PageRank
Roads
junctions → roads, shortest path

Vertices don’t have to be one kind. Facebook keeps people, places, events, check-ins, and comments in one graph.

Property graph in two PostgreSQL tables
CREATE TABLE vertices (id bigint PRIMARY KEY, props jsonb);
CREATE TABLE edges (
  id bigint PRIMARY KEY,
  tail bigint REFERENCES vertices, -- from
  head bigint REFERENCES vertices, -- to
  label text, props jsonb);
CREATE INDEX ON edges (tail); CREATE INDEX ON edges (head);
Why it’s flexible
  • Any vertex can link to any other
  • Traverse forward and backward (two indexes)
  • Labels keep many relationship kinds in one clean model
  • Easy to extend as features grow

Lab: who was born in X and lives in Y?

Matches:

      

Same question, four languages.

Lines of code

When one model needs 10× more code, you picked the wrong model for that question.

Triple-stores: everything is a sentence.

subjectpredicateobject
Object is a value → it’s a property
_:sara  :name  "Sara" .
Object is a vertex → it’s an edge
_:sara  :livesIn  _:berlin .

Same idea as property graphs, different words. RDF adds URIs so your data can merge with everyone else’s.

Isn’t this just CODASYL again? No.

CODASYL (1970s)
Graph database
Schema restricts which record nests in which
Any vertex can link to any vertex
Reach a record only via an access path
Jump to any vertex by ID or index
Children are ordered sets the DB must maintain
Unordered; sort at query time
Imperative queries, broken by schema changes
Declarative Cypher, SPARQL, Datalog

The whole chapter in one picture.

Hierarchicalone big tree Relationalsolves many-to-many Documentself-contained, few links Grapheverything relates to everything

Each model can emulate the others, awkwardly. That’s why we have different tools, not one size fits all.

Now, our system.

Impedance mismatch

Where does JPA leak through? N+1 queries, lazy-loading surprises, awkward mappings?

IDs or copies?

Which settlement fields reference merchants, currencies, banks by ID, and which copy values on purpose as a snapshot of that moment?

JSON we can’t query

Do we keep JSON in text columns? Should any become jsonb with an index?

Hidden graphs

Merchant groups, account hierarchies, fraud links: are we walking them in Java loops instead of WITH RECURSIVE?

Next session

How databases store your data

From a two-line bash database to LSM-trees and B-trees: what really happens below layer 3. Chapter 3, part 1.