Mobile Computing · Mobile Networking
Radio Tail Energy and the Cost of the Second Request
Energy per byte is not a constant on a cellular radio; it depends on how long the radio stays powered, which makes request spacing a first-order energy decision.
A radio’s power draw has three regimes, and they differ by orders of magnitude. Transmitting is the most expensive, receiving is cheaper, and idle-with-receiver-off is cheaper still by a large factor. The interesting behaviour comes from the transitions: after the last packet, the radio does not return to the cheapest regime immediately. It holds an intermediate state for a period set by the network, and during that period the device draws power for a connection it is not using.
The energy cost of a request is therefore not proportional to its size. It is proportional to the time the radio is powered, and that time includes a fixed tail that the request itself does not need. Once the tail is larger than the transfer, the dominant term in the energy budget is the number of times the radio is woken, not the number of bytes moved.
An energy model with a tail
The model that captures the behaviour has four terms: a fixed cost to bring the radio up, a cost per unit of time while the connection is held, a cost per byte transferred, and a tail that is charged once per wake-up.
Bringing the radio up requires a random access exchange and a connection setup, described in Radio resource control states and the tail timer after the last byte. It is a fixed energy cost, largely independent of the payload, and it is paid whenever the device transitions from idle.
While the connection is held, the device monitors the control channel for scheduling grants. That monitoring is the reason a connected-but-idle device still draws substantial power: it must decode enough of the downlink to know whether it is being addressed. The cost per unit time in this state is the largest sustained draw after active transmission.
While data moves, the cost is roughly proportional to bytes, modulated by the modulation and coding scheme and by the transmit power the device chooses, which itself depends on distance from the cell and on the uplink interference it experiences. A device at the edge of a cell transmits at higher power for the same bits, so the energy per byte is not a property of the payload but of the link.
The tail is the period after the last packet during which the connection is retained. It is charged in full even if nothing else happens, and it is the term that dominates for small, isolated transfers.
Expressed as a cost function over a sequence of requests, the structure is what matters:
def sequence_energy(times, tail, wake_cost, hold_rate):
"""Relative energy for request timestamps, ignoring bytes."""
total = 0.0
end = None # when the radio goes idle again
for t in times:
if end is None or t > end: # cold: radio must be brought up
total += wake_cost
end = t + tail
else: # inside the tail: extend it
end = t + tail
total += hold_rate * (end - t)
return total
The key line is the extension: a request that lands inside the tail pushes the tail’s end forward, so it does not pay a new wake cost but does extend the period of high draw. That is why batching within a tail is cheap but not free, and why a stream of requests spaced just under the tail can hold the radio open indefinitely at the sustained rate — the worst outcome in energy terms, and a real pattern for anything that polls.
Why the tail exists at all
The tail is a network decision, not a device one, and the reason is that tearing down and rebuilding a connection costs the network more than keeping it.
Releasing a connection involves signalling to the core network, updating the device’s state to idle, and arranging for paging. Re-establishing it involves random access, contention resolution, and a setup exchange, and it consumes scheduling resources on the shared channel that every other device in the cell is competing for. From the operator’s perspective, a short tail means more setup signalling and more contention; a long tail means more connected devices and more control-channel overhead. The chosen value balances the two, and it is set per cell or per subscriber class rather than per application.
This is the crux of the energy problem: the timer that determines the device’s energy consumption is chosen by a party that does not bear the energy cost. The device cannot influence it, cannot query it, and cannot end it early except by asking the network to release the connection — a request that is itself signalling, and that the network may refuse.
The consequence for design is that the tail must be treated as a fixed charge on every wake-up, and the number of wake-ups must be minimised.
The arithmetic of coalescing
Given the model, the comparison between two designs is straightforward, and it explains why batching is worth the complexity it costs.
Consider an application that must send eight small requests. Sent individually as the user’s actions occur, spaced by tens of seconds, each one is a cold start: eight wake costs and eight tails. Sent together, they are one wake cost and one tail, extended slightly by the time the eight take to send.
The ratio is roughly the number of requests, because the wake cost and the tail are per-wake-up constants and the payload is small. The same eight requests moved as one batch therefore cost on the order of one-eighth of the energy, and the figure holds over a wide range of payload sizes precisely because the payload is not the dominant term.
The trade is latency. A batch waits for a trigger, whether a timer, a size threshold, or an application event, and the wait is visible to the user if the request was supposed to have an effect. The resolution is to separate requests by whether they are on the user’s critical path. A request whose result the user is waiting for must go immediately; a request that merely synchronises state can wait. Most applications have far more of the second kind than they assume, and the ones that do not are usually performing synchronous writes where an outbox would do, which is the argument in The durable outbox.
Where the byte cost re-enters
The model above ignores bytes, and that is wrong for transfers large enough that the payload term dominates. The crossover is worth estimating rather than guessing.
The wake cost and the tail are fixed; the byte cost scales. If a tail is ten seconds and the sustained draw while connected is on the order of a watt, the tail accounts for about ten joules. A payload of a hundred kilobytes at a cost of roughly a microjoule per byte is a tenth of a joule, so the tail dominates by two orders of magnitude. At ten megabytes the payload is around ten joules and the two terms are comparable. Beyond that, the payload dominates and batching’s benefit shrinks toward the ratio of the tails alone.
These figures are order-of-magnitude reasoning, not measurements, and the constants vary with the radio generation, the band, the distance to the cell, and the device. The structural conclusion is stable: for small payloads the tail dominates, for large payloads the bytes dominate, and the crossover is somewhere in the megabytes rather than in the kilobytes.
That conclusion has a direct design implication: batching helps small requests and is irrelevant to large ones, so a policy that batches uniformly wastes latency on the requests that would not benefit and gains nothing on the downloads that dominate the energy budget anyway. The two should be handled by different policies.
Prefetch and the asymmetry of a miss
Prefetching is batching across time rather than within a moment: fetching data before it is requested, so that the request finds it already there and pays nothing.
The energy argument for it is that a prefetch issued while the radio is already active has a marginal cost of bytes, and the request it satisfies later has a marginal cost of zero instead of a full wake-up. The energy argument against it is that a prefetch that is never used has a marginal cost of bytes for nothing.
The asymmetry favours prefetching when the hit rate is high and the payload is small relative to the tail. A list screen that prefetches the detail of its first item, while the list itself is loading, pays a few kilobytes for a chance to eliminate a whole wake-up. If the user opens a different item, the prefetched bytes are wasted but the tail was already paid for the list. The expected cost is favourable as long as the prefetch does not itself trigger a separate wake-up, which is why the prefetch must be issued concurrently with the load rather than after it.
The failure mode is prefetching on speculation across sessions, where the prefetch triggers its own wake-up and the hit rate is low. That pattern adds wake-ups rather than removing them, and it is common in applications that prefetch on a schedule “to keep content fresh”. Freshness is better obtained from push, which lets the network decide when a wake-up is warranted, as described in Push notification as a store-and-forward channel.
Measuring what cannot be queried
The parameters in the model, meaning tail length, per-state draw, and wake cost, are not exposed by any platform API, and the device’s own battery statistics aggregate by application and by hardware component rather than by request.
The practical approach is differential: run the same workload with one design and with another, and compare the platform’s reported energy attribution for the application over a fixed period. The measurement is coarse and noisy, and it is enough to detect a difference of the size the model predicts, because that difference is a factor rather than a percentage. Comparing two designs that differ by eight wake-ups will show a difference; comparing two that differ by one will not, and the latter is not worth measuring.
The instrument that matters more is the request timeline itself. Recording every network operation with its timestamp and its duration makes the wake-ups visible as clusters, and the number of clusters is the quantity the model says to minimise. A timeline that shows requests every twenty seconds for the length of a session is a timeline that shows the radio held open for the length of the session, and no measurement is needed to know that it is the wrong shape.
The limit of the optimisation
Energy is one axis and it trades against three others: latency, freshness, and server load.
Batching improves energy and worsens latency. Prefetching improves latency and energy when it hits, and wastes bandwidth when it misses. Push improves energy and latency relative to polling and depends on a channel that is best-effort. Reducing wake-ups by sending less improves energy and degrades the product.
The designs that work are the ones that separate traffic by its requirement: immediate for what the user is waiting on, batched for what can wait, pushed for what the server knows about first, and never polled. That separation is a product decision about which requests matter, expressed as a policy in the network layer, and the energy model is what makes the policy’s consequences predictable rather than mysterious.
References
- 3GPP TS 36.331: Radio Resource Control (RRC) Protocol Specification — 3GPP
- 3GPP TS 36.300: Evolved Universal Terrestrial Radio Access (E-UTRA) and Evolved Universal Terrestrial Radio Access Network (E-UTRAN); Overall Description — 3GPP
- Android Developers: Optimize for Doze and App Standby — Android Open Source Project