a curated list of database news from authoritative sources

August 20, 2026

Migrate multilingual full-text search from SQL Server to PostgreSQL

Migrating full-text search from SQL Server to PostgreSQL can silently change results because the engines handle text, linguistics, and accents differently. This post shows how to reproduce SQL Server full-text search on Amazon Aurora PostgreSQL and Amazon RDS for PostgreSQL, covering collation, tokenization, accent-insensitive search, and synonyms.

August 19, 2026

Security Advisory: Privileged ClickHouse access through the Grafana data source in PMM

Date of release: 19 August 2026 Severity: High Affected product: PMM Impacted versions: 3.9.0 and below Summary Percona has recently been made aware of a security vulnerability affecting PMM. We take the security of our products and the protection of our customers’ data with the utmost seriousness. This advisory describes the vulnerability, the immediate steps … Continued

The post Security Advisory: Privileged ClickHouse access through the Grafana data source in PMM appeared first on Percona.

Stop guessing at gcache: inspect Galera/PXC write sets with gcache-inspector

The common practice is to size the Galera Cache based on write volume measured during peak load, but often it is more of a guesswork. The writeset cache capacity planning is crucial to shorten the maintenance time and avoid long state transfers while the cluster runs with reduced compute power. Now, if you could understand … Continued

The post Stop guessing at gcache: inspect Galera/PXC write sets with gcache-inspector appeared first on Percona.

August 18, 2026

Scale pgvector with binary quantization on Amazon Aurora PostgreSQL

Learn how to use binary quantization with reranking (HNSW+BQ) in pgvector to scale vector search to hundreds of millions or billions of vectors on Amazon Aurora PostgreSQL, with practical guidance on index sizing, recall validation, and the scenarios where the approach works best.

Connect client traces to your logs

supabase-js now propagates W3C Trace Context to Supabase, so a client trace and the matching Supabase log share one trace_id.

Poisoned Postgres connection pools

An engineer's worst nightmare is waking up to a seemingly read-only database with no clear issue in sight. Let's dig into why this happens, and how AI can help solve it.

August 17, 2026

Amazon Aurora DSQL observability concepts and usage with Amazon CloudWatch

Amazon Aurora DSQL offers time-based observability through Amazon CloudWatch Database Insights. Learn how the DSQL observability model, DASH, Database Insights, PromQL, and the system diagnostics AI skill help you find performance bottlenecks and connect session time directly to cost.

Unlocking real-time analytics: Streaming Aurora DSQL changes into Apache Iceberg

Stream Amazon Aurora DSQL change data capture (CDC) events into Apache Iceberg tables on Amazon S3 with Amazon Data Firehose, then query them using Amazon Athena. This post walks through a two-table design that keeps a full audit trail and a current-state view, plus deployment and a dashboard for exploring the results.

Skip Scan vs. Loose Index Scan

Both optimizations avoid (skip) reading (scan) irrelevant leaf pages by repositioning (seek) via a fresh index descent instead of scanning sequentially. This surface similarity explains why people often used "skip scan" and "loose index scan" interchangeably before the distinction was clearly defined. For example, I wrote YugabyteDB Skip Scan aka Loose Index Scan on compound index in 2022 because the two concepts were not clearly distinguished in the PostgreSQL wiki at that time. PostgreSQL implemented neither of those features yet, and YugabyteDB implemented both with the same hybrid scan mechanism. However, today the distinction matters because PostgreSQL 18 has added Skip Scan but not Loose Index Scan.

What they have in common: repositioning an index scan between multiple sub-ranges

A plain range scan seeks once to the start of a range and then reads forward or backward until past the end. Both Skip Scan and Loose Index Scan can instead reposition to a new range, or "grouping" (a run of index entries sharing the same leading-column value), via a new index descent, rather than reading through a single range sequentially. This avoids reading irrelevant leaf pages.

What's different: what happens within each sub-range

Skip Scan avoids reading leaf pages entirely outside a relevant sub-range. PostgreSQL does not skip index entries that might match: within a grouping, it still examines the relevant entries and returns every match, exactly as a normal scan would within that sub-range. Separately, PostgreSQL can sometimes avoid rechecking a scan key on a page whose high key proves that all entries satisfy it (as a CPU optimization), but it does not avoid reading the page.

