Designing Data Intensive Applications
September 13, 2025- #book
- #data
https://learning.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/
Foundataions of Data Systems
Type of Data
- Database
- Cache
- Search Index
- Stream Processing
- Batch Processing
Reliability
The system should continue to work correctly (performing the correct function at the desired level of performance) even in the face of adversity (hardware or software faults, and even human error)
- tolerate hardware and software faults
- human error
Scalability
As the system grows (in data volume, traffic volume, or complexity), there should be reasonable ways of dealing with that growth.
- measureing load and performance
- latency percentiles, throughput
Maintainability
Over time, many different people will work on the system (engineering and operations, both maintaining current behavior and adapting the system to new use cases), and they should all be able to work on it productively.
- operability, siplicity, and evolveability
Fault vs. Failure
- Fault is when it deviates from the spec.
- Failure is when the system stops responding under load.
Latency vs. Response Time
- Latency is the time waiting to fulfill
- Response time is total time.
Data Models and Query Languages
| SQL | NoSQL |
|---|---|
| Impedence mismatch with OOP | High Locality |
| Added JSON and XML later | Better JSON Support |
| Relational | Scalability on very large datasets and high write apps |
| Strict Schema | Flxible or Dynamic Schema |
| Normalized | Denormalized |
| Many-to-one and Many-to-Many well supported | Limits on Many-to-Many, Embedding with doc size concerns |
| ORM reduce impedence mismatch | ORM may help |
| Index = Path | |
| Schema on Write | Schema on Read |
| Functions | Map Reduce |
| Declarative Language | Declarative Language |
| Hierarchical | Network | Graph |
|---|---|---|
| 1 Parent | N-Parents | N-Edges |
| Tree | Network | Graph |
| IBM's IMS | CODASYL | Neo4J, etc. |
| Stock Keeping, Business Process | Social, Web, Road or Rail | |
| Parent/Child with schema on record types | Parent/Child | Heterogenus Relations |
| One path | Unique ids and many paths | |
| Ordered | Unordered | |
| Imperative queries that break | Can be imparative, supports delcarative like Cypher or SPARQL |
- All Require path traversal and algorithms
Schema on Read vs. Schema on Write
- READ (SQL)
- Slow schema changes, especially with MySQL
- WRITE (NoSQL)
- Different object types in same table
- Structure is determined by external systems
Graph
A graph consists of two kinds of objects: vertices (also known as nodes or entities) and edges (also known as relationships or arcs). Many kinds of data can be modeled as a graph
Property Graphs
-
Vertex
- A unique identifier
- A set of outgoing edges
- A set of incoming edges
- A collection of properties (key-value pairs)
-
Edge
- A unique identifier
- The vertex at which the edge starts (the tail vertex)
- The vertex at which the edge ends (the head vertex)
- A label to describe the kind of relationship between the two vertices
- A collection of properties (key-value pairs)
Query Language
Cypher is a declarative query language for property graphs, created for the Neo4j graph database. (It is named after a character in the movie The Matrix and is not related to ciphers in cryptography
Can you build graphs in SQL?
- Yes, but the query is complex and likely slow.
Triple-Stores and SPARQL
In a triple-store, all information is stored in the form of very simple three-part statements: (subject, predicate, object). For example, in the triple (Jim, likes, bananas), Jim is the subject, likes is the predicate (verb), and bananas is the object.
- Subject is equivelent to a vertex in a graph.
- Object is either:
- A value in a primitive datatype, such as a string or a number. In that case, the predicate and object of the triple are equivalent to the key and value of a property on the subject vertex. For example, (lucy, age, 33) is like a vertex lucy with properties {"age":33}.
- Another vertex in the graph. In that case, the predicate is an edge in the graph, the subject is the tail vertex, and the object is the head vertex. For example, in (lucy, marriedTo, alain) the subject and object lucy and alain are both vertices, and the predicate marriedTo is the label of the edge that connects them.
- SPARQL
- SPARQL is a query language for triple-stores using the RDF data model. (It is an acronym for SPARQL Protocol and RDF Query Language, pronounced “sparkle.”) It predates Cypher, and since Cypher’s pattern matching is borrowed from SPARQL, they look quite similar
Datalog
Datalog is a much older language than SPARQL or Cypher, having been studied extensively by academics in the 1980s. It is less well known among software engineers, but it is nevertheless important, because it provides the foundation that later query languages build upon.
Storage and Retrieval
Databases need to do two things:
- When given data, it should store it
- When you ask for it again, it should give the data back to you
Optimize for two types of data:
- transactional
- log
Indexes
- Optimize reads
- Additional structure
- Any index usually slows down writes. Choose wisely.
Hash Indexes
- Key/Value pairs
- If only appending to a file. Simple strategy is to track each byte offset of keys. Like Bitcask.
- Limitation: hash table must fit in memory, so if you have a very large number of keys, your out of luck. It's difficult to make on disk hash perform well. It requires a lot of random access I/O, it is expensive to grow when it becomes full, and hash collisions require fiddly logic.
- Limitation: Range queries are not efficient.
SSTables and LSM-Trees
- Sequence of key/value pairs is sorted by key. This is Sorted String Tabls (SSTable).
- Cannot append in any order
- Advantage: Merging segments is simple and efficient, even if the files are bigger than available memory. Similar to mergesort algorithm.
- Advantage: In order to finda particular key in the file, you no longer need to keep an index of all the keys in memory. e.g. looking for handiwork, don't know the exact offset of that key in the segment file. You do know th eoffsets for keys handbag and handsome and it's sorted, handiwork must appear between the two. You still need in-memory index for some keys, but it can be sparse.
- Advantage: Since read requests need to scan over several key-value pairs in the requested range anyway, it is possible to group those records into a block and compress it before writing to disk.
Log Based
- Fast writes, just appends
- Slow reads, has to iterate
- File format: CSV is not the best, faster/easier to use binary format that first encodes the length of a string in bytes, followed by the raw string.
- Delete: if you want to delete a key and it's vaule, you have to append a special deletion record to the data file, sometimes called a tombstone. When merging the tombstone tells the merge process to discard any previous values for the deleted key.
- Crash Recovery: If the database is restarted the in-memory has maps are lost. Having to rebuild from log can be slow. Bitcask speeds up recoery by storing a snapshot of each segment's hash map on disk, which can be loaded into memory more quickly.
- Partially written records: The database may crash at any time, including halfway through appending a record to the log. Bitcask files include checksums, allowing such corrupted parts of the log to be detected and ignored.
- Concurrency control: as writes are appended to the log in a strictly sequential order, a common implementation choice is to have only one writer thread. Data file segments are append-only and otherwise immutable, so they can be read concurrently.
Append Only
What about when you eventually run out of disk space?
Break the log into segments of a certain size by closing a segment fille when it reaches a certain size, and making subsequent writes to a new segment file.
This makes compaction possible. This allows deleting duplicate data and merging several segments together.