Reference · gazar.dev ← From Senior to Staff

Distributed systems · time

The backend clocks & time cheat sheet.

S6 said it: across machines there is no global "now". So how does a backend handle time at all? Three families do the work: physical clocks (what time is it), logical clocks (what happened first), and hybrids (both at once). This sheet is the whole menu, plus the one bug everyone hits. Companion to S6 · Distributed Systems Patterns.

01

First question: a time, or an order?

Before any tool, diagnose what you actually need. A wall clock tells you it is about 3pm. A numbered deli ticket tells you who is next. They are not the same tool, and you cannot order the queue by glancing at everyone's watch.

T

You need a time

A timestamp to log, store, or show a human. Approximate is fine. Use a physical clock kept honest by NTP. For "how long did it take," use a monotonic clock.

O

You need an order

Which event happened before which, across machines. Wall time cannot answer this. Use a logical clock (Lamport or vector), or a hybrid if you also want real-ish time.

The trap

Sorting events from different machines by their raw timestamps. The clocks disagree by milliseconds to seconds, so "latest write wins" silently picks the wrong winner. Order is something you impose (sequence numbers, a leader, a logical clock), not something you read off a clock.

02

The three families at a glance

Every time mechanism in a backend falls into one of these. Know which question each answers.

FamilyAnswersExamplesGives youCannot
Physical"what time is it?"NTP, chrony, PTP, cloud timehuman-meaningful, approximate timeprove cross-machine order
Logical"what happened first?"Lamport, vector, version vectorsexact causal ordertell you the wall-clock time
Hybridboth at onceHLC, Google TrueTimereal-ish time that is also orderedescape a small accuracy / wait cost

The one-line map

Wall clock tells you approximately when. Monotonic clock tells you how long. Logical clocks tell you definitely in what order. Hybrids give you when and order together. You almost never want the wall clock deciding order.

03

Wall vs monotonic: the bug everyone hits

Two clocks live on every machine and they are not interchangeable. Mixing them is the single most common time bug in application code.

ClockUse it forReads asCan it jump?Call in code
Walla timestamp to store or showreal calendar timeyes, even backwards on NTP correctiontime.time() · System.currentTimeMillis() · Date.now()
Monotonicdurations, timeouts, rate limitsseconds since some arbitrary pointnever, only moves forwardtime.monotonic() · System.nanoTime() · performance.now()

The rule

Never measure "how long" with the wall clock. When NTP corrects the clock it can step, sometimes backwards. If you timed something with the wall clock and it stepped mid-measurement, your duration goes negative or wildly wrong. Use the calendar to say "the meeting was at 3pm," use a stopwatch to say "it lasted 40 minutes." Durations always come from the stopwatch.

The same bug, in code

# WRONG — wall clock can step back, latency goes negative
start = time.time()
do_work()
latency = time.time() - start          # may be < 0 after an NTP step

# RIGHT — monotonic only ever moves forward
start = time.monotonic()
do_work()
latency = time.monotonic() - start     # always sane

per language:  Python time.monotonic()  ·  Java System.nanoTime()
               Go time.Since() (monotonic since 1.9)  ·  JS performance.now()
               Rust Instant::now()  ·  C clock_gettime(CLOCK_MONOTONIC)

Rule of thumb: anything you subtract (latency, timeouts, retry backoff, rate-limit windows, cache TTL deadlines, leader leases) uses the monotonic clock. Anything you store or display (created_at, log lines, an expiry shown to a user) uses the wall clock.

04

Physical clocks: keeping wall-time honest

Clocks drift because each machine counts on a cheap crystal that runs slightly fast or slow. A sync daemon talks to a more accurate reference and continuously nudges the local clock back. You run this layer, you rarely write it, but you must know it exists. Each card: accuracy typical accuracy, use when to reach for it, watch the catch.

