Database Federation: Splitting by Function
After this topic, you will be able to:
- Explain functional partitioning as an alternative to sharding
- Demonstrate federation pattern application for service-oriented architectures
- Compare federation vs sharding for different scaling scenarios
TL;DR
Federation (functional partitioning) splits databases by business function or service boundary rather than by data volume. Instead of one monolithic database, you create separate databases for users, orders, products, etc., each optimized for its domain. This reduces contention, enables parallel writes, and aligns naturally with microservices architectures.
Cheat Sheet:
- Pattern: Split by function/service, not by rows
- Key Benefit: Independent scaling per domain
- Trade-off: Cross-database joins become application-level
- When to Use: Service-oriented architectures with distinct bounded contexts
The Problem It Solves
As monolithic databases grow, they become bottlenecks in multiple dimensions. A single database handling users, orders, products, payments, and analytics creates several painful problems. First, write contention increases as all services compete for the same connection pool and transaction locks. Second, schema changes require coordinating across all teams, slowing development velocity. Third, scaling decisions become all-or-nothing—you can’t give more resources to the high-traffic orders table without also scaling the rarely-accessed admin settings table.
The real pain emerges when different domains have conflicting requirements. Your user service needs strong consistency for authentication, but your product catalog can tolerate eventual consistency for better read performance. Your payment service requires strict ACID guarantees, while your analytics queries want denormalized data for fast aggregations. A single database forces you to pick one set of trade-offs for all use cases, leading to either over-engineering (everything gets ACID when only payments need it) or under-engineering (analytics queries slow down transactional workloads).
This problem intensifies in organizations adopting microservices. When multiple teams share a database, they’re not truly independent—a schema migration by the orders team can break the inventory service. Database-level coupling defeats the purpose of service boundaries.
Solution Overview
Federation solves this by splitting databases along functional or service boundaries. Instead of one database with 50 tables, you create separate databases: users_db, orders_db, products_db, payments_db. Each database is owned by its corresponding service and optimized for that domain’s specific needs.
This is fundamentally different from sharding (see Sharding for horizontal partitioning). Sharding splits a single table’s rows across multiple databases (e.g., users 1-1M on shard1, users 1M-2M on shard2). Federation splits different tables into different databases based on what they represent. You might shard within a federated database—for example, orders_db could itself be sharded by order_id—but federation is the higher-level organizational principle.
The key insight is that most applications naturally decompose into bounded contexts. E-commerce has clear boundaries: user management, catalog, ordering, fulfillment, payments. Each context has different access patterns, consistency requirements, and scaling needs. Federation makes these boundaries explicit at the database level, enabling each service to evolve independently.
Federation vs Sharding: Functional vs Horizontal Partitioning
graph TB
subgraph "Monolithic Database"
M["Single Database<br/><i>All Tables</i>"]
M1["users table"]
M2["orders table"]
M3["products table"]
M4["payments table"]
M --> M1 & M2 & M3 & M4
end
subgraph "Federation (Functional Split)"
F1[("users_db<br/><i>PostgreSQL</i>")]
F2[("orders_db<br/><i>PostgreSQL</i>")]
F3[("products_db<br/><i>Elasticsearch</i>")]
F4[("payments_db<br/><i>PostgreSQL</i>")]
end
subgraph "Sharding (Horizontal Split)"
S1[("users_shard_1<br/><i>IDs 1-1M</i>")]
S2[("users_shard_2<br/><i>IDs 1M-2M</i>")]
S3[("users_shard_3<br/><i>IDs 2M-3M</i>")]
end
M -."Split by function".-> F1 & F2 & F3 & F4
M1 -."Split by rows".-> S1 & S2 & S3
Federation splits different tables into separate databases by business function, enabling polyglot persistence and independent scaling. Sharding splits a single table’s rows across multiple databases for horizontal scalability. These patterns solve different problems and can be combined.
How It Works
Step 1: Identify Functional Boundaries
Start by analyzing your domain model. Look for clusters of tables that are frequently joined together but rarely joined with other clusters. In an e-commerce system, you might identify: users (users, profiles, preferences), catalog (products, categories, inventory), orders (orders, order_items, shipments), payments (transactions, refunds, payment_methods).
Step 2: Create Separate Databases
Provision a dedicated database for each functional area. At Netflix, this might look like: user_profiles_db (PostgreSQL for strong consistency), viewing_history_db (Cassandra for high write throughput), content_metadata_db (Elasticsearch for search), billing_db (PostgreSQL with strict ACID). Each database can use different technology optimized for its workload.
Step 3: Assign Ownership
Each database becomes the responsibility of a specific team. The catalog team owns products_db, the orders team owns orders_db. This ownership includes schema design, performance tuning, backup strategies, and capacity planning. Teams can deploy schema changes independently without cross-team coordination.
Step 4: Handle Cross-Database References
This is where federation gets interesting. When the orders service needs user information, it can’t do a SQL join across databases. Instead, it stores the user_id as a foreign key and makes an API call to the user service to fetch details. For example:
-- orders_db
SELECT order_id, user_id, total FROM orders WHERE order_id = 12345;
-- Returns: order_id=12345, user_id=789, total=99.99
-- Application layer calls user service API
GET /users/789
-- Returns: {name: "Alice", email: "alice@example.com"}
For performance-critical paths, you denormalize selectively. The orders service might cache user email addresses in orders_db to avoid API calls for every order confirmation email.
Step 5: Implement Eventual Consistency Patterns
When data needs to flow between federated databases, use event-driven patterns. When a user updates their shipping address in users_db, the user service publishes an event. The orders service subscribes and updates its denormalized copy in orders_db. This maintains loose coupling while keeping frequently-accessed data local.
Cross-Database Query Flow with API Calls
sequenceDiagram
participant Client
participant OrderService
participant OrderDB as orders_db
participant UserService
participant UserDB as users_db
Client->>OrderService: GET /orders/12345
activate OrderService
OrderService->>OrderDB: SELECT order_id, user_id, total<br/>FROM orders WHERE order_id=12345
activate OrderDB
OrderDB-->>OrderService: {order_id: 12345, user_id: 789, total: 99.99}
deactivate OrderDB
Note over OrderService: No SQL join possible<br/>Must call User Service API
OrderService->>UserService: GET /users/789
activate UserService
UserService->>UserDB: SELECT name, email<br/>FROM users WHERE user_id=789
activate UserDB
UserDB-->>UserService: {name: "Alice", email: "alice@example.com"}
deactivate UserDB
UserService-->>OrderService: {name: "Alice", email: "alice@example.com"}
deactivate UserService
Note over OrderService: Join data in application layer
OrderService-->>Client: {order_id: 12345, total: 99.99,<br/>user: {name: "Alice", email: "alice@example.com"}}
deactivate OrderService
When orders_db needs user information, it cannot perform a SQL join across databases. Instead, the order service makes an API call to the user service, then joins the data in application code. This is the fundamental trade-off of federation: parallel writes at the cost of cross-database query complexity.
Event-Driven Denormalization for Performance
graph LR
subgraph "User Service"
UserAPI["User API"]
UserDB[("users_db<br/><i>Source of Truth</i>")]
end
subgraph "Event Bus"
EventBus["Message Queue<br/><i>Kafka/RabbitMQ</i>"]
end
subgraph "Order Service"
OrderAPI["Order API"]
OrderDB[("orders_db<br/><i>Denormalized Copy</i>")]
OrderCache["Denormalized Data<br/>user_email, shipping_address"]
end
UserAPI --"1. User updates<br/>shipping address"--> UserDB
UserAPI --"2. Publish<br/>UserAddressUpdated event"--> EventBus
EventBus --"3. Subscribe to<br/>user events"--> OrderAPI
OrderAPI --"4. Update denormalized<br/>shipping_address"--> OrderDB
OrderDB -."Cached locally".-> OrderCache
Note1["Eventual Consistency:<br/>Orders may show old address<br/>for a few milliseconds"]
Note1 -.-> OrderCache
To avoid API calls on every order query, the order service maintains a denormalized copy of frequently-accessed user data. When users update their information, an event triggers eventual consistency updates across federated databases. This pattern trades strong consistency for query performance.
Trade-offs
Write Throughput vs. Cross-Database Queries
Federation eliminates single-database write bottlenecks. Each service writes to its own database in parallel, and there’s no central serialization point. However, queries that previously used SQL joins now require multiple database queries or API calls. A product listing with user reviews requires fetching from products_db and reviews_db separately, then joining in application code.
Decision Framework: Choose federation when write throughput and independent deployment matter more than query simplicity. E-commerce order processing benefits from federation; complex reporting dashboards that join across many entities may not.
Team Autonomy vs. Data Consistency
Federation gives teams full control over their database schema, technology choices, and scaling strategies. The payments team can use PostgreSQL while the analytics team uses ClickHouse. But maintaining consistency across databases becomes harder. If a user is deleted from users_db, you need distributed coordination to delete their orders from orders_db.
Decision Framework: Accept eventual consistency for most cross-service data flows. Reserve distributed transactions (two-phase commit) for critical business invariants like payment processing.
Cache Locality vs. Operational Complexity
Smaller databases fit more data in memory, improving cache hit rates. A 10GB users_db can cache its entire working set, while a 500GB monolithic database constantly evicts hot data. However, you now manage multiple databases, each needing monitoring, backups, and capacity planning.
Decision Framework: Federation makes sense when you have 5-20 distinct functional areas. Below 5, the operational overhead outweighs benefits. Above 20, you might be over-fragmenting.
Federation Trade-offs: Parallel Writes vs Query Complexity
graph TB
subgraph "Before Federation: Monolithic Database"
M1["Single Connection Pool<br/><i>Write Contention</i>"]
M2["Easy SQL Joins<br/><i>SELECT * FROM orders<br/>JOIN users ON orders.user_id = users.id</i>"]
M3["Single Point of Failure<br/><i>All services blocked if DB down</i>"]
end
subgraph "After Federation: Distributed Databases"
F1["Parallel Writes<br/><i>No contention between services</i>"]
F2["Application-Level Joins<br/><i>API calls + in-memory merge</i>"]
F3["Independent Failures<br/><i>Orders DB down ≠ Users DB down</i>"]
F4["Eventual Consistency<br/><i>Denormalized data may lag</i>"]
end
Decision{"Choose Federation?"}
UseCase1["✓ Microservices architecture<br/>✓ High write throughput<br/>✓ Different domain requirements"]
UseCase2["✗ Heavy cross-domain analytics<br/>✗ Strong consistency needed<br/>✗ Small scale (<50GB)"]
M1 & M2 & M3 --> Decision
Decision --"Yes"--> F1 & F2 & F3 & F4
Decision -."Evaluate".-> UseCase1
Decision -."Evaluate".-> UseCase2
UseCase1 --"Benefits outweigh costs"--> F1
UseCase2 --"Costs outweigh benefits"--> M2
Federation eliminates write contention and enables parallel writes across services, but introduces complexity in cross-database queries and eventual consistency. The decision to federate depends on whether your workload prioritizes write throughput and service independence over query simplicity and strong consistency.
When to Use (and When Not To)
Use Federation When:
-
Adopting Microservices: Federation aligns naturally with database-per-service patterns. If you’re decomposing a monolith into services, federate databases along the same boundaries.
-
Different Domains Have Conflicting Requirements: Your user authentication needs PostgreSQL’s ACID guarantees, but your product catalog wants Cassandra’s write scalability. Federation lets each domain use the right tool.
-
Write Contention is the Bottleneck: If your database CPU is maxed out from concurrent writes across different tables, federation distributes the load. Uber federated early because ride requests, driver locations, and payment processing all generated high write volumes.
-
Teams Need Independent Deployment: When schema changes require coordinating across 5 teams, federation breaks the dependency. Each team deploys database changes with their service.
Avoid Federation When:
-
Heavy Cross-Functional Queries: If your primary use case is analytics joining users, orders, products, and reviews, federation makes this painful. Consider a data warehouse instead.
-
Strong Consistency Across Domains: If you need ACID transactions spanning users and orders (e.g., deducting loyalty points during checkout), federation introduces distributed transaction complexity.
-
Small Scale: If your entire database is 50GB and handles 100 QPS, federation adds operational overhead without meaningful benefits. Vertical scaling is simpler.
Real-World Examples
Netflix: Content Metadata vs. Viewing History
Netflix federates databases by functional area. content_metadata_db stores movie titles, descriptions, and artwork using a relational database optimized for complex queries. viewing_history_db uses Cassandra to handle billions of “user X watched Y” events with high write throughput. recommendation_db uses a graph database for personalization algorithms. Each database scales independently—viewing history handles 10x more writes than metadata, so it gets more resources. Interesting detail: They denormalize movie titles into viewing_history_db to avoid cross-database joins when displaying “Continue Watching” rows.
Uber: Trips, Drivers, and Payments
Uber’s early architecture federated into trips_db, drivers_db, riders_db, and payments_db. Each database handled distinct write patterns: trips had bursty writes during ride requests, drivers had constant location updates, payments had strict consistency requirements. This federation enabled independent scaling—they could provision more capacity for driver location writes without over-provisioning the payments database. The challenge came with features like “driver earnings,” which required joining trips and payments data. They solved this with event-driven denormalization: when a trip completes, an event updates both trips_db and a denormalized earnings table in drivers_db.
Shopify: Stores, Products, and Orders
Shopify federates databases per merchant store at the highest level, but within each store, they further federate by function: products_db, orders_db, customers_db. This two-level federation provides both tenant isolation and functional separation. A store’s product catalog can scale independently from its order processing. Interesting detail: They use read replicas extensively—the products_db replica serves storefront queries while the primary handles admin updates, preventing customer browsing from impacting merchant operations.
Netflix Federation Architecture by Domain
graph TB
subgraph "Client Layer"
WebApp["Web/Mobile App"]
end
subgraph "Content Domain"
ContentAPI["Content Service"]
ContentDB[("content_metadata_db<br/><i>PostgreSQL</i><br/>Movies, descriptions, artwork")]
end
subgraph "Viewing Domain"
ViewingAPI["Viewing Service"]
ViewingDB[("viewing_history_db<br/><i>Cassandra</i><br/>Billions of watch events")]
ViewingCache["Denormalized:<br/>Movie titles cached<br/>for 'Continue Watching'"]]
end
subgraph "Recommendation Domain"
RecoAPI["Recommendation Service"]
RecoDB[("recommendation_db<br/><i>Graph Database</i><br/>Personalization algorithms")]
end
WebApp --"1. Browse catalog"--> ContentAPI
WebApp --"2. Watch movie"--> ViewingAPI
WebApp --"3. Get recommendations"--> RecoAPI
ContentAPI --> ContentDB
ViewingAPI --> ViewingDB
ViewingDB -."Avoids cross-DB joins".-> ViewingCache
RecoAPI --> RecoDB
ViewingAPI --"Event: Movie watched"--> RecoAPI
ContentAPI --"Event: New content"--> RecoAPI
Note1["Each DB scales independently:<br/>Viewing handles 10x more writes<br/>than content metadata"]
Note1 -.-> ViewingDB
Netflix federates databases by functional domain, using different database technologies optimized for each workload. Content metadata uses PostgreSQL for complex queries, viewing history uses Cassandra for high write throughput, and recommendations use a graph database. Movie titles are denormalized into viewing_history_db to avoid cross-database joins when displaying ‘Continue Watching’ rows.
Interview Essentials
Mid-Level
Explain federation as splitting databases by function/service rather than by data volume. Contrast with sharding: “Sharding splits one table horizontally, federation splits different tables into different databases.” Describe the basic trade-off: parallel writes and independent scaling vs. cross-database join complexity. Walk through a simple example: users_db, orders_db, products_db in an e-commerce system. Explain how you’d handle a query needing both user and order data (API call or denormalization).
Senior
Discuss when federation is preferable to sharding. Key insight: federation works when you have natural functional boundaries with different scaling needs. Explain how to handle cross-database consistency: event-driven patterns for eventual consistency, saga pattern for distributed transactions when needed. Describe denormalization strategies: what data to duplicate, how to keep it consistent, cache invalidation approaches. Compare operational complexity: monitoring multiple databases, backup coordination, capacity planning per service. Explain how federation enables polyglot persistence (PostgreSQL for users, Cassandra for events, Elasticsearch for search).
Staff+
Architect a migration from monolithic to federated databases. Key considerations: identifying bounded contexts, handling existing foreign key relationships, phased rollout strategy. Discuss federation at scale: when to shard within federated databases, how to manage 20+ databases without operational chaos (standardized tooling, observability platforms). Explain advanced consistency patterns: change data capture (CDC) for cross-database synchronization, event sourcing to maintain audit trails across services. Address organizational aspects: team ownership models, schema governance without central control, how to prevent accidental coupling through shared data models. Discuss when federation becomes a liability: over-fragmentation, excessive denormalization, distributed query performance.
Common Interview Questions
How does federation differ from sharding? (Functional vs. horizontal split, different tables vs. same table)
How do you handle queries that need data from multiple federated databases? (API calls, denormalization, materialized views)
What happens when you need a transaction across federated databases? (Saga pattern, eventual consistency, two-phase commit for critical paths)
How do you maintain referential integrity across databases? (Application-level enforcement, eventual consistency, compensating transactions)
When would you choose federation over keeping a monolithic database? (Microservices adoption, conflicting requirements, write contention, team autonomy)
Red Flags to Avoid
Confusing federation with sharding—they solve different problems
Not acknowledging the cross-database query complexity trade-off
Suggesting distributed transactions (2PC) as the default solution for consistency
Ignoring operational overhead of managing multiple databases
Proposing federation for a system with heavy cross-functional analytics queries
Not discussing denormalization strategies for performance-critical paths
Key Takeaways
Federation splits databases by function/service (users_db, orders_db), not by data volume like sharding. It’s an organizational pattern aligned with bounded contexts.
Key benefit: independent scaling and technology choices per domain. Orders can use PostgreSQL while analytics uses ClickHouse. Teams deploy schema changes independently.
Key trade-off: eliminates write contention and enables parallel writes, but cross-database joins move to application layer. Requires denormalization or API calls for multi-domain queries.
Use federation when adopting microservices, when domains have conflicting requirements (ACID vs. eventual consistency), or when write contention is the bottleneck. Avoid for analytics-heavy workloads.
Real-world pattern: federate at service boundaries, then shard within federated databases as needed. Netflix federates by function (metadata, viewing history), then shards viewing history by user_id.