Determining Whether a Read Was Served by a Leader or a Follower

YugabyteDB supports follower reads for YSQL, allowing read-only queries to be served from follower replicas where the data is exactly stale equal to yb_follower_read_staleness_ms. Internally, this mechanism is implemented using consistent prefix reads, which provide a consistent (but stale) snapshot snapshot without requiring coordination with the tablet leader.

Follower reads are especially valuable in multi-region architectures, where serving reads from the closest replica can significantly reduce latency and offload leaders.

However, enabling follower reads does not guarantee that every read will be served by a follower. Depending on replica freshness and staleness constraints, YugabyteDB may still serve the read from a leader or redirect the request if needed.

This tip shows how to accurately determine:

  • ● Which node actually served a consistent prefix read, and

  • ● Whether that node was the tablet leader or a follower for the specific row being read.

Why would you use Follower Reads?

Follower reads are a great fit when:

  • πŸ—ΊοΈ Lower read latency matters more than absolute freshness
  • πŸ“‰ Read traffic needs to be offloaded from leaders
  • 🌍 Multi-region applications want local reads in every region
  • 🧊 Dashboards, reporting, and background jobs can tolerate slightly stale data

Follower reads are not a replacement for strongly consistent reads:

  • ● They only apply to READ ONLY transactions

  • ● They return data that may be slightly stale

  • ● YugabyteDB may still fall back to a leader if a follower cannot satisfy the staleness bound

Because of this, follower reads are best used selectively, where lower latency and higher read scalability matter more than always reading the absolute latest write.

πŸ“Œ Key Concepts (Critical to Understanding the Metrics)

YugabyteDB exposes a rich set of Prometheus-compatible metrics from each database process (Masters, Tablet Servers, YSQL/CQL layers). These metrics are available via HTTP endpoints such as:

				
					http://<tserver-host>:9000/prometheus-metrics
				
			

Each metric is emitted with a name, a numeric value, and a set of labels (for example, table name, tablet ID, or server role). Importantly:

  • ● Metrics are emitted per process, not globally aggregated

  • ● Many metrics are local to a specific tablet server

  • ● Understanding where a metric is emitted is essential to interpreting it correctly

With that context, the following metrics are key to understanding follower reads and consistent prefix reads.

  • πŸ”Ή consistent_prefix_read_requests
    • ● Tablet-server–local metric

    • ● Incremented whenever a tablet replica (leader or follower) serves a consistent prefix read

    • ● Emitted by the tablet server that actually served the request

    • ● Does not distinguish between leader and follower by itself

  • This metric answers: β€œWhich tablet server served the read?” β€” not β€œWas it a follower?”
  • πŸ”Ή consistent_prefix_successful_reads and consistent_prefix_failed_reads
    • ● Client-side metrics (emitted by the node issuing the read)

    • ● consistent_prefix_successful_reads: the closest replica successfully served the read

    • ● consistent_prefix_failed_reads: the closest replica could not satisfy the request and the read was redirected (often to the leader)

  • These metrics are useful for understanding routing and redirection behavior, but they do not identify which replica ultimately served the data.

Why multiple metrics are needed

No single metric answers all of the following questions:

  • ● Which node served the read?

  • ● Was that node a leader or a follower for the target tablet?

  • ● Did the closest replica serve the read, or was it redirected?

By combining:

  • ● Prometheus metrics (per-node),

  • ● tablet leadership metadata (yb_tablet_metadata),

you can accurately determine where a consistent prefix read was served.

πŸ€” The Challenge: How Do You Know Follower Reads Are Actually Being Used?

YSQL does not expose a per-query indicator that says:

  • β€œThis SELECT was served by a follower.”

So how do you prove follower reads are happening?

The answer is to look one layer lower… at the tablet server metrics that track consistent-prefix reads, which are the internal mechanism used by follower reads.

πŸ§ͺ Demo Setup (3-Node yugabyted Cluster)

Cluster topology:

				
					SELECT host, cloud, region, zone FROM yb_servers() ORDER BY 1;
				
			
				
					yugabyte=# SELECT host, cloud, region, zone FROM yb_servers() ORDER BY 1;
   host    | cloud |    region    |     zone