A Loose Index Scan, by contrast, deliberately reads only the first entry of each range and then jumps to the next range — it never reads the remaining matching rows because it isn't trying to satisfy a later-column predicate, only to enumerate the distinct values of a prefix.

In short:

Skip Scan Loose Index Scan
Skips Leaf pages belonging to sub-ranges that can't satisfy the later-column predicate Leaf pages/entries belonging to any range after its first entry
Within a matched range Reads/checks every entry against the later-column Reads one entry, then repositions to the next range
Purpose of the leading column Vehicle for enumerating candidate ranges so a later-column predicate can be applied efficiently The thing being enumerated (e.g., DISTINCT) — no later-column predicate needed

They benefit from similar data distributions

Both optimizations benefit when the leading index column has relatively few distinct values and many rows per value. Repeated descents are then cheaper than scanning large groups of entries sequentially.

Skip Scan may be rejected when the number of descents exceeds the cost of scanning the index normally. A Loose Index Scan has the same trade-off: if almost every row has a different prefix value, one descent per value is not worthwhile. Its advantage appears when each group contains enough duplicate entries to make skipping the rest of the group profitable.

The distinction is therefore not primarily the index definition or the data distribution. It is the query's objective: Skip Scan must return all matching rows, whereas Loose Index Scan deliberately returns only one representative per group.

Example

I created a table with an index on two columns:

CREATE EXTENSION IF NOT EXISTS pageinspect;

DROP TABLE IF EXISTS demo CASCADE;

CREATE TABLE demo (
    a       integer NOT NULL,
    b       text    NOT NULL,
    payload text    NOT NULL
);

INSERT INTO demo (a, b, payload)
SELECT
    a,
    repeat(md5(g::text), 28),       -- approximately 900 bytes
    repeat('payload-' || a || '-' || g, 20)
FROM generate_series(1, 8) AS a
CROSS JOIN generate_series(1, 80) AS g;

CREATE INDEX demo_ab_idx ON demo (a, b);

VACUUM ANALYZE demo;

Here is a query that shows the index entries in their logical order:

SELECT
    s.blkno AS index_block,
    i.itemoffset,
    d.a,
    left(d.b, 100) || '...' AS b,
    i.itemlen,
    i.htid,
    i.data as data 
FROM bt_multi_page_stats('demo_ab_idx', 1, -1) AS s
CROSS JOIN LATERAL bt_page_items('demo_ab_idx', s.blkno) AS i
JOIN demo AS d
  ON d.ctid = i.htid
WHERE s.type = 'l'
ORDER BY
    d.a,
    d.b;

My example has 640 rows:

A Skip Scan will scan all values of "a" but may skip the portions of each "a" grouping outside the "b" range, for example WHERE b LIKE '28%':

 index_block | itemoffset | a |                                                    b                                                    | itemlen |  htid   |                                                                                              data
-------------+------------+---+---------------------------------------------------------------------------------------------------------+---------+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
           1 |         14 | 1 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (8,2)   | 01 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           1 |         15 | 1 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (12,4)  | 01 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           1 |         94 | 2 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (21,3)  | 02 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           1 |         95 | 2 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (25,5)  | 02 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           2 |         78 | 3 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (34,3)  | 03 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           2 |         79 | 3 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (38,5)  | 03 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           4 |         62 | 4 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (47,3)  | 04 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           4 |         63 | 4 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (51,5)  | 04 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           5 |         46 | 5 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (60,3)  | 05 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           5 |         47 | 5 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (64,5)  | 05 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           6 |         30 | 6 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (73,3)  | 06 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           6 |         31 | 6 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (77,5)  | 06 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           7 |         14 | 7 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (86,3)  | 07 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           7 |         15 | 7 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (90,5)  | 07 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00
           7 |         94 | 8 | 2838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838023a778dfaecdc212708f721b7882838... |      72 | (99,3)  | 08 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 33 38 30 32 33 61 37 37 38 64 66 61 65 63 64 63 32 31 32 37 30 38 66 37 32 31 62 37 38 38 20 00 ff ff ff 4b 50 31 62 37 38 38 00 00 00 00 00 00
           7 |         95 | 8 | 28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd2c7955ce926456240b2ff0100bde28dd... |      72 | (103,5) | 08 00 00 00 da 00 00 00 80 03 00 40 ff 11 32 38 64 64 32 63 37 39 35 35 63 65 39 32 36 34 35 36 32 34 30 62 32 66 66 30 31 30 30 62 64 65 20 00 ff ff ff 4b 50 30 30 62 64 65 00 00 00 00 00 00

