Peeking into YugabyteDB’s Hybrid Logical Clock (HLC)

Distributed databases need a way to order events across nodes that don’t share a perfectly synchronized clock. YugabyteDB solves this problem with the Hybrid Logical Clock (HLC) … a 64-bit timestamp that combines:

  • ● Physical time (microseconds since epoch, from the system clock)

  • ● Logical counter (to break ties when multiple events share the same physical microsecond)

HLC is central to YugabyteDB’s behavior:

  • Transaction ordering

  • Conflict resolution (last writer wins in xCluster active-active)

  • Replication lag measurement

But how can you see HLC in action? Let’s explore.

What is the Hybrid Logical Clock?

According to the YugabyteDB documentation:

  • HLC = (physical component, logical component)

  • The physical component is the node’s wall-clock time, measured in microseconds.

  • The logical component increments when multiple events share the same physical time, or when a node receives a timestamp higher than its local clock.

  • This ensures monotonicity: time never goes backwards, even if system clocks drift or get adjusted by NTP.

Every write is assigned an HLC. If two conflicting versions of the same row exist, YugabyteDB resolves it by comparing the HLCs … the larger one wins.

1. Querying HLC from YSQL

YugabyteDB exposes the current hybrid time at the SQL layer:

				
					-- Returns the current hybrid-time LSN as a bigint
SELECT yb_get_current_hybrid_time_lsn();
				
			

Example:

				
					yugabyte=# SELECT yb_get_current_hybrid_time_lsn();
 yb_get_current_hybrid_time_lsn
--------------------------------
            7205561858242490368
(1 row)
				
			

This is a 64-bit integer that encodes both physical and logical components of the clock.

2. (Optional) Pretty-print like an LSN

PostgreSQL users may recognize the X/Y hex style used for WAL LSNs. You can format HLC the same way if you like:

				
					CREATE OR REPLACE FUNCTION get_current_lsn_format() RETURNS text AS $$
DECLARE
  ht_lsn bigint;
BEGIN
  SELECT yb_get_current_hybrid_time_lsn() INTO ht_lsn;
  RETURN UPPER(format('%s/%s',
    to_hex(ht_lsn >> 32),
    to_hex(ht_lsn & 4294967295)));
END;
$$ LANGUAGE plpgsql;
				
			

Example:

				
					yugabyte=# SELECT yb_get_current_hybrid_time_lsn(), get_current_lsn_format();
 yb_get_current_hybrid_time_lsn | get_current_lsn_format
--------------------------------+------------------------
            7205563911740420096 | 63FF4E33/75AAD000
(1 row)
				
			

➡️ This format is purely cosmetic … it’s still the same 64-bit number underneath. If you prefer, just compare the raw bigint values directly.

3. The Real Encoding

Internally, the 64-bit HLC is structured as:

				
					[ upper 52 bits ] = physical time (microseconds since epoch)
[ lower 12 bits ] = logical counter
				
			
  • ● Physical time → corresponds to real-world wall clock

  • ● Logical counter → increments when multiple events happen in the same microsecond

This makes HLC lexicographically comparable: YugabyteDB just picks the greater HLC when resolving conflicts. This is how last writer wins conflict resolution works in xCluster.

4. Decoding into Physical + Logical Parts

You can decode an HLC bigint directly inside YSQL:

				
					CREATE OR REPLACE FUNCTION decode_hybrid_time(ht_lsn bigint)
RETURNS TABLE(physical_usec bigint, physical_ts timestamptz, logical_counter int) AS $$
BEGIN
  -- Extract physical microseconds
  physical_usec := ht_lsn >> 12;
  -- Convert to timestamp
  physical_ts := to_timestamp(physical_usec / 1000000.0);
  -- Extract logical counter (lower 12 bits)
  logical_counter := ht_lsn & ((1<<12)-1);
  RETURN NEXT;
END;
$$ LANGUAGE plpgsql;
				
			

Example:

				
					yugabyte=# SELECT * FROM decode_hybrid_time(7205563911740420096);
  physical_usec   |          physical_ts          | logical_counter
------------------+-------------------------------+-----------------
 1759170876889751 | 2025-09-29 18:34:36.889751+00 |               0
(1 row)
				
			

Note: Seeing logical_counter = 0 is expected on an idle or lightly loaded system. It turns non-zero when multiple events share the same microsecond or when causality requires bumping logic to avoid time going backward. To catch it, you need very concurrent writes or observe a node applying replicated writes.

