A BRIN index does not maintain one index tuple for every heap row. It stores summary data for groups of adjacent heap pages. That compact structure also creates a maintenance boundary: a newly allocated block range can exist without a summary tuple, leaving the index without summary data for that range until a summarization event occurs.

This state matters most on append-heavy tables. Existing summarized ranges continue to track inserted values, but a new range is not automatically given its initial summary under the default settings.

BRIN summaries represent physical block ranges

BRIN divides a table into block ranges whose size is controlled by pages_per_range. The default is 128 pages. An operator class determines the summary stored for each range. The common minmax classes record minimum and maximum values, while other classes can store different representations.

During a BRIN scan, PostgreSQL compares the query condition with those summaries. Ranges whose summaries cannot match can be skipped. Ranges that remain candidates contribute heap pages to a bitmap scan, and tuples from those pages are checked against the original condition because BRIN scans are lossy.

The range size therefore affects both index size and summary precision. Smaller ranges require more summary entries but can isolate matching values more closely. Larger ranges reduce the number of entries while grouping more heap pages under each summary.

New ranges need an initial summary

Index creation scans existing heap pages and creates summary tuples for their ranges, including the incomplete range at the end of the relation.

Later inserts behave differently depending on where their heap pages fall. If a tuple lands in a range that already has a summary, PostgreSQL updates that summary as needed. Once allocation reaches a range that has no summary tuple, that range stays unsummarized until PostgreSQL runs summarization.

Regular VACUUM, including autovacuum when it processes the table, summarizes existing unsummarized ranges. PostgreSQL also exposes brin_summarize_new_values for all unsummarized ranges of an index and brin_summarize_range for the range containing a specified page.

SELECT brin_summarize_new_values('events_created_at_brin');

The function returns the number of newly summarized page ranges. It operates on a BRIN index, not on the table name.

Autosummarization changes the timing

A BRIN index can enable the autosummarize storage parameter:

CREATE INDEX events_created_at_brin
ON events USING brin (created_at)
WITH (autosummarize = on);

The default is off. With autosummarization enabled, crossing into the next block range can queue a request for autovacuum to summarize the previous filled range. The work is not performed synchronously by the inserting transaction.

The request is also not an absolute guarantee of immediate processing. PostgreSQL documents that the request queue can fill. If a request cannot be recorded, the range remains unsummarized until a later regular vacuum or an explicit summarization call handles it.

Summary freshness is separate from row visibility

BRIN summarization describes values associated with physical page ranges. It does not replace MVCC visibility checks, and a matching summary does not prove that every tuple on the referenced pages satisfies the predicate.

That distinction is visible in the scan shape: BRIN identifies candidate ranges, while the executor rechecks tuples from their pages. The index is useful when its summaries exclude substantial portions of the table, especially when indexed values correlate with physical row placement.

For steadily growing relations, BRIN maintenance is therefore partly about keeping new block ranges summarized. The index can remain structurally valid while recent ranges await summaries; the operational question is how quickly those summaries appear relative to the table’s growth rate and query pattern.