A Loose Index Scan will read only the first row for each value of "a", for example, in SELECT DISTINCT ON (a):

 index_block | itemoffset | a |                                                    b                                                    | itemlen |  htid  |                                                                                              data
-------------+------------+---+---------------------------------------------------------------------------------------------------------+---------+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
           1 |          2 | 1 | 02e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e7... |      72 | (4,2)  | 01 00 00 00 da 00 00 00 80 03 00 40 ff 11 30 32 65 37 34 66 31 30 65 30 33 32 37 61 64 38 36 38 64 31 33 38 66 32 62 34 66 64 64 36 66 30 20 00 ff ff ff 4b 50 64 64 36 66 30 00 00 00 00 00 00
           1 |         82 | 2 | 02e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e7... |      72 | (17,3) | 02 00 00 00 da 00 00 00 80 03 00 40 ff 11 30 32 65 37 34 66 31 30 65 30 33 32 37 61 64 38 36 38 64 31 33 38 66 32 62 34 66 64 64 36 66 30 20 00 ff ff ff 4b 50 64 64 36 66 30 00 00 00 00 00 00
           2 |         66 | 3 | 02e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e7... |      72 | (30,3) | 03 00 00 00 da 00 00 00 80 03 00 40 ff 11 30 32 65 37 34 66 31 30 65 30 33 32 37 61 64 38 36 38 64 31 33 38 66 32 62 34 66 64 64 36 66 30 20 00 ff ff ff 4b 50 64 64 36 66 30 00 00 00 00 00 00
           4 |         50 | 4 | 02e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e7... |      72 | (43,3) | 04 00 00 00 da 00 00 00 80 03 00 40 ff 11 30 32 65 37 34 66 31 30 65 30 33 32 37 61 64 38 36 38 64 31 33 38 66 32 62 34 66 64 64 36 66 30 20 00 ff ff ff 4b 50 64 64 36 66 30 00 00 00 00 00 00
           5 |         34 | 5 | 02e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e74f10e0327ad868d138f2b4fdd6f002e7... |      72 | (56,3) | 05 00 00 
                                    
                                    
                                    
                                    
                                

Percona University Comes to Uruguay

Percona University is coming to Montevideo. On September 23rd, 2026, we’re getting together for a full day of technical talks on open source software, and you are invited! If you work or study with open source software in Uruguay, this one is for you. It’s a whole day of learning, with the people who build … Continued

The post Percona University Comes to Uruguay appeared first on Percona.

What is a data topology?

A data topology describes the sharding scheme a Neki router uses to map logical PostgreSQL tables to physical shards and route queries.

August 16, 2026

OrioleDB Multi-Version Concurrency Control

MVCC not only tracks row history but also records the search-key space. To better understand PostgreSQL MVCC, it's useful to look at some alternatives. One is zheap, which introduced out-of-place undo logging to rebuild historical row versions by storing old tuples in an undo log instead of the heap. This was done entirely at the table access method level, without modifying the index access method, but it was eventually abandoned. A more recent development is OrioleDB, which extends this approach to cover the search-key space with an MVCC index access method. Due to PostgreSQL's extensive ecosystem that supports multiple index types such as hash, GIN, BRIN, SPGiST, and others through extensions, OrioleDB uses a "bridge index" to ensure compatibility.

OrioleDB: Why MVCC Reaches Into the B-tree

OrioleDB is a PostgreSQL table access method that replaces the ordinary heap with index-organized, version-aware B-trees. Complete rows are stored in the primary B-tree. Native secondary indexes store the secondary key plus the primary key, allowing them to point back to the complete row.

That layout is easy to compare with InnoDB's clustered index, but the key difference is not clustering. It is how OrioleDB preserves old snapshots as rows, keys, and B-tree pages change. OrioleDB keeps two kinds of history:

  1. row-level undo reconstructs an older tuple value
  2. page-level undo reconstructs older B-tree leaf contents and key ranges

The second kind is what makes OrioleDB architecturally interesting. MVCC must preserve not only what a row used to contain but also where an old query would have found it.

The indexed-key problem

Suppose an old snapshot is open while another transaction modifies an order:

UPDATE orders
SET status = 'closed'
WHERE order_id = 42;

Before the update, a secondary index contains an entry conceptually like ('open', 42). Afterward, the current tree contains ('closed', 42).

Row undo can reconstruct the old value status = 'open' after row 42 has been found. It cannot scan the old open range to find row 42. The current secondary B-tree routes the row under closed.

An MVCC engine must therefore solve two distinct problems:

Problem Question
Payload visibility Which version of row 42 may this snapshot read?
Predicate reachability Should a scan of status = 'open' enumerate row 42?

PostgreSQL solves reachability by retaining old heap tuples and old index entries until VACUUM can safely remove them. InnoDB retains old secondary records as delete-marked entries until purge. OrioleDB's native indexes instead use page-level undo to reconstruct the historical searchable key space.

How OrioleDB stores the current database

The primary B-tree stores complete rows. Its key is the table's primary key, or an internal key if no primary key is defined. A native secondary leaf stores the secondary key and primary key rather than a PostgreSQL heap TID.

A secondary lookup therefore follows this path from the secondary key:

  1. search the secondary B-tree to find the primary key of the row
  2. search the primary B-tree to find the row with all its values

Both trees participate in snapshot visibility. A secondary index is not merely a current-key candidate generator whose result can always be repaired at the primary row. It must first enumerate the keys that were part of the requested snapshot.

This tight integration has both benefits and costs. OrioleDB can make its native indexes understand its version model directly. However, PostgreSQL index access methods such as GiST, GIN, SP-GiST, BRIN, hash, and any new index type brought by a PostgreSQL extension do not understand OrioleDB primary keys or undo.

OrioleDB supports those methods through bridge indexes. An ordinary index stores a synthetic heap-shaped bridge_ctid. An internal OrioleDB bridge tree maps that identifier to the primary key. This preserves PostgreSQL's index-AM extensibility but adds an extra lookup and reintroduces stale index entries and a VACUUM cleanup cycle into the design. Native and bridged indexes therefore have different maintenance contracts.

A secondary lookup from such indexes now follows this path:

  1. search the secondary index to find the bridge_ctid
  2. search the bridge index to find the primary key of the row
  3. search the primary B-tree to find the row

Why OrioleDB needs two undo histories

Row-level undo maintains a tuple's history. It records enough information to select or reconstruct the row version visible at a snapshot. The same undo machinery also supports transaction rollback.

Page-level undo records changes to B-tree leaves. It covers ordinary changes, such as insertions and deletions, as well as physical maintenance, such as compaction, splits, and merges. These operations can move keys or change page boundaries, even when their purpose is only to keep the current tree balanced.

That distinction matters during a range scan. A page split may move an old key onto a page that did not exist at the snapshot. A merge may erase the boundary that an old scan needs. OrioleDB can reconstruct a historical page image and, where necessary, merge historical and live items while preserving order and avoiding omissions or duplicates.

The conceptual read path for the example is:

  1. reconstruct the old secondary range containing ('open', 42)
  2. obtain primary key 42
  3. find row 42 in the primary tree at the same snapshot
  4. apply row undo if the current row is too new
  5. return the old row

Page undo answers why the scan finds the row. Row undo answers what the row contained.

B-tree maintenance becomes MVCC maintenance

In PostgreSQL, a B-tree split is primarily a physical index operation. Historical membership persists because old index tuples still point to old heap tuples. The index does not need to preserve the old page topology for snapshot reads.

In OrioleDB, a split also changes the partitioning of the searchable key space. Compaction, merge, and page reuse carry the same additional obligation. A page cannot be treated as irrelevant merely because it is obsolete in the current tree. Retained snapshots may still need its earlier contents or boundaries.

This moves work rather than eliminating it:

  • native trees avoid PostgreSQL's heap-to-index vacuum cycle
  • row and page undo consume space while old snapshots remain relevant
  • scans may reconstruct historical images
  • split and merge code participates in visibility and retention
  • recycling history too early produces a snapshot too old boundary
  • checkpoints and recovery must preserve consistency across primary and secondary tree updates

A single logical update can modify the primary tree and several secondaries. Current OrioleDB tracks the short primary-applied, secondary-pending interval to prevent a checkpoint from permanently merging mismatched tree states. Recovery starts from a copy-on-write checkpoint, replays row-level WAL, repairs derived secondary work as needed, and rolls back incomplete transactions.

