Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x95fD-00000001jhs-2dqW for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Sep 2026 18:56:24 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x95fC-00000000iZV-2luA for pgsql-hackers@arkaria.postgresql.org; Tue, 22 Sep 2026 18:56:22 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x95fC-00000000iZL-1F5f for pgsql-hackers@lists.postgresql.org; Tue, 22 Sep 2026 18:56:22 +0000 Received: from mail-dy2-x10.google.com ([2607:f8b0:4864:36::10]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1x95f7-00000000kV5-3Fle for pgsql-hackers@lists.postgresql.org; Tue, 22 Sep 2026 18:56:22 +0000 Received: by mail-dy2-x10.google.com with SMTP id 5a478bee46e88-339795849f6so33297eec.1 for ; Tue, 22 Sep 2026 11:56:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790103374; x=1790708174; darn=lists.postgresql.org; h=content-type:mime-version:references:in-reply-to:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=Sl7y/RscFmZVufCiduQXM/C71FwPGU6t6vmEQUAMcFI=; b=cysaciVp6tWPhF2A+XiqC/jV7SufgskUt3Q6CFN5ND/GKLov0Bo0Az2RXU/RPdEvuA MVp6u4n4MCd++x2N0Gw7lik1O95pnUoTdoe+CTTn6dw5D+E0YZR+q2DqaRF6WPsqOygP 6+fT4/rpFekFn1ztEds4RP6YW/70t2KKynyG2qGYIQ2vqJ3F7jk8o5DLaIRHKbI+d/jv gKJ46SX4LCEr9qEylJptN4cu5wDwrzOuixwC1er9hxxuIycj7wE+9EL7niBuFlmRGFfW WZHxQelOwtqEEmg98X+V//W+1lUFmnKpUlhIDUhxcekp0ykIk3RKiN8xrk/SNsAvUIZi f9yg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790103374; x=1790708174; h=content-type:mime-version:references:in-reply-to:message-id:date :subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=Sl7y/RscFmZVufCiduQXM/C71FwPGU6t6vmEQUAMcFI=; b=Vs6dZQ8KidhYKxu3VQ+49/CslyHCuBRv9tfRhzdsbuDFXY1LsUvDEGFexuRnFLm9bU 4gSnX5T/LqrQOZ1iLEcQng+z9pk1auK7FgKLnS+Nu2NwnIfqswNVv/I6dkDtw43r4vz3 2cT4hRgewoY93hfFud+wRgrxz5Djm+YTYhPw4N0BPVSAa/nAaHkOWD/4lKI8O19Aa514 U6AvrTultuFBBTlSjtFgz0/kYUNAWvppUSfsjzHEt4LTZGaO6HWsiFfslHQAkAD0EosJ XdQfn88Hz802Wu0EdhAwcClVCjfBPpwaFnOUrpGV1v84kAWg4OIhYSvxjR8PBslUNghf VgUA== X-Forwarded-Encrypted: i=1; AKwUvBwzcFX3+wFe0yEimulQTQ0T0saxQdekqukitrR/z56Ip5nWVerAEaOeUmGZ2Am5nuZ7If2/yo9VOdxFKhJM@lists.postgresql.org X-Gm-Message-State: AFuF++nmuMn5W/mTw2ETRMmJmX2u6UhqRa9xSbOdAelm6O6+eqSqm7Q6 aKOTPP2GJ25d8uiOH8oJn17vwrP2y6Vu/y/yzAnzJY2NwwLcqJDXeBw7 X-Gm-Gg: AYBFou06mxeT62w72lv9gcX5PclqgCWajLPEYGInhlqxZcTRSdRC/PUO1/VH++xoGLK 2hkcw06zVgbgZ6V3tPBkeceLhfpEPBrrggC2+r6RMn64ncbKfI2R9Zye7JPLIfYftxHlbBD2IxO GDAlTlilQNjTYttgxE12nRHG16cQWkjIhQvCxsl0Y41vJCrDh/VQ3T04xDq5kpGxkGmITHEDS0B EivYCfRXe7DKU/3pqfBNXlhLO/HJdYAcLyq75jFvuQgA3ZZ4qBjRjk5699oPBvPEtmn0/QXWkna PFVnXNkMJZpuUm6mKi9GF6dWU4TVKdcllfsJZqzouLnERgTP+WeoWGVZKu3CjNBJZaKqwZ/1nei ouXNJ2eKtIUCzxRv+jotX2gIlioXvsyTZIV1OIUa1UKBR5E0jfqutJjwzw/GA3lGgV4tHbGcMCI 7V5r46nApn4aEtwd0CYf0s7aWnKJTPrXVOE+P7RONh74VZ+Z/dGW7/fr6PhqfPHYFM3dre3mjZa Hz11N5u1jdE4+HtjAR3g2+covA1hrd4Tp6Hl1zvL/tM5EdwO15+xsgjRxAB3fj5jS9x4d3oByMY gxEjjqyCNmPeFrXEHhApNFj+rw== X-Received: by 2002:a05:7301:6197:10b0:33c:1bd2:1db6 with SMTP id 5a478bee46e88-33e88c2b243mr380341eec.0.1790103374019; Tue, 22 Sep 2026 11:56:14 -0700 (PDT) Received: from 192.168.1.109 ([2803:3c90:cee:ea00:5c3c:7e70:29e3:7a55]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-33e96d47157sm197165eec.24.2026.09.22.11.56.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 22 Sep 2026 11:56:13 -0700 (PDT) From: Manu To: Josh Curtis Cc: Andrey Borodin , Mihail Nikalayeu , pgsql-hackers@lists.postgresql.org Subject: Re: Fix race condition in SSI when reading PredXact->SxactGlobalXmin Date: Tue, 22 Sep 2026 15:56:10 -0300 Message-ID: <179010337091.3589417.887457215545919415@gmail.com> In-Reply-To: <8950FE6F-F07A-43AF-B8F7-F6556EEB904A@yandex-team.ru> References: <8950FE6F-F07A-43AF-B8F7-F6556EEB904A@yandex-team.ru> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2091776528546542296==" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --===============2091776528546542296== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Hi Josh, Andrey, Andrey Borodin wrote: > I suspect a race in the test: > > +step s2_insert_wait_at_page_split: <... completed> > +step s1_commit_wait_in_SetNewSxactGlobalXmin: <... completed> > Could it instead calculate the new xmin and count in local variables, > then publish them after scanning the active list? I tried both, on master e8a3ee5b197 with --enable-cassert and --enable-injection-points. v4 needs a rebase, but only in the injection_points Makefile and meson.build lists. The test. Run 50 times against one cluster (the attached run_spec_n.sh), v4's spec gives the other completion order in 50 of 50 runs with v4 applied. And it doesn't catch the bug: with v4's test and injection points but the readers as on master, it showed id 2 twice in 1 run of 50, the first one on a fresh cluster. Under make check in injection_points it fails there only on the completion order; verify has no duplicate. wakeup_s2_then_s1 wakes both sessions at once, and the window only exists while s1 is still inside SetNewSxactGlobalXmin(), so most of the time s1 is done before s2 reads SxactGlobalXmin. A second injection point right after the check in PredicateLockPageSplit() makes it deterministic: wake s2 alone, so it checks while s1 is still inside SetNewSxactGlobalXmin() and stops right after the check, then wake s1, then s2 again, with markers for both. If s2 saw InvalidTransactionId it returned early and never gets to the second point. duplicate id distinct outputs readers as on master 13 of 13 1 Andrey's approach 0 of 30 1 v4 (reader locks) 0 of 15 2 With v4, s2 checks under SerializableXactHashLock, which s1 holds in exclusive mode while it waits inside SetNewSxactGlobalXmin(), so after the first wakeup s2 waits on an LWLock, which isolationtester doesn't see, and the output depends on whether it looks at s2 before or after that. In an earlier round one run of eleven stopped right after wakeup_s2 until the 60 s timeout; in the 15 above none did. (Without the fix, one run in fourteen hung the same way, which is why only the 13 before it are counted.) So the reader locks also keep the test from being deterministic. Andrey's approach. The attached diff, on top of v4, does it: SetNewSxactGlobalXmin() computes the xmin and the count in local variables and publishes them after the scan, SxactGlobalXmin last; the three readers go back to the lockless check, with the comments that explained why it is safe put back plus a sentence on why it is safe again; and the spec is the deterministic one above. With it, injection_points (make check, three times in a row), src/test/isolation (133) and make check (239) pass, and pgindent leaves it unchanged. I also tried to measure the reader locks, with the attached page_split_bench.sh: 50-row inserts of random keys into a btree, so page splits are frequent, 8 clients, 15 s, 7 runs round-robin between release builds, median [min - max] TPS: no serializable xact inside SERIALIZABLE master 27859 [18240-30818] 27643 [23660-29449] v4 27878 [22204-31266] 27497 [24554-28122] Andrey's approach 28570 [18464-32231] 27033 [25626-28803] So I could not measure the cost of the locks here: the differences are smaller than the spread between runs on this machine. That's not an argument for the locks, just one I can't make against them. Josh, feel free to take any of it into a v5; I'm happy to review it. Regards, Manu --===============2091776528546542296== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="nocfbot-v4-publish-xmin-after-scan.diff.txt" MIME-Version: 1.0 diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/= predicate.c index b2509ac0765..82a9fcdd9f5 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -2879,14 +2879,15 @@ DropAllPredicateLocksFromTable(Relation relation, boo= l transfer) =20 /* * Bail out quickly if there are no serializable transactions running. + * It's safe to check this without taking locks because the caller is + * holding an ACCESS EXCLUSIVE lock on the relation. No new locks which + * would matter here can be acquired while that is held. + * SetNewSxactGlobalXmin() publishes a new value only after scanning the + * active list, so InvalidTransactionId is never seen here while + * serializable transactions are still active. */ - LWLockAcquire(SerializableXactHashLock, LW_SHARED); if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) - { - LWLockRelease(SerializableXactHashLock); return; - } - LWLockRelease(SerializableXactHashLock); =20 if (!PredicateLockingNeededForRelation(relation)) return; @@ -3082,15 +3083,21 @@ PredicateLockPageSplit(Relation relation, BlockNumber= oldblkno, =20 /* * Bail out quickly if there are no serializable transactions running. + * + * It's safe to do this check without taking any additional locks. Even if + * a serializable transaction starts concurrently, we know it can't take + * any SIREAD locks on the page being split because the caller is holding + * the associated buffer page lock. Memory reordering isn't an issue; the + * memory barrier in the LWLock acquisition guarantees that this read + * occurs while the buffer page lock is held. SetNewSxactGlobalXmin() + * publishes a new value only after scanning the active list, so + * InvalidTransactionId is never seen here while serializable transactions + * are still active. */ INJECTION_POINT("predicate-lock-page-split", NULL); - LWLockAcquire(SerializableXactHashLock, LW_SHARED); if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) - { - LWLockRelease(SerializableXactHashLock); return; - } - LWLockRelease(SerializableXactHashLock); + INJECTION_POINT("predicate-lock-page-split-after-check", NULL); =20 if (!PredicateLockingNeededForRelation(relation)) return; @@ -3182,12 +3189,17 @@ static void SetNewSxactGlobalXmin(void) { dlist_iter iter; + TransactionId xmin =3D InvalidTransactionId; + int xmincount =3D 0; =20 Assert(LWLockHeldByMe(SerializableXactHashLock)); =20 - PredXact->SxactGlobalXmin =3D InvalidTransactionId; - PredXact->SxactGlobalXminCount =3D 0; - + /* + * Compute the new values in local variables, and publish them only at the + * end: some callers read SxactGlobalXmin without holding + * SerializableXactHashLock, and must not see a transient + * InvalidTransactionId while serializable transactions are still active. + */ #ifdef USE_INJECTION_POINTS INJECTION_POINT_CACHED("predicate-set-sxact-global-xmin-invalid", NULL); #endif @@ -3202,20 +3214,21 @@ SetNewSxactGlobalXmin(void) && sxact !=3D OldCommittedSxact) { Assert(sxact->xmin !=3D InvalidTransactionId); - if (!TransactionIdIsValid(PredXact->SxactGlobalXmin) - || TransactionIdPrecedes(sxact->xmin, - PredXact->SxactGlobalXmin)) + if (!TransactionIdIsValid(xmin) + || TransactionIdPrecedes(sxact->xmin, xmin)) { - PredXact->SxactGlobalXmin =3D sxact->xmin; - PredXact->SxactGlobalXminCount =3D 1; + xmin =3D sxact->xmin; + xmincount =3D 1; } - else if (TransactionIdEquals(sxact->xmin, - PredXact->SxactGlobalXmin)) - PredXact->SxactGlobalXminCount++; + else if (TransactionIdEquals(sxact->xmin, xmin)) + xmincount++; } } =20 - SerialSetActiveSerXmin(PredXact->SxactGlobalXmin); + PredXact->SxactGlobalXminCount =3D xmincount; + PredXact->SxactGlobalXmin =3D xmin; + + SerialSetActiveSerXmin(xmin); } =20 /* @@ -4364,14 +4377,15 @@ CheckTableForSerializableConflictIn(Relation relation) =20 /* * Bail out quickly if there are no serializable transactions running. + * It's safe to check this without taking locks because the caller is + * holding an ACCESS EXCLUSIVE lock on the relation. No new locks which + * would matter here can be acquired while that is held. + * SetNewSxactGlobalXmin() publishes a new value only after scanning the + * active list, so InvalidTransactionId is never seen here while + * serializable transactions are still active. */ - LWLockAcquire(SerializableXactHashLock, LW_SHARED); if (!TransactionIdIsValid(PredXact->SxactGlobalXmin)) - { - LWLockRelease(SerializableXactHashLock); return; - } - LWLockRelease(SerializableXactHashLock); =20 if (!SerializationNeededForWrite(relation)) return; diff --git a/src/test/modules/injection_points/expected/predicate-lock-page-s= plit.out b/src/test/modules/injection_points/expected/predicate-lock-page-spl= it.out index 8f5613c9d8e..d80353abf5e 100644 --- a/src/test/modules/injection_points/expected/predicate-lock-page-split.out +++ b/src/test/modules/injection_points/expected/predicate-lock-page-split.out @@ -1,6 +1,6 @@ Parsed test spec with 5 sessions =20 -starting permutation: s1_begin bump_xmin s2_begin s3_begin s1_insert s2_inse= rt_wait_at_page_split s1_commit_wait_in_SetNewSxactGlobalXmin wakeup_s2_then_= s1 s3_insert s3_commit s2_commit verify +starting permutation: s1_begin bump_xmin s2_begin s3_begin s1_insert s2_inse= rt_wait_at_page_split s1_commit_wait_in_SetNewSxactGlobalXmin wakeup_s2 wakeu= p_s1 wakeup_s2_after_check s3_insert s3_commit s2_commit verify injection_points_attach ----------------------- =20 @@ -67,24 +67,35 @@ step s2_insert_wait_at_page_split: step s1_commit_wait_in_SetNewSxactGlobalXmin:=20 COMMIT; -step wakeup_s2_then_s1:=20 +step wakeup_s2:=20 SELECT injection_points_wakeup('predicate-lock-page-split'); + +injection_points_wakeup +----------------------- + =20 +(1 row) + +step wakeup_s1:=20 SELECT injection_points_wakeup('predicate-set-sxact-global-xmin-invalid'= ); -step s2_insert_wait_at_page_split: <... completed> step s1_commit_wait_in_SetNewSxactGlobalXmin: <... completed> +step wakeup_s1: <... completed> +injection_points_wakeup +----------------------- + =20 +(1 row) + +step wakeup_s2_after_check:=20 + SELECT injection_points_wakeup('predicate-lock-page-split-after-check'); + step s3_insert:=20 INSERT INTO test_table SELECT max(id) + 1 FROM test_table; =20 ERROR: could not serialize access due to read/write dependencies among tran= sactions -step wakeup_s2_then_s1: <... completed> -injection_points_wakeup ------------------------ - =20 -(1 row) - +step s2_insert_wait_at_page_split: <... completed> +step wakeup_s2_after_check: <... completed> injection_points_wakeup ----------------------- =20 diff --git a/src/test/modules/injection_points/specs/predicate-lock-page-spli= t.spec b/src/test/modules/injection_points/specs/predicate-lock-page-split.sp= ec index fd905a37b07..8ed3f0f4223 100644 --- a/src/test/modules/injection_points/specs/predicate-lock-page-split.spec +++ b/src/test/modules/injection_points/specs/predicate-lock-page-split.spec @@ -1,4 +1,6 @@ -# Test for race condition in PredicateLockPageSplit +# Test for race condition in PredicateLockPageSplit (deterministic variant: +# s2 decides whether to transfer the SIREAD locks while s1 is still inside +# SetNewSxactGlobalXmin(), and stops right after that decision) # # When SetNewSxactGlobalXmin() temporarily sets SxactGlobalXmin to # InvalidTransactionId, a concurrent PredicateLockPageSplit() can see @@ -58,6 +60,7 @@ session s2 setup { SELECT injection_points_set_local(); SELECT injection_points_attach('predicate-lock-page-split', 'wait'); + SELECT injection_points_attach('predicate-lock-page-split-after-check', = 'wait'); } step s2_begin { BEGIN ISOLATION LEVEL SERIALIZABLE; @@ -96,28 +99,21 @@ step s3_commit { } =20 session s4 -step wakeup_s2_then_s1 { +step wakeup_s2 { SELECT injection_points_wakeup('predicate-lock-page-split'); +} +step wakeup_s2_after_check { + SELECT injection_points_wakeup('predicate-lock-page-split-after-check'); +} +step wakeup_s1 { SELECT injection_points_wakeup('predicate-set-sxact-global-xmin-invalid'= ); } =20 -# s1_begin: s1 reads from the table, establishing SIREAD locks on the index -# bump_xmin: advance xmin so s2/s3 get a higher xmin than s1 -# s2_begin, s3_begin: s2 and s3 read from the table (same snapshot as s1) -# -# s1_insert: s1 inserts max(id)+1 =3D 2 -# s2_insert_wait_at_page_split: s2 inserts descending values until a real -# btree page split happens, then waits in PredicateLockPageSplit before -# checking SxactGlobalXmin. The values must be descending so that the -# split moves ids 1 and 2 to the new page -# s1_commit_wait_in_SetNewSxactGlobalXmin: after s2 is already waiting, -# s1 commits and waits after SetNewSxactGlobalXmin sets SxactGlobalXmin -# to InvalidTransactionId -# wakeup_s2_then_s1: wake s2 (sees InvalidTransactionId, skips SIREAD -# lock transfer), then wake s1 -# s3_insert: s3 inserts max(id)+1 =3D 2, computed from its snapshot -# s3_commit: s3 commits (should have been aborted by SSI) -# s2_commit: s2 aborts due to serialization failure +# s1 waits inside SetNewSxactGlobalXmin(). s2 is woken alone: it reads +# SxactGlobalXmin while s1 is still there and, if it sees a valid value, sto= ps +# right after the check (a lock-free check never waits on s1). Then s1 is +# woken, and then s2. If s2 saw InvalidTransactionId, it skipped the SIREAD +# lock transfer and never reaches the second point: the id is inserted twice. permutation s1_begin bump_xmin @@ -126,7 +122,9 @@ permutation s1_insert s2_insert_wait_at_page_split s1_commit_wait_in_SetNewSxactGlobalXmin - wakeup_s2_then_s1(s2_insert_wait_at_page_split,s1_commit_wait_in_SetNewS= xactGlobalXmin) + wakeup_s2 + wakeup_s1(s1_commit_wait_in_SetNewSxactGlobalXmin) + wakeup_s2_after_check(s2_insert_wait_at_page_split) s3_insert s3_commit s2_commit --===============2091776528546542296== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="run_spec_n.sh.txt" MIME-Version: 1.0 #!/bin/bash # usage: run_spec_n.sh = # # Runs one isolation spec times against a single throwaway cluster # and counts the distinct outputs, and the runs where verify shows id 2 # twice. Each run gets 60 seconds; a run that hangs leaves backends waiting # on injection points, so the runs after it are not meaningful. P=3D$1; IT=3D$2; SPEC=3D$3; RUNS=3D$4; PORT=3D$5 D=3D/tmp/specn_$PORT rm -rf $D; mkdir -p $D/out $P/bin/initdb -D $D/data -U postgres --no-sync >/dev/null 2>&1 $P/bin/pg_ctl -D $D/data -o "-p $PORT -k /tmp" -l $D/log -w start >/dev/null trap '$P/bin/pg_ctl -D $D/data -m immediate -w stop >/dev/null 2>&1; rm -rf $= D' EXIT for r in $(seq 1 $RUNS); do timeout 60 $IT "host=3D/tmp port=3D$PORT dbname=3Dpostgres user=3Dpostgres"= < $SPEC > $D/out/$r 2>&1 || echo "run $r: exit $? (124 =3D timed out)" done echo "distinct outputs:" md5sum $D/out/* | awk '{print $1}' | sort | uniq -c echo "runs with id 2 twice: $(grep -lE '^ *2\| *2$' $D/out/* | wc -l)" --===============2091776528546542296== Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="page_split_bench.sh.txt" MIME-Version: 1.0 #!/bin/bash # usage: page_split_bench.sh [ ..= .] # # Inserts of random keys into a btree (many page splits), 50 rows per # transaction, with pgbench: # A: read committed, no serializable transaction around # B: the same inside BEGIN ISOLATION LEVEL SERIALIZABLE (--max-tries 50) # One cluster per prefix (release builds), run round-robin, median TPS. # The clusters live in $WORKDIR (default ./psbench, on disk): millions of # inserted rows and their WAL would fill a tmpfs /tmp. C=3D$1; T=3D$2; R=3D$3; shift 3 W=3D${WORKDIR:-$PWD/psbench}; rm -rf $W; mkdir -p $W cat > $W/A.sql <<'EOF' INSERT INTO t (k) SELECT (random() * 2000000000)::int FROM generate_series(1,= 50); EOF cat > $W/B.sql <<'EOF' BEGIN ISOLATION LEVEL SERIALIZABLE; INSERT INTO t (k) SELECT (random() * 2000000000)::int FROM generate_series(1,= 50); COMMIT; EOF i=3D0 for P in "$@"; do i=3D$((i + 1)); port=3D$((56500 + i)) $P/bin/initdb -D $W/d$i -U postgres --no-sync >/dev/null 2>&1 cat >> $W/d$i/postgresql.conf </dev/nu= ll done trap 'i=3D0; for P in "$@"; do i=3D$((i + 1)); $P/bin/pg_ctl -D $W/d$i -m imm= ediate -w stop >/dev/null 2>&1; done; rm -rf $W' EXIT for w in A B; do for r in $(seq 1 $R); do i=3D0 for P in "$@"; do i=3D$((i + 1)); port=3D$((56500 + i)) $P/bin/psql -X -q -h /tmp -p $port -U postgres postgres \ -c "DROP TABLE IF EXISTS t" -c "CREATE TABLE t (k int)" -c "CREATE IN= DEX ON t (k)" -c "CHECKPOINT" $P/bin/pgbench -n -h /tmp -p $port -U postgres -c $C -j $C -T $T --max-= tries=3D50 \ -f $W/$w.sql postgres > $W/pgbench.out 2>&1 tps=3D$(grep -oE 'tps =3D [0-9.]+' $W/pgbench.out | grep -oE '[0-9.]+') [ -n "$tps" ] || { echo "pgbench failed ($w, $P):" >&2; tail -3 $W/pgbe= nch.out >&2; } echo "$w $i $tps" | tee -a $W/raw done done done echo "TPS ($C clients, $T s, $R runs): median [min - max]" for w in A B; do i=3D0 for P in "$@"; do i=3D$((i + 1)) awk -v w=3D$w -v n=3D$i '$1=3D=3Dw && $2=3D=3Dn && $3 !=3D "" {print $3}'= $W/raw | sort -n | awk -v w=3D$w -v p=3D$P '{a[NR]=3D$1} END {printf " %s %-40s %8.0f [= %.0f - %.0f] (%d runs)\n", w, p, a[int((NR+1)/2)], a[1], a[NR], NR}' done done --===============2091776528546542296==--