pgconfigurator
pgconfigurator

Tid Range Scan

Appears in EXPLAIN asTid Range Scan

Scans a contiguous range of physical row locations.

What it is

A Tid Range Scan reads a contiguous span of ctids — for example WHERE ctid >= '(100,0)' AND ctid < '(200,0)' — which maps to a range of heap blocks.

When the planner picks it

When the query restricts ctid to a range. It's mostly used for chunked, parallelizable full-table processing.

Is it good or bad?

Useful for splitting a big table into block ranges for batch jobs. Like Tid Scan, it relies on physical addresses that aren't stable across row movement.

In depth

Scanning a range of physical addresses

A Tid Range Scan reads rows whose CTID falls within a contiguous range of (block, offset) pairs:

SELECT *
FROM big
WHERE ctid >= '(0, 0)' AND ctid < '(10000, 0)';

That maps to "give me the rows from blocks 0 up to (but not including) 10000." Compared to a Tid Scan's single-point fetch, a Tid Range Scan is a mini sequential read of a block range — efficient and forward-only.

Why it's useful

The use case is splitting a huge table into block ranges for parallelizable batch processing. Each worker takes a CTID range, scans only its slice, and never overlaps with another worker's pages. With the table's size known from pg_class.relpages, you can divide it evenly:

-- worker N of K:
SELECT *
FROM big
WHERE ctid >= ctid_lo
  AND ctid <  ctid_hi
  AND …  -- your business filter

This pairs well with external job queues and Map-Reduce-style jobs over large tables where you can't (or don't want to) use a partition column.

Same instability caveat

CTID ranges still aren't durable: VACUUM FULL, CLUSTER, and pg_repack rewrite the table and shift every row. Use Tid Range Scan inside a single transactional pass — produce the range, do the work, finish — and never persist the ranges as keys.

See also