Data Model Selector
by @quochungto
Choose between relational, document, and graph data models for an application by analyzing data shape, relationship complexity, and query patterns. Use when...
clawhub install bookforge-data-model-selectorπ About This Skill
name: data-model-selector description: | Choose between relational, document, and graph data models for an application by analyzing data shape, relationship complexity, and query patterns. Use when asked "should I use MongoDB or PostgreSQL?", "when does a graph database make sense?", "how do I choose between SQL and NoSQL?", or "what data model fits my access patterns?" Also use for: evaluating impedance mismatch between data model and application code; deciding schema-on-read vs. schema-on-write for heterogeneous data; diagnosing whether many-to-many relationships call for relational or graph model; choosing between property graphs and triple-stores; deciding when polyglot persistence is appropriate. Produces a concrete recommendation with trade-off analysis β not "it depends." Covers relational (PostgreSQL, MySQL), document (MongoDB, CouchDB), and graph (Neo4j, Datomic) models including schema enforcement strategies and data locality trade-offs. For storage engine internals (LSM-tree vs B-tree), use storage-engine-selector instead. For OLTP vs. analytics routing, use oltp-olap-workload-classifier instead. version: 1.0.0 homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/designing-data-intensive-applications/skills/data-model-selector metadata: {"openclaw":{"emoji":"π","homepage":"https://github.com/bookforge-ai/bookforge-skills"}} status: draft depends-on: [] source-books: - id: designing-data-intensive-applications title: "Designing Data-Intensive Applications" authors: ["Martin Kleppmann"] chapters: [2] tags: [data-model, relational, document, graph, nosql, schema-on-read, schema-on-write, data-locality, joins, many-to-many, polyglot-persistence, neo4j, mongodb, postgresql, property-graph, triple-store, cypher, sparql, architecture-decision] execution: tier: 1 mode: hybrid inputs: - type: document description: "System description, data relationship description, and expected query patterns β as a written brief, existing schema files (.sql, .json), architecture document, or codebase to analyze" tools-required: [Read, Write] tools-optional: [Grep] mcps-required: [] environment: "Any agent environment. Works with pasted descriptions, schema files, docker-compose.yml, or architecture.md documents." discovery: goal: "Produce a concrete data model recommendation with trade-off analysis β not a balanced overview of options" tasks: - "Classify data shape (tree-structured vs. interconnected graph vs. tabular)" - "Score each model against relationship complexity, join requirements, schema flexibility needs, and query patterns" - "Identify schema enforcement strategy (schema-on-read vs. schema-on-write)" - "Analyze data locality requirements" - "Produce a recommendation document with trade-off rationale and implementation guidance" audience: roles: ["backend-engineer", "software-architect", "data-engineer", "tech-lead", "technical-manager"] experience: "intermediate-to-advanced β assumes familiarity with relational databases and SQL" triggers: - "User is designing a new system and needs to choose a database model" - "User has an existing system with schema complexity or join explosion symptoms" - "User is evaluating document databases (MongoDB, CouchDB, RethinkDB) vs. relational" - "User has graph-like data (social networks, recommendation engines, fraud detection, routing)" - "User is experiencing object-relational impedance mismatch and wants to know if document model helps" - "User needs to decide between schema-on-read and schema-on-write for a heterogeneous data source" - "User wants to validate whether their current data model fits their access patterns" not_for: - "Choosing specific databases within a model (e.g., PostgreSQL vs. MySQL, MongoDB vs. CouchDB) β this skill selects the model, not the product" - "Storage engine selection (LSM-tree vs. B-tree) β use storage-engine-selector" - "Partitioning strategy for distributed deployments β use partitioning-strategy-advisor" - "Replication topology decisions β out of scope for this skill"
Data Model Selector
When to Use
You are designing a new system or evaluating an existing one and need to decide how to structure your data: relational tables with joins, self-contained documents with nested structure, or a graph of vertices and edges.
This skill applies when:
This skill selects the model, not the product. Once you have a recommendation, proceed to product selection based on operational requirements (replication, consistency guarantees, operational complexity). For storage engine decisions within your chosen model, see storage-engine-selector. For partitioning strategy once you have a model and product, see partitioning-strategy-advisor.
Context & Input Gathering
Before running the decision framework, collect:
Required
Important
Optional (improves recommendation precision)
.sql, .json schema, or ORM model definitions for analysisIf the data shape and relationship description are missing, ask for them before proceeding. A model recommendation without relationship complexity analysis is unreliable.
Process
Step 1: Classify the Data Shape
Action: Determine the primary structure of your data. Every data model has a structural assumption β violate it, and complexity moves into your application code.
WHY: The three models evolved to solve different structural problems. The hierarchical model (predecessor to document databases) worked for one-to-many tree structures but broke on many-to-many relationships. The relational model solved many-to-many but introduced a translation layer between objects and tables. Graph databases are the right tool when connections between entities are as important as the entities themselves. Choosing a model that matches your data's natural structure minimizes the impedance mismatch between your application and your storage layer β meaning less translation code, simpler queries, and a data model that evolves naturally with your application.
Identify which category best describes your data:
Category A β Tree structure (one-to-many, self-contained)
Category B β Interconnected entities (many-to-many, tabular)
Category C β Highly connected graph (many-to-many with traversal)
Step 2: Score Each Model Against Your Data
Action: Apply the five-criteria scoring rubric to rate each model's fit. Score each criterion 1β5 per model.
WHY: Scoring all three models β even the obvious misfits β forces explicit trade-off reasoning. Practitioners who skip this step often discover later that their "obvious" choice scores poorly on a criterion they hadn't considered (e.g., choosing a document model for "flexibility" without noticing that many-to-many relationships are already present, which will require application-side join emulation). Running the full rubric also produces the rationale needed for an architecture decision record.
Scoring criteria:
1. Data locality fit β Does the model match how data is naturally retrieved?
2. Relationship complexity fit β Does the model handle the relationship types present?
3. Schema flexibility β Does the model handle heterogeneous or evolving record structures?
4. Query pattern support β Does the model's native query language match how you will query?
5. Normalization requirement β Does the model support the deduplication level your data needs?
Score each model:
Relational Document Graph
Data locality [1-5] [1-5] [1-5]
Relationship complexity [1-5] [1-5] [1-5]
Schema flexibility [1-5] [1-5] [1-5]
Query pattern support [1-5] [1-5] [1-5]
Normalization [1-5] [1-5] [1-5]
Total [5-25] [5-25] [5-25]
See references/decision-matrix.md for a pre-filled scoring guide with example systems and their typical scores.
Step 3: Apply the Decision Rules
Action: Apply explicit if/then decision rules to produce a primary model recommendation. These rules encode the structural logic of the chapter's analysis β they are not heuristics, but direct consequences of how each model handles relationships.
WHY: Scoring produces numbers; decision rules produce a recommendation. The rules encode the non-obvious consequences of model choice that practitioners discover only after the fact: document model users discovering that many-to-many relationships require application-side join emulation; relational model users discovering that object-relational impedance mismatch creates thousands of lines of translation code; graph database adopters discovering that simple one-to-many queries are awkward in Cypher. The rules prevent these surprises.
Rule 1 β Use the document model if ALL of the following are true:
Rule 2 β Use the relational model if ANY of the following are true:
Rule 3 β Use the graph model if ALL of the following are true:
Rule 4 β Consider polyglot persistence if:
Tie-breaker when rules conflict: The relationship complexity criterion is the primary tie-breaker. Many-to-many relationships are the historical dividing line between models β they are why the relational model was invented (to solve the hierarchical model's many-to-many problem) and why graph databases exist (to handle cases where relational joins become impractical). If many-to-many relationships are present or expected, the document model is strongly contraindicated.
Step 4: Analyze Schema Enforcement Strategy
Action: Determine whether schema-on-read or schema-on-write is appropriate, independent of the model choice. Note that most relational databases now support document-like JSON columns, and most document databases are adding join-like capabilities β model convergence means this is increasingly a configuration choice, not just a database product choice.
WHY: Schema-on-read (structure is implicit, interpreted by application code at read time) vs. schema-on-write (structure is explicit, enforced by the database at write time) is a distinct decision from model selection, but it profoundly affects how easily your data can evolve. Schema-on-write is like static type checking: it catches structural errors at write time, provides documentation, and makes queries predictable. Schema-on-read is like dynamic type checking: it is more flexible, tolerates heterogeneous records, but pushes structural validation into application code. Neither is superior β the right choice depends on whether your records have heterogeneous structure and how rapidly your schema evolves.
Prefer schema-on-write (relational enforced schema, JSON Schema validation) when:
Prefer schema-on-read (document databases, JSON columns without schema validation) when:
Schema-on-read migration pattern: When changing a field format, write new documents with new fields and handle both old and new format at read time in application code β no migration required.
Schema-on-write migration pattern: Use ALTER TABLE + UPDATE. Schema changes have a reputation for downtime that is mostly undeserved β PostgreSQL executes most ALTER TABLE in milliseconds; MySQL is the exception (full table copy). See references/decision-matrix.md for tooling options.
Step 5: Evaluate Data Locality
Action: Determine whether data locality is a meaningful performance factor for your application, and whether it argues for or against the document model.
WHY: The document model's storage locality advantage β all related data stored as a single continuous string, fetched in one disk read β is real but conditional. It only matters if you need large parts of the document at the same time. If you access only part of a document (e.g., you only need the user's name, not all their positions and education history), the database still loads the entire document β making locality wasteful. For documents that are large or frequently partially accessed, relational row-level access is more efficient. Furthermore, on writes, modifying a document that grows in size usually requires rewriting the entire document. Keep this in mind: locality is an advantage only when documents are small and accessed whole.
Locality strongly favors document model when:
Locality advantage is diminished when:
Note: Locality is not unique to document databases. Google Spanner's interleaved tables and Oracle's multi-table index cluster tables bring locality to relational models. Cassandra and HBase use column families for locality. If locality is the primary driver, evaluate whether your target relational database offers nested table features before switching models entirely.
Step 6: Assess Graph Model Subtypes (if applicable)
Action: If the graph model scored highest in Steps 2β3, determine which graph subtype is appropriate: property graph or triple-store.
WHY: Both subtypes model the same underlying graph structure (vertices and edges with properties), but they use different storage formats, query languages, and tooling. The property graph model (Neo4j, Titan, InfiniteGraph) stores structured property key-value pairs on vertices and edges and uses the Cypher query language. The triple-store model (Datomic, AllegroGraph) stores all information as subject-predicate-object triples and uses SPARQL or Datalog. The choice matters because it determines your query language, ecosystem, and how naturally your data maps to the storage format. For most application development use cases, property graphs are the more practical starting point. Triple-stores become more relevant when you need to combine data from multiple external sources (the original motivation for RDF and SPARQL).
Property graph (Neo4j) is appropriate when:
Triple-store (RDF/SPARQL) is appropriate when:
Graph traversal in relational databases: SQL supports variable-depth traversal via recursive common table expressions (WITH RECURSIVE). The same query that takes 4 lines in Cypher typically takes 29 lines in SQL. If graph queries are occasional (not primary), staying in a relational model with WITH RECURSIVE is a viable option. If traversal queries are central to your application, a native graph model pays for itself quickly in query expressiveness and performance.
Step 7: Produce the Recommendation Document
Action: Write a structured recommendation that covers the primary model, the rationale, key trade-offs, what was ruled out and why, and implementation guidance.
WHY: A recommendation without explicit rationale cannot be reviewed, challenged, or revised when requirements change. Stating what was ruled out β and why β is as important as stating what was chosen: it prevents the recommendation from being relitigated by team members who weren't present for the analysis, and it records the assumptions so that if those assumptions change (e.g., a many-to-many relationship is added), the decision can be revisited.
Output format:
## Data Model RecommendationSystem: [System name / brief description]
Date: [Date]
Recommended Model: [Relational / Document / Graph / Polyglot]
Primary rationale: [1β2 sentences connecting the data shape and relationship
complexity analysis to the model choice]
Data shape classification: [Category A (tree) / B (interconnected) / C (graph)]
Scoring Summary
| Criterion | Relational | Document | Graph |
|-----------------------|-----------|----------|-------|
| Data locality | [1-5] | [1-5] | [1-5] |
| Relationship complexity| [1-5] | [1-5] | [1-5] |
| Schema flexibility | [1-5] | [1-5] | [1-5] |
| Query pattern support | [1-5] | [1-5] | [1-5] |
| Normalization | [1-5] | [1-5] | [1-5] |
| Total | [5-25] | [5-25] | [5-25]|
Winner: [Model] with [score]/25
Key Trade-offs of the Recommended Model
Strengths for this system:
[Specific strength tied to your data shape]
[Specific strength tied to your query patterns] Limitations to manage:
[Specific limitation and how to mitigate it]
[Specific limitation and how to mitigate it]
Schema Enforcement Strategy
Recommendation: Schema-on-[read/write]
Rationale: [Why this fits the data heterogeneity and team workflow]
Ruled Out
[Model]: [1β2 sentences on why it scored lower and what specific criterion failed]
[Model]: [1β2 sentences on why it scored lower]
Implementation Guidance
Immediate next steps:
1. [Concrete first step]
2. [Concrete second step]
Watch for:
[Specific signal that would indicate the model choice needs revisiting]
[Specific signal] Related decisions:
Storage engine selection: see storage-engine-selector
Partitioning strategy: see partitioning-strategy-advisor (if distributing)
What Can Go Wrong
These are the most common failure modes when selecting a data model. Review each before finalizing a recommendation.
Choosing document model for "flexibility," then adding many-to-many relationships later. The document model's join support is weak by design. When many-to-many relationships appear (as they almost always do as applications evolve β organizations become entities, recommendations reference users, tags cross-reference content), the join work moves into application code. Application-side joins are slower than database joins (multiple round trips vs. optimized index lookups inside the database) and create significant complexity. If your roadmap includes any features that create cross-entity references, score the relational model at least as a baseline.
Treating "schemaless" as "no schema." Document databases don't eliminate schema β they move it from the database into the application code that reads data. This is sometimes called the implicit schema problem. Every piece of code that reads a document assumes something about its structure. When that structure changes, all reading code must be updated. The difference from schema-on-write is that the database cannot help you find all the places where the assumption was made. Budget for the discipline required to manage implicit schemas.
Using graph query syntax for simple data. Graph databases excel at traversal queries (variable-depth path following). For simple one-to-many reads, Cypher is more verbose than SQL, and a graph database adds operational complexity without benefit. Do not choose a graph model because your data "could" be a graph β choose it because your primary queries require graph traversal.
Underestimating the impedance mismatch tax. If your application code uses objects, and your data store uses tables with rows, every read and write passes through a translation layer (ORM). ORMs reduce boilerplate but cannot eliminate the impedance mismatch. When this translation layer becomes a bottleneck (complex join queries expressed as object graphs, N+1 query problems, schema migrations that require coordinating application and database changes simultaneously), it is a signal that the data shape mismatches the model β not that the ORM is inadequate.
Choosing model based on write throughput alone, ignoring read patterns. NoSQL databases were partly adopted for write scalability. But the data model decision (relational vs. document vs. graph) is a separate concern from the scalability decision. A relational database can scale writes with appropriate partitioning. A document database with many-to-many relationships will have read complexity problems regardless of how fast it writes. Separate the model-fit question from the scalability question.
Locking in on a model before the data shape is understood. Starting with a document database because JSON is "natural for web applications" without first analyzing relationship complexity is a common path to regret. Run Step 1 (data shape classification) before any other discussion. If the shape is not yet clear because the product is early-stage, prefer relational: it can be refactored to document or graph more easily than the reverse, because it enforces the normalization that makes data portable.
Inputs / Outputs
Inputs
Outputs
Key Principles
Data relationships are the primary decision axis. The three models evolved to solve different relationship problems: the hierarchical model handled one-to-many, the relational model solved many-to-many, graph databases handle variable-depth traversal where relational joins become impractical. Map your data's relationship type to the model designed for it.
Many-to-many relationships are the historical dividing line. When many-to-many relationships appear, the document model loses its advantage β joins move into application code. The entire history of database systems (IMS β relational/network model debate of the 1970s) is the history of solving this problem. Do not repeat it by choosing a model that doesn't handle the relationships you have.
Schema-on-read is not schemaless β it is implicit schema. Every piece of code that reads a document assumes a structure. That assumption is your schema; the question is only whether the database enforces it (schema-on-write) or the application enforces it (schema-on-read). Use schema-on-read for heterogeneous records; schema-on-write when records are uniform and correctness must be enforced at write time.
Document and relational databases are converging. PostgreSQL 9.3+ supports JSON, MySQL 5.7+ supports JSON, RethinkDB supports joins, MongoDB reinvented SQL as an aggregation pipeline. The practical choice is increasingly about which model fits your data as primary β secondary capabilities are often available in either system.
Examples
Example 1: User Profile Service (SaaS Application)
Scenario: A SaaS application needs to store user profiles. Each profile contains basic info (name, email), job history (multiple positions with title and organization), education history (multiple schools with dates), and contact info (multiple URLs by type). The primary read pattern is: load the complete profile for display on a page. Cross-profile queries are rare β mostly search by email or user ID.
Trigger: "Should we use MongoDB or PostgreSQL for our user profiles?"
Process:
Output:
## Data Model RecommendationSystem: User Profile Service
Recommended Model: Document
Primary rationale: Profile data forms a self-contained tree β one user with
many subordinate records that are always fetched together. The document model's
locality advantage applies directly: one query returns the complete profile.
Data shape classification: Category A (tree, one-to-many)
Scoring Summary
| Criterion | Relational | Document | Graph |
|------------------------|-----------|----------|-------|
| Data locality | 2 | 5 | 1 |
| Relationship complexity| 4 | 5 | 2 |
| Schema flexibility | 3 | 4 | 4 |
| Query pattern support | 3 | 5 | 1 |
| Normalization | 5 | 3 | 3 |
| Total | 17 | 22 | 11 |
Winner: Document with 22/25
Key Trade-offs
Strengths:
One query fetches a complete profile; no joins
Flexible contact_info types (blog, twitter, phone) without schema changes Limitations to manage:
If organizations become shared entities, many-to-many appears β reassess model
Application must manage implicit schema (validate structure at write time in code) Schema Enforcement Strategy
Recommendation: Schema-on-read
Rationale: Contact info types vary per user; new contact types can be added
without migration. Application validates structure at write time.
Ruled Out
Relational: Normalized representation requires multi-table joins on every
profile read; locality benefit is lost. Normalization benefit is minimal since
organizations and schools are not shared entities needing deduplication.
Graph: No traversal queries; no many-to-many relationship patterns present.
Implementation Guidance
1. Store each profile as a single document; keep documents under ~16KB
2. Index on user_id and email for primary lookup patterns
3. Monitor for feature requests that add cross-profile references (organization entities, recommendations from other users) β these signal relational migration
Watch for: Any feature where one user's data references another user's data
(recommendations, shared organization entities) β this introduces many-to-many
and breaks the document model's join-free assumption.
Example 2: E-commerce Platform
Scenario: An e-commerce platform has: users, orders, order line items, products, product categories, and reviews. Users write reviews of products. Products belong to multiple categories. Orders reference products. The team needs analytics queries (top products by category, revenue by user segment). The team currently uses MongoDB and is experiencing complex application-side join code.
Trigger: "Our MongoDB queries are getting complex and we're doing a lot of application-side joining. Should we migrate to PostgreSQL?"
Process:
Output (abbreviated β full format shown in Example 1):
## Data Model Recommendation: E-commerce PlatformRecommended Model: Relational (Score: 21/25 vs Document 9/25 vs Graph 13/25)
Primary rationale: Many-to-many relationships are pervasive (products β categories,
users β products via reviews, orders β products). Application-side join complexity
is the document model failing at exactly what it was not designed for.
Schema strategy: Schema-on-write β entity types are uniform; multiple services
write to the same database; schema is the contract.
Ruled out β Document: Application-side joins are the current pain point; migrating
to another document store does not solve it.
Ruled out β Graph: Many-to-many but not variable-depth; SQL joins are sufficient.
Next steps:
1. Migrate one entity type at a time; start with products (most referenced)
2. Use PostgreSQL JSON columns for product attributes that vary by category
Related: See storage-engine-selector for covering index strategy on category +
price range queries.
Example 3: Fraud Detection System
Scenario: A financial services company needs to detect fraud by finding connections between transactions, accounts, devices, IP addresses, and known fraudsters. Queries include: "find all accounts connected to this device within 3 hops," "identify clusters of accounts that share IP addresses," "shortest path between a flagged account and any account in this transaction."
Trigger: "We're trying to build fraud detection. Our PostgreSQL queries for finding connected accounts are very slow β 29-table recursive CTEs. Would a graph database help?"
Process:
WITH RECURSIVE which the team has already found unwieldy. The 29-line recursive CTE is the canonical signal that graph traversal in relational has exceeded its practical limit.Output (abbreviated β full format shown in Example 1):
## Data Model Recommendation: Fraud Detection SystemRecommended Model: Graph β Property Graph subtype (Score: 23/25)
Relational: 12/25. Document: 9/25.Primary rationale: The 29-line recursive CTE is the canonical signal that graph
traversal in relational has exceeded its practical limit. Variable-depth traversal
(account β device β account β IP β account within N hops) is exactly what the
property graph model is designed for.
Subtype rationale: Property graph (Neo4j) β data is application-internal, edge
properties (amount, timestamp) must be queryable, Cypher pattern-matching maps
directly to ring detection and shared-device cluster queries.
Schema strategy: Schema-on-read β fraud patterns require different vertex/edge
property sets; heterogeneous structure is expected.
Ruled out β Relational: WITH RECURSIVE is syntactically clumsy and cannot use
graph-optimized index structures; query expressiveness gap is the deciding factor.
Ruled out β Document: No traversal capability; relationship chain following is
the entire problem.
Next steps:
1. Model as labeled vertices (Account, Device, IPAddress, Transaction) + labeled
edges (USES_DEVICE, SHARES_IP, TRANSFERS_TO) with amount/timestamp properties
2. Start with shared-device cluster detection as the first migrated query
3. Polyglot: keep relational for transaction ledger and compliance reporting
Watch for: If queries simplify to fixed-depth lookups (always 2 hops),
re-evaluate whether SQL joins are sufficient.
References
| File | Contents |
|------|----------|
| references/decision-matrix.md | Pre-filled scoring guide with 8 common system types (e-commerce, social network, IoT time-series, CMS, fraud detection, recommendation engine, ERP, analytics pipeline) showing typical scores per model and decision rationale; cross-reference table mapping query patterns to model fit |
License
This skill is licensed under CC-BY-SA-4.0. Source: BookForge β Designing Data-Intensive Applications by Martin Kleppmann.
Related BookForge Skills
This skill is standalone. Browse more BookForge skills: bookforge-skills
β‘ When to Use
π‘ Examples
Example 1: User Profile Service (SaaS Application)
Scenario: A SaaS application needs to store user profiles. Each profile contains basic info (name, email), job history (multiple positions with title and organization), education history (multiple schools with dates), and contact info (multiple URLs by type). The primary read pattern is: load the complete profile for display on a page. Cross-profile queries are rare β mostly search by email or user ID.
Trigger: "Should we use MongoDB or PostgreSQL for our user profiles?"
Process:
Output: ```