Essay / Software Engineering
Everything Is a Trade-off: What Software Architecture Actually Looks Like in Practice
A forest-monitoring exercise shows how buffering, recovery targets and limited bandwidth turn software architecture into a series of explicit trade-offs.
Architecture became more useful to me when the diagram stopped being the answer. Boxes and arrows could show where measurements travelled. They could not explain what should happen when the arrows stopped working for a day.
That was the interesting part of a forest-monitoring architecture exercise: sensors in remote locations, intermittent connectivity, and a requirement to recover measurements after an interruption. This was an academic scenario, not a monitoring system I deployed in a forest. Its value was making apparently reasonable choices collide.
Store measurements locally, and disconnections become manageable. But local storage introduces capacity limits and recovery decisions. Upload everything when connectivity returns, and historical data catches up. But new measurements are still arriving. Prioritize those instead, and the backlog may remain unresolved.
There was no box labelled “reliability” that made these questions disappear.
Someone had to decide what mattered, under which conditions, and what the system could afford.
Start with the inconvenient constraint
For this exercise, take a connectivity interruption lasting up to 24 hours. The sensors must continue collecting measurements, retain them persistently, and send the accumulated data after the connection returns. Now add a recovery window: the backlog should clear within two hours while normal acquisition continues.
The two-hour window is an illustrative target for the calculation below, not a reported production result or an original requirement I can claim was measured. Choosing it forces the design to confront a workload that “recover quickly” conceals.
What counts as recovered? Bytes transmitted by the device? Records accepted by an endpoint? Measurements durably stored and available to the application? These are different milestones. If the sender deletes its local copy after a network write while the receiver has not safely stored it, a successful transmission can still end in lost data.
I would define recovery at the application boundary: all expected records are durably accepted, with duplicates handled and missing ranges identified. That decision affects acknowledgements, identifiers, retention and monitoring. A sentence in the requirements changes several parts of the design before a technology has been selected.
A little arithmetic changes the discussion
Suppose a device produces an average of r bytes per second after serialization. A 24-hour outage produces a backlog of approximately 86,400 × r bytes, ignoring storage overhead. To clear it in two hours, the system must transfer that backlog over 7,200 seconds.
That takes 12 × r bytes per second for historical data alone. Normal acquisition contributes another r. Under these simplified assumptions, the sustained useful upload capacity must be at least 13 × r.
This is a capacity estimate, not a network benchmark. Protocol overhead, retries, compression, bursts and time spent reconnecting change the real requirement. The calculation is still useful because it exposes a basic mistake: a connection that merely matches the incoming data rate cannot also clear an existing backlog.
If sustained upload capacity is c, backlog drains at roughly c − r. Recovery time is therefore backlog / (c − r), provided c is greater than r. Near that boundary, small reductions in throughput produce large increases in recovery time.
Consider an explicitly hypothetical device producing one kilobyte per second. It accumulates about 86.4 megabytes over a day, using decimal units. A useful upload rate of thirteen kilobytes per second just meets the simplified two-hour target. Calling a link “fast enough for the sensor” would miss most of that requirement.
This is why I want arithmetic beside the architecture diagram. It gives us something specific to disagree about.
Buffering moves the problem somewhere tangible
Persistent local buffering sounds like a complete solution until the buffer approaches its limit. Does acquisition stop? Are the oldest records discarded? Does the device reduce sampling? Is any loss acceptable, and how is it reported?
I would want those choices written down before implementing a queue. A silently overwritten measurement is particularly awkward because downstream consumers may interpret an incomplete series as a complete one. Reporting a gap does not restore the missing data, but it preserves the meaning of what remains.
There is also a difference between surviving a network outage and surviving a restart during that outage. An in-memory queue addresses the former only while the process remains alive. Persistent storage changes the failure boundary, but its write behavior, capacity and integrity still need testing on the intended device.
A practical design might retain records until durable acknowledgement, give each measurement a stable identity, and resend unacknowledged batches after reconnection. That is a proposal for this scenario, not a claim that every sensor network should use the same protocol. Its cost includes bookkeeping at both ends and explicit duplicate handling.
Even the choice of batch size has consequences. Larger batches can reduce per-request overhead, while smaller batches reduce the amount of work repeated after an interruption. I would test a few plausible sizes against the actual connection pattern rather than make the most elegant-looking number a permanent constant.
Quality attributes help name the disagreement
The Software Engineering Institute’s work on reasoning about quality attributes describes scenarios as a way to make architectural qualities concrete. It also examines how a design choice that helps one attribute can affect others. That is a useful vocabulary for this exercise; it is not a scorecard that chooses the architecture automatically.
Here, availability of acquisition, recoverability of stored measurements and freshness of central data are separate concerns. A disconnected device might keep collecting perfectly while the central application has no recent readings. Calling the whole system simply “available” would hide that distinction.
The trade-off becomes clearer when framed around a user. A researcher examining yesterday’s complete series may value eventual recovery. Someone responding to a current condition may value recent readings more. The same upload scheduler can satisfy one need and frustrate the other.
I would separate live and recovery traffic in the scheduling policy, then define how each receives capacity. That policy needs a limit: promising both immediate freshness and rapid catch-up without enough bandwidth is just promising the same resource twice.
Compare decisions, including the option to do less
Imagine three candidate responses to the recovery constraint. We could buy more connection capacity, extend the recovery window, or reduce the amount of data transmitted. Each changes a different part of the problem.
More capacity might be unavailable in the remote location. A longer recovery window might be acceptable for analysis but unsuitable for another use. Reducing data volume might mean lossless compression, or it might mean aggregation that permanently removes detail. Those alternatives should not be treated as equivalent savings.
The useful comparison is therefore tied to the scenario:
| Decision | What it helps | What still needs an answer |
|---|---|---|
| Larger local buffer | Longer disconnections | Storage limits and restart behavior |
| More upload capacity | Faster backlog recovery | Cost and real coverage |
| Longer recovery window | Lower peak bandwidth demand | Acceptable delay for consumers |
| Aggregated measurements | Smaller transfer volume | Whether lost detail matters |
I would record the chosen option alongside the assumption that made it acceptable. “Two-hour catch-up is required because…” is more useful to a future maintainer than “we selected a queue because queues are reliable.” If the reason changes, the decision can be revisited intelligently.
The exercise also suggests a sensible stopping point. A complex failover mechanism might address a failure with little consequence while consuming the time needed to verify persistent buffering. More mechanisms do not automatically mean a better fit. I would spend effort first on the failure scenarios that threaten the stated purpose.
Make the choice observable
A recovery policy needs evidence after implementation. For this scenario, I would track the oldest unacknowledged measurement, buffer occupancy, useful upload throughput and the number of records still awaiting durable acceptance. An empty transport queue alone would not prove that the application has recovered its data.
Then I would interrupt connectivity, keep acquisition running, restore the link and measure the outcome. A second interruption during catch-up would be especially revealing. The details of that test belong in the requirements discussion, because they determine whether the original promise is even testable.
What I took from the exercise was a change in emphasis. Architecture work becomes concrete when a choice has a stated benefit, a visible cost and a condition under which it stops being acceptable. The diagram remains useful. It just has to carry the weight of a decision someone is prepared to explain.