-----------+-------+--------------+---------------
 127.0.0.1 | avs   | us-east-1    | us-east-1a
 127.0.0.2 | aws   | us-west-1    | us-west-1a
 127.0.0.3 | aws   | us-central-1 | us-central-1a
(3 rows)
				
			

Create a simple test table:

				
					CREATE TABLE my_test (c1 INT, PRIMARY KEY(c1)) SPLIT INTO 3 TABLETS;
INSERT INTO my_test SELECT generate_series(1, 100000);
				
			
🧩 Identify the Tablet and Leader for a Specific Row

To reason about where a read should go, first identify the tablet and its leader for the key being queried.

Tablet placement

				
					SELECT tablet_id, start_hash_code, end_hash_code, leader, replicas 
FROM yb_tablet_metadata
WHERE db_name = current_database() AND relname = 'my_test';

				
			
				
					yugabyte=# SELECT tablet_id, start_hash_code, end_hash_code, leader, replicas
yugabyte-# FROM yb_tablet_metadata
yugabyte-# WHERE db_name = current_database() AND relname = 'my_test';
            tablet_id             | start_hash_code | end_hash_code |     leader     |                    replicas
----------------------------------+-----------------+---------------+----------------+------------------------------------------------
 96e208317cf9424da851a4a5e93d0d7a |               0 |         21845 | 127.0.0.1:5433 | {127.0.0.1:5433,127.0.0.2:5433,127.0.0.3:5433}
 8b6f7d4135fa4f329c069555cc16b648 |           21845 |         43690 | 127.0.0.3:5433 | {127.0.0.1:5433,127.0.0.2:5433,127.0.0.3:5433}
 d09f29ea870c4ed3ae4c32fb8147da13 |           43690 |         65536 | 127.0.0.2:5433 | {127.0.0.1:5433,127.0.0.2:5433,127.0.0.3:5433}
(3 rows)
				
			

Map a specific key to its leader

				
					SELECT yb_hash_code(1) AS hash_code, t.tablet_id, t.leader
FROM yb_tablet_metadata t
WHERE t.relname = 'my_test' 
AND yb_hash_code(10) >= t.start_hash_code 
AND yb_hash_code(10) < t.end_hash_code;
				
			
				
					yugabyte=# SELECT yb_hash_code(1) AS hash_code, t.tablet_id, t.leader
yugabyte-# FROM yb_tablet_metadata t
yugabyte-# WHERE t.relname = 'my_test'
yugabyte-# AND yb_hash_code(10) >= t.start_hash_code
yugabyte-# AND yb_hash_code(10) < t.end_hash_code;
 hash_code |            tablet_id             |     leader
-----------+----------------------------------+----------------
      4624 | 8b6f7d4135fa4f329c069555cc16b648 | 127.0.0.3:5433
(1 row)
				
			

➑️ For c1 = 10, the tablet leader is 127.0.0.3.

πŸ”Ž Metrics We’ll Watch

We’ll track this Prometheus metric exposed by the tablet server:

				
					consistent_prefix_read_requests{table_name="my_test", ...}
				
			

This counter increments only when a consistent-prefix read request is issued, which is exactly what follower reads use.

Helper A (recommended): Sum all matching series for the table

				
					curl -s http://IP:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
				
			

Helper B (debug): Show closest_replica_served and redirected_to_leader:

				
					curl -s http://IP:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
				
			

Helper C (debug): Show the raw metric lines for the table

				
					curl -s http://IP:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
				
			
🧠 Example 1: A Normal β€œStrongly Consistent” SELECT Doesn’t Move the Counter

It’s easy to assume that consistent_prefix_read_requests will increase for any read served by a tablet leader… but that’s not what it tracks.

consistent_prefix_read_requests increments only for consistent prefix reads (the follower-read / bounded-staleness path).

A normal strongly-consistent YSQL read will not change this counter.

1️⃣ Capture Per-Node Metrics (Before the Read)

				
					\! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
\! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
\! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'

\! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
\! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
\! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
				
			

All values should initially be 0.

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0
				
			

