Course contents
On this page
Computation boundaries — extended reference
Original detailed coverage and citations accompanying the focused computation boundaries lesson.
This reference preserves the earlier detailed chapter and its citations. Start with the guided explanation, then use these sections for additional mechanisms and qualifications. References to the original lesson sequence are historical.
Part 3: Turning requests into execution
Prerequisites: Lesson 17 and the preceding foundations.
Learning outcomes
- Trace the work removed by moving computation across an application-to-database boundary.
- Compare embedded calls, server RPCs, stored procedures, and sandboxed modules in deployment and resource ownership.
- Explain why removing round trips does not remove validation, isolation, durability, or external-side-effect obligations.
The duration of a transaction includes its boundaries
A transfer reads two accounts, checks a balance, updates both records, and commits. In one deployment, application logic sends several requests across a network to the database. In another, a procedure performs the same logic inside the database. In a third, an embedded library executes in the application process. The logical invariant can be identical while the dependency path differs.
Computation placement changes communication, ownership, deployment, security, and failure behavior. It is a powerful latency decision, but it should be compared under equivalent transaction and durability contracts rather than by stripping guarantees from one alternative.
Embedded libraries
An embedded database runs as a library in the application’s process. Function calls can access engine APIs without a separate network server. This reduces some communication and deployment overhead. It also places the database and application in a shared process failure boundary.
An embedded engine still implements storage, queries, transactions, and recovery. It can use files and support concurrency under its documented rules. Embedding does not imply that every thread may mutate arbitrary internal structures or that several machines can safely share the same file without a supported coordination mechanism.
The application owns operational responsibilities around the library: process lifecycle, file placement, backup integration, upgrades, resource limits, and often remote access if multiple clients are involved. Adding a server around an embedded engine creates a new architecture with its own protocol and scheduling decisions.
Separate database servers
A database server centralizes access through sessions and a protocol. It can serve several applications, enforce access boundaries, manage resources, and evolve independently from one client. The network and serialization path adds work but also establishes useful separation.
Round trips matter especially when logic depends on intermediate results. Four dependent exchanges at an illustrative 1-millisecond RTT contribute 4 milliseconds before database execution. More concurrent connections can overlap independent transactions but cannot eliminate that dependency chain inside one transaction.
The separate process also changes fault isolation. An application crash need not terminate the database. A database upgrade need not require rebuilding every application binary. Those benefits must be weighed against operational complexity, protocol overhead, and the risk of holding transaction resources while application code pauses or waits elsewhere.
Stored procedures and database-side logic
A stored procedure or equivalent database-side function can perform several operations near the data. It can shorten transaction duration by avoiding intermediate network exchanges. The database needs a language runtime, permissions, resource controls, and a way to deploy and version the logic.
Moving code into the engine can also move expensive computation into a shared resource. A long-running procedure may block or compete with unrelated transactions. External I/O can create unpredictable waiting and side effects that do not share the database’s rollback semantics. The procedure API must state which operations are allowed and how transactions are controlled.
PostgreSQL supports user-defined function mechanisms and procedural languages. The relevant documented fact is that computation can be placed within the server. Whether doing so improves a workload depends on communication avoided, runtime cost, and resource interference. The course does not assume that every application should be rewritten as stored logic.
Sandboxed modules and typed interfaces
A database can expose a module model in which developers define schema and transactional functions together. A sandboxed runtime can restrict filesystem, network, and system access while allowing controlled data operations. Generated bindings can align client types with the schema and callable functions.
Sandboxing supplies a defined interface and some isolation properties; it does not automatically establish correct authorization or low resource consumption. A valid module can contain an expensive loop or an invalid business rule. The host needs execution budgets, error handling, and clear transaction behavior.
SpacetimeDB’s documented architecture places module logic and tables in one application model, with transactional reducers and client subscriptions. It is a relevant example of making computation placement central to the developer experience. This implementation fact supports a conditional analysis: reducing server-to-database exchanges can help short, contended state transitions, while the module’s work still consumes engine resources.
Shared state, visibility, and caching
An application server often maintains its own objects or caches in addition to database records. That can make simulation or rendering convenient but creates another representation to synchronize. An integrated state model can reduce this duplication by making tables the authoritative shared state and supplying subscribed local copies.
The client copy is still a copy. Its freshness depends on delivery and processing. Reading it locally avoids a new query but does not prove that the latest server change has arrived. An architecture should distinguish authoritative transitions from cached observations and presentation state.
A separate simulation server can deliberately keep ephemeral state outside the durable database. That may suit high-frequency movement where persisting every intermediate position is unnecessary. It then needs an explicit policy for authoritative ownership, snapshots, recovery, and the boundary between transient and durable facts. There is a real tradeoff rather than one universally superior state representation.
Deployment and evolution
Bundling schema and logic can reduce mismatched releases and simplify local reasoning. It can also couple deployment of code and data representation. A compatible code update is different from a schema change that old clients cannot interpret. Generated clients need a version policy just as hand-written protocols do.
Separate services allow independent scaling and release cadence but introduce distributed compatibility and transaction boundaries. A service split that forces every purchase through several synchronous calls can add latency and partial-failure states. A monolithic deployment can simplify those interactions while increasing shared resource and release responsibility.
The decision should follow ownership and workload boundaries. Independent tenants or features may justify separation. A tightly coupled invariant may benefit from colocated logic. Splitting code merely to increase the number of deployable units does not establish a scalable data architecture.
A worked contention comparison
Suppose every transfer competes over one hot account. In illustrative design A, the protected interval includes four 1-millisecond exchanges plus 100 microseconds of local work. Its ideal fully serialized rate is at most about 1/0.0041, or 244 operations per second. Design B performs only the 100 microseconds of local work within the comparable protected interval, giving an ideal 10,000 per second ceiling for that interval.
These are constructed bounds, not product measurements. Durability, scheduling, log work, and other overhead can lower both rates. The result demonstrates why reducing time spent holding contended ownership can improve throughput as well as latency. It does not establish that one engine is generally forty times faster for every workload.
If transfers instead touch independent accounts, parallel work can hide some communication. The performance difference may change. If procedure execution includes heavy computation, that can become the new bottleneck. A fair comparison preserves data, invariants, isolation, durability, and offered load while changing the boundary under investigation.
Trace equivalent transfers across three boundaries
Assume the invariant is that account balances remain nonnegative and a transfer preserves the sum of the two balances. The database provides serializable transactions and synchronous durable commit in all three hypothetical deployments. Each successful call also records an application operation identifier so a lost reply can be handled without transferring twice. These shared assumptions prevent a comparison from silently changing correctness while discussing speed.
In the remote-server design, the application begins a transaction, obtains the two accounts under the chosen isolation mechanism, validates the source balance, applies both changes, records the result, and commits. If these steps are sent as individual dependent commands, each completion gates the next. Some can be combined in SQL or pipelined, but a client cannot safely speculate about a result on which its next decision genuinely depends.
In the stored-logic design, one remote call enters a procedure that performs those same steps. The caller still pays for sending intent and receiving a result. The database performs internal accesses without returning intermediate account data to the caller. Authorization must use the caller’s actual authority rather than assuming procedure execution grants permission over every account.
In the embedded design, the application invokes a library function. There is no application-to-database network hop, but there are still query execution, contention, logging, flushing, and possibly an application-facing network request. If the user is connecting from another machine, the embedded database has moved one boundary; it has not removed all remote communication.
Remote commands: client -> app -> DB -> app -> DB -> app -> DB
Stored logic: client -> app -> DB[validate; mutate; commit] -> app
Embedded: client -> app[DB library; validate; mutate; commit]The diagram is schematic and does not assert an exact protocol count. Its purpose is to distinguish where intermediate decisions execute. Batching commands can shorten the first path, and stored procedures can make its inner work resemble the second. Architecture categories contain multiple possible implementations.
The protected interval is the important multiplier
A hot record is constrained by the time during which another incompatible action cannot advance. Suppose local validation and mutation take 120 microseconds, commit persistence takes 800 microseconds, and three required application round trips add 1.5 milliseconds each while protection remains held. The illustrative protected interval is 5.42 milliseconds. Its reciprocal is approximately 184.5 operations per second for a completely serialized hot-key path.
Moving intermediate logic into the database removes those particular 4.5 milliseconds. The comparable interval becomes 920 microseconds and its ideal reciprocal becomes approximately 1,087 operations per second. This is not a product benchmark. It assumes identical work, isolation, flushing, and contention and ignores other resource ceilings. It demonstrates that reducing boundary delays can shorten both a transaction and the queue behind it.
A no-force logging design might release some resources at a different point, and group commit can overlap flush waiting across transactions. An optimistic design may execute competing attempts rather than block them. Those changes affect the model, so the relevant interval must be measured for the actual algorithm. Counting network hops without knowing which resources stay occupied can overstate or understate the benefit.
Independent accounts expose a different limit. If each transaction spends most of its time waiting for remote replies and does not contend with others, concurrency can overlap that waiting. The server may sustain high throughput while each individual transaction remains relatively slow. Moving the logic still reduces latency and connection occupancy, but throughput may then be bounded by logging or CPU rather than the removed waits.
Isolation boundaries also contain failures
A separate database process can survive an application segmentation fault. An embedded engine usually shares that process termination, although its recovery protocol should preserve committed data under the supported storage assumptions. A sandbox can constrain a module’s permitted instructions and host calls, but the sandbox runtime, host integration, and resource accounting remain trusted code.
Resource isolation has several dimensions. Memory limits prevent one module from exhausting the entire host; execution budgets bound CPU time; quotas bound emitted results and subscription work. A valid computation can be harmful through sheer cost. A loop over every player on each movement request is expensive even if the code never escapes its sandbox.
Timeout enforcement must respect safe interruption. The host cannot stop arbitrary native code at a random instruction and assume engine latches, buffers, and allocator state remain coherent. A restricted runtime can provide controlled interruption points or fuel accounting. Transaction abort must then release every resource acquired on behalf of the invocation. This is one reason database-side extensibility is an engine-design concern rather than merely a language choice.
Conversely, excessive separation can turn one atomic action into a partial-failure workflow. If inventory, payment authorization, and order state reside behind independent services, one service’s success does not commit the others. External payment systems may require compensation and reconciliation even when all local code is packaged together. Deployment boundaries and transactional boundaries are related but not identical.
What crosses a trust boundary
An API accepting set_balance(account, value) gives a client greater expressive power than transfer(operation, destination, amount). The latter allows the server to derive the authenticated source, check a positive amount, verify available funds, and enforce ownership in one place. Narrow intent interfaces can improve security and reduce round trips because the server has enough information to complete the decision locally.
However, narrowing the interface does not justify trusting parameters blindly. A transfer amount needs bounds and overflow-safe arithmetic. Destination identifiers must resolve within allowed domains. An operation identifier must be scoped so one tenant cannot retrieve another tenant’s cached result. Returning detailed errors can also expose existence or private state. These rules apply to generated SDKs and typed modules as much as to handwritten HTTP services.
Serialization costs depend on what is transmitted. Sending two full account objects with metadata may move far more bytes than sending an amount and destination identifier. Binary encoding can save parsing and payload overhead, but preventing unnecessary data from crossing the boundary usually has the larger structural benefit. A compact serialization format does not compensate for an API that repeatedly exports a large state only to send a tiny decision back.
Generated types help with interface drift and representation errors. They cannot prove that a caller is authorized, that a database constraint holds, or that a client receiving an older schema will understand a new meaning. Type compatibility and semantic compatibility require separate review.
Determinism, side effects, and replay
If replicated state machines rerun application logic, they need compatible inputs and deterministic effects under their chosen replication model. A call to the current clock, random generator, filesystem, or remote service can make replicas diverge unless its result becomes part of the agreed input or is handled outside deterministic execution. Not every system replicates by reexecuting application commands; some replicate resulting changes. The constraint follows from the replication design, not from a universal property of stored procedures.
A useful split separates an authoritative state transition from its external consequences. A transaction writes the new order and a durable outbox record. A delivery worker later sends the message or request using an idempotency key and records progress. This introduces delay and operational state but avoids claiming that a local rollback can undo an already delivered external effect.
A procedure that can make network requests needs similarly explicit semantics. It may read data before the request, wait while the external world changes, then open another transaction to apply the result. Validation at the final transaction matters because the earlier read may be obsolete. Holding a global execution resource across arbitrary external latency is usually a poor default for high-concurrency state transitions.
The resulting interface should state which calls are transactional, which can perform external I/O, whether retries rerun user code, and what a timeout means. Those are user-visible guarantees. Hiding them behind a function-shaped SDK can make an architecture look simpler while leaving the difficult uncertainty with the application.
Deploy code and state without losing the contract
Suppose a module originally stores amounts in integer cents and a new client sends decimal units. A generated binding can successfully transmit a numeric value while the two versions disagree by a factor of one hundred. Versioned fields, explicit units, and compatibility rules prevent this semantic failure. Database-side logic can centralize the conversion, but the conversion itself must be defined.
A schema-and-code bundle should validate compatibility before activation. The activation boundary must specify whether old invocations finish with the old code, whether new invocations wait, and how subscriptions transition to a new schema. Migration can create significant read, write, and index-maintenance work even when the final application code is faster. Operational latency during change belongs in the design.
A separate database service also has evolution hazards: a long-lived application may keep prepared statements or cached metadata across a change. Invalidation, protocol errors, and safe retries need a clear policy. Independent deployment is useful, but it does not imply independent semantics.
For an embedded engine, deployment may update the application and file-format reader together. Downgrade compatibility deserves explicit treatment. Once a new binary writes a new format, reinstalling an old binary may not restore operability. Backups and documented upgrade paths remain necessary even when the architecture has no standalone database server.
Choosing placement from what must stay together
The most important grouping is the invariant’s ownership boundary. If every purchase reads and changes the same small set of records, placing the decision beside those records is a strong candidate. If the operation spends seconds running a model or contacting outside services, keeping that computation away from short transaction execution can preserve responsiveness. The eventual result can enter through a short validated transaction.
An architecture can therefore combine in-database transitions, external orchestration, and ephemeral presentation state without inconsistency if authority is explicit. Player movement prediction can remain local; durable item ownership can remain authoritative; a payment provider remains authoritative for its own settlement. The design must say how conflicting observations are reconciled and which source wins for each fact.
SpacetimeDB makes an integrated path easy to adopt, while embedded libraries and database procedures provide other forms of locality. The analytical question is not whether integration is modern or separation is old. It is how much useful work is removed, which ownership interval shrinks, and which resources now carry the combined load. That framing leads naturally into transaction mechanics: proximity is valuable only when the shorter operation still commits the correct outcome.
Measure the boundary rather than its label
A controlled comparison should record time at application receipt, database admission, first protected access, commit durability, and final client receipt. The difference between application and database spans reveals network and scheduling time. The protected-access span reveals whether those waits amplify contention. Payload counters identify whether compact intent calls actually reduced transferred bytes.
An embedded benchmark that omits the remote application API cannot be compared directly with a network database benchmark that includes client transport. Similarly, one durable transaction cannot be compared with several independent autocommitted writes and called an equivalent transfer. The workload must preserve operation identity, isolation, validation, persistence settings, and response semantics. Otherwise the apparent speedup may be a weaker contract or a different measurement boundary.
A useful hypothetical diagnostic is a transfer with 12 ms end-to-end latency, of which 7 ms lies between application and database commands, 1 ms is execution, 2 ms is durable commit, and 2 ms is external transport and output. Eliminating every intermediate command delay would lower the modeled result to 5 ms. It would not lower it to the 1 ms execution figure. Once the boundary is shortened, persistence and external transport become a larger share of the remaining path.
That changing composition explains why an optimization can be worthwhile and still fail to meet the product’s target. Subsequent work should address the new measured bottleneck. Replacing an already colocated execution path with a second abstraction that saves no communication only adds complexity. Computation placement is a precise tool whose value depends on the dependency and ownership graph.
Case interpretation and design conclusion
SQLite’s documented appropriate-use discussion, PostgreSQL’s function facilities, and SpacetimeDB’s module model represent different places to draw the application/engine boundary. Their usefulness depends on workload, operational ownership, and failure requirements. A single architecture can remove one class of coordination while concentrating another responsibility.
The next part introduces transactions and recovery in detail. Computation placement can make a transaction shorter, but the engine still needs a correct definition of atomicity, isolation, and durable completion. Those guarantees are what allow a shorter path to remain a valid implementation of the same business action.