NTP / chronythe default
Accuracy
a few milliseconds over the internet, sub-ms on a LAN.
Use
everywhere, as the baseline. chrony is the modern daemon: settles faster, copes with laptops and flaky links.
Watch
corrections can step the clock. Two synced servers still differ by a few ms, never assume they agree exactly.
PTPprecision time protocol
Accuracy
sub-microsecond, with hardware timestamping in the NIC and switches.
Use
finance and telco, where ordering money or trades needs tighter-than-NTP agreement.
Watch
needs PTP-aware hardware end to end. Overkill for ordinary web backends.
Cloud time servicemanaged
Accuracy
low single-digit ms, sometimes microseconds, served from the hypervisor.
Use
AWS Time Sync, Google public NTP. Free, accurate, zero ops. AWS even exposes a bounded error window via ClockBound.
Watch
still a physical clock, still drifts between corrections. It does not give you ordering.
GPS / atomic referencethe source
Accuracy
nanoseconds at the source; it is what the NTP/PTP chain ultimately syncs to.
Use
your own reference clock in the rack, what big players feed every datacentre so the whole fleet agrees tightly.
Watch
hardware and cost. Only worth it at scale or for the strictest guarantees.

Pattern: run chrony and forget it for almost everything. Reach down the list only when "a few ms of skew" genuinely is not good enough, and even then, physical clocks never give you order.

05

Logical clocks: order without trusting wall time

The breakthrough: forget wall time for ordering. Track causality with counters that travel inside your messages. If A could have caused B, A's counter is lower. No synchronised clocks required, just arithmetic. Think numbered deli tickets: ticket 7 was clearly here before ticket 12, no clock needed.

Lamport timestampone counter per node
Rule
bump your counter on each local event; on receive, set it to max(mine, theirs) + 1.
Gives
a total order consistent with cause and effect: anything downstream has a bigger number than its cause.
Limit
two unrelated events can share a number, so it cannot prove two things were truly concurrent.
Use for
cheap consistent ordering in logs and messaging.
Vector clockone counter per node
Rule
keep a count for every node. Compare two vectors slot by slot.
Gives
conflict detection: bigger in every slot means it came after; bigger in some and smaller in others means genuinely concurrent.
Limit
size grows with the number of nodes; you must store and ship the whole vector.
Use for
reconciling replica writes: Dynamo, Riak, Cassandra hinted handoff.

Worked example: vector clocks spot a conflict Lamport would hide