2️⃣ Execute a Consistent Read

				
					SELECT * FROM my_test WHERE c1 = 10;
				
			
				
					yugabyte=# SELECT * FROM my_test WHERE c1 = 10;
 c1
----
 10
(1 row)
				
			

3️⃣ Check the counters again:

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0
				
			

βœ… The values are unchanged.

This confirms that a normal SELECT does not use the consistent-prefix (follower read) path.

Note, we can also show the raw metric lines for the table:

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 0 1765994676373

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 0 1765994681618

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 0 1765994686393
				
			
Example 2: A Follower Read Uses the Consistent Prefix Path

1️⃣ Now explicitly run a follower read using a READ ONLY transaction and a staleness bound.

				
					BEGIN READ ONLY;
SET LOCAL yb_read_from_followers = true;
SET LOCAL yb_follower_read_staleness_ms = 30000;
SELECT * FROM my_test WHERE c1 = 10;
SELECT * FROM my_test WHERE c1 = 10;;
SELECT * FROM my_test WHERE c1 = 10;;
COMMIT;
				
			

Example:

				
					yugabyte=# BEGIN READ ONLY;
BEGIN

yugabyte=*# SET LOCAL yb_read_from_followers = true;
SET

yugabyte=*# SET LOCAL yb_follower_read_staleness_ms = 30000;
SET

yugabyte=*# SELECT * FROM test;
 c1
----
  1
(1 row)

yugabyte=*# SELECT * FROM test;
 c1
----
  1
(1 row)

yugabyte=*# SELECT * FROM test;
 c1
----
  1
(1 row)

yugabyte=*# COMMIT;
COMMIT
				
			

2️⃣ Check the counter again:

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
2

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
0

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {sum += $2} END {print sum}'
1

yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=3
redirected_to_leader=1

yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=3
redirected_to_leader=1

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0

yugabyte=# \! curl -s http://127.0.0.4:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_successful_reads\{/ {print "closest_replica_served=" $2} $1 ~ /^consistent_prefix_failed_reads\{/ {print "redirected_to_leader=" $2}'
closest_replica_served=0
redirected_to_leader=0
				
			

Given your leader mapping for c1 = 10 (tablet leader = 127.0.0.3:5433), here’s how to read what happened:

  • 127.0.0.1 β†’ 2 βœ… two of the consistent-prefix reads were served by a FOLLOWER (for that tablet)

  • 127.0.0.2 β†’ 0Β βœ… none were served there

  • 127.0.0.3 β†’ 1Β βœ… one of the consistent-prefix reads was served by the LEADER

Follower reads are best-effort: even with a large staleness window, YugabyteDB may serve a read from the leader if the closest follower cannot safely satisfy the read at that moment.Β  In this case, it may have been that the the follower under momentary load.

Again, we can also show the raw metric lines for the table:

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 2 1765994998621

yugabyte=# \! curl -s http://127.0.0.2:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 0 1765995004086

yugabyte=# \! curl -s http://127.0.0.3:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {print}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 1 1765995009136
				
			

For fun, you can print the full raw metric line with a human-readable timestamp:

				
					yugabyte=# \! curl -s http://127.0.0.1:9000/prometheus-metrics | awk '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ /table_name="my_test"/ {printf "%s  [human_time=%s]\n", $0, strftime("%Y-%m-%d %H:%M:%S %Z", $3/1000)}'
consistent_prefix_read_requests{table_id="000034d4000030008000000000004000",table_name="my_test",namespace_name="yugabyte",table_type="PGSQL_TABLE_TYPE",metric_type="tablet",exported_instance="localhost:9000"} 2 1765996816359  [human_time=2025-12-17 18:40:16 UTC]
				
			
πŸ§ͺ yb_cp_probe.sh: A Lightweight Probe for Observing Follower Reads

The yb_cp_probe.sh script is a read-path inspection tool, designed to help you understand where YugabyteDB served consistent prefix reads … without modifying data or executing any SQL queries itself.

