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://: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?β
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);
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:
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
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:
π§ͺ 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.
-k -H [-p ] [-d ] -n [--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.
..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_requestsincrements
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. π