Skip to content

Fix #218 - could not open relation with OID - #295

Open
twooster wants to merge 2 commits into
eulerto:masterfrom
twooster:fix-218
Open

Fix #218 - could not open relation with OID#295
twooster wants to merge 2 commits into
eulerto:masterfrom
twooster:fix-218

Conversation

@twooster

@twooster twooster commented Jul 23, 2026

Copy link
Copy Markdown

Full disclosure: This is a Fable bug-hunt and PR for bug #218

I personally don't know enough about pg-internals to fix this bug. At the very least, if you want to fix it differently, Fable wrote some repros for the bug. We hit this bug at least once a month and it wedges our replication slots badly.


Fix "could not open relation with OID" during decoding after CREATE INDEX CONCURRENTLY

The bug

wal2json calls RelationGetIndexAttrBitmap() from its change callback — on every DELETE, every UPDATE without an old tuple, and on all changes when include-pk is set. That function opens every index on the table from rd_indexlist, including invalid, in-progress ones created by CREATE INDEX CONCURRENTLY. It is a current-snapshot, lock-taking API; no in-core output plugin calls it during decoding (pgoutput uses the decoding-safe RelationGetIdentityKeyBitmap() instead).

During logical decoding, transactions are replayed in commit order but each under its own historic snapshot, so the installed snapshot moves backward across transaction boundaries while the backend's relcache persists. If decoding a newer transaction caches an index created by a concurrent CIC into rd_indexlist, and an older, longer-running transaction's change is then decoded, RelationGetIndexAttrBitmap() tries to open that index under a snapshot to which its pg_class row is not yet visible:

ERROR: could not open relation with OID

Because this is a deterministic function of WAL order, every restart fails at the same LSN — the slot is permanently wedged.

How to trip it

No special options needed; the index doesn't have to be related to the replica identity. The minimal interleaving (encoded in the new isolation test):

  1. Transaction A updates the table and stays open.
  2. CREATE INDEX CONCURRENTLY commits its first phase, then blocks waiting on A.
  3. Any insert-only transaction commits (its decode caches the new index in the relcache).
  4. A commits. Decoding A's update now fails.

In the wild this shows up on busy databases running CREATE INDEX CONCURRENTLY (or drop/create, REINDEX CONCURRENTLY) while long-lived write transactions are in flight.

The fix

Replace RelationGetIndexAttrBitmap() with a helper, get_index_column_bitmap(), that builds the column bitmap from the single index actually needed — rd_replidindex for identity output, rd_pkindex for include-pk — opened via lock-free RelationIdGetRelation(), mirroring pgoutput's RelationGetIdentityKeyBitmap(). If the index can't be opened under the current historic snapshot, it returns NULL and output degrades gracefully (keys omitted) instead of erroring and wedging the slot. Semantics are unchanged in all normal cases: the PK/identity bitmaps were always derived from exactly these single indexes; PG < 10 include-pk behavior is untouched.

Testing

  • New isolation spec specs/concurrent_index.spec (wired up via ISOLATION in the Makefile, PG ≥ 12; runs under make installcheck) reproduces the interleaving deterministically: fails with the error above before this change, passes with it.
  • Existing regression suite: 30/30 pass.
  • Perf: decoding no longer takes an AccessShareLock on every index per change; the removed per-relation bitmap cache costs well under a microsecond per change in the worst case and is a wash-to-win once relcache invalidations (autovacuum, DDL) occur.

@twooster twooster changed the title Fix 218 Fix #218 - could not open relation with OID Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant