On Sat, Nov 30, 2013 at 01:06:09AM +0000, Alvaro Herrera wrote:
> Fix a couple of bugs in MultiXactId freezing
> 
> Both heap_freeze_tuple() and heap_tuple_needs_freeze() neglected to look
> into a multixact to check the members against cutoff_xid.

> !                     /*
> !                      * This is a multixact which is not marked LOCK_ONLY, 
> but which
> !                      * is newer than the cutoff_multi.  If the update_xid 
> is below the
> !                      * cutoff_xid point, then we can just freeze the Xmax 
> in the
> !                      * tuple, removing it altogether.  This seems simple, 
> but there
> !                      * are several underlying assumptions:
> !                      *
> !                      * 1. A tuple marked by an multixact containing a very 
> old
> !                      * committed update Xid would have been pruned away by 
> vacuum; we
> !                      * wouldn't be freezing this tuple at all.
> !                      *
> !                      * 2. There cannot possibly be any live locking members 
> remaining
> !                      * in the multixact.  This is because if they were 
> alive, the
> !                      * update's Xid would had been considered, via the 
> lockers'
> !                      * snapshot's Xmin, as part the cutoff_xid.

READ COMMITTED transactions can reset MyPgXact->xmin between commands,
defeating that assumption; see SnapshotResetXmin().  I have attached an
isolationtester spec demonstrating the problem.  The test spec additionally
covers a (probably-related) assertion failure, new in 9.3.2.

> !                      *
> !                      * 3. We don't create new MultiXacts via 
> MultiXactIdExpand() that
> !                      * include a very old aborted update Xid: in that 
> function we only
> !                      * include update Xids corresponding to transactions 
> that are
> !                      * committed or in-progress.
> !                      */
> !                     update_xid = HeapTupleGetUpdateXid(tuple);
> !                     if (TransactionIdPrecedes(update_xid, cutoff_xid))
> !                             freeze_xmax = true;

That was the only concrete runtime problem I found during a study of the
newest heap_freeze_tuple() and heap_tuple_needs_freeze() code.  One thing that
leaves me unsure is the fact that vacuum_set_xid_limits() does no locking to
ensure a consistent result between GetOldestXmin() and GetOldestMultiXactId().
Transactions may start or end between those calls, making the
GetOldestMultiXactId() result represent a later set of transactions than the
GetOldestXmin() result.  I suspect that's fine.  New transactions have no
immediate effect on either cutoff, and transaction end can only increase a
cutoff.  Using a slightly-lower cutoff than the maximum safe cutoff is always
okay; consider vacuum_defer_cleanup_age.

Thanks,
nm

-- 
Noah Misch
EnterpriseDB                                 http://www.enterprisedb.com
setup
{
  CREATE TABLE t (k int PRIMARY KEY, c int);
  INSERT INTO t VALUES (1, 1);
  CREATE EXTENSION pageinspect;
}

teardown
{
  DROP EXTENSION pageinspect;
  DROP TABLE t;
}

session "updater"
step "u_begin"  { BEGIN ISOLATION LEVEL READ COMMITTED; SELECT txid_current(); }
step "u_regupd" { UPDATE t SET c = 5; }
step "u_abort"  { ROLLBACK; }
step "u_keyupd" { UPDATE t SET k = 2; }

session "locker"
step "l_begin"  { BEGIN ISOLATION LEVEL READ COMMITTED; SELECT txid_current(); }
step "l_keylck" { SELECT * FROM t FOR KEY SHARE; }
step "l_commit" { COMMIT; }

session "spectator"
step "s_vacfrz" { VACUUM FREEZE t; }
step "s_incxid" { SELECT txid_current(); }
step "s_snap"   { SELECT txid_current_snapshot(); }
step "s_cutoff" { SELECT relfrozenxid, relminmxid
        FROM pg_class WHERE oid = 't'::regclass; }
step "s_tuphdr" { select lp, t_xmin, t_xmax, to_hex(t_infomask) as mask,
        to_hex(t_infomask2) as mask2 from heap_page_items(get_raw_page('t', 
0)); }

# Key lock must block key update.  Works in master, 9.3.2, and 9.3.1.
permutation "l_begin" "l_keylck" "u_begin" "u_keyupd" "l_commit" "u_abort"

# Concurrently update and key-lock; abort the update; VACUUM FREEZE; try a key
# update.  Key lock must remain outstanding and block the key update.  Works
# in 9.3.1.  Fails in master and 9.3.2: VACUUM FREEZE leaves the affected
# tuple with t_infomask 0x900, unlocked.
permutation "u_begin" "u_regupd" "l_begin" "l_keylck" "u_abort" "s_vacfrz" 
"s_tuphdr" "u_keyupd" "l_commit"

# Previous permutation, minus the VACUUM FREEZE.  Again, key lock must remain
# outstanding and block the key update.  Works in 9.3.1.  master and 9.3.2 hit
# an assertion failure during u_keyupd:
#
# TRAP: FailedAssertion("!(((OldestMemberMXactId[MyBackendId]) != 
((MultiXactId) 0)))", File: "multixact.c", Line: 868)
permutation "u_begin" "u_regupd" "l_begin" "l_keylck" "u_abort" "u_keyupd" 
"l_commit"
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to