Non-blocking writer barrier for publication table additions
@AbarnaaSree がすでに取り組んでいます。
2026年8月28日 から。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- elixir, postgresql
調査の方向性
Configurator で do_publication_update(:add, …) と notify_filters_result(…, {:ok, :configured}) の周辺から開始し、Snapshotter の add_shape 呼び出しとその 5 s のタイムアウトを追跡します。これらの経路全体で、リレーションごとのバリアと deadline をどのように連携させるかを決めます。pg_locks を使用する場合のパーティション処理も含めてください。完了とは、writer がキューに入れられず、固定されたトランザクション集合を待機し、ADD TABLE のコミット後もタイムアウトによる失敗を再試行可能な状態に保つことです。
索引モデルが issue の本文から書いたものです。
説明
Non-blocking writer barrier for publication table additions
Follow-up to #4773 / the fix in Electric.Postgres.Configuration.add_table_to_publication/4 (wait_for_in_flight_writers/2).
Background
Logical decoding evaluates publication membership per change, against the catalog state at that point in the WAL. A transaction that wrote to a table before ALTER PUBLICATION … ADD TABLE committed never has those writes emitted by the replication stream, even if it commits after. They can only reach a shape through the initial snapshot, which only includes them if the writer has committed by the time the snapshot is taken.
The fix for #4773 closes that gap by taking LOCK TABLE … IN SHARE MODE inside the same transaction as the ADD TABLE. SHARE conflicts with the ROW EXCLUSIVE lock every writer holds until its transaction ends, so the addition can't commit — and waiting shapes can't start snapshotting — until every in-flight writer has finished. This mirrors what the ALTER TABLE … REPLICA IDENTITY FULL on the first-time-add path has always done implicitly through its ACCESS EXCLUSIVE lock. The wait is bounded by the configuration action timeout (5 s by default); on expiry the addition is rolled back and shape creation fails with a retryable error.
The side effect
SHARE is the weakest lock mode that conflicts with writers, but it also conflicts with SHARE UPDATE EXCLUSIVE, i.e. with VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY, REINDEX CONCURRENTLY. While Electric's SHARE request is queued behind one of those, every new writer on the table queues behind Electric (Postgres makes new lock requests wait behind conflicting waiters to avoid starvation). Autovacuum cancels itself when it blocks a lock request (except anti-wraparound), but a manual VACUUM or a long CREATE INDEX CONCURRENTLY won't, so the outcome is:
- shape creation on that table fails after the action timeout with
table_lock_timeout; - each attempt stalls all writers on the table for up to that timeout;
- clients retry, so a long-running maintenance operation turns into periodic write stalls for its duration.
The same holds for a genuinely long-running writer (a slow bulk load): shape creation is correctly blocked until it commits, but other writers on the table are stalled for the timeout on every attempt.
None of this is new to Electric — the first-time-add path has had it, with a stronger lock, since the replica identity change was introduced — but the #4773 fix makes it reachable on every re-add (after TRUNCATE invalidation, shape expiry, schema-change invalidation, etc.), not only the first time a table is synced.
Proposal
Replace the lock with a wait that blocks nobody. Immediately after ADD TABLE commits, capture the fixed set of transactions that could have written to the table before the commit, then poll until they have all ended before reporting the relation as configured:
- exact:
pg_locksrows withlocktype = 'relation',relation = <oid>(plus partition oids for partitioned tables), modeRowExclusiveLockor stronger, keyed byvirtualtransaction; or - simpler and slightly over-approximate: the
xip_listofpg_current_snapshot()— only transactions that have written something have an xid, so idle read-only sessions are excluded, but writers on unrelated tables are not.
The set is fixed at capture time, so the wait can't starve under continuous write load, doesn't queue anyone, and needs no privilege beyond reading pg_locks.
What it needs
- The natural place is the Configurator, between
do_publication_update(:add, …)returning andnotify_filters_result(…, {:ok, :configured}). The Configurator processes relation actions sequentially in one process, so a polling wait there would stall publication updates for every other table; it needs to become a per-relation task with its own deadline. - That deadline has to be coordinated with the
add_shapecall timeout in the Snapshotter (currently the 5 sGenServer.calldefault →SnapshotError.table_lock_timeout). - Partition enumeration for partitioned tables if the
pg_locksvariant is chosen. - The
ADD TABLEtransaction would then commit before the barrier, so the barrier failing (deadline) must leave the table published and the waiters failed-but-retryable, rather than roll the addition back as the current fix does.
When to do it
Not urgent. Pick it up if table_lock_timeout snapshot errors show up in telemetry at any meaningful rate, or if a user reports write stalls on a table during maintenance operations that correlate with shape creation.
Related
Separately from Electric, the same "write while unpublished, commit after ADD TABLE" transaction triggers a walsender relation-cache bug in PostgreSQL that drops later changes to the table until the next invalidation. It's fixed upstream (PG 18 commit 4909b38af0, back-patched to supported branches in the August 2025 minor releases — verify against the release notes). The Electric-side barrier doesn't cover it; the docs should recommend running a PG minor that includes the fix.
- 主要言語
- TypeScript
- スター
- 10.4k
- フォーク
- 399
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
electric-sql/electric のほかの issue
-
camelToSnake and snakeCamelMapper() drop the underscore when a digit precedes a camelCase boundaryオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
electric-sql/electric#4855 ·
-
ShapeStream does not notify subscribers when it gives up on a non-Error value対応中かも @Tyagiquamar が 19 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
electric-sql/electric#4830 ·
-
@electric-sql/client throws SecurityError on import when the browser blocks site storage (typeof localStorage outside try)対応中かも @mohanedmoh が 37 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
electric-sql/electric#4826 · コメント 1 件 ·
-
agents-chat-starter: spawn PUT goes to the durable-streams proxy, so no entity is created and agents never wake対応中かも @adityavkk が 133 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
electric-sql/electric#4687 ·
-
Listen IP addressオープンfeature request triage
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
electric-sql/electric#4150 · コメント 1 件 ·
electric-sql/electric の issue をすべて見る
似ている issue
-
Bug: Broken RTL / BiDi text rendering when mixing Persian (Farsi) with English or numbers対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug
難易度 2/5 半日 初心者へのやさしさ 68/100
amosblomqvist/learnharness#758 ·
メンテナーはふだん 2 日以内に返信
-
Change Workspacesオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
bitfocus/companion-module-renewedvision-propresenter-api#134 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
rcpch/digital-growth-charts-react-component-library#304 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
dsh-market/dsh-market#1210 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
trustjonathan/STUDY-HUB#13 ·