When Timestamp Caching Backfires: Sentinel’s Adaptive Solution Explained
The article examines how naive timestamp caching can increase CPU usage under high concurrency, analyzes implementations in Alibaba’s Cobar and Sentinel, presents performance test results, and explains Sentinel’s adaptive algorithm that switches between cached and direct timestamps based on QPS.
After a previous Netty article, the author discusses how newcomers can contribute to open‑source projects not only with code but also by fixing documentation, writing tutorials, and improving examples.
When retrieving the current time, the common code long ts = System.currentTimeMillis(); (or its Go equivalent) works fine in most cases, but under high concurrency it becomes a bottleneck because all threads contend for a single global clock source.
Cache Timestamp
The idea of caching timestamps was first seen in Alibaba’s Cobar database middleware, which updates a cached value every 20 ms in a dedicated thread and lets the rest of the system read the cached value.
public class TimeUtil {
private static long CURRENT_TIME = System.currentTimeMillis();
public static final long currentTimeMillis() { return CURRENT_TIME; }
public static final void update() { CURRENT_TIME = System.currentTimeMillis(); }
}In Cobar the cached timestamp is considered "weak‑precision" and is only used for statistics, so occasional inaccuracy is acceptable.
Sentinel, Alibaba’s flow‑control and circuit‑breaker library, adopts a similar cache to reduce overhead, but because Sentinel’s decisions (rate‑limit or circuit‑break) depend on the timestamp, the cache interval must be much finer—typically 1 ms.
Negative Yield
Benchmarks showed that the cached‑timestamp implementation in Sentinel consumes about 50 % of CPU under moderate load, providing no performance gain unless the query‑per‑second (QPS) exceeds roughly 4 000. Consequently, Sentinel‑Go disables the feature by default.
Adaptive Algorithm
Sentinel (Java ≥ 1.8.2) later introduced an adaptive algorithm that switches between direct system calls and cached timestamps based on runtime QPS. The algorithm maintains three states:
RUNNING – cache is active and QPS is measured.
IDLE – no caching; the thread sleeps for 300 ms.
PREPARE – cache is updated but QPS is not yet measured.
A periodic check method (every 3 seconds) transitions states: if the read QPS exceeds HITS_UPPER_BOUNDARY (1200) while idle, it moves to PREPARE; if the read QPS falls below HITS_LOWER_BOUNDARY (800) while running, it returns to IDLE. The PREPARE state smooths the transition from IDLE to RUNNING.
When in RUNNING, the system returns the cached timestamp; otherwise it falls back to System.currentTimeMillis(). This design reduces CPU usage at low load while preserving low latency at high load.
The QPS thresholds are hard‑coded in the current implementation, but the author suggests making them configurable because they may depend on hardware.
Sentinel uses the LeapArray structure to count read QPS, the same mechanism described in the author’s earlier article on sliding time‑window algorithms.
Performance tests confirm that the adaptive cache lowers CPU consumption significantly under low load, with negligible impact under high load, matching expectations.
Finally, the author notes that Sentinel‑Go has not yet implemented this adaptive logic, presenting an opportunity for contributors.
For more details, see the linked source code and related articles.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Bin's Tech Cabin
Original articles dissecting source code and sharing personal tech insights. A modest space for serious discussion, free from noise and bureaucracy.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
