God Service
Learn why a single service that everything depends on becomes a single point of failure disguised as modularity, and how to break it apart without a full rewrite.
TL;DR
- A god service is a single service that every other service depends on for core functionality (auth, user data, configuration, or any other cross-cutting concern handled in one place).
- It looks like clean modularity: one service owns "users." In production, a degradation or outage can affect many dependent request paths at once.
- The god service becomes the hardest to change (any bug affects all consumers), the hardest to scale (it must handle traffic from every caller), and the highest-stakes deployment in the system.
- Break it apart by caching aggressively at the consumer level, embedding stable data in auth tokens, and decomposing by subdomain to reduce synchronous fan-in.
- When it's acceptable: early-stage systems with fewer than 5 services, or when every consumer has a tested circuit breaker and graceful degradation path.
Introduction
A service becomes a god service when too many unrelated capabilities and request paths depend on it synchronously. Central ownership may be appropriate; the anti-pattern is making that owner a mandatory network hop for nearly every consumer.
Mental Model
Separate authority from dependency:
- Source of truth: one service owns writes and business rules for a domain.
- Read dependency: a consumer must call that service synchronously to serve a request.
- God service: the source of truth also becomes a high-fan-in, hot-path dependency for unrelated domains.
The goal is to preserve authoritative ownership while distributing safe reads through tokens, consumer-side caches, local read models, or events. Resilience controls reduce blast radius while the topology is being improved.
The Problem
"Every service calls the User Service." This sounds like good design, a single source of truth for user data. In reality, it means the User Service is in every request's critical path.
At 2:47 a.m. on your biggest flash sale, the User Service develops a memory leak. CPU climbs. Response times degrade from 5ms to 500ms. Every API endpoint in the system (product browsing, checkout, order history) slows down by 500ms because every one of them calls the User Service to validate the session or fetch the user's locale.
At 500ms User Service latency, your checkout API degrades from 120ms to 620ms. Your product pages go from 80ms to 580ms. The common root cause in this pattern is a single service sitting in many request paths.
You have a god service. Everything depends on it. When it has a bad day, everything has a bad day.
Every arrow into that red box is a synchronous dependency. When the god service is slow, every consumer is slow.
The cascade in action
Here's what the 2:47 a.m. incident actually looks like in a distributed trace:
Two calls to the god service. Each one is 100x slower than normal. The user waits over a second for what should take 140ms. Multiply this across every service that calls the User Service, and you have a system-wide degradation triggered by a single memory leak in a single process.
Why restarts don't help
The natural instinct is to restart the User Service. But if the memory leak was triggered by traffic patterns (a flash sale generating unusual query patterns), the restarted instance hits the same conditions within minutes. Worse, during the restart window, all consumers that don't have fallbacks will fail hard instead of just being slow. Rolling restarts help, but only if you have multiple instances and consumers are load-balanced across them.
The bottom line: operational playbooks help, but architectural resilience is also needed. A god service can turn one performance problem into a system-wide incident.
Before and After
Before, unrelated consumers make synchronous calls for every request:
Checkout βββ
Products βββΌββ> User Service ββ> Users DB
Notifications ββ
After, the User Service remains authoritative for writes, while each consumer uses the narrowest read mechanism that meets its freshness needs:
Checkout ββ> local token/cache
Product Pages ββ> local read model or short-TTL cache
Notifications ββ> user.updated events
User Service ββ> authoritative profile and state writes
The after design does not remove every live call. It removes unnecessary synchronous calls from unrelated hot paths and gives each consumer an explicit staleness and failure policy.
Why It Happens
God services emerge from correct-sounding design decisions:
- "Auth should be centralized" so every service calls the Auth Service to validate tokens.
- "User data should be consistent" so every service calls the User Service to read user attributes.
- "Configuration should be centralized" so every service calls the Config Service on each request.
Each decision is individually reasonable. In aggregate, they produce a web of hard dependencies on a few services that are now critical path for everything.
The deeper driver is organizational. Early in a project, one team builds the User Service. Other teams need user data, so they call it. Nobody designs a caching or event strategy because the User Service is fast enough at low scale. By the time it's a problem, 15 services depend on it synchronously and unwinding that dependency graph is a multi-quarter effort.
There's also a knowledge gap. Junior engineers often conflate "single source of truth" with "single synchronous dependency." You can have authoritative data ownership without requiring every consumer to make a network call on every request. Tokens, caches, and events are all strategies for distributing reads without giving up write authority.
The fan-in scoring model
A practical way to start measuring god-service risk is to score each service by its fan-in impact. These thresholds are heuristics, not universal limits:
| Fan-in count | Risk level | Recommended action |
|---|---|---|
| 1-3 | Low | Normal service, no special treatment needed |
| 4-6 | Moderate | Add consumer-side caching, monitor for correlated failures |
| 7-10 | High | Decompose or add circuit breakers on all consumers |
| 10+ | Critical | This is a god service. Prioritize decomposition immediately |
The fan-in count alone doesn't tell the full story. A service called by 4 hot-path services is more dangerous than one called by 10 batch jobs. Weight by call frequency and whether the call is on the user-facing critical path.
Track fan-in as a metric over time. If it's growing, you're accumulating god-service risk even if the current count seems manageable.
How to Detect It
| Symptom | What It Means | How to Check |
|---|---|---|
| Fan-in ratio > 5 | Too many services call this one synchronously | Count inbound edges in your service graph (Datadog, Jaeger) |
| Correlated latency spikes | When service X is slow, 5+ other services spike too | Correlation analysis on p99 latency dashboards |
| Deployment fear | Engineers avoid deploying because "everything breaks" | Ask your team: "Which service scares you most to deploy?" |
| Unbounded responsibility | One service owns auth, preferences, billing, notifications | Count distinct domain concepts in one service's API surface |
| Connection pool exhaustion | Downstream services exhaust connections to the god service | Monitor connection pool usage on both sides |
If three or more of these symptoms match, treat the service as a strong god-service candidate and inspect the dependency graph.
Quick diagnostic
Run this mental test: "If the User Service (or whatever service you suspect) goes down for 5 minutes, which user-facing features break?" If the answer is "most of them" or "all of them," that's a god service. A healthy service topology means that a single service failure degrades one feature, not everything.
You can also check your distributed traces. If more than 50% of all traces in your system include a span from the same service, investigate whether that service has too much fan-in. Tools like Datadog's Service Map or Jaeger's dependency graph make this visible.
// Quick check: count inbound callers to a service
// If this returns more than 5-6 unique callers, investigate
const callers = traceData
.filter((span) => span.downstream === "user-service")
.map((span) => span.upstream);
const uniqueCallers = new Set(callers);
console.log(`Fan-in: ${uniqueCallers.size} services call user-service`);
The Fix
Fix 1: Cache aggressively at the consumer
If services call the User Service for data that rarely changes (user locale, plan tier, display name), cache it at the consumer with a short TTL. Repeated reads can then avoid the User Service while the entry is fresh.
async function getUserLocale(userId: string): Promise<string> {
const cached = await cache.get(`user:${userId}:locale`);
if (cached) return cached; // covers repeated reads while the entry is fresh
const locale = await userServiceClient.getLocale(userId);
await cache.set(`user:${userId}:locale`, locale, { ttl: 300 }); // 5-min cache
return locale;
}
Trade-off: you accept up to 5 minutes of staleness for user attributes. This is appropriate only when the business rule allows that freshness window.
A crucial detail: the consumer must have a fallback for when the cache is empty AND the User Service is down. Without this, you've just added a cache that delays the failure by one TTL cycle.
// GOOD: cache-aside with graceful degradation
async function getUserLocale(userId: string): Promise<string> {
const cached = await cache.get(`user:${userId}:locale`);
if (cached) return cached;
try {
const locale = await userServiceClient.getLocale(userId);
await cache.set(`user:${userId}:locale`, locale, { ttl: 300 });
return locale;
} catch (err) {
// God service is down. Use a sensible default rather than failing.
console.warn(`User Service unavailable, using default locale for ${userId}`);
return "en-US";
}
}
Fix 2: Embed critical data in auth tokens
If you need user properties (role, tenant, plan) on every request, put them in the JWT payload at login time. Services verify the token locally without any downstream call. The User Service is only called to update data, not to read it on the hot path.
{
"sub": "user-123",
"role": "admin",
"tenant_id": "acme-corp",
"plan": "enterprise",
"exp": 1714000000
}
Trade-off: token data can be stale until the user refreshes their session. For attributes that change rarely (role, plan), this is a good tradeoff. For attributes that change often (cart contents), it's not.
Fix 3: Decompose by subdomain
If the User Service handles auth, preferences, billing, and notifications, split it. Auth becomes a dedicated Auth Service. Billing becomes a Billing Service. Each service depends on fewer others, and failures are scoped.
This is not "going back to the monolith." It is correctly drawing service boundaries around business domains rather than around a single entity.
Fix 4: Event-driven read path
For data that changes infrequently but needs to be available everywhere, publish change events. The User Service emits user.updated events. Each consumer subscribes and maintains a local read-model with the data it needs.
// User Service publishes on write
async function updateUserLocale(userId: string, locale: string) {
await db.query("UPDATE users SET locale = $1 WHERE id = $2", [locale, userId]);
await eventBus.publish("user.updated", {
userId,
changes: { locale },
timestamp: Date.now(),
});
}
// Product Service consumes and caches locally
eventBus.subscribe("user.updated", async (event) => {
if (event.changes.locale) {
await localStore.set(`user:${event.userId}:locale`, event.changes.locale);
}
});
Trade-off: eventual consistency. When a user changes their locale, there's a brief window (typically milliseconds to seconds) where other services still see the old value. Use this only when that freshness window is acceptable. For auth-critical data (is this user banned?), prefer the JWT approach or a short-TTL cache with a circuit breaker.
Which fix to use when
Severity and Blast Radius
A god service is a high-severity anti-pattern because the blast radius is proportional to fan-in. If 12 services depend on it synchronously, a single degradation event affects all 12 simultaneously.
Recovery is not straightforward. You can't just restart the god service and move on. If it crashed due to load, restarting it under the same load causes the same crash. The immediate fix is typically shedding load (rate limiting callers), but the structural fix (decomposition or caching) takes weeks to months.
The worst case: a god service failure triggers cascading timeouts across many consumers, which exhaust their own connection pools and cause their callers to fail. The incident can look like a system-wide outage even though the root cause is one service.
| Impact dimension | God service with fan-in of 10 |
|---|---|
| Blast radius | 10 services degraded simultaneously |
| Recovery time (immediate) | Minutes (restart, shed load) |
| Recovery time (structural) | Weeks to months (decompose, add caching) |
| Deployment risk | Every deploy is high-risk; any bug affects all consumers |
| Scaling cost | Must scale to handle combined traffic of all consumers |
The most insidious aspect: the god service's failure mode is indistinguishable from a "the whole system is down" event. Incident responders waste time investigating every consumer service before realizing the root cause is a single upstream dependency.
When It's Actually OK
- Early-stage startups (< 5 services): If you have 3 services and one is the User Service, the fan-in is manageable. Over-decomposing at this stage wastes time. Ship the god service, and plan to break it apart when you hit 8+ consumers.
- Internal tooling with low traffic: Admin dashboards, reporting tools, or batch jobs that call a central service infrequently don't create hot-path dependency pressure. If the User Service goes down and the only impact is "the admin dashboard is unavailable for 10 minutes," that's acceptable risk.
- Read-only aggregators: If the "god service" is a read-only data aggregator (not a write-path dependency), the blast radius of its failure is limited to stale reads, not broken writes.
- Behind a circuit breaker with graceful degradation: If every consumer has a fallback for when the central service is unavailable, the god service pattern is tolerable. The question is whether your team will actually implement and test those fallbacks.
The key test
Ask yourself: "If I'm paged at 3 a.m. because this service is down, how many people will also be paged?" If the answer is "every on-call engineer in the company," the god service has already become a structural problem, not just a latency concern.
Common Mistakes and Misconceptions
- "Single source of truth means every read must be synchronous." Keep one write authority, but distribute read data when freshness and authorization rules allow it.
- "A shared cache in front fixes the topology." It may reduce load, but it can create another high-fan-in dependency. Prefer consumer-side caches or local read models for isolated failure domains.
- "Add retries until the dependency recovers." Unbounded retries amplify load and consume downstream connection pools. Use timeouts, circuit breakers, bounded retries, and backpressure.
- "Decomposition alone creates resilience." New services can form a distributed monolith if callers still synchronously require all of them. Remove hot-path dependencies and test degradation behavior.
30-Second Explanation
A god service is a high-fan-in synchronous dependency that handles unrelated concerns. It may be the source of truth, but if every request must call it, one slow query or deploy can slow many features at once. Keep writes authoritative and move safe reads to tokens, consumer caches, local read models, or events, with circuit breakers for live calls.
5-Minute Explanation
Measure the service graph by fan-in, call frequency, and whether calls sit on user-facing critical paths. Then classify each datum by freshness and security needs: stable claims can live in tokens, slowly changing attributes can use consumer-side caches, and broad read-heavy data can use event-driven local models. Keep live calls for data that truly needs current authority, but bound them with timeouts and fallbacks. The trade-off is eventual consistency, cache invalidation, token staleness, and duplicated read models; the benefit is a smaller failure domain and independently scalable consumers.
Single source of truth != synchronous dependency
You can have one authoritative service for user data without requiring every service to synchronously call it on every request. Push data to consumers through tokens, caches, or events so each consumer can operate with an explicit freshness policy.
Test Your Understanding
Quick Recap
- A god service has excessive fan-in: too many other services depend on it synchronously in their hot path.
- When it has a bad day (memory leak, slow query, deployment bug) every service that depends on it degrades simultaneously.
- The blast radius is proportional to fan-in. Ten consumers means ten services affected by a single degradation event.
- Fix 1: Cache at the consumer to absorb repeated read traffic. A 5-minute TTL can cover the freshness window for attributes that do not need immediate propagation.
- Fix 2: Embed stable user attributes in JWT tokens so services can validate sessions without a network call.
- Fix 3: Decompose by subdomain: auth, preferences, and billing are separate concerns that should be owned by separate services.
- Fix 4: Use event-driven read paths to push updates to consumers asynchronously rather than requiring synchronous reads.
- The decision is not "centralize vs. distribute" but "synchronous dependency vs. async data push."
Related Concepts
- Circuit breakers and graceful degradation: Stop a slow dependency from consuming every caller's budget.
- Bulkheads: Isolate worker pools, connection pools, or feature paths so one failure does not consume all capacity.
- Caching and local read models: Distribute stable or eventually consistent reads to consumers.
- Distributed monolith: Many separately deployed services can still be tightly coupled when their hot paths are synchronous.
- Event-driven architecture: Propagate changes asynchronously while preserving authoritative ownership.
Related Articles
Learn why long chains of synchronous microservice calls multiply failure probability, add latency with each hop, and how to identify and fix chattiness in your architecture.
Understand why microservices that share a database are worse than a monolith, how to detect a distributed monolith, and how to fix service boundaries without a rewrite.