flowchart TB
  S["start: A:0 B:0 C:0"]
  S --> AW["A writes: A:1 B:0 C:0
A has heard nothing from B"] S --> BW["B writes: A:0 B:1 C:0
B has heard nothing from A"] AW --> CMP["compare A:1 B:0 vs A:0 B:1
neither is greater-or-equal in every slot"] BW --> CMP CMP --> CONC["CONCURRENT: a real conflict you must merge"] CONC --> LAM["a Lamport clock would give them 1 and 1,
so you silently pick a winner and lose one write"] class CONC bad class LAM accent classDef bad fill:#FEE4E2,stroke:#C8102E,color:#09244B classDef accent fill:#DCEBFE,stroke:#4A90D9,color:#09244B

Version vectors: the same idea for stored data

A version vector applies vector-clock logic to object versions, so replicas can tell whether one copy strictly supersedes another or whether they diverged and need reconciling. It is how eventually-consistent stores avoid blindly overwriting.

06

Hybrids: real-ish time that is also ordered

Pure logical clocks lose the human-friendly "it is 3pm." Pure physical clocks lie about order. Hybrids glue them together, and this is what modern distributed databases run on.

Hybrid Logical ClockHLC
Idea
a physical timestamp plus a small logical counter, written as physical : logical.
Gives
values that are close to real time, never go backwards, and respect causality.
Seen in
CockroachDB, MongoDB cluster time, YugabyteDB.
Watch
still bounded by clock skew; the logical counter only breaks ties within the same instant.
Google TrueTimeSpanner · ClockBound
Idea
don't pretend the clock is exact. The API returns an interval: real time is between earliest and latest, a few ms wide.
Gives
globally consistent transaction order across datacentres, kept tight with GPS and atomic clocks.
Move
commit-wait: pause until latest has passed before acknowledging, so nobody later can claim an earlier time.
Watch
you pay that wait (a few ms) on every commit, and it needs special hardware. AWS ClockBound is the open analogue.

The TrueTime trick in one line

Rather than claim "it is exactly 3:00:00," the clock says "it is between 3:00:00.000 and 3:00:00.004." Before calling a commit done, wait those 4ms out. Now no later transaction can honestly claim an earlier moment. You buy correctness with a tiny pause.

07

Time-based IDs: the practical payoff

Where clocks meet everyday backend work. You need primary keys that are unique without coordination across many machines, and ideally sortable by creation time so they cluster well in a B-tree index. This is one of the most common system-design interview questions ("design a unique ID generator").

SchemeShapeSortable by time?Coordination-free?Catch
Auto-incrementDB sequenceyesno, single writerhard to shard; leaks row counts
UUIDv4122 random bitsnoyesrandom inserts fragment the index (page splits)
Snowflake41b time + 10b node + 12b seqyesyes (per-node)needs a unique worker id; clock-skew sensitive
ULID48b time + 80b randomyesyes128-bit, base32 text
UUIDv748b unix-ms + 74b randomyesyesthe modern default, drop-in UUID

Snowflake: a 64-bit ID is mostly a timestamp

flowchart LR
  Z["1 bit: 0"]
  T["41 bits timestamp
ms since epoch
sorts chronologically"] N["10 bits node
machine"] Q["12 bits seq
per-ms counter
4096 ids per node per ms"] Z --> T --> N --> Q Q --> R["sortable by time, unique across nodes,
no central coordinator"] class R accent classDef accent fill:#DCEBFE,stroke:#4A90D9,color:#09244B

Interview gold: "why not a random UUIDv4 primary key?"

Because v4 is random, every insert lands in a random spot in the index B-tree, causing page splits and write amplification and killing cache locality. A time-ordered ID (UUIDv7, ULID, Snowflake) inserts at the "end" of the index, so writes stay sequential. Reach for UUIDv7 as the modern default; Snowflake when you want a compact 64-bit integer.

08

Backend rules of thumb

The day-to-day decisions where clocks bite real services. Each of these is a common interview follow-up and a common production incident.

Store UTC, alwayspersistence
Rule
store and compute in UTC; convert to local time only at the display edge. Postgres timestamptz, never naive local times.
Why
local times are ambiguous across DST transitions and zones. Keep the original zone separately only if the user's wall time itself matters (calendars).
Stamp time server-sidetrust
Rule
generate created_at and ordering timestamps at the server edge, not from the client payload.
Why
phone and browser clocks are routinely minutes off and user-settable. Never trust a client clock for anything that matters.
JWT / token expiryauth
Rule
validate exp / nbf with a small clock-skew leeway (typically 30 to 60s) between issuer and verifier.
Why
zero leeway rejects valid tokens when the two servers differ by a few ms to seconds. Too much leeway widens the replay window, keep it tight.
Distributed locks need fencingconcurrency
Rule
a TTL lock (Redis, etcd lease) is not safe on its own. Hand out a monotonically increasing fencing token and have the resource reject stale tokens.
Why
a GC pause or slow syscall can make a process believe it still holds an expired lock. Time alone cannot prevent two writers; the token can.
Rate limits & timeoutsresilience
Rule
token-bucket refill, retry backoff, and request deadlines all run on the monotonic clock.
Why
a wall-clock jump could refill a bucket instantly or make a timeout fire early or never. Durations come from the stopwatch.
Leap secondstrivia that bit prod
Rule
don't handle them yourself; use a provider that smears the leap second over a window (Google, AWS) so you never see 23:59:60.
Why
a naive jump or repeated second has crashed real systems. Smearing keeps every second normal-length.
09

Which clock do I reach for?

Pick by the job, not by habit. Start at the top question every time: do I need order, or just a time?

flowchart TB
  Q{"do you need a TIME, or an ORDER?"}
  Q --> T["just a TIME to log or show:
WALL clock + NTP / chrony"] Q --> M["how LONG it took, a timeout, a rate:
MONOTONIC clock"] Q --> O["the ORDER of events"] O --> O1["one stream is fine:
LAMPORT timestamps"] O --> O2["must detect conflicts:
VECTOR / version vectors"] Q --> B["real TIME and ORDER together"] B --> B1["one distributed store:
HYBRID LOGICAL CLOCK, HLC"] B --> B2["cross-datacentre transactions:
TRUETIME / ClockBound + commit-wait"] class T,M,O1,O2,B1,B2 accent classDef accent fill:#DCEBFE,stroke:#4A90D9,color:#09244B
You need…Reach forSeen in
Roughly-right timestamps for logs & recordsWall clock + NTP / chronyeverywhere
Correct durations, timeouts, rate limitsMonotonic clockevery language stdlib
Sub-microsecond accuracyPTP / GPS referencefinance, telco
Order events by causalityLamport timestampsmessaging, logs
Detect concurrent / conflicting writesVector / version vectorsDynamo, Riak, Cassandra
Real-ish time that is also orderedHybrid Logical ClockCockroachDB, MongoDB, Yugabyte
Globally consistent transactionsTrueTime / ClockBound + commit-waitSpanner, AWS
10

Interview questions, with crisp answers

The ones that come up, and the one or two sentences that show you actually understand the trade-off rather than reciting a term.

Q · Why can't you order events across servers by timestamp?

Each machine has its own drifting clock, so two timestamps a few ms apart prove nothing. Impose order instead: a sequence number, a single leader that stamps, or a logical clock.

Q · Wall vs monotonic, when each?

Wall clock for a timestamp to store or show. Monotonic for anything you subtract: latency, timeouts, backoff, leases. Wall clocks can step backwards; monotonic never does.

Q · Lamport vs vector clock?

Lamport gives a total order with one counter but can't tell concurrent from causal. A vector clock (one count per node) can detect genuine concurrency, so you can flag conflicts to merge.

Q · How does Spanner stay consistent without a perfect clock?

TrueTime returns a time interval with bounded error, then Spanner commit-waits out that uncertainty before acknowledging, so no later transaction can claim an earlier time.

Q · Design a unique ID generator. Why not UUIDv4?

v4 is random, so inserts fragment the index. Use a time-ordered ID, UUIDv7 / ULID / Snowflake, so keys are unique, coordination-free, and sort by creation time for sequential writes.

Q · What breaks a Snowflake generator?

The clock moving backwards (NTP step). It can mint duplicate or out-of-order IDs, so the generator must wait for the clock to catch up or refuse to issue.

Q · Why are TTL-based distributed locks unsafe?

A paused process can think it still holds an expired lock, so two writers act at once. Fix with a fencing token: an increasing number the resource checks, rejecting stale holders.

Q · How do you validate a JWT given clock skew?

Check exp / nbf with a small leeway (30 to 60s) for skew between issuer and verifier. Tight enough to limit replay, loose enough to not reject valid tokens.

11

Traps & the repeatable move

The ways smart people get time wrong, and the one habit that catches all of them.

The traps

  • Duration via wall clock, NTP steps mid-way and your latency metric goes negative.
  • Sorting events by timestamp across machines, drift makes "last write wins" drop the real winner.
  • Assuming NTP means identical clocks, a few ms of skew is normal; never design as if two servers agree to the ms.
  • Last-write-wins with no vector clock, you cannot even tell a conflict happened, so you can't choose to merge.
  • Trusting a client's clock, phones and browsers are routinely minutes off; stamp time at the server edge.
  • Random UUIDv4 primary keys, they fragment the index; use a time-ordered ID for sequential inserts.
  • TTL lock with no fencing token, a paused process double-writes; the token is what actually protects the resource.

The repeatable move

1. a time, or an order?
2. if time: wall (when) or monotonic (how long)?
3. if order: do you need to spot conflicts?
4. need both? reach for a hybrid

The tell in any design review: a wall-clock timestamp being used to decide order, when it should only ever say when. Spot that phrase and push back.

Companion to S6 · Distributed Systems Patterns. Further reading: Lamport, Time, Clocks & the Ordering of Events · Hybrid Logical Clocks · Google Spanner / TrueTime · RFC 9562 · UUIDv7 · ULID spec · Kleppmann · distributed locking & fencing · AWS Time Sync & ClockBound.