5. Comparing to clock_timestamp()

The HLC’s physical component usually tracks wall time closely. You can compare:

				
					WITH ht AS (
  SELECT (decode_hybrid_time(yb_get_current_hybrid_time_lsn())).*
)
SELECT
  ht.physical_ts                           AS hlc_physical_ts_utc,
  clock_timestamp() AT TIME ZONE 'UTC'     AS session_clock_ts_utc,
  EXTRACT(EPOCH FROM (
    (clock_timestamp() AT TIME ZONE 'UTC') - ht.physical_ts
  )) * 1000.0                              AS delta_ms
FROM ht;
				
			

Example:

				
					yugabyte=# WITH ht AS (
yugabyte(#   SELECT (decode_hybrid_time(yb_get_current_hybrid_time_lsn())).*
yugabyte(# )
yugabyte-# SELECT
yugabyte-#   ht.physical_ts                           AS hlc_physical_ts_utc,
yugabyte-#   clock_timestamp() AT TIME ZONE 'UTC'     AS session_clock_ts_utc,
yugabyte-#   EXTRACT(EPOCH FROM (
yugabyte(#     (clock_timestamp() AT TIME ZONE 'UTC') - ht.physical_ts
yugabyte(#   )) * 1000.0                              AS delta_ms
yugabyte-# FROM ht;
      hlc_physical_ts_utc      |    session_clock_ts_utc    | delta_ms
-------------------------------+----------------------------+-----------
 2025-09-29 19:30:19.871758+00 | 2025-09-29 19:30:19.871792 | 0.0350000
(1 row)
				
			

✅ Small differences (a few ms) are expected.
🚨 Larger differences may point to clock skew or replication lag.

Why HLC May Lag Behind the Clock

It’s normal for the HLC physical time to lag slightly behind clock_timestamp():

  • ● Replication buffering: HLCs are attached at write time, not query time.

  • ● NTP/chrony corrections: System clocks may move forward, but HLC advances monotonically and conservatively.

  • ● Logical counter overflow protection: If the logical counter is exhausted for a microsecond, HLC may wait before advancing physical time.

This ensures consistency: HLC never “jumps backward,” even if the system clock is adjusted.

5. Other Ways to View HLC
  • Tablet Server CLI:
				
					yb-ts-cli --server_address 127.0.0.1:9100 current_hybrid_time
				
			

Example:

				
					[root@localhost ]# yb-ts-cli --server_address 127.0.0.1:9100 current_hybrid_time
7205578795025354752
				
			
  • Master Web UI:
				
					curl -s http://127.0.0.1:7000/tablet-server-clocks \
  | grep -oP '<td>[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:.]+' \
  | sed 's/<td>//' \
  | awk 'NR % 2 == 0'
				
			

Example:

				
					[root@localhost ]# curl -s http://127.0.0.1:7000/tablet-server-clocks \
  | grep -oP '<td>[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:.]+' \
  | sed 's/<td>//' \
  | awk 'NR % 2 == 0'
2025-09-29 19:58:52.747253
				
			
  • Prometheus Metrics
				
					curl -s http://127.0.0.1:7000/prometheus-metrics \
  | grep '^hybrid_clock_hybrid_time' \
  | awk '{print $2}'
				
			

Example:

				
					[root@localhost ]# curl -s http://127.0.0.1:7000/prometheus-metrics \
  | grep '^hybrid_clock_hybrid_time' \
  | awk '{print $2}'
7205592342642266112
				
			
Summary:

The Hybrid Logical Clock (HLC) is YugabyteDB’s internal timekeeper, combining physical microseconds from the system clock with a logical counter to guarantee monotonically increasing timestamps across a distributed cluster.

  • You can view HLC in SQL using yb_get_current_hybrid_time_lsn(), pretty-print it with a helper into X/Y hex form, or fully decode it into physical time and logical counter.

  • At the infrastructure level, HLC is exposed through yb-ts-cli current_hybrid_time, the master/tserver /clocks web page, and in prometheus metrics.

  • The logical counter is usually zero unless multiple events occur in the same microsecond or a node must catch up with a higher timestamp it observed elsewhere.

  • The HLC’s physical time usually tracks closely with clock_timestamp(), but may lag slightly because of how writes are stamped, NTP/chrony adjustments, or logical counter handling.

Have Fun!

We’ve found burn areas in almost every National Park we visit, including here in Gunnison. A tough reminder of nature’s challenges ... and resilience.