At a high level, the script:

  • ● Determines which tablet owns a given primary key

  • ● Identifies the tablet leader for that key using yb_tablet_metadata

  • ● Queries each tablet server’s Prometheus metrics endpoint

  • ● Extracts consistent_prefix_read_requests for the target table

  • ● Labels each node as LEADER or FOLLOWER for that specific key

  • ● Optionally reports deltas between runs to highlight what changed

Importantly:

  • ● The script does not execute any SELECTs.
  • ● You run queries manually (for example, follower reads in a READ ONLY transaction), and then use this script to observe which nodes actually served those reads.
The yb_cp_probe.sh script:
				
					#!/usr/bin/env bash
set -euo pipefail

usage() {
  echo "Usage: $0 -t <table> -k <pk_value> -H <ysql_host> [-p <ysql_port>] [-d <db>] -n <tserver_web_list> [--delta]"
  echo
  echo "  -t  Table name (required)                         e.g. my_test"
  echo "  -k  Primary key value (required)                  e.g. 1"
  echo "  -H  YSQL host for ysqlsh (required)               e.g. 127.0.0.1"
  echo "  -p  YSQL port (default: 5433)                     e.g. 5433"
  echo "  -d  Database name (default: yugabyte)             e.g. yugabyte"
  echo "  -n  Comma-separated tserver web endpoints         e.g. 127.0.0.1:9000,127.0.0.2:9000,127.0.0.3:9000"
  echo "  --delta  Show delta since last run (state in /tmp/yb_cp_probe.<table>.<pk>.state)"
  exit 1
}

TABLE=""
PK=""
YSQL_HOST=""
YSQL_PORT="5433"
DB="yugabyte"
TSERVERS=""
SHOW_DELTA="false"

while [[ $# -gt 0 ]]; do
  case "$1" in
    -t) TABLE="$2"; shift 2;;
    -k) PK="$2"; shift 2;;
    -H) YSQL_HOST="$2"; shift 2;;
    -p) YSQL_PORT="$2"; shift 2;;
    -d) DB="$2"; shift 2;;
    -n) TSERVERS="$2"; shift 2;;
    --delta) SHOW_DELTA="true"; shift 1;;
    *) usage;;
  esac
done

[[ -z "$TABLE" || -z "$PK" || -z "$YSQL_HOST" || -z "$TSERVERS" ]] && usage

IFS=',' read -r -a TS <<< "$TSERVERS"

ysql() { ysqlsh -h "$YSQL_HOST" -p "$YSQL_PORT" -d "$DB" -Atc "$1"; }

metric_table_reads() {
  local ep="$1"
  curl -s "http://${ep}/prometheus-metrics" \
    | awk -v tbl="$TABLE" '$1 ~ /^consistent_prefix_read_requests/ && $0 ~ ("table_name=\"" tbl "\"") {sum += $2} END {printf "%.0f\n", sum+0}'
}

# Determine leader for the given key (assumes HASH partitioning and numeric PK)
LEADER="$(ysql "SELECT leader FROM yb_tablet_metadata t WHERE t.db_name=current_database() AND t.relname='${TABLE}' AND yb_hash_code(${PK}) >= t.start_hash_code AND yb_hash_code(${PK}) < t.end_hash_code LIMIT 1;")"
TABLET_ID="$(ysql "SELECT tablet_id FROM yb_tablet_metadata t WHERE t.db_name=current_database() AND t.relname='${TABLE}' AND yb_hash_code(${PK}) >= t.start_hash_code AND yb_hash_code(${PK}) < t.end_hash_code LIMIT 1;")"
HASH_CODE="$(ysql "SELECT yb_hash_code(${PK});")"

if [[ -z "$LEADER" || -z "$TABLET_ID" ]]; then
  echo "ERROR: Could not determine leader/tablet for TABLE=${TABLE} PK=${PK}. Check table name, DB, and that PK is numeric."
  exit 2
fi

LEADER_HOST="${LEADER%%:*}"

STATE_FILE="/tmp/yb_cp_probe.${TABLE}.${PK}.state"
declare -A PREV
if [[ "$SHOW_DELTA" == "true" && -f "$STATE_FILE" ]]; then
  while IFS=$'\t' read -r ep val; do
    PREV["$ep"]="$val"
  done < "$STATE_FILE"
