Base and aggregate scans take no SIREAD locks, so SERIALIZABLE misses phantom write skew
What happens?
Under SERIALIZABLE, the base scan and the aggregate scan take no SIREAD locks. So when two transactions each count the matching rows and then each insert a new matching row, both commit. PostgreSQL's own plans for the same read abort one of them. A seq scan locks the heap, and an index scan on the bm25 index locks the index (index_beginscan does it because the AM has no ampredlocks).
pg_search has no PredicateLock* calls. Heap fetches lock the tuples they read, but a new row has nothing to lock. After VACUUM, a columnar scan on all-visible pages takes no SIREAD lock at all, while PostgreSQL's index-only scan on the same page takes a page lock.
The Guarantees page says writes respect Postgres' isolation levels.
To Reproduce
- Set up the table:
CREATE EXTENSION IF NOT EXISTS pg_search;
CREATE TABLE ssi_doctors (id int PRIMARY KEY, name text, status text);
INSERT INTO ssi_doctors
SELECT g, 'doc' || g, CASE WHEN g <= 2 THEN 'oncall' ELSE 'offcall' END FROM generate_series(1, 10) g;
CREATE INDEX ssi_doctors_idx ON ssi_doctors USING bm25 (id, name, status)
WITH (text_fields = '{"status": {"tokenizer": {"type": "keyword"}, "fast": true}, "name": {"fast": true}}');- Run this in two sessions, in this order:
-- session 1
BEGIN ISOLATION LEVEL SERIALIZABLE;
SET paradedb.enable_aggregate_custom_scan = on;
SELECT count(*) FROM ssi_doctors WHERE status @@@ 'oncall'; -- 2
-- session 2
BEGIN ISOLATION LEVEL SERIALIZABLE;
SET paradedb.enable_aggregate_custom_scan = on;
SELECT count(*) FROM ssi_doctors WHERE status @@@ 'oncall'; -- 2
-- session 1
INSERT INTO ssi_doctors VALUES (101, 'new1', 'oncall');
-- session 2
INSERT INTO ssi_doctors VALUES (102, 'new2', 'oncall');
-- session 1
COMMIT;
-- session 2
COMMIT;Session 2's COMMIT succeeds, and 4 rows are 'oncall' now. The base scan does the same, with SET paradedb.enable_aggregate_custom_scan = off and SELECT id, name FROM ssi_doctors WHERE status @@@ 'oncall'.
With SET paradedb.enable_custom_scan = off in both sessions, the plan is Index Scan using ssi_doctors_idx, and session 2's COMMIT fails:
ERROR: could not serialize access due to read/write dependencies among transactionsSeen on a PG 15 debug build of main plus #6378, #6380, #6381, #6382 and #6383, which don't touch locking.
Source: paradedb/paradedb