Comparison with other engines

The engines below often use similar words while assigning history to different structures:

Engine Current organization How old indexed membership survives Principal deferred cost
PostgreSQL Heap tuples plus separate indexes Old index entries continue to reference old version TIDs Heap and index VACUUM, freezing, and bloat control
OrioleDB native Complete rows in primary B-tree. Secondaries carry primary keys Page undo reconstructs historical leaf items and ranges Undo retention, reconstruction, and tree-aware reclamation
OrioleDB bridged Native primary plus synthetic identities in ordinary index AMs External entries plus a versioned bridge mapping Extra lookups and bridge-aware VACUUM
Oracle Database Heap blocks, indexes, undo, and consistent-read block images Transactional index changes and undo/CR preserve logical visibility Undo retention and optional physical coalesce/rebuild
InnoDB Clustered primary plus secondary B-trees Old secondary records remain delete-marked until safe purge Undo history and purge lag
WiredTiger Per-key update chains and reconciled B-tree images Older key values remain in update chains or the history-store B-tree Reconciliation, eviction, cache pressure, and history cleanup

Oracle is similar in that it uses undo to construct a consistent view, but its block formats, index algorithms, and recovery machinery differ. It should not be described as rewinding an old root-to-leaf tree for every query.

WiredTiger is especially useful for comparison because it also keeps version history close to B-tree keys. Its in-memory update chains and history store optimize storage-engine keys. OrioleDB additionally ensures PostgreSQL table and secondary-index consistency across several trees.

Two research alternatives

Recent research shows that storing versions "in the B-tree" can mean different things.

MV-PBT writes changes to a current memory-resident partition, persists full partitions sequentially, and later merges immutable partitions. Version information in index records enables index-side visibility filtering. It trades OrioleDB-style page reconstruction for cross-partition search and a merge policy closer to LSM storage economics.

The cMVBT preprint represents historical trees explicitly as a partially persistent DAG. Snapshot scans traverse immutable committed nodes without latches, while writers perform proactive version and key splits and merges with optimistic latching. OrioleDB instead maintains a current topology and reconstructs historical leaves from page undo.

cMVBT is a valuable design comparison, not yet a production-engine comparison. Its reported implementation is in-memory, uses fixed-size keys and values, and supports single-operation write transactions. External storage, crash recovery, and arbitrary multi-operation ACID transactions remain outside the scope of that evaluation.

What OrioleDB is betting on

OrioleDB's claim is not just that undo operations are less costly than keeping old heap tuples. Instead, it argues that row undo, page undo, version-aware native B-trees, copy-on-write checkpoints, and row-level WAL can work together more efficiently than PostgreSQL's heap, index, and vacuum system.

The most indicative tests are not just current-key lookups. They include historical snapshots covering indexed-key modifications, long-range scans during split and merge processes, ongoing deletes and space reuse, checkpoint crashes within primary/secondary update windows, and bridged-index workloads with delayed vacuum.

OrioleDB is still a work-in-progress open-source project in beta, requiring a patched version of PostgreSQL. Its source code reflects an ambitious and well-structured architecture rather than a stable core PostgreSQL implementation. The effectiveness of the design should be evaluated based on how well it manages historical key ranges, maintenance, cleanup, and recovery processes under real-world mixed workloads.

The core concept is simple: an old row value is useful only if the old predicate can still locate it. OrioleDB incorporates this requirement directly into the B-tree and uses an additional bridge index to remain compatible with other PostgreSQL index types.

August 14, 2026

Faster scaling for Aurora serverless to support agentic AI and other spiky workloads

Aurora serverless now automatically adds 12 Aurora Capacity Units to its current capacity within a second, and continues scaling to 256 ACUs as your workload grows. In this post, we show how an Aurora serverless cluster responds to a sudden workload spike, and compare its throughput against a provisioned db.r8g.xlarge instance using benchmark data.

August 13, 2026

Replicating from InnoDB into a DuckDB storage engine

Our first post showed MySQL 9.7 with one change: mark a table ENGINE=DuckDB and its analytical queries run in DuckDB instead of InnoDB. The question we kept getting after that was about replication. Can you keep a normal InnoDB primary for the writes, and run a replica where the big tables are ENGINE=DuckDB? Then the … Continued

The post Replicating from InnoDB into a DuckDB storage engine appeared first on Percona.