fi

echo "================================================================================"
echo "NOTE (the missing piece): consistent_prefix_read_requests increments ONLY for"
echo "CONSISTENT PREFIX reads (the follower-read / bounded-staleness path)."
echo
echo "A normal strongly-consistent YSQL SELECT (default behavior) will NOT change this"
echo "counter, even if the read is served by the tablet LEADER."
echo
echo "Workflow:"
echo "  1) Run this script (baseline)"
echo "  2) Run a follower read (READ ONLY + yb_read_from_followers=true)"
echo "  3) Run this script again to see which node(s) served the consistent-prefix read"
echo "================================================================================"
echo

echo "== Key placement =="
echo "DB=${DB} TABLE=${TABLE} PK=${PK} hash_code=${HASH_CODE} tablet_id=${TABLET_ID} leader=${LEADER}"
echo

printf "%-18s %-10s %-12s" "tserver_web" "role" "cp_reads"
if [[ "$SHOW_DELTA" == "true" ]]; then printf " %-12s" "delta"; fi
printf "\n"
echo "---------------------------------------------------------------"

: > "$STATE_FILE.tmp"
for ep in "${TS[@]}"; do
  host="${ep%%:*}"
  role="FOLLOWER"
  [[ "$host" == "$LEADER_HOST" ]] && role="LEADER"

  cur="$(metric_table_reads "$ep")"
  printf "%-18s %-10s %-12s" "$ep" "$role" "$cur"

  if [[ "$SHOW_DELTA" == "true" ]]; then
    prev="${PREV[$ep]:-0}"
    delta=$(( cur - prev ))
    printf " %-12s" "+${delta}"
  fi
  printf "\n"

  printf "%s\t%s\n" "$ep" "$cur" >> "$STATE_FILE.tmp"
done

mv "$STATE_FILE.tmp" "$STATE_FILE" >/dev/null 2>&1 || true

echo
echo "Tip: after you run follower-read SELECTs, re-run with --delta to see increments."
				
			

Example:

				
					[root@localhost ~]# ./yb_cp_probe.sh -t my_test -k 1 -H 127.0.0.1 -n 127.0.0.1:9000,127.0.0.2:9000,127.0.0.3:9000 --delta
================================================================================
NOTE (the missing piece): consistent_prefix_read_requests increments ONLY for
CONSISTENT PREFIX reads (the follower-read / bounded-staleness path).

A normal strongly-consistent YSQL SELECT (default behavior) will NOT change this
counter, even if the read is served by the tablet LEADER.

Workflow:
  1) Run this script (baseline)
  2) Run a follower read (READ ONLY + yb_read_from_followers=true)
  3) Run this script again to see which node(s) served the consistent-prefix read
================================================================================

== Key placement ==
DB=yugabyte TABLE=my_test PK=1 hash_code=4624 tablet_id=59a1218e386d43b69f8f6e35a640dac9 leader=127.0.0.1:5433

tserver_web        role       cp_reads     delta
---------------------------------------------------------------
127.0.0.1:9000     LEADER     2            +2
127.0.0.2:9000     FOLLOWER   0            +0
127.0.0.3:9000     FOLLOWER   1            +1

Tip: after you run follower-read SELECTs, re-run with --delta to see increments.
				
			
🏁 Conclusion

To prove follower reads are being used in YugabyteDB:

  • ❌ Normal SELECT β†’ consistent_prefix_read_requests does not change

  • βœ… READ ONLY + yb_read_from_followers β†’ consistent_prefix_read_requests increments

By watching consistent_prefix_read_requests (summed across tablets), you get a clear, repeatable, demo-friendly way to prove the follower-read (consistent-prefix) path is engaged… especially valuable in multi-region YugabyteDB deployments, where follower reads deliver real latency and scalability wins.

Special thanks toΒ Piyush Jain,Β Staff Engineer @ YugabyteDB, for reviewing this tip for accuracy!

Have Fun!

Every year there are fewer Christmas lights on the house, but the Christmas spirit is still going strong. πŸŽ„