A proposal moves a CPU-heavy pricing calculation from the application tier into database procedures so it runs next to the data. How would you evaluate that proposal from a capacity and scaling perspective?
answer
- does it remove data movement, or just relocate CPU?
- app tier = elastic + bulkheaded; primary = one shared node
- replicas cannot take write-path work
- per-core licensing makes DB CPU dearest
- decide on measured CPU-per-op and bytes saved
basics
~20 sCompare where the work is cheapest to add capacity. Application servers are stateless and scale horizontally for money; the primary database is one writable node, shared by every query, and often licensed per core. Move work down only when it removes far more data movement than the CPU it adds.
solid answer
~60 sI would ask what moving it removes, not just where it runs. - **Does it reduce data movement?** If the calculation consumes a large set and emits a small result, running it in the database removes network and serialization cost that may exceed the CPU spent. If it consumes one row and emits one row, nothing is saved and pure CPU has been relocated. - **What is the headroom on each tier?** Application capacity is elastic and isolated per instance. The primary database is a single writable node whose CPU is shared by every session, so heavy procedures raise latency for unrelated queries - a noisy-neighbour effect with no bulkhead. - **What is the escape hatch if it saturates?** For the app tier: add instances. For the database: a bigger instance, then read replicas that cannot take write-path work, then sharding - each far more expensive and slower to execute than autoscaling. - **What does the CPU cost?** Under per-core licensing, database CPU can be several times the price of application CPU. - **Measure, do not argue.** Prototype, measure CPU per operation on each tier and the bytes saved, and decide on numbers with a defined rollback.
go deeper
Know that application servers scale out easily while the primary database is usually one node, so heavy work there affects every query.
Distinguish data-reduction work (worth moving down) from pure computation (relocation only), and name replicas' write-path limitation.
Quantify: CPU per operation on each tier, bytes saved, effect on peak latency and lock hold time, and the concrete scaling escape hatches.
Own the decision framework - measured tradeoff, cost per core, blast radius on a shared tier, reversibility, and the alternatives (push down reduction only, precompute, offload) - with an explicit rollback plan.
## Reframe the proposal 'Run it next to the data' is a real principle, but it is about data movement, not about the database being a better computer. The right question is what the move removes. Two very different cases hide behind the same sentence: - **Data-reduction work.** The computation reads a large volume and emits a small result - aggregation, matching, filtering, joining. Running it in the database removes rows crossing the network, the serialization and deserialization on both ends, and often the memory pressure of holding them client-side. This can be a large net win even though database CPU rises. - **Pure computation.** The computation reads roughly as much as it writes and spends its time in arithmetic or rules evaluation. Moving it relocates CPU without removing any transfer. That is the case in the proposal as stated, and it needs a much stronger justification. ## Asymmetry of the tiers The reason this matters is that the two tiers do not scale alike. **Application tier.** Stateless instances behind a load balancer. Capacity is a number in a config; failure of one instance is absorbed; a runaway workload on one instance is naturally bulkheaded from others. Adding capacity takes minutes and costs commodity money. **Primary database.** Usually exactly one node accepts writes. Its CPU is shared by every session, so a heavy procedure competes with the transactional queries that pay the bills - and unlike an application instance, there is no isolation boundary between them. Connection counts are bounded (each backend costs memory and scheduler attention), so saturation shows up as queueing and latency spikes across the board. Scaling options are, in order: vertical (bounded, needs a failover to apply), read replicas (cannot take write-path or read-your-write logic), and sharding or read-model offload (a major project, months not minutes). Failover itself is an event you would rather not trigger. **Cost.** Where licensing is per core, a core in the database can cost several times a core in the application tier, before considering that databases are typically provisioned for peak with generous headroom. ## How I would evaluate it concretely 1. **Quantify current cost.** CPU-milliseconds per pricing operation in the application, operations per second at peak, and bytes currently transferred to perform it. 2. **Prototype the procedural version** and measure the same: database CPU per operation, and bytes no longer transferred. Procedural languages in the database are usually slower per unit of computation than a compiled or JIT-ed application runtime, so expect the CPU number to rise, sometimes substantially. 3. **Convert to headroom.** Express the added load as a percentage of the primary's peak CPU, and check what that does to the latency of unrelated queries under peak concurrency - not average, peak with contention. 4. **Check the tail.** Does the procedure hold locks or a transaction while computing? Long-running procedural work inside a transaction adds lock hold time and blocks cleanup of old row versions, which damages the whole instance far beyond its CPU share. 5. **Ask what else changes.** Testability, deployment, lock-in, ORM friction - the non-capacity costs still apply and should be priced. 6. **Define the exit.** If it does saturate the primary, what do we do? If the honest answer is 'buy a bigger instance and hope', the proposal is under-baked. ## Alternatives that usually dominate - **Push down the reduction, keep the rules up.** Let SQL do the filtering, joining, and aggregation, and return the compact set the calculation needs. This captures most of the data-locality benefit with none of the procedural cost. - **Cache or precompute.** If pricing inputs change slowly, precompute and store results, or maintain them asynchronously. - **Move the work off the write path entirely.** Read replicas, a separate analytics store, or an asynchronous worker can absorb heavy computation without touching the primary. - **Batch smarter.** Often the perceived need to move logic down is really a chatty access pattern, fixable with set-based queries. ## The judgement to express Say yes when the calculation is a data-reduction step over volumes that are expensive to ship, when the database has demonstrated headroom, and when the team accepts the testing and deployment discipline. Say no when it is pure computation relocated onto a shared, hard-to-scale, expensively licensed tier, especially when the application side has elastic capacity sitting idle. And in both cases, insist the decision rests on measured numbers with a defined rollback, because this is a decision that is much easier to make than to reverse.
- Could read replicas absorb the extra load from procedural pricing logic?Only if the calculation is read-only and tolerant of replication lag. Anything on the write path, or anything that must read its own writes, has to run on the primary, so replicas do not help. Replicas are also not free: they consume the primary's replication bandwidth, and routing logic must be certain about which queries can tolerate staleness.
- When is moving computation into the database clearly the right call on capacity grounds?When it collapses a large set into a small result, so the database spends CPU to avoid shipping and serializing far more data than the CPU is worth - aggregations, matching, and bulk state transitions are typical. It is also right when the alternative is a chatty per-row loop whose latency cost dwarfs any CPU consideration. The common thread is that the move removes work overall rather than relocating it.
Application CPU is rented by the hour and multiplies on demand; primary-database CPU is a single shared kitchen - one chef doing long prep work slows every order in the restaurant.
saying these in an interview costs you the question
- Assuming 'closer to the data' is always faster, regardless of whether data movement is reduced
- Ignoring that the primary database is typically a single writable, shared, hard-to-scale node
- Suggesting read replicas for write-path logic
- Assuming procedural database languages compute as fast as the application runtime
- Deciding on architecture principle alone without measuring CPU per operation and bytes saved