public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
79+ messages / 5 participants
[nested] [flat]
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 5 +++--
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6d357565a8 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -147,8 +147,9 @@ The following infomask bits are applicable:
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ this tuple and changed the key values, or it deleted the tuple. It can also
+ be set in combination of HEAP_XMAX_LOCK_ONLY. It's set regardless of whether
+ the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--+HP7ph2BbKc20aGI--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/README.tuplock | 7 ++++---
src/backend/access/heap/hio.c | 8 +++-----
4 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/README.tuplock b/src/backend/access/heap/README.tuplock
index d03ddf6cdc..6441e8baf0 100644
--- a/src/backend/access/heap/README.tuplock
+++ b/src/backend/access/heap/README.tuplock
@@ -146,9 +146,10 @@ The following infomask bits are applicable:
FOR UPDATE; this is implemented by the HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
- This bit lives in t_infomask2. If set, indicates that the XMAX updated
- this tuple and changed the key values, or it deleted the tuple.
- It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
+ This bit lives in t_infomask2. If set, indicates that the operation(s) done
+ by the XMAX compromise the tuple key, such as a SELECT FOR UPDATE, an UPDATE
+ that modifies the columns of the key, or a DELETE. It's set regardless of
+ whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--0ntfKIWw70PvrIHh--
^ permalink raw reply [nested|flat] 79+ messages in thread
* [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination.
@ 2021-01-21 16:06 Julien Rouhaud <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Julien Rouhaud @ 2021-01-21 16:06 UTC (permalink / raw)
This combination of hint bits was previously detected as a form of corruption,
but it can be obtained with some mix of SELECT ... FOR UPDATE and UPDATE
queries.
Discussion: https://postgr.es/m/20210124061758.GA11756@nol
---
contrib/amcheck/t/001_verify_heapam.pl | 14 +++++++++++++-
contrib/amcheck/verify_heapam.c | 7 -------
src/backend/access/heap/hio.c | 8 +++-----
3 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index a2f65b826d..6050feb712 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -4,7 +4,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 79;
+use Test::More tests => 80;
my ($node, $result);
@@ -47,6 +47,9 @@ detects_heap_corruption(
#
fresh_test_table('test');
$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test));
+detects_no_corruption(
+ "verify_heapam('test')",
+ "all-frozen not corrupted table");
corrupt_first_page('test');
detects_heap_corruption("verify_heapam('test')",
"all-frozen corrupted table");
@@ -92,6 +95,15 @@ sub fresh_test_table
ALTER TABLE $relname ALTER b SET STORAGE external;
INSERT INTO $relname (a, b)
(SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs);
+ BEGIN;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ RELEASE s1;
+ SAVEPOINT s1;
+ SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE;
+ UPDATE $relname SET b = b WHERE a = 42;
+ COMMIT;
));
}
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 88ab32490c..49f5ca0ef2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -608,13 +608,6 @@ check_tuple_header_and_visibilty(HeapTupleHeader tuphdr, HeapCheckContext *ctx)
ctx->tuphdr->t_hoff, ctx->lp_len));
header_garbled = true;
}
- if ((ctx->tuphdr->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (ctx->tuphdr->t_infomask2 & HEAP_KEYS_UPDATED))
- {
- report_corruption(ctx,
- pstrdup("tuple is marked as only locked, but also claims key columns were updated"));
- header_garbled = true;
- }
if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) &&
(ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI))
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index fb7ad0bab4..75223c9bea 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -49,12 +49,10 @@ RelationPutHeapTuple(Relation relation,
/*
* Do not allow tuples with invalid combinations of hint bits to be placed
- * on a page. These combinations are detected as corruption by the
- * contrib/amcheck logic, so if you disable one or both of these
- * assertions, make corresponding changes there.
+ * on a page. This combination is detected as corruption by the
+ * contrib/amcheck logic, so if you disable this assertion, make
+ * corresponding changes there.
*/
- Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_LOCK_ONLY) &&
- (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)));
Assert(!((tuple->t_data->t_infomask & HEAP_XMAX_COMMITTED) &&
(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)));
--
2.20.1
--CE+1k2dSO48ffgeK--
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-08 11:25 Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-08 11:25 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
The more I look at TransactionGroupUpdateXidStatus, the more I think
it's broken, and while we do have some tests, I don't have confidence
that they cover all possible cases.
Or, at least, if this code is good, then it hasn't been sufficiently
explained.
If we have multiple processes trying to write bits to clog, and they are
using different banks, then the LWLockConditionalAcquire will be able to
acquire the bank lock
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"The saddest aspect of life right now is that science gathers knowledge faster
than society gathers wisdom." (Isaac Asimov)
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-08 13:18 Dilip Kumar <[email protected]>
parent: Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 79+ messages in thread
From: Dilip Kumar @ 2024-01-08 13:18 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Jan 8, 2024 at 4:55 PM Alvaro Herrera <[email protected]> wrote:
>
> The more I look at TransactionGroupUpdateXidStatus, the more I think
> it's broken, and while we do have some tests, I don't have confidence
> that they cover all possible cases.
>
> Or, at least, if this code is good, then it hasn't been sufficiently
> explained.
Any thought about a case in which you think it might be broken, I mean
any vague thought might also help where you think it might not work as
expected so that I can also think in that direction. It might be
possible that I might not be thinking of some perspective that you are
thinking and comments might be lacking from that point of view.
> If we have multiple processes trying to write bits to clog, and they are
> using different banks, then the LWLockConditionalAcquire will be able to
> acquire the bank lock
Do you think there is a problem with multiple processes getting the
lock? I mean they are modifying different CLOG pages so that can be
done concurrently right?
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-08 15:42 Alvaro Herrera <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 1 reply; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-08 15:42 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2024-Jan-08, Dilip Kumar wrote:
> On Mon, Jan 8, 2024 at 4:55 PM Alvaro Herrera <[email protected]> wrote:
> >
> > The more I look at TransactionGroupUpdateXidStatus, the more I think
> > it's broken, and while we do have some tests, I don't have confidence
> > that they cover all possible cases.
> >
> > Or, at least, if this code is good, then it hasn't been sufficiently
> > explained.
>
> Any thought about a case in which you think it might be broken, I mean
> any vague thought might also help where you think it might not work as
> expected so that I can also think in that direction. It might be
> possible that I might not be thinking of some perspective that you are
> thinking and comments might be lacking from that point of view.
Eh, apologies. This email was an unfinished draft that I had laying
around before the holidays which I intended to discard it but somehow
kept around, and just now I happened to press the wrong key combination
and it ended up being sent instead. We had some further discussion,
after which I no longer think that there is a problem here, so please
ignore this email.
I'll come back to this patch later this week.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"El que vive para el futuro es un iluso, y el que vive para el pasado,
un imbécil" (Luis Adler, "Los tripulantes de la noche")
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-10 13:20 Dilip Kumar <[email protected]>
parent: Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 79+ messages in thread
From: Dilip Kumar @ 2024-01-10 13:20 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Mon, Jan 8, 2024 at 9:12 PM Alvaro Herrera <[email protected]> wrote:
>
> Eh, apologies. This email was an unfinished draft that I had laying
> around before the holidays which I intended to discard it but somehow
> kept around, and just now I happened to press the wrong key combination
> and it ended up being sent instead. We had some further discussion,
> after which I no longer think that there is a problem here, so please
> ignore this email.
>
> I'll come back to this patch later this week.
No problem
The patch was facing some compilation issues after some recent
commits, so I have changed it. Reported by Julien Tachoires (offlist)
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
Attachments:
[application/octet-stream] v13-0002-Divide-SLRU-buffers-into-banks.patch (13.6K, ../../CAFiTN-sGOOZC5E5WsKXXdW9Cj_k4=SKXy7mrwyitpkDQ1Gs2bw@mail.gmail.com/2-v13-0002-Divide-SLRU-buffers-into-banks.patch)
download | inline diff:
From 5fc4579e34fc417129e697542b4ac71c93523d1c Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Thu, 30 Nov 2023 13:41:50 +0530
Subject: [PATCH v13 2/3] Divide SLRU buffers into banks
As we have made slru buffer pool configurable, we want to
eliminate linear search within whole SLRU buffer pool. To
do so we divide SLRU buffers into banks. Each bank holds 16
buffers. Each SLRU pageno may reside only in one bank.
Adjacent pagenos reside in different banks. Along with this
also ensure that the number of slru buffers are given in
multiples of bank size.
Andrey M. Borodin and Dilip Kumar based on fedback by Alvaro Herrera
---
src/backend/access/transam/clog.c | 10 ++++++
src/backend/access/transam/commit_ts.c | 10 ++++++
src/backend/access/transam/multixact.c | 19 +++++++++++
src/backend/access/transam/slru.c | 44 +++++++++++++++++++++++---
src/backend/access/transam/subtrans.c | 10 ++++++
src/backend/commands/async.c | 10 ++++++
src/backend/storage/lmgr/predicate.c | 10 ++++++
src/backend/utils/misc/guc_tables.c | 14 ++++----
src/include/access/slru.h | 15 +++++++++
src/include/utils/guc_hooks.h | 11 +++++++
10 files changed, 141 insertions(+), 12 deletions(-)
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 5d96195c53..7d349d2213 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -43,6 +43,7 @@
#include "pgstat.h"
#include "storage/proc.h"
#include "storage/sync.h"
+#include "utils/guc_hooks.h"
/*
* Defines for CLOG page sizes. A page is the same BLCKSZ as is used
@@ -1029,3 +1030,12 @@ clogsyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(XactCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for xact_buffers
+ */
+bool
+check_xact_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("xact_buffers", newval);
+}
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 27aab51162..41337471e2 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -33,6 +33,7 @@
#include "pg_trace.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"
@@ -1027,3 +1028,12 @@ committssyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(CommitTsCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for commit_ts_buffers
+ */
+bool
+check_commit_ts_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("commit_ts_buffers", newval);
+}
\ No newline at end of file
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 1957845f58..f8eceeac30 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -88,6 +88,7 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@@ -3421,3 +3422,21 @@ multixactmemberssyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(MultiXactMemberCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for multixact_offsets_buffers
+ */
+bool
+check_multixact_offsets_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_offsets_buffers", newval);
+}
+
+/*
+ * GUC check_hook for multixact_members_buffers
+ */
+bool
+check_multixact_members_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_members_buffers", newval);
+}
\ No newline at end of file
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 9ac4790f16..211527b075 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,6 +59,7 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/guc_hooks.h"
static inline int
SlruFileName(SlruCtl ctl, char *path, int64 segno)
@@ -284,7 +285,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
else
+ {
Assert(found);
+ Assert(shared->num_slots == nslots);
+ }
/*
* Initialize the unshared control struct, including directory path. We
@@ -293,6 +297,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ctl->shared = shared;
ctl->sync_handler = sync_handler;
ctl->long_segment_names = long_segment_names;
+ ctl->bank_mask = (nslots / SLRU_BANK_SIZE) - 1;
strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir));
}
@@ -524,12 +529,18 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
/* Try to find the page while holding only shared lock */
LWLockAcquire(shared->ControlLock, LW_SHARED);
- /* See if page is already in a buffer */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY &&
@@ -1056,9 +1067,15 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
- /* See if page already has a buffer assigned */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY)
@@ -1093,7 +1110,7 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* multiple pages with the same lru_count.
*/
cur_count = (shared->cur_lru_count)++;
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
int64 this_page_number;
@@ -1666,3 +1683,20 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
errno = save_errno;
return result;
}
+
+/*
+ * Helper function for GUC check_hook to check whether slru buffers are in
+ * multiples of SLRU_BANK_SIZE.
+ */
+bool
+check_slru_buffers(const char *name, int *newval)
+{
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval % SLRU_BANK_SIZE == 0)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"%s\" must be in multiple of %d", name,
+ SLRU_BANK_SIZE);
+ return false;
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 6059999a3c..82243c2728 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -33,6 +33,7 @@
#include "access/transam.h"
#include "miscadmin.h"
#include "pg_trace.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
@@ -383,3 +384,12 @@ SubTransPagePrecedes(int64 page1, int64 page2)
return (TransactionIdPrecedes(xid1, xid2) &&
TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
}
+
+/*
+ * GUC check_hook for subtrans_buffers
+ */
+bool
+check_subtrans_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("subtrans_buffers", newval);
+}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 20a4dfec2a..9059c0a202 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -148,6 +148,7 @@
#include "storage/sinval.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
@@ -2378,3 +2379,12 @@ ClearPendingActionsAndNotifies(void)
pendingActions = NULL;
pendingNotifies = NULL;
}
+
+/*
+ * GUC check_hook for notify_buffers
+ */
+bool
+check_notify_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("notify_buffers", newval);
+}
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 7fc34720bf..10c51e2883 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -208,6 +208,7 @@
#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "utils/guc_hooks.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
@@ -5012,3 +5013,12 @@ AttachSerializableXact(SerializableXactHandle handle)
if (MySerializableXact != InvalidSerializableXact)
CreateLocalPredicateLockHash();
}
+
+/*
+ * GUC check_hook for serial_buffers
+ */
+bool
+check_serial_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("serial_buffers", newval);
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e56c14b78f..8aad05eaf5 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2319,7 +2319,7 @@ struct config_int ConfigureNamesInt[] =
},
&multixact_offsets_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_multixact_offsets_buffers, NULL, NULL
},
{
@@ -2330,7 +2330,7 @@ struct config_int ConfigureNamesInt[] =
},
&multixact_members_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_multixact_members_buffers, NULL, NULL
},
{
@@ -2341,7 +2341,7 @@ struct config_int ConfigureNamesInt[] =
},
&subtrans_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_subtrans_buffers, NULL, NULL
},
{
{"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
@@ -2351,7 +2351,7 @@ struct config_int ConfigureNamesInt[] =
},
¬ify_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_notify_buffers, NULL, NULL
},
{
@@ -2362,7 +2362,7 @@ struct config_int ConfigureNamesInt[] =
},
&serial_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_serial_buffers, NULL, NULL
},
{
@@ -2373,7 +2373,7 @@ struct config_int ConfigureNamesInt[] =
},
&xact_buffers,
64, 0, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, show_xact_buffers
+ check_xact_buffers, NULL, show_xact_buffers
},
{
@@ -2384,7 +2384,7 @@ struct config_int ConfigureNamesInt[] =
},
&commit_ts_buffers,
64, 0, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, show_commit_ts_buffers
+ check_commit_ts_buffers, NULL, show_commit_ts_buffers
},
{
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 72b30bba7f..2b74e11d42 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,14 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * SLRU bank size for slotno hash banks. Limit the bank size to 16 because we
+ * perform sequential search within a bank (while looking for a target page or
+ * while doing victim buffer search) and if we keep this size big then it may
+ * affect the performance.
+ */
+#define SLRU_BANK_SIZE 16
+
/*
* To avoid overflowing internal arithmetic and the size_t data type, the
* number of buffers should not exceed this number.
@@ -147,6 +155,12 @@ typedef struct SlruCtlData
* it's always the same, it doesn't need to be in shared memory.
*/
char Dir[64];
+
+ /*
+ * Mask for slotno banks, considering 1GB SLRU buffer pool size and the
+ * SLRU_BANK_SIZE bits16 should be sufficient for the bank mask.
+ */
+ bits16 bank_mask;
} SlruCtlData;
typedef SlruCtlData *SlruCtl;
@@ -184,5 +198,6 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
int64 segpage, void *data);
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
+extern bool check_slru_buffers(const char *name, int *newval);
#endif /* SLRU_H */
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index a7bcb6b42a..f458da88ac 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -130,6 +130,17 @@ extern bool check_ssl(bool *newval, void **extra, GucSource source);
extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
extern bool check_synchronous_standby_names(char **newval, void **extra,
GucSource source);
+extern bool check_multixact_offsets_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_multixact_members_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_subtrans_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_notify_buffers(int *newval, void **extra, GucSource source);
+extern bool check_serial_buffers(int *newval, void **extra, GucSource source);
+extern bool check_xact_buffers(int *newval, void **extra, GucSource source);
+extern bool check_commit_ts_buffers(int *newval, void **extra,
+ GucSource source);
extern void assign_synchronous_standby_names(const char *newval, void *extra);
extern void assign_synchronous_commit(int newval, void *extra);
extern void assign_syslog_facility(int newval, void *extra);
--
2.39.2 (Apple Git-143)
[application/octet-stream] v13-0003-Remove-the-centralized-control-lock-and-LRU-coun.patch (79.8K, ../../CAFiTN-sGOOZC5E5WsKXXdW9Cj_k4=SKXy7mrwyitpkDQ1Gs2bw@mail.gmail.com/3-v13-0003-Remove-the-centralized-control-lock-and-LRU-coun.patch)
download | inline diff:
From 84d8a0eb7afbdb63ea49925c7c2bb02384a9c418 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Thu, 14 Dec 2023 13:43:38 +0530
Subject: [PATCH v13 3/3] Remove the centralized control lock and LRU counter
The previous patch has divided SLRU buffer pool into associative
banks. This patch is further optimizing it by introducing
multiple SLRU locks instead of a common centralized lock this
will reduce the contention on the slru control lock. Basically,
we will have at max 128 bank locks and if the number of banks
is <= 128 then each lock will cover exactly one bank otherwise
they will cover multiple banks we will find the bank-to-lock
mapping by (bankno % 128). This patch also removes the
centralized lru counter and now we will have bank-wise lru
counters that will help in frequent cache invalidation while
modifying this counter.
Dilip Kumar based on design inputs from Robert Haas, Andrey M. Borodin,
and Alvaro Herrera
---
src/backend/access/transam/clog.c | 155 ++++++++---
src/backend/access/transam/commit_ts.c | 42 +--
src/backend/access/transam/multixact.c | 173 +++++++++----
src/backend/access/transam/slru.c | 245 +++++++++++++-----
src/backend/access/transam/subtrans.c | 58 ++++-
src/backend/commands/async.c | 43 ++-
src/backend/storage/lmgr/lwlock.c | 14 +
src/backend/storage/lmgr/lwlocknames.txt | 14 +-
src/backend/storage/lmgr/predicate.c | 34 +--
.../utils/activity/wait_event_names.txt | 8 +-
src/include/access/slru.h | 64 +++--
src/include/storage/lwlock.h | 7 +
src/test/modules/test_slru/test_slru.c | 35 +--
13 files changed, 640 insertions(+), 252 deletions(-)
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 7d349d2213..d9a18ad35c 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -285,15 +285,20 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
+ LWLock *lock;
+
/* Can't use group update when PGPROC overflows. */
StaticAssertDecl(THRESHOLD_SUBTRANS_CLOG_OPT <= PGPROC_MAX_CACHED_SUBXIDS,
"group clog threshold less than PGPROC cached subxids");
+ /* Get the SLRU bank lock for the page we are going to access. */
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+
/*
- * When there is contention on XactSLRULock, we try to group multiple
+ * When there is contention on Xact SLRU lock, we try to group multiple
* updates; a single leader process will perform transaction status
- * updates for multiple backends so that the number of times XactSLRULock
- * needs to be acquired is reduced.
+ * updates for multiple backends so that the number of times the Xact SLRU
+ * lock needs to be acquired is reduced.
*
* For this optimization to be safe, the XID and subxids in MyProc must be
* the same as the ones for which we're setting the status. Check that
@@ -311,17 +316,17 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
nsubxids * sizeof(TransactionId)) == 0))
{
/*
- * If we can immediately acquire XactSLRULock, we update the status of
+ * If we can immediately acquire SLRU lock, we update the status of
* our own XID and release the lock. If not, try use group XID
* update. If that doesn't work out, fall back to waiting for the
* lock to perform an update for this transaction only.
*/
- if (LWLockConditionalAcquire(XactSLRULock, LW_EXCLUSIVE))
+ if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
{
/* Got the lock without waiting! Do the update. */
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
return;
}
else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
@@ -334,10 +339,10 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
}
/* Group update not applicable, or couldn't accept this page number. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -356,7 +361,8 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
Assert(status == TRANSACTION_STATUS_COMMITTED ||
status == TRANSACTION_STATUS_ABORTED ||
(status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
- Assert(LWLockHeldByMeInMode(XactSLRULock, LW_EXCLUSIVE));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
+ LW_EXCLUSIVE));
/*
* If we're doing an async commit (ie, lsn is valid), then we must wait
@@ -407,14 +413,13 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
}
/*
- * When we cannot immediately acquire XactSLRULock in exclusive mode at
+ * When we cannot immediately acquire SLRU bank lock in exclusive mode at
* commit time, add ourselves to a list of processes that need their XIDs
* status update. The first process to add itself to the list will acquire
- * XactSLRULock in exclusive mode and set transaction status as required
- * on behalf of all group members. This avoids a great deal of contention
- * around XactSLRULock when many processes are trying to commit at once,
- * since the lock need not be repeatedly handed off from one committing
- * process to the next.
+ * the lock in exclusive mode and set transaction status as required on behalf
+ * of all group members. This avoids a great deal of contention when many
+ * processes are trying to commit at once, since the lock need not be
+ * repeatedly handed off from one committing process to the next.
*
* Returns true when transaction status has been updated in clog; returns
* false if we decided against applying the optimization because the page
@@ -428,6 +433,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
PGPROC *proc = MyProc;
uint32 nextidx;
uint32 wakeidx;
+ int prevpageno;
+ LWLock *prevlock = NULL;
/* We should definitely have an XID whose status needs to be updated. */
Assert(TransactionIdIsValid(xid));
@@ -442,6 +449,41 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
proc->clogGroupMemberPage = pageno;
proc->clogGroupMemberLsn = lsn;
+ /*
+ * The underlying SLRU is using bank-wise lock so it is possible that here
+ * we might get requesters who are contending on different SLRU-bank locks.
+ * But in the group, we try to only add the requesters who want to update
+ * the same page i.e. they would be requesting for the same SLRU-bank lock
+ * as well. The main reason for now allowing requesters of different pages
+ * together is 1) Once the leader acquires the lock they don't need to
+ * fetch multiple pages and do multiple I/O under the same lock 2) The
+ * leader need not switch the SLRU-bank lock if the different pages are
+ * from different SLRU banks 3) And the most important reason is that most
+ * of the time the contention will occur in high concurrent OLTP workload
+ * is going on and at that time most of the transactions would be generated
+ * during the same time and most of them would fall in same clog page as
+ * each page can hold status of 32k transactions. However, there is an
+ * exception where in some extreme conditions we might get different page
+ * requests added in the same group but we have handled that by switching
+ * the bank lock, although that is not the most performant way that's not
+ * the common case either so we are fine with that.
+ *
+ * Also to be noted that unless the leader of the current group does not
+ * get the lock we don't clear the 'procglobal->clogGroupFirst' that means
+ * concurrently if we get the requesters for different SLRU pages then
+ * those will have to go for the normal update instead of group update and
+ * that's fine as that is not the common case. As soon as the leader of
+ * the current group gets the lock for the required bank that time we clear
+ * this value and now other requesters (which might want to update a
+ * different page and that might fall into the different bank as well) are
+ * allowed to form a new group as the first group is now detached. So if
+ * the new group has a request for a different SLRU-bank lock then the
+ * group leader of this group might also get the lock while the first group
+ * is performing the update and these two groups can perform the group
+ * update concurrently but it is completely safe as these two leaders are
+ * operating on completely different SLRU pages and they both are holding
+ * their respective SLRU locks.
+ */
nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
while (true)
@@ -508,8 +550,17 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
return true;
}
- /* We are the leader. Acquire the lock on behalf of everyone. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ /*
+ * Acquire the SLRU bank lock for the first page in the group before we
+ * close this group by setting procglobal->clogGroupFirst as
+ * INVALID_PGPROCNO so that we do not close the new entries to the group
+ * even before getting the lock and losing whole purpose of the group
+ * update.
+ */
+ nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
+ prevpageno = ProcGlobal->allProcs[nextidx].clogGroupMemberPage;
+ prevlock = SimpleLruGetBankLock(XactCtl, prevpageno);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
/*
* Now that we've got the lock, clear the list of processes waiting for
@@ -526,6 +577,37 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
while (nextidx != INVALID_PGPROCNO)
{
PGPROC *nextproc = &ProcGlobal->allProcs[nextidx];
+ int thispageno = nextproc->clogGroupMemberPage;
+
+ /*
+ * If the SLRU bank lock for the current page is not the same as that
+ * of the last page then we need to release the lock on the previous
+ * bank and acquire the lock on the bank for the page we are going to
+ * update now.
+ *
+ * Although on the best effort basis we try that all the requests
+ * within a group are for the same clog page there are some
+ * possibilities that there are request for more than one page in the
+ * same group (for details refer to the comment in the previous while
+ * loop). That scenario might not be very performant because while
+ * switching the lock the group leader might need to wait on the new
+ * lock if the pages are from different SLRU bank but it is safe
+ * because a) we are releasing the old lock before acquiring the new
+ * lock so there is should not be any deadlock situation b) and, we
+ * are always modifying the page under the correct SLRU lock.
+ */
+ if (thispageno != prevpageno)
+ {
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, thispageno);
+
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ prevlock = lock;
+ prevpageno = thispageno;
+ }
/*
* Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs
@@ -545,7 +627,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
}
/* We're done with the lock now. */
- LWLockRelease(XactSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
/*
* Now that we've released the lock, go back and wake everybody up. We
@@ -574,7 +657,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
/*
* Sets the commit status of a single transaction.
*
- * Must be called with XactSLRULock held
+ * Must be called with slot specific SLRU bank's lock held
*/
static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
@@ -666,7 +749,7 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
lsnindex = GetLSNIndex(slotno, xid);
*lsn = XactCtl->shared->group_lsn[lsnindex];
- LWLockRelease(XactSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
return status;
}
@@ -700,8 +783,8 @@ CLOGShmemInit(void)
{
XactCtl->PagePrecedes = CLOGPagePrecedes;
SimpleLruInit(XactCtl, "Xact", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
- XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER,
- SYNC_HANDLER_CLOG, false);
+ "pg_xact", LWTRANCHE_XACT_BUFFER,
+ LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
}
@@ -715,8 +798,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, 0);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the commit log */
slotno = ZeroCLOGPage(0, false);
@@ -725,7 +809,7 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -760,14 +844,10 @@ StartupCLOG(void)
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
/*
* Initialize our idea of the latest page number.
*/
- XactCtl->shared->latest_page_number = pageno;
-
- LWLockRelease(XactSLRULock);
+ pg_atomic_init_u64(&XactCtl->shared->latest_page_number, pageno);
}
/*
@@ -778,8 +858,9 @@ TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* Zero out the remainder of the current clog page. Under normal
@@ -811,7 +892,7 @@ TrimCLOG(void)
XactCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -843,6 +924,7 @@ void
ExtendCLOG(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -853,13 +935,14 @@ ExtendCLOG(TransactionId newestXact)
return;
pageno = TransactionIdToPage(newestXact);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCLOGPage(pageno, true);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
@@ -997,16 +1080,18 @@ clog_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
else if (info == CLOG_TRUNCATE)
{
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 41337471e2..9e932a161b 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -228,8 +228,9 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
{
int slotno;
int i;
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid);
@@ -239,13 +240,13 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
CommitTsCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
* Sets the commit timestamp of a single transaction.
*
- * Must be called with CommitTsSLRULock held
+ * Must be called with slot specific SLRU bank's Lock held
*/
static void
TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
@@ -346,7 +347,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (nodeid)
*nodeid = entry.nodeid;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(CommitTsCtl, pageno));
return *ts != 0;
}
@@ -536,8 +537,8 @@ CommitTsShmemInit(void)
CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
SimpleLruInit(CommitTsCtl, "CommitTs", CommitTsShmemBuffers(), 0,
- CommitTsSLRULock, "pg_commit_ts",
- LWTRANCHE_COMMITTS_BUFFER,
+ "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
+ LWTRANCHE_COMMITTS_SLRU,
SYNC_HANDLER_COMMIT_TS,
false);
SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
@@ -695,9 +696,7 @@ ActivateCommitTs(void)
/*
* Re-Initialize our idea of the latest page number.
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
- CommitTsCtl->shared->latest_page_number = pageno;
- LWLockRelease(CommitTsSLRULock);
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number, pageno);
/*
* If CommitTs is enabled, but it wasn't in the previous server run, we
@@ -724,12 +723,13 @@ ActivateCommitTs(void)
if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno))
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/* Change the activation status in shared memory. */
@@ -778,9 +778,9 @@ DeactivateCommitTs(void)
* be overwritten anyway when we wrap around, but it seems better to be
* tidy.)
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ SimpleLruAcquireAllBankLock(CommitTsCtl, LW_EXCLUSIVE);
(void) SlruScanDirectory(CommitTsCtl, SlruScanDirCbDeleteAll, NULL);
- LWLockRelease(CommitTsSLRULock);
+ SimpleLruReleaseAllBankLock(CommitTsCtl);
}
/*
@@ -812,6 +812,7 @@ void
ExtendCommitTs(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -832,12 +833,14 @@ ExtendCommitTs(TransactionId newestXact)
pageno = TransactionIdToCTsPage(newestXact);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCommitTsPage(pageno, !InRecovery);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -991,16 +994,18 @@ commit_ts_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
else if (info == COMMIT_TS_TRUNCATE)
{
@@ -1012,7 +1017,8 @@ commit_ts_redo(XLogReaderState *record)
* During XLOG replay, latest_page_number isn't set up yet; insert a
* suitable value to bypass the sanity test in SimpleLruTruncate.
*/
- CommitTsCtl->shared->latest_page_number = trunc->pageno;
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number,
+ trunc->pageno);
SimpleLruTruncate(CommitTsCtl, trunc->pageno);
}
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index f8eceeac30..dbabc187b9 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -193,10 +193,10 @@ static SlruCtlData MultiXactMemberCtlData;
/*
* MultiXact state shared across all backends. All this state is protected
- * by MultiXactGenLock. (We also use MultiXactOffsetSLRULock and
- * MultiXactMemberSLRULock to guard accesses to the two sets of SLRU
- * buffers. For concurrency's sake, we avoid holding more than one of these
- * locks at a time.)
+ * by MultiXactGenLock. (We also use SLRU bank's lock of MultiXactOffset and
+ * MultiXactMember to guard accesses to the two sets of SLRU buffers. For
+ * concurrency's sake, we avoid holding more than one of these locks at a
+ * time.)
*/
typedef struct MultiXactStateData
{
@@ -871,12 +871,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int slotno;
MultiXactOffset *offptr;
int i;
-
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLock *lock;
+ LWLock *prevlock = NULL;
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+
/*
* Note: we pass the MultiXactId to SimpleLruReadPage as the "transaction"
* to complain about if there's any I/O error. This is kinda bogus, but
@@ -892,10 +895,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
- /* Exchange our lock */
- LWLockRelease(MultiXactOffsetSLRULock);
-
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ /* Release MultiXactOffset SLRU lock. */
+ LWLockRelease(lock);
prev_pageno = -1;
@@ -917,6 +918,20 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
if (pageno != prev_pageno)
{
+ /*
+ * MultiXactMember SLRU page is changed so check if this new page
+ * fall into the different SLRU bank then release the old bank's
+ * lock and acquire lock on the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -937,7 +952,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
}
/*
@@ -1240,6 +1256,8 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
+ LWLock *lock;
+ LWLock *prevlock = NULL;
debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
@@ -1343,11 +1361,22 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
* time on every multixact creation.
*/
retry:
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
-
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ /*
+ * If this page falls under a different bank, release the old bank's lock
+ * and acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1380,7 +1409,21 @@ retry:
entryno = MultiXactIdToOffsetEntry(tmpMXact);
if (pageno != prev_pageno)
+ {
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact);
+ }
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1389,7 +1432,8 @@ retry:
if (nextMXOffset == 0)
{
/* Corner case 2: next multixact is still being filled in */
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
CHECK_FOR_INTERRUPTS();
pg_usleep(1000L);
goto retry;
@@ -1398,13 +1442,11 @@ retry:
length = nextMXOffset - offset;
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
- /* Now get the members themselves. */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
-
truelength = 0;
prev_pageno = -1;
for (i = 0; i < length; i++, offset++)
@@ -1420,6 +1462,20 @@ retry:
if (pageno != prev_pageno)
{
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -1443,7 +1499,8 @@ retry:
truelength++;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock)
+ LWLockRelease(prevlock);
/* A multixid with zero members should not happen */
Assert(truelength > 0);
@@ -1853,15 +1910,15 @@ MultiXactShmemInit(void)
SimpleLruInit(MultiXactOffsetCtl,
"MultiXactOffset", multixact_offsets_buffers, 0,
- MultiXactOffsetSLRULock, "pg_multixact/offsets",
- LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
"MultiXactMember", multixact_members_buffers, 0,
- MultiXactMemberSLRULock, "pg_multixact/members",
- LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ "pg_multixact/members", LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
SYNC_HANDLER_MULTIXACT_MEMBER,
false);
/* doesn't call SimpleLruTruncate() or meet criteria for unit tests */
@@ -1897,8 +1954,10 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ LWLock *lock;
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the offsets log */
slotno = ZeroMultiXactOffsetPage(0, false);
@@ -1907,9 +1966,10 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the members log */
slotno = ZeroMultiXactMemberPage(0, false);
@@ -1918,7 +1978,7 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -1978,10 +2038,12 @@ static void
MaybeExtendOffsetSlru(void)
{
int64 pageno;
+ LWLock *lock;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno))
{
@@ -1996,7 +2058,7 @@ MaybeExtendOffsetSlru(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2018,13 +2080,15 @@ StartupMultiXact(void)
* Initialize offset's idea of the latest page number.
*/
pageno = MultiXactIdToOffsetPage(multi);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Initialize member's idea of the latest page number.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
}
/*
@@ -2049,13 +2113,13 @@ TrimMultiXact(void)
LWLockRelease(MultiXactGenLock);
/* Clean up offsets state */
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for offsets.
*/
pageno = MultiXactIdToOffsetPage(nextMXact);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Zero out the remainder of the current offsets page. See notes in
@@ -2070,7 +2134,9 @@ TrimMultiXact(void)
{
int slotno;
MultiXactOffset *offptr;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -2078,18 +2144,17 @@ TrimMultiXact(void)
MemSet(offptr, 0, BLCKSZ - (entryno * sizeof(MultiXactOffset)));
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactOffsetSLRULock);
-
/* And the same for members */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for members.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
/*
* Zero out the remainder of the current members page. See notes in
@@ -2101,7 +2166,9 @@ TrimMultiXact(void)
int slotno;
TransactionId *xidptr;
int memberoff;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
memberoff = MXOffsetToMemberOffset(offset);
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset);
xidptr = (TransactionId *)
@@ -2116,10 +2183,9 @@ TrimMultiXact(void)
*/
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactMemberSLRULock);
-
/* signal that we're officially up */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->finishedStartup = true;
@@ -2407,6 +2473,7 @@ static void
ExtendMultiXactOffset(MultiXactId multi)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2417,13 +2484,14 @@ ExtendMultiXactOffset(MultiXactId multi)
return;
pageno = MultiXactIdToOffsetPage(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactOffsetPage(pageno, true);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2456,15 +2524,17 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
if (flagsoff == 0 && flagsbit == 0)
{
int64 pageno;
+ LWLock *lock;
pageno = MXOffsetToMemberPage(offset);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactMemberPage(pageno, true);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2762,7 +2832,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
offset = *offptr;
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(MultiXactOffsetCtl, pageno));
*result = offset;
return true;
@@ -3244,31 +3314,35 @@ multixact_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactOffsetPage(pageno, false);
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactMemberPage(pageno, false);
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
@@ -3334,7 +3408,8 @@ multixact_redo(XLogReaderState *record)
* SimpleLruTruncate.
*/
pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);
LWLockRelease(MultiXactTruncationLock);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 211527b075..33670f7cfe 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -97,6 +97,21 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
*/
#define MAX_WRITEALL_BUFFERS 16
+/*
+ * Macro to get the index of lock for a given slotno in bank_lock array in
+ * SlruSharedData.
+ *
+ * Basically, the slru buffer pool is divided into banks of buffer and there is
+ * total SLRU_MAX_BANKLOCKS number of locks to protect access to buffer in the
+ * banks. Since we have max limit on the number of locks we can not always have
+ * one lock for each bank. So until the number of banks are
+ * <= SLRU_MAX_BANKLOCKS then there would be one lock protecting each bank
+ * otherwise one lock might protect multiple banks based on the number of
+ * banks.
+ */
+#define SLRU_SLOTNO_GET_BANKLOCKNO(slotno) \
+ (((slotno) / SLRU_BANK_SIZE) % SLRU_MAX_BANKLOCKS)
+
typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
@@ -118,34 +133,6 @@ typedef struct SlruWriteAllData *SlruWriteAll;
(a).segno = (xx_segno) \
)
-/*
- * Macro to mark a buffer slot "most recently used". Note multiple evaluation
- * of arguments!
- *
- * The reason for the if-test is that there are often many consecutive
- * accesses to the same page (particularly the latest page). By suppressing
- * useless increments of cur_lru_count, we reduce the probability that old
- * pages' counts will "wrap around" and make them appear recently used.
- *
- * We allow this code to be executed concurrently by multiple processes within
- * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
- * this should not cause any completely-bogus values to enter the computation.
- * However, it is possible for either cur_lru_count or individual
- * page_lru_count entries to be "reset" to lower values than they should have,
- * in case a process is delayed while it executes this macro. With care in
- * SlruSelectLRUPage(), this does little harm, and in any case the absolute
- * worst possible consequence is a nonoptimal choice of page to evict. The
- * gain from allowing concurrent reads of SLRU pages seems worth it.
- */
-#define SlruRecentlyUsed(shared, slotno) \
- do { \
- int new_lru_count = (shared)->cur_lru_count; \
- if (new_lru_count != (shared)->page_lru_count[slotno]) { \
- (shared)->cur_lru_count = ++new_lru_count; \
- (shared)->page_lru_count[slotno] = new_lru_count; \
- } \
- } while (0)
-
/* Saved info for SlruReportIOError */
typedef enum
{
@@ -173,6 +160,7 @@ static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
int64 segpage, void *data);
static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
/*
@@ -183,6 +171,8 @@ Size
SimpleLruShmemSize(int nslots, int nlsns)
{
Size sz;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
/* we assume nslots isn't so large as to risk overflow */
sz = MAXALIGN(sizeof(SlruSharedData));
@@ -192,6 +182,8 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
+ sz += MAXALIGN(nbanklocks * sizeof(LWLockPadded)); /* bank_locks[] */
+ sz += MAXALIGN(nbanks * sizeof(int)); /* bank_cur_lru_count[] */
if (nlsns > 0)
sz += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr)); /* group_lsn[] */
@@ -208,16 +200,19 @@ SimpleLruShmemSize(int nslots, int nlsns)
* nlsns: number of LSN groups per page (set to zero if not relevant).
* ctllock: LWLock to use to control access to the shared control structure.
* subdir: PGDATA-relative subdirectory that will contain the files.
- * tranche_id: LWLock tranche ID to use for the SLRU's per-buffer LWLocks.
+ * buffer_tranche_id: tranche ID to use for the SLRU's per-buffer LWLocks.
+ * bank_tranche_id: tranche ID to use for the bank LWLocks.
* sync_handler: which set of functions to use to handle sync requests
*/
void
SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
+ const char *subdir, int buffer_tranche_id, int bank_tranche_id,
SyncRequestHandler sync_handler, bool long_segment_names)
{
SlruShared shared;
bool found;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
shared = (SlruShared) ShmemInitStruct(name,
SimpleLruShmemSize(nslots, nlsns),
@@ -229,18 +224,16 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
char *ptr;
Size offset;
int slotno;
+ int bankno;
+ int banklockno;
Assert(!found);
memset(shared, 0, sizeof(SlruSharedData));
- shared->ControlLock = ctllock;
-
shared->num_slots = nslots;
shared->lsn_groups_per_page = nlsns;
- shared->cur_lru_count = 0;
-
/* shared->latest_page_number will be set later */
shared->slru_stats_idx = pgstat_get_slru_index(name);
@@ -261,6 +254,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
/* Initialize LWLocks */
shared->buffer_locks = (LWLockPadded *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(LWLockPadded));
+ shared->bank_locks = (LWLockPadded *) (ptr + offset);
+ offset += MAXALIGN(nbanklocks * sizeof(LWLockPadded));
+ shared->bank_cur_lru_count = (int *) (ptr + offset);
+ offset += MAXALIGN(nbanks * sizeof(int));
if (nlsns > 0)
{
@@ -272,7 +269,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
for (slotno = 0; slotno < nslots; slotno++)
{
LWLockInitialize(&shared->buffer_locks[slotno].lock,
- tranche_id);
+ buffer_tranche_id);
shared->page_buffer[slotno] = ptr;
shared->page_status[slotno] = SLRU_PAGE_EMPTY;
@@ -281,6 +278,15 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ptr += BLCKSZ;
}
+ /* Initialize the bank locks. */
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockInitialize(&shared->bank_locks[banklockno].lock,
+ bank_tranche_id);
+
+ /* Initialize the bank lru counters. */
+ for (bankno = 0; bankno < nbanks; bankno++)
+ shared->bank_cur_lru_count[bankno] = 0;
+
/* Should fit to estimated shmem size */
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
@@ -335,7 +341,7 @@ SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
SimpleLruZeroLSNs(ctl, slotno);
/* Assume this page is now the latest active page */
- shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&shared->latest_page_number, pageno);
/* update the stats counter of zeroed pages */
pgstat_count_slru_page_zeroed(shared->slru_stats_idx);
@@ -374,12 +380,13 @@ static void
SimpleLruWaitIO(SlruCtl ctl, int slotno)
{
SlruShared shared = ctl->shared;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* See notes at top of file */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);
LWLockRelease(&shared->buffer_locks[slotno].lock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
/*
* If the slot is still in an io-in-progress state, then either someone
@@ -430,10 +437,14 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
{
SlruShared shared = ctl->shared;
+ /* Caller must hold the bank lock for the input page. */
+ Assert(LWLockHeldByMe(SimpleLruGetBankLock(ctl, pageno)));
+
/* Outer loop handles restart if we must wait for someone else's I/O */
for (;;)
{
int slotno;
+ int banklockno;
bool ok;
/* See if page already is in memory; if not, pick victim slot */
@@ -476,9 +487,10 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
/* Acquire per-buffer lock (cannot deadlock, see notes at top) */
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
+ banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the read */
ok = SlruPhysicalReadPage(ctl, pageno, slotno);
@@ -487,7 +499,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
SimpleLruZeroLSNs(ctl, slotno);
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_READ_IN_PROGRESS &&
@@ -531,9 +543,10 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
int slotno;
int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
int bankend = bankstart + SLRU_BANK_SIZE;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(bankstart);
/* Try to find the page while holding only shared lock */
- LWLockAcquire(shared->ControlLock, LW_SHARED);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_SHARED);
/*
* See if the page is already in a buffer pool. The buffer pool is
@@ -557,8 +570,8 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
}
/* No luck, so switch to normal exclusive lock and do regular read */
- LWLockRelease(shared->ControlLock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
return SimpleLruReadPage(ctl, pageno, true, xid);
}
@@ -580,6 +593,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
SlruShared shared = ctl->shared;
int64 pageno = shared->page_number[slotno];
bool ok;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* If a write is in progress, wait for it to finish */
while (shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS &&
@@ -608,7 +622,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the write */
ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
@@ -623,7 +637,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
}
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS);
@@ -1067,13 +1081,14 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
- int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankno = pageno & ctl->bank_mask;
+ int bankstart = bankno * SLRU_BANK_SIZE;
int bankend = bankstart + SLRU_BANK_SIZE;
/*
* See if the page is already in a buffer pool. The buffer pool is
- * divided into banks of buffers and each pageno may reside only in one
- * bank so limit the search within the bank.
+ * divided into banks of buffers and each pageno may reside only in
+ * one bank so limit the search within the bank.
*/
for (slotno = bankstart; slotno < bankend; slotno++)
{
@@ -1109,7 +1124,7 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* That gets us back on the path to having good data when there are
* multiple pages with the same lru_count.
*/
- cur_count = (shared->cur_lru_count)++;
+ cur_count = (shared->bank_cur_lru_count[bankno])++;
for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
@@ -1131,7 +1146,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
this_delta = 0;
}
this_page_number = shared->page_number[slotno];
- if (this_page_number == shared->latest_page_number)
+ if (this_page_number ==
+ pg_atomic_read_u64(&shared->latest_page_number))
continue;
if (shared->page_status[slotno] == SLRU_PAGE_VALID)
{
@@ -1205,6 +1221,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
int slotno;
int64 pageno = 0;
int i;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
bool ok;
/* update the stats counter of flushes */
@@ -1215,10 +1232,23 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
*/
fdata.num_files = 0;
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
SlruInternalWritePage(ctl, slotno, &fdata);
/*
@@ -1232,7 +1262,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
!shared->page_dirty[slotno]));
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/*
* Now close any files that were open
@@ -1272,6 +1302,7 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
+ int prevlockno;
/* update the stats counter of truncates */
pgstat_count_slru_truncate(shared->slru_stats_idx);
@@ -1282,25 +1313,38 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
* or just after a checkpoint, any dirty pages should have been flushed
* already ... we're just being extra careful here.)
*/
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
-
restart:
/*
* While we are holding the lock, make an important safety check: the
* current endpoint page must not be eligible for removal.
*/
- if (ctl->PagePrecedes(shared->latest_page_number, cutoffPage))
+ if (ctl->PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
+ cutoffPage))
{
- LWLockRelease(shared->ControlLock);
ereport(LOG,
(errmsg("could not truncate directory \"%s\": apparent wraparound",
ctl->Dir)));
return;
}
+ prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
if (!ctl->PagePrecedes(shared->page_number[slotno], cutoffPage))
@@ -1330,10 +1374,12 @@ restart:
SlruInternalWritePage(ctl, slotno, NULL);
else
SimpleLruWaitIO(ctl, slotno);
+
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
goto restart;
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/* Now we can remove the old segment(s) */
(void) SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, &cutoffPage);
@@ -1374,15 +1420,29 @@ SlruDeleteSegment(SlruCtl ctl, int64 segno)
SlruShared shared = ctl->shared;
int slotno;
bool did_write;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
/* Clean out any possibly existing references to the segment. */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
restart:
did_write = false;
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
- int pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
+ int pagesegno;
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
+ pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
@@ -1416,7 +1476,7 @@ restart:
SlruInternalDeleteSegment(ctl, segno);
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
}
/*
@@ -1684,6 +1744,37 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
return result;
}
+/*
+ * Function to mark a buffer slot "most recently used".
+ *
+ * The reason for the if-test is that there are often many consecutive
+ * accesses to the same page (particularly the latest page). By suppressing
+ * useless increments of bank_cur_lru_count, we reduce the probability that old
+ * pages' counts will "wrap around" and make them appear recently used.
+ *
+ * We allow this code to be executed concurrently by multiple processes within
+ * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
+ * this should not cause any completely-bogus values to enter the computation.
+ * However, it is possible for either bank_cur_lru_count or individual
+ * page_lru_count entries to be "reset" to lower values than they should have,
+ * in case a process is delayed while it executes this function. With care in
+ * SlruSelectLRUPage(), this does little harm, and in any case the absolute
+ * worst possible consequence is a nonoptimal choice of page to evict. The
+ * gain from allowing concurrent reads of SLRU pages seems worth it.
+ */
+static inline void
+SlruRecentlyUsed(SlruShared shared, int slotno)
+{
+ int bankno = slotno / SLRU_BANK_SIZE;
+ int new_lru_count = shared->bank_cur_lru_count[bankno];
+
+ if (new_lru_count != shared->page_lru_count[slotno])
+ {
+ shared->bank_cur_lru_count[bankno] = ++new_lru_count;
+ shared->page_lru_count[slotno] = new_lru_count;
+ }
+}
+
/*
* Helper function for GUC check_hook to check whether slru buffers are in
* multiples of SLRU_BANK_SIZE.
@@ -1700,3 +1791,37 @@ check_slru_buffers(const char *name, int *newval)
SLRU_BANK_SIZE);
return false;
}
+
+/*
+ * Function to acquire all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, mode);
+}
+
+/*
+ * Function to release all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruReleaseAllBankLock(SlruCtl ctl)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 82243c2728..c55d709846 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -87,12 +87,14 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
+ LWLock *lock;
TransactionId *ptr;
Assert(TransactionIdIsValid(parent));
Assert(TransactionIdFollows(xid, parent));
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
@@ -110,7 +112,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
SubTransCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -140,7 +142,7 @@ SubTransGetParent(TransactionId xid)
parent = *ptr;
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
return parent;
}
@@ -203,9 +205,8 @@ SUBTRANSShmemInit(void)
{
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
SimpleLruInit(SubTransCtl, "Subtrans", subtrans_buffers, 0,
- SubtransSLRULock, "pg_subtrans",
- LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
- false);
+ "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
+ LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
}
@@ -223,8 +224,9 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
slotno = ZeroSUBTRANSPage(0);
@@ -233,7 +235,7 @@ BootStrapSUBTRANS(void)
SimpleLruWritePage(SubTransCtl, slotno);
Assert(!SubTransCtl->shared->page_dirty[slotno]);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -263,6 +265,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
FullTransactionId nextXid;
int64 startPage;
int64 endPage;
+ LWLock *prevlock;
+ LWLock *lock;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -270,23 +274,47 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
* Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
* the new page without regard to whatever was previously on disk.
*/
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
-
startPage = TransactionIdToPage(oldestActiveXID);
nextXid = TransamVariables->nextXid;
endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
+ prevlock = SimpleLruGetBankLock(SubTransCtl, startPage);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
while (startPage != endPage)
{
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release
+ * the lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
(void) ZeroSUBTRANSPage(startPage);
startPage++;
/* must account for wraparound */
if (startPage > TransactionIdToPage(MaxTransactionId))
startPage = 0;
}
- (void) ZeroSUBTRANSPage(startPage);
- LWLockRelease(SubtransSLRULock);
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release the
+ * lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ (void) ZeroSUBTRANSPage(startPage);
+ LWLockRelease(lock);
}
/*
@@ -320,6 +348,7 @@ void
ExtendSUBTRANS(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -331,12 +360,13 @@ ExtendSUBTRANS(TransactionId newestXact)
pageno = TransactionIdToPage(newestXact);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page */
ZeroSUBTRANSPage(pageno);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 9059c0a202..0c2ac60946 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -267,9 +267,10 @@ typedef struct QueueBackendStatus
* both NotifyQueueLock and NotifyQueueTailLock in EXCLUSIVE mode, backends
* can change the tail pointers.
*
- * NotifySLRULock is used as the control lock for the pg_notify SLRU buffers.
+ * SLRU buffer pool is divided in banks and bank wise SLRU lock is used as
+ * the control lock for the pg_notify SLRU buffers.
* In order to avoid deadlocks, whenever we need multiple locks, we first get
- * NotifyQueueTailLock, then NotifyQueueLock, and lastly NotifySLRULock.
+ * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock.
*
* Each backend uses the backend[] array entry with index equal to its
* BackendId (which can range from 1 to MaxBackends). We rely on this to make
@@ -543,7 +544,7 @@ AsyncShmemInit(void)
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
- NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
+ "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
SYNC_HANDLER_NONE, true);
if (!found)
@@ -1357,7 +1358,7 @@ asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe)
* Eventually we will return NULL indicating all is done.
*
* We are holding NotifyQueueLock already from the caller and grab
- * NotifySLRULock locally in this function.
+ * page specific SLRU bank lock locally in this function.
*/
static ListCell *
asyncQueueAddEntries(ListCell *nextNotify)
@@ -1367,9 +1368,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
int64 pageno;
int offset;
int slotno;
-
- /* We hold both NotifyQueueLock and NotifySLRULock during this operation */
- LWLockAcquire(NotifySLRULock, LW_EXCLUSIVE);
+ LWLock *prevlock;
/*
* We work with a local copy of QUEUE_HEAD, which we write back to shared
@@ -1390,6 +1389,11 @@ asyncQueueAddEntries(ListCell *nextNotify)
* page should be initialized already, so just fetch it.
*/
pageno = QUEUE_POS_PAGE(queue_head);
+ prevlock = SimpleLruGetBankLock(NotifyCtl, pageno);
+
+ /* We hold both NotifyQueueLock and SLRU bank lock during this operation */
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
+
if (QUEUE_POS_IS_ZERO(queue_head))
slotno = SimpleLruZeroPage(NotifyCtl, pageno);
else
@@ -1435,6 +1439,17 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Advance queue_head appropriately, and detect if page is full */
if (asyncQueueAdvance(&(queue_head), qe.length))
{
+ LWLock *lock;
+
+ pageno = QUEUE_POS_PAGE(queue_head);
+ lock = SimpleLruGetBankLock(NotifyCtl, pageno);
+ if (lock != prevlock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
/*
* Page is full, so we're done here, but first fill the next page
* with zeroes. The reason to do this is to ensure that slru.c's
@@ -1461,7 +1476,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Success, so update the global QUEUE_HEAD */
QUEUE_HEAD = queue_head;
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(prevlock);
return nextNotify;
}
@@ -1932,9 +1947,9 @@ asyncQueueReadAllNotifications(void)
/*
* We copy the data from SLRU into a local buffer, so as to avoid
- * holding the NotifySLRULock while we are examining the entries
- * and possibly transmitting them to our frontend. Copy only the
- * part of the page we will actually inspect.
+ * holding the SLRU lock while we are examining the entries and
+ * possibly transmitting them to our frontend. Copy only the part
+ * of the page we will actually inspect.
*/
slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage,
InvalidTransactionId);
@@ -1954,7 +1969,7 @@ asyncQueueReadAllNotifications(void)
NotifyCtl->shared->page_buffer[slotno] + curoffset,
copysize);
/* Release lock that we got from SimpleLruReadPage_ReadOnly() */
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(SimpleLruGetBankLock(NotifyCtl, curpage));
/*
* Process messages up to the stop position, end of page, or an
@@ -1995,7 +2010,7 @@ asyncQueueReadAllNotifications(void)
*
* The current page must have been fetched into page_buffer from shared
* memory. (We could access the page right in shared memory, but that
- * would imply holding the NotifySLRULock throughout this routine.)
+ * would imply holding the SLRU bank lock throughout this routine.)
*
* We stop if we reach the "stop" position, or reach a notification from an
* uncommitted transaction, or reach the end of the page.
@@ -2148,7 +2163,7 @@ asyncQueueAdvanceTail(void)
if (asyncQueuePagePrecedes(oldtailpage, boundary))
{
/*
- * SimpleLruTruncate() will ask for NotifySLRULock but will also
+ * SimpleLruTruncate() will ask for SLRU bank locks but will also
* release the lock again.
*/
SimpleLruTruncate(NotifyCtl, newtailpage);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b4b989ac56..a4f881f34d 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -190,6 +190,20 @@ static const char *const BuiltinTrancheNames[] = {
"LogicalRepLauncherDSA",
/* LWTRANCHE_LAUNCHER_HASH: */
"LogicalRepLauncherHash",
+ /* LWTRANCHE_XACT_SLRU: */
+ "XactSLRU",
+ /* LWTRANCHE_COMMITTS_SLRU: */
+ "CommitTSSLRU",
+ /* LWTRANCHE_SUBTRANS_SLRU: */
+ "SubtransSLRU",
+ /* LWTRANCHE_MULTIXACTOFFSET_SLRU: */
+ "MultixactOffsetSLRU",
+ /* LWTRANCHE_MULTIXACTMEMBER_SLRU: */
+ "MultixactMemberSLRU",
+ /* LWTRANCHE_NOTIFY_SLRU: */
+ "NotifySLRU",
+ /* LWTRANCHE_SERIAL_SLRU: */
+ "SerialSLRU"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index d621f5507f..bcfe82a359 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -16,11 +16,11 @@ WALBufMappingLock 7
WALWriteLock 8
ControlFileLock 9
# 10 was CheckpointLock
-XactSLRULock 11
-SubtransSLRULock 12
+# 11 was XactSLRULock
+# 12 was SubtransSLRULock
MultiXactGenLock 13
-MultiXactOffsetSLRULock 14
-MultiXactMemberSLRULock 15
+# 14 was MultiXactOffsetSLRULock
+# 15 was MultiXactMemberSLRULock
RelCacheInitLock 16
CheckpointerCommLock 17
TwoPhaseStateLock 18
@@ -31,19 +31,19 @@ AutovacuumLock 22
AutovacuumScheduleLock 23
SyncScanLock 24
RelationMappingLock 25
-NotifySLRULock 26
+#26 was NotifySLRULock
NotifyQueueLock 27
SerializableXactHashLock 28
SerializableFinishedListLock 29
SerializablePredicateListLock 30
-SerialSLRULock 31
+SerialControlLock 31
SyncRepLock 32
BackgroundWorkerLock 33
DynamicSharedMemoryControlLock 34
AutoFileLock 35
ReplicationSlotAllocationLock 36
ReplicationSlotControlLock 37
-CommitTsSLRULock 38
+#38 was CommitTsSLRULock
CommitTsLock 39
ReplicationOriginLock 40
MultiXactTruncationLock 41
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 10c51e2883..ea4392ab15 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -809,9 +809,9 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- serial_buffers, 0, SerialSLRULock, "pg_serial",
- LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
- false);
+ serial_buffers, 0, "pg_serial",
+ LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
+ SYNC_HANDLER_NONE, false);
#ifdef USE_ASSERT_CHECKING
SerialPagePrecedesLogicallyUnitTests();
#endif
@@ -848,12 +848,14 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
int slotno;
int64 firstZeroPage;
bool isNewPage;
+ LWLock *lock;
Assert(TransactionIdIsValid(xid));
targetPage = SerialPage(xid);
+ lock = SimpleLruGetBankLock(SerialSlruCtl, targetPage);
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* If no serializable transactions are active, there shouldn't be anything
@@ -903,7 +905,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
SerialValue(slotno, xid) = minConflictCommitSeqNo;
SerialSlruCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -921,10 +923,10 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
Assert(TransactionIdIsValid(xid));
- LWLockAcquire(SerialSLRULock, LW_SHARED);
+ LWLockAcquire(SerialControlLock, LW_SHARED);
headXid = serialControl->headXid;
tailXid = serialControl->tailXid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
if (!TransactionIdIsValid(headXid))
return 0;
@@ -936,13 +938,13 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
return 0;
/*
- * The following function must be called without holding SerialSLRULock,
+ * The following function must be called without holding SLRU bank lock,
* but will return with that lock held, which must then be released.
*/
slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
SerialPage(xid), xid);
val = SerialValue(slotno, xid);
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SerialSlruCtl, SerialPage(xid)));
return val;
}
@@ -955,7 +957,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
static void
SerialSetActiveSerXmin(TransactionId xid)
{
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/*
* When no sxacts are active, nothing overlaps, set the xid values to
@@ -967,7 +969,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = InvalidTransactionId;
serialControl->headXid = InvalidTransactionId;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -985,7 +987,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = xid;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -994,7 +996,7 @@ SerialSetActiveSerXmin(TransactionId xid)
serialControl->tailXid = xid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
}
/*
@@ -1008,12 +1010,12 @@ CheckPointPredicate(void)
{
int truncateCutoffPage;
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/* Exit quickly if the SLRU is currently not in use. */
if (serialControl->headPage < 0)
{
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -1073,7 +1075,7 @@ CheckPointPredicate(void)
serialControl->headPage = -1;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
/* Truncate away pages that are no longer required */
SimpleLruTruncate(SerialSlruCtl, truncateCutoffPage);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index f625473ad4..afa18ce54a 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -292,11 +292,7 @@ SInvalWrite "Waiting to add a message to the shared catalog invalidation queue."
WALBufMapping "Waiting to replace a page in WAL buffers."
WALWrite "Waiting for WAL buffers to be written to disk."
ControlFile "Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
-XactSLRU "Waiting to access the transaction status SLRU cache."
-SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
MultiXactGen "Waiting to read or update shared multixact state."
-MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
-MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
RelCacheInit "Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
CheckpointerComm "Waiting to manage fsync requests."
TwoPhaseState "Waiting to read or update the state of prepared transactions."
@@ -307,19 +303,17 @@ Autovacuum "Waiting to read or update the current state of autovacuum workers."
AutovacuumSchedule "Waiting to ensure that a table selected for autovacuum still needs vacuuming."
SyncScan "Waiting to select the starting location of a synchronized table scan."
RelationMapping "Waiting to read or update a <filename>pg_filenode.map</filename> file (used to track the filenode assignments of certain system catalogs)."
-NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
NotifyQueue "Waiting to read or update <command>NOTIFY</command> messages."
SerializableXactHash "Waiting to read or update information about serializable transactions."
SerializableFinishedList "Waiting to access the list of finished serializable transactions."
SerializablePredicateList "Waiting to access the list of predicate locks held by serializable transactions."
-SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SerialControl "Waiting to access the serializable transaction conflict SLRU cache."
SyncRep "Waiting to read or update information about the state of synchronous replication."
BackgroundWorker "Waiting to read or update background worker state."
DynamicSharedMemoryControl "Waiting to read or update dynamic shared memory allocation information."
AutoFile "Waiting to update the <filename>postgresql.auto.conf</filename> file."
ReplicationSlotAllocation "Waiting to allocate or free a replication slot."
ReplicationSlotControl "Waiting to read or update replication slot state."
-CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
CommitTs "Waiting to read or update the last value set for a transaction commit timestamp."
ReplicationOrigin "Waiting to create, drop or use a replication origin."
MultiXactTruncation "Waiting to read or truncate multixact information."
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 2b74e11d42..46767f6f84 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -25,6 +25,14 @@
*/
#define SLRU_BANK_SIZE 16
+/*
+ * Number of bank locks to protect the in memory buffer slot access within a
+ * SLRU bank. If the number of banks are <= SLRU_MAX_BANKLOCKS then there will
+ * be one lock per bank otherwise each lock will protect multiple banks depends
+ * upon the number of banks.
+ */
+#define SLRU_MAX_BANKLOCKS 128
+
/*
* To avoid overflowing internal arithmetic and the size_t data type, the
* number of buffers should not exceed this number.
@@ -65,8 +73,6 @@ typedef enum
*/
typedef struct SlruSharedData
{
- LWLock *ControlLock;
-
/* Number of buffers managed by this SLRU structure */
int num_slots;
@@ -79,8 +85,30 @@ typedef struct SlruSharedData
bool *page_dirty;
int64 *page_number;
int *page_lru_count;
+
+ /* The buffer_locks protects the I/O on each buffer slots */
LWLockPadded *buffer_locks;
+ /* Locks to protect the in memory buffer slot access in SLRU bank. */
+ LWLockPadded *bank_locks;
+
+ /*----------
+ * A bank-wise LRU counter is maintained because we do a victim buffer
+ * search within a bank. Furthermore, manipulating an individual bank
+ * counter avoids frequent cache invalidation since we update it every time
+ * we access the page.
+ *
+ * We mark a page "most recently used" by setting
+ * page_lru_count[slotno] = ++bank_cur_lru_count[bankno];
+ * The oldest page in the bank is therefore the one with the highest value
+ * of
+ * bank_cur_lru_count[bankno] - page_lru_count[slotno]
+ * The counts will eventually wrap around, but this calculation still
+ * works as long as no page's age exceeds INT_MAX counts.
+ *----------
+ */
+ int *bank_cur_lru_count;
+
/*
* Optional array of WAL flush LSNs associated with entries in the SLRU
* pages. If not zero/NULL, we must flush WAL before writing pages (true
@@ -92,23 +120,12 @@ typedef struct SlruSharedData
XLogRecPtr *group_lsn;
int lsn_groups_per_page;
- /*----------
- * We mark a page "most recently used" by setting
- * page_lru_count[slotno] = ++cur_lru_count;
- * The oldest page is therefore the one with the highest value of
- * cur_lru_count - page_lru_count[slotno]
- * The counts will eventually wrap around, but this calculation still
- * works as long as no page's age exceeds INT_MAX counts.
- *----------
- */
- int cur_lru_count;
-
/*
* latest_page_number is the page number of the current end of the log;
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int64 latest_page_number;
+ pg_atomic_uint64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -165,11 +182,24 @@ typedef struct SlruCtlData
typedef SlruCtlData *SlruCtl;
+/*
+ * Get the SLRU bank lock for given SlruCtl and the pageno.
+ *
+ * This lock needs to be acquired to access the slru buffer slots in the
+ * respective bank.
+ */
+static inline LWLock *
+SimpleLruGetBankLock(SlruCtl ctl, int64 pageno)
+{
+ int banklockno = (pageno & ctl->bank_mask) % SLRU_MAX_BANKLOCKS;
+
+ return &(ctl->shared->bank_locks[banklockno].lock);
+}
extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
- SyncRequestHandler sync_handler,
+ const char *subdir, int buffer_tranche_id,
+ int bank_tranche_id, SyncRequestHandler sync_handler,
bool long_segment_names);
extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
@@ -199,5 +229,7 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
extern bool check_slru_buffers(const char *name, int *newval);
+extern void SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode);
+extern void SimpleLruReleaseAllBankLock(SlruCtl ctl);
#endif /* SLRU_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 167ae34208..d6bfcb5aa3 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -207,6 +207,13 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_PGSTATS_DATA,
LWTRANCHE_LAUNCHER_DSA,
LWTRANCHE_LAUNCHER_HASH,
+ LWTRANCHE_XACT_SLRU,
+ LWTRANCHE_COMMITTS_SLRU,
+ LWTRANCHE_SUBTRANS_SLRU,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
+ LWTRANCHE_NOTIFY_SLRU,
+ LWTRANCHE_SERIAL_SLRU,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 4b31f331ca..068a21f125 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -40,10 +40,6 @@ PG_FUNCTION_INFO_V1(test_slru_delete_all);
/* Number of SLRU page slots */
#define NUM_TEST_BUFFERS 16
-/* SLRU control lock */
-LWLock TestSLRULock;
-#define TestSLRULock (&TestSLRULock)
-
static SlruCtlData TestSlruCtlData;
#define TestSlruCtl (&TestSlruCtlData)
@@ -63,9 +59,9 @@ test_slru_page_write(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = text_to_cstring(PG_GETARG_TEXT_PP(1));
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
-
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruZeroPage(TestSlruCtl, pageno);
/* these should match */
@@ -80,7 +76,7 @@ test_slru_page_write(PG_FUNCTION_ARGS)
BLCKSZ - 1);
SimpleLruWritePage(TestSlruCtl, slotno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_VOID();
}
@@ -99,13 +95,14 @@ test_slru_page_read(PG_FUNCTION_ARGS)
bool write_ok = PG_GETARG_BOOL(1);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(TestSlruCtl, pageno,
write_ok, InvalidTransactionId);
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -116,14 +113,15 @@ test_slru_page_readonly(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
pageno,
InvalidTransactionId);
- Assert(LWLockHeldByMe(TestSLRULock));
+ Assert(LWLockHeldByMe(lock));
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -133,10 +131,11 @@ test_slru_page_exists(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool found;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
found = SimpleLruDoesPhysicalPageExist(TestSlruCtl, pageno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_BOOL(found);
}
@@ -221,6 +220,7 @@ test_slru_shmem_startup(void)
const bool long_segment_names = true;
const char slru_dir_name[] = "pg_test_slru";
int test_tranche_id;
+ int test_buffer_tranche_id;
if (prev_shmem_startup_hook)
prev_shmem_startup_hook();
@@ -234,12 +234,15 @@ test_slru_shmem_startup(void)
/* initialize the SLRU facility */
test_tranche_id = LWLockNewTrancheId();
LWLockRegisterTranche(test_tranche_id, "test_slru_tranche");
- LWLockInitialize(TestSLRULock, test_tranche_id);
+
+ test_buffer_tranche_id = LWLockNewTrancheId();
+ LWLockRegisterTranche(test_tranche_id, "test_buffer_tranche");
TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
SimpleLruInit(TestSlruCtl, "TestSLRU",
- NUM_TEST_BUFFERS, 0, TestSLRULock, slru_dir_name,
- test_tranche_id, SYNC_HANDLER_NONE, long_segment_names);
+ NUM_TEST_BUFFERS, 0, slru_dir_name,
+ test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
+ long_segment_names);
}
void
--
2.39.2 (Apple Git-143)
[application/octet-stream] v13-0001-Make-all-SLRU-buffer-sizes-configurable.patch (23.9K, ../../CAFiTN-sGOOZC5E5WsKXXdW9Cj_k4=SKXy7mrwyitpkDQ1Gs2bw@mail.gmail.com/4-v13-0001-Make-all-SLRU-buffer-sizes-configurable.patch)
download | inline diff:
From 5c34c8fd74a9220d2434bf00743ace6f772af615 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Thu, 30 Nov 2023 13:32:01 +0530
Subject: [PATCH v13 1/3] Make all SLRU buffer sizes configurable.
Provide new GUCs to set the number of buffers, instead of using hard
coded defaults.
Default sizes are also set to 64 as sizes much larger than the old
limits have been shown to be useful on modern systems.
Patch by Andrey M. Borodin, Dilip Kumar
Reviewed By Anastasia Lubennikova, Tomas Vondra, Alexander Korotkov,
Gilles Darold, Thomas Munro
---
doc/src/sgml/config.sgml | 135 ++++++++++++++++++
src/backend/access/transam/clog.c | 19 +--
src/backend/access/transam/commit_ts.c | 7 +-
src/backend/access/transam/multixact.c | 8 +-
src/backend/access/transam/subtrans.c | 5 +-
src/backend/commands/async.c | 8 +-
src/backend/commands/variable.c | 25 ++++
src/backend/storage/lmgr/predicate.c | 4 +-
src/backend/utils/init/globals.c | 8 ++
src/backend/utils/misc/guc_tables.c | 77 ++++++++++
src/backend/utils/misc/postgresql.conf.sample | 9 ++
src/include/access/multixact.h | 4 -
src/include/access/slru.h | 5 +
src/include/access/subtrans.h | 3 -
src/include/commands/async.h | 5 -
src/include/miscadmin.h | 7 +
src/include/storage/predicate.h | 4 -
src/include/utils/guc_hooks.h | 2 +
18 files changed, 293 insertions(+), 42 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 61038472c5..bb5bae95df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2006,6 +2006,141 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-multixact-offsets-buffers" xreflabel="multixact_offsets_buffers">
+ <term><varname>multixact_offsets_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_offsets_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/offsets</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-members-buffers" xreflabel="multixact_members_buffers">
+ <term><varname>multixact_members_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_members_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/members</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-subtrans-buffers" xreflabel="subtrans_buffers">
+ <term><varname>subtrans_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>subtrans_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_subtrans</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-notify-buffers" xreflabel="notify_buffers">
+ <term><varname>notify_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>notify_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_notify</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-serial-buffers" xreflabel="serial_buffers">
+ <term><varname>serial_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>serial_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_serial</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-xact-buffers" xreflabel="xact_buffers">
+ <term><varname>xact_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>xact_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_xact</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname> / 512, but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-commit-ts-buffers" xreflabel="commit_ts_buffers">
+ <term><varname>commit_ts_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>commit_ts_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of memory to use to cache the contents of
+ <literal>pg_commit_ts</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname> / 256, but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
<term><varname>max_stack_depth</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index f6e7da7ffc..5d96195c53 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -673,23 +673,16 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
/*
* Number of shared CLOG buffers.
*
- * On larger multi-processor systems, it is possible to have many CLOG page
- * requests in flight at one time which could lead to disk access for CLOG
- * page if the required page is not found in memory. Testing revealed that we
- * can get the best performance by having 128 CLOG buffers, more than that it
- * doesn't improve performance.
- *
- * Unconditionally keeping the number of CLOG buffers to 128 did not seem like
- * a good idea, because it would increase the minimum amount of shared memory
- * required to start, which could be a problem for people running very small
- * configurations. The following formula seems to represent a reasonable
- * compromise: people with very low values for shared_buffers will get fewer
- * CLOG buffers as well, and everyone else will get 128.
+ * By default, we'll use 2MB of for every 1GB of shared buffers, up to the
+ * maximum value that slru.c will allow, but always at least 16 buffers.
*/
Size
CLOGShmemBuffers(void)
{
- return Min(128, Max(4, NBuffers / 512));
+ /* Use configured value if provided. */
+ if (xact_buffers > 0)
+ return Max(16, xact_buffers);
+ return Min(SLRU_MAX_ALLOWED_BUFFERS, Max(16, NBuffers / 512));
}
/*
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 61b82385f3..27aab51162 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -502,11 +502,16 @@ pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
* We use a very similar logic as for the number of CLOG buffers (except we
* scale up twice as fast with shared buffers, and the maximum is twice as
* high); see comments in CLOGShmemBuffers.
+ * By default, we'll use 4MB of for every 1GB of shared buffers, up to the
+ * maximum value that slru.c will allow, but always at least 16 buffers.
*/
Size
CommitTsShmemBuffers(void)
{
- return Min(256, Max(4, NBuffers / 256));
+ /* Use configured value if provided. */
+ if (commit_ts_buffers > 0)
+ return Max(16, commit_ts_buffers);
+ return Min(256, Max(16, NBuffers / 256));
}
/*
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 59523be901..1957845f58 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1834,8 +1834,8 @@ MultiXactShmemSize(void)
mul_size(sizeof(MultiXactId) * 2, MaxOldestSlot))
size = SHARED_MULTIXACT_STATE_SIZE;
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTOFFSET_BUFFERS, 0));
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTMEMBER_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_offsets_buffers, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_members_buffers, 0));
return size;
}
@@ -1851,14 +1851,14 @@ MultiXactShmemInit(void)
MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
SimpleLruInit(MultiXactOffsetCtl,
- "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0,
+ "MultiXactOffset", multixact_offsets_buffers, 0,
MultiXactOffsetSLRULock, "pg_multixact/offsets",
LWTRANCHE_MULTIXACTOFFSET_BUFFER,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
- "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0,
+ "MultiXactMember", multixact_members_buffers, 0,
MultiXactMemberSLRULock, "pg_multixact/members",
LWTRANCHE_MULTIXACTMEMBER_BUFFER,
SYNC_HANDLER_MULTIXACT_MEMBER,
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index b2ed82ac56..6059999a3c 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -31,6 +31,7 @@
#include "access/slru.h"
#include "access/subtrans.h"
#include "access/transam.h"
+#include "miscadmin.h"
#include "pg_trace.h"
#include "utils/snapmgr.h"
@@ -193,14 +194,14 @@ SubTransGetTopmostTransaction(TransactionId xid)
Size
SUBTRANSShmemSize(void)
{
- return SimpleLruShmemSize(NUM_SUBTRANS_BUFFERS, 0);
+ return SimpleLruShmemSize(subtrans_buffers, 0);
}
void
SUBTRANSShmemInit(void)
{
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
- SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0,
+ SimpleLruInit(SubTransCtl, "Subtrans", subtrans_buffers, 0,
SubtransSLRULock, "pg_subtrans",
LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
false);
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 8b24b22293..20a4dfec2a 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -116,7 +116,7 @@
* frontend during startup.) The above design guarantees that notifies from
* other backends will never be missed by ignoring self-notifies.
*
- * The amount of shared memory used for notify management (NUM_NOTIFY_BUFFERS)
+ * The amount of shared memory used for notify management (notify_buffers)
* can be varied without affecting anything but performance. The maximum
* amount of notification data that can be queued at one time is determined
* by max_notify_queue_pages GUC.
@@ -234,7 +234,7 @@ typedef struct QueuePosition
*
* Resist the temptation to make this really large. While that would save
* work in some places, it would add cost in others. In particular, this
- * should likely be less than NUM_NOTIFY_BUFFERS, to ensure that backends
+ * should likely be less than notify_buffers, to ensure that backends
* catch up before the pages they'll need to read fall out of SLRU cache.
*/
#define QUEUE_CLEANUP_DELAY 4
@@ -492,7 +492,7 @@ AsyncShmemSize(void)
size = mul_size(MaxBackends + 1, sizeof(QueueBackendStatus));
size = add_size(size, offsetof(AsyncQueueControl, backend));
- size = add_size(size, SimpleLruShmemSize(NUM_NOTIFY_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(notify_buffers, 0));
return size;
}
@@ -541,7 +541,7 @@ AsyncShmemInit(void)
* names are used in order to avoid wraparound.
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
- SimpleLruInit(NotifyCtl, "Notify", NUM_NOTIFY_BUFFERS, 0,
+ SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
SYNC_HANDLER_NONE, true);
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 30efcd554a..2924bae915 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -18,6 +18,8 @@
#include <ctype.h>
+#include "access/clog.h"
+#include "access/commit_ts.h"
#include "access/htup_details.h"
#include "access/parallel.h"
#include "access/xact.h"
@@ -400,6 +402,29 @@ show_timezone(void)
return "unknown";
}
+/*
+ * GUC show_hook for xact_buffers
+ */
+const char *
+show_xact_buffers(void)
+{
+ static char nbuf[16];
+
+ snprintf(nbuf, sizeof(nbuf), "%zu", CLOGShmemBuffers());
+ return nbuf;
+}
+
+/*
+ * GUC show_hook for commit_ts_buffers
+ */
+const char *
+show_commit_ts_buffers(void)
+{
+ static char nbuf[16];
+
+ snprintf(nbuf, sizeof(nbuf), "%zu", CommitTsShmemBuffers());
+ return nbuf;
+}
/*
* LOG_TIMEZONE
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index ee5ea1175c..7fc34720bf 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -808,7 +808,7 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial",
+ serial_buffers, 0, SerialSLRULock, "pg_serial",
LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
false);
#ifdef USE_ASSERT_CHECKING
@@ -1348,7 +1348,7 @@ PredicateLockShmemSize(void)
/* Shared memory structures for SLRU tracking of old committed xids. */
size = add_size(size, sizeof(SerialControlData));
- size = add_size(size, SimpleLruShmemSize(NUM_SERIAL_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(serial_buffers, 0));
return size;
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 88b03e8fa3..504293229c 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -156,3 +156,11 @@ int64 VacuumPageDirty = 0;
int VacuumCostBalance = 0; /* working state for vacuum */
bool VacuumCostActive = false;
+
+int multixact_offsets_buffers = 64;
+int multixact_members_buffers = 64;
+int subtrans_buffers = 64;
+int notify_buffers = 64;
+int serial_buffers = 64;
+int xact_buffers = 64;
+int commit_ts_buffers = 64;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e53ebc6dc2..e56c14b78f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -28,6 +28,7 @@
#include "access/commit_ts.h"
#include "access/gin.h"
+#include "access/slru.h"
#include "access/toast_compression.h"
#include "access/twophase.h"
#include "access/xlog_internal.h"
@@ -2310,6 +2311,82 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"multixact_offsets_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the MultiXact offset SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_offsets_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"multixact_members_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the MultiXact member SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_members_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"subtrans_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the sub-transaction SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &subtrans_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+ {
+ {"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the NOTIFY message SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ ¬ify_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"serial_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the serializable transaction SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &serial_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"xact_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the transaction status SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &xact_buffers,
+ 64, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, show_xact_buffers
+ },
+
+ {
+ {"commit_ts_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the commit timestamp SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &commit_ts_buffers,
+ 64, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, show_commit_ts_buffers
+ },
+
{
{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the maximum number of temporary buffers used by each session."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index b2809c711a..7cd747e3d9 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -50,6 +50,15 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+# - SLRU Buffers (change requires restart) -
+
+#xact_buffers = 0 # memory for pg_xact (0 = auto)
+#subtrans_buffers = 64 # memory for pg_subtrans
+#multixact_offsets_buffers = 64 # memory for pg_multixact/offsets
+#multixact_members_buffers = 64 # memory for pg_multixact/members
+#notify_buffers = 64 # memory for pg_notify
+#serial_buffers = 64 # memory for pg_serial
+#commit_ts_buffers = 0 # memory for pg_commit_ts (0 = auto)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 233f67dbcc..7ffd256c74 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -29,10 +29,6 @@
#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
-/* Number of SLRU buffers to use for multixact */
-#define NUM_MULTIXACTOFFSET_BUFFERS 8
-#define NUM_MULTIXACTMEMBER_BUFFERS 16
-
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index b05f6bc71d..72b30bba7f 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,11 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * To avoid overflowing internal arithmetic and the size_t data type, the
+ * number of buffers should not exceed this number.
+ */
+#define SLRU_MAX_ALLOWED_BUFFERS ((1024 * 1024 * 1024) / BLCKSZ)
/*
* Define SLRU segment size. A page is the same BLCKSZ as is used everywhere
diff --git a/src/include/access/subtrans.h b/src/include/access/subtrans.h
index b0d2ad57e5..e2213cf3fd 100644
--- a/src/include/access/subtrans.h
+++ b/src/include/access/subtrans.h
@@ -11,9 +11,6 @@
#ifndef SUBTRANS_H
#define SUBTRANS_H
-/* Number of SLRU buffers to use for subtrans */
-#define NUM_SUBTRANS_BUFFERS 32
-
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
extern TransactionId SubTransGetParent(TransactionId xid);
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 80b8583421..78daa25fa0 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -15,11 +15,6 @@
#include <signal.h>
-/*
- * The number of SLRU page buffers we use for the notification queue.
- */
-#define NUM_NOTIFY_BUFFERS 8
-
extern PGDLLIMPORT bool Trace_notify;
extern PGDLLIMPORT int max_notify_queue_pages;
extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0b01c1f093..6192365bc8 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -177,6 +177,13 @@ extern PGDLLIMPORT int MaxBackends;
extern PGDLLIMPORT int MaxConnections;
extern PGDLLIMPORT int max_worker_processes;
extern PGDLLIMPORT int max_parallel_workers;
+extern PGDLLIMPORT int multixact_offsets_buffers;
+extern PGDLLIMPORT int multixact_members_buffers;
+extern PGDLLIMPORT int subtrans_buffers;
+extern PGDLLIMPORT int notify_buffers;
+extern PGDLLIMPORT int serial_buffers;
+extern PGDLLIMPORT int xact_buffers;
+extern PGDLLIMPORT int commit_ts_buffers;
extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index a7edd38fa9..14ee9b94a2 100644
--- a/src/include/storage/predicate.h
+++ b/src/include/storage/predicate.h
@@ -26,10 +26,6 @@ extern PGDLLIMPORT int max_predicate_locks_per_xact;
extern PGDLLIMPORT int max_predicate_locks_per_relation;
extern PGDLLIMPORT int max_predicate_locks_per_page;
-
-/* Number of SLRU buffers to use for Serial SLRU */
-#define NUM_SERIAL_BUFFERS 16
-
/*
* A handle used for sharing SERIALIZABLEXACT objects between the participants
* in a parallel query.
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 5300c44f3b..a7bcb6b42a 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -163,4 +163,6 @@ extern void assign_wal_consistency_checking(const char *newval, void *extra);
extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern const char *show_xact_buffers(void);
+extern const char *show_commit_ts_buffers(void);
#endif /* GUC_HOOKS_H */
--
2.39.2 (Apple Git-143)
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-23 05:35 Dilip Kumar <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 2 replies; 79+ messages in thread
From: Dilip Kumar @ 2024-01-23 05:35 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Jan 10, 2024 at 6:50 PM Dilip Kumar <[email protected]> wrote:
>
> On Mon, Jan 8, 2024 at 9:12 PM Alvaro Herrera <[email protected]> wrote:
> >
> > Eh, apologies. This email was an unfinished draft that I had laying
> > around before the holidays which I intended to discard it but somehow
> > kept around, and just now I happened to press the wrong key combination
> > and it ended up being sent instead. We had some further discussion,
> > after which I no longer think that there is a problem here, so please
> > ignore this email.
> >
> > I'll come back to this patch later this week.
>
> No problem
>
> The patch was facing some compilation issues after some recent
> commits, so I have changed it. Reported by Julien Tachoires (offlist)
The last patch conflicted with some of the recent commits, so here is
the updated version of the patch, I also noticed that the slur bank
lock wat event details were missing from the wait_event_names.txt file
so added that as well.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
Attachments:
[application/octet-stream] v14-0002-Divide-SLRU-buffers-into-banks.patch (13.6K, ../../CAFiTN-tu7=EEL7sB5uFg5aHOeef9eeeE=_+-HbdzLgOYOuZwTg@mail.gmail.com/2-v14-0002-Divide-SLRU-buffers-into-banks.patch)
download | inline diff:
From d5ec64733cb1689f9263028e858d8c49ca684814 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Thu, 30 Nov 2023 13:41:50 +0530
Subject: [PATCH v14 2/3] Divide SLRU buffers into banks
As we have made slru buffer pool configurable, we want to
eliminate linear search within whole SLRU buffer pool. To
do so we divide SLRU buffers into banks. Each bank holds 16
buffers. Each SLRU pageno may reside only in one bank.
Adjacent pagenos reside in different banks. Along with this
also ensure that the number of slru buffers are given in
multiples of bank size.
Andrey M. Borodin and Dilip Kumar based on fedback by Alvaro Herrera
---
src/backend/access/transam/clog.c | 10 ++++++
src/backend/access/transam/commit_ts.c | 10 ++++++
src/backend/access/transam/multixact.c | 19 +++++++++++
src/backend/access/transam/slru.c | 44 +++++++++++++++++++++++---
src/backend/access/transam/subtrans.c | 10 ++++++
src/backend/commands/async.c | 10 ++++++
src/backend/storage/lmgr/predicate.c | 10 ++++++
src/backend/utils/misc/guc_tables.c | 14 ++++----
src/include/access/slru.h | 15 +++++++++
src/include/utils/guc_hooks.h | 11 +++++++
10 files changed, 141 insertions(+), 12 deletions(-)
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 5d96195c53..7d349d2213 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -43,6 +43,7 @@
#include "pgstat.h"
#include "storage/proc.h"
#include "storage/sync.h"
+#include "utils/guc_hooks.h"
/*
* Defines for CLOG page sizes. A page is the same BLCKSZ as is used
@@ -1029,3 +1030,12 @@ clogsyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(XactCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for xact_buffers
+ */
+bool
+check_xact_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("xact_buffers", newval);
+}
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 27aab51162..41337471e2 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -33,6 +33,7 @@
#include "pg_trace.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"
@@ -1027,3 +1028,12 @@ committssyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(CommitTsCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for commit_ts_buffers
+ */
+bool
+check_commit_ts_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("commit_ts_buffers", newval);
+}
\ No newline at end of file
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 1957845f58..f8eceeac30 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -88,6 +88,7 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@@ -3421,3 +3422,21 @@ multixactmemberssyncfiletag(const FileTag *ftag, char *path)
{
return SlruSyncFileTag(MultiXactMemberCtl, ftag, path);
}
+
+/*
+ * GUC check_hook for multixact_offsets_buffers
+ */
+bool
+check_multixact_offsets_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_offsets_buffers", newval);
+}
+
+/*
+ * GUC check_hook for multixact_members_buffers
+ */
+bool
+check_multixact_members_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_members_buffers", newval);
+}
\ No newline at end of file
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 9ac4790f16..211527b075 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,6 +59,7 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/guc_hooks.h"
static inline int
SlruFileName(SlruCtl ctl, char *path, int64 segno)
@@ -284,7 +285,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
else
+ {
Assert(found);
+ Assert(shared->num_slots == nslots);
+ }
/*
* Initialize the unshared control struct, including directory path. We
@@ -293,6 +297,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ctl->shared = shared;
ctl->sync_handler = sync_handler;
ctl->long_segment_names = long_segment_names;
+ ctl->bank_mask = (nslots / SLRU_BANK_SIZE) - 1;
strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir));
}
@@ -524,12 +529,18 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
/* Try to find the page while holding only shared lock */
LWLockAcquire(shared->ControlLock, LW_SHARED);
- /* See if page is already in a buffer */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY &&
@@ -1056,9 +1067,15 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
- /* See if page already has a buffer assigned */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY)
@@ -1093,7 +1110,7 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* multiple pages with the same lru_count.
*/
cur_count = (shared->cur_lru_count)++;
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
int64 this_page_number;
@@ -1666,3 +1683,20 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
errno = save_errno;
return result;
}
+
+/*
+ * Helper function for GUC check_hook to check whether slru buffers are in
+ * multiples of SLRU_BANK_SIZE.
+ */
+bool
+check_slru_buffers(const char *name, int *newval)
+{
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval % SLRU_BANK_SIZE == 0)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"%s\" must be in multiple of %d", name,
+ SLRU_BANK_SIZE);
+ return false;
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 6059999a3c..82243c2728 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -33,6 +33,7 @@
#include "access/transam.h"
#include "miscadmin.h"
#include "pg_trace.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
@@ -383,3 +384,12 @@ SubTransPagePrecedes(int64 page1, int64 page2)
return (TransactionIdPrecedes(xid1, xid2) &&
TransactionIdPrecedes(xid1, xid2 + SUBTRANS_XACTS_PER_PAGE - 1));
}
+
+/*
+ * GUC check_hook for subtrans_buffers
+ */
+bool
+check_subtrans_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("subtrans_buffers", newval);
+}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 20a4dfec2a..9059c0a202 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -148,6 +148,7 @@
#include "storage/sinval.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
@@ -2378,3 +2379,12 @@ ClearPendingActionsAndNotifies(void)
pendingActions = NULL;
pendingNotifies = NULL;
}
+
+/*
+ * GUC check_hook for notify_buffers
+ */
+bool
+check_notify_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("notify_buffers", newval);
+}
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 7fc34720bf..10c51e2883 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -208,6 +208,7 @@
#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "utils/guc_hooks.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
@@ -5012,3 +5013,12 @@ AttachSerializableXact(SerializableXactHandle handle)
if (MySerializableXact != InvalidSerializableXact)
CreateLocalPredicateLockHash();
}
+
+/*
+ * GUC check_hook for serial_buffers
+ */
+bool
+check_serial_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("serial_buffers", newval);
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index ea15f478fc..938ce3cd47 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2329,7 +2329,7 @@ struct config_int ConfigureNamesInt[] =
},
&multixact_offsets_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_multixact_offsets_buffers, NULL, NULL
},
{
@@ -2340,7 +2340,7 @@ struct config_int ConfigureNamesInt[] =
},
&multixact_members_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_multixact_members_buffers, NULL, NULL
},
{
@@ -2351,7 +2351,7 @@ struct config_int ConfigureNamesInt[] =
},
&subtrans_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_subtrans_buffers, NULL, NULL
},
{
{"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
@@ -2361,7 +2361,7 @@ struct config_int ConfigureNamesInt[] =
},
¬ify_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_notify_buffers, NULL, NULL
},
{
@@ -2372,7 +2372,7 @@ struct config_int ConfigureNamesInt[] =
},
&serial_buffers,
64, 16, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, NULL
+ check_serial_buffers, NULL, NULL
},
{
@@ -2383,7 +2383,7 @@ struct config_int ConfigureNamesInt[] =
},
&xact_buffers,
64, 0, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, show_xact_buffers
+ check_xact_buffers, NULL, show_xact_buffers
},
{
@@ -2394,7 +2394,7 @@ struct config_int ConfigureNamesInt[] =
},
&commit_ts_buffers,
64, 0, SLRU_MAX_ALLOWED_BUFFERS,
- NULL, NULL, show_commit_ts_buffers
+ check_commit_ts_buffers, NULL, show_commit_ts_buffers
},
{
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 72b30bba7f..2b74e11d42 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,14 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * SLRU bank size for slotno hash banks. Limit the bank size to 16 because we
+ * perform sequential search within a bank (while looking for a target page or
+ * while doing victim buffer search) and if we keep this size big then it may
+ * affect the performance.
+ */
+#define SLRU_BANK_SIZE 16
+
/*
* To avoid overflowing internal arithmetic and the size_t data type, the
* number of buffers should not exceed this number.
@@ -147,6 +155,12 @@ typedef struct SlruCtlData
* it's always the same, it doesn't need to be in shared memory.
*/
char Dir[64];
+
+ /*
+ * Mask for slotno banks, considering 1GB SLRU buffer pool size and the
+ * SLRU_BANK_SIZE bits16 should be sufficient for the bank mask.
+ */
+ bits16 bank_mask;
} SlruCtlData;
typedef SlruCtlData *SlruCtl;
@@ -184,5 +198,6 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
int64 segpage, void *data);
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
+extern bool check_slru_buffers(const char *name, int *newval);
#endif /* SLRU_H */
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index a7bcb6b42a..f458da88ac 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -130,6 +130,17 @@ extern bool check_ssl(bool *newval, void **extra, GucSource source);
extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
extern bool check_synchronous_standby_names(char **newval, void **extra,
GucSource source);
+extern bool check_multixact_offsets_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_multixact_members_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_subtrans_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_notify_buffers(int *newval, void **extra, GucSource source);
+extern bool check_serial_buffers(int *newval, void **extra, GucSource source);
+extern bool check_xact_buffers(int *newval, void **extra, GucSource source);
+extern bool check_commit_ts_buffers(int *newval, void **extra,
+ GucSource source);
extern void assign_synchronous_standby_names(const char *newval, void *extra);
extern void assign_synchronous_commit(int newval, void *extra);
extern void assign_syslog_facility(int newval, void *extra);
--
2.39.2 (Apple Git-143)
[application/octet-stream] v14-0001-Make-all-SLRU-buffer-sizes-configurable.patch (23.9K, ../../CAFiTN-tu7=EEL7sB5uFg5aHOeef9eeeE=_+-HbdzLgOYOuZwTg@mail.gmail.com/3-v14-0001-Make-all-SLRU-buffer-sizes-configurable.patch)
download | inline diff:
From 8b9b7fbd213c664445fea329c64e3fd9c7addaf5 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Thu, 30 Nov 2023 13:32:01 +0530
Subject: [PATCH v14 1/3] Make all SLRU buffer sizes configurable.
Provide new GUCs to set the number of buffers, instead of using hard
coded defaults.
Default sizes are also set to 64 as sizes much larger than the old
limits have been shown to be useful on modern systems.
Patch by Andrey M. Borodin, Dilip Kumar
Reviewed By Anastasia Lubennikova, Tomas Vondra, Alexander Korotkov,
Gilles Darold, Thomas Munro
---
doc/src/sgml/config.sgml | 135 ++++++++++++++++++
src/backend/access/transam/clog.c | 19 +--
src/backend/access/transam/commit_ts.c | 7 +-
src/backend/access/transam/multixact.c | 8 +-
src/backend/access/transam/subtrans.c | 5 +-
src/backend/commands/async.c | 8 +-
src/backend/commands/variable.c | 25 ++++
src/backend/storage/lmgr/predicate.c | 4 +-
src/backend/utils/init/globals.c | 8 ++
src/backend/utils/misc/guc_tables.c | 77 ++++++++++
src/backend/utils/misc/postgresql.conf.sample | 9 ++
src/include/access/multixact.h | 4 -
src/include/access/slru.h | 5 +
src/include/access/subtrans.h | 3 -
src/include/commands/async.h | 5 -
src/include/miscadmin.h | 7 +
src/include/storage/predicate.h | 4 -
src/include/utils/guc_hooks.h | 2 +
18 files changed, 293 insertions(+), 42 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 61038472c5..bb5bae95df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2006,6 +2006,141 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-multixact-offsets-buffers" xreflabel="multixact_offsets_buffers">
+ <term><varname>multixact_offsets_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_offsets_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/offsets</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-members-buffers" xreflabel="multixact_members_buffers">
+ <term><varname>multixact_members_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_members_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/members</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-subtrans-buffers" xreflabel="subtrans_buffers">
+ <term><varname>subtrans_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>subtrans_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_subtrans</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-notify-buffers" xreflabel="notify_buffers">
+ <term><varname>notify_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>notify_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_notify</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-serial-buffers" xreflabel="serial_buffers">
+ <term><varname>serial_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>serial_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_serial</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>64</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-xact-buffers" xreflabel="xact_buffers">
+ <term><varname>xact_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>xact_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_xact</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname> / 512, but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-commit-ts-buffers" xreflabel="commit_ts_buffers">
+ <term><varname>commit_ts_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>commit_ts_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of memory to use to cache the contents of
+ <literal>pg_commit_ts</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname> / 256, but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
<term><varname>max_stack_depth</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index f6e7da7ffc..5d96195c53 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -673,23 +673,16 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
/*
* Number of shared CLOG buffers.
*
- * On larger multi-processor systems, it is possible to have many CLOG page
- * requests in flight at one time which could lead to disk access for CLOG
- * page if the required page is not found in memory. Testing revealed that we
- * can get the best performance by having 128 CLOG buffers, more than that it
- * doesn't improve performance.
- *
- * Unconditionally keeping the number of CLOG buffers to 128 did not seem like
- * a good idea, because it would increase the minimum amount of shared memory
- * required to start, which could be a problem for people running very small
- * configurations. The following formula seems to represent a reasonable
- * compromise: people with very low values for shared_buffers will get fewer
- * CLOG buffers as well, and everyone else will get 128.
+ * By default, we'll use 2MB of for every 1GB of shared buffers, up to the
+ * maximum value that slru.c will allow, but always at least 16 buffers.
*/
Size
CLOGShmemBuffers(void)
{
- return Min(128, Max(4, NBuffers / 512));
+ /* Use configured value if provided. */
+ if (xact_buffers > 0)
+ return Max(16, xact_buffers);
+ return Min(SLRU_MAX_ALLOWED_BUFFERS, Max(16, NBuffers / 512));
}
/*
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 61b82385f3..27aab51162 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -502,11 +502,16 @@ pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
* We use a very similar logic as for the number of CLOG buffers (except we
* scale up twice as fast with shared buffers, and the maximum is twice as
* high); see comments in CLOGShmemBuffers.
+ * By default, we'll use 4MB of for every 1GB of shared buffers, up to the
+ * maximum value that slru.c will allow, but always at least 16 buffers.
*/
Size
CommitTsShmemBuffers(void)
{
- return Min(256, Max(4, NBuffers / 256));
+ /* Use configured value if provided. */
+ if (commit_ts_buffers > 0)
+ return Max(16, commit_ts_buffers);
+ return Min(256, Max(16, NBuffers / 256));
}
/*
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 59523be901..1957845f58 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1834,8 +1834,8 @@ MultiXactShmemSize(void)
mul_size(sizeof(MultiXactId) * 2, MaxOldestSlot))
size = SHARED_MULTIXACT_STATE_SIZE;
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTOFFSET_BUFFERS, 0));
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTMEMBER_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_offsets_buffers, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_members_buffers, 0));
return size;
}
@@ -1851,14 +1851,14 @@ MultiXactShmemInit(void)
MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
SimpleLruInit(MultiXactOffsetCtl,
- "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0,
+ "MultiXactOffset", multixact_offsets_buffers, 0,
MultiXactOffsetSLRULock, "pg_multixact/offsets",
LWTRANCHE_MULTIXACTOFFSET_BUFFER,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
- "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0,
+ "MultiXactMember", multixact_members_buffers, 0,
MultiXactMemberSLRULock, "pg_multixact/members",
LWTRANCHE_MULTIXACTMEMBER_BUFFER,
SYNC_HANDLER_MULTIXACT_MEMBER,
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index b2ed82ac56..6059999a3c 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -31,6 +31,7 @@
#include "access/slru.h"
#include "access/subtrans.h"
#include "access/transam.h"
+#include "miscadmin.h"
#include "pg_trace.h"
#include "utils/snapmgr.h"
@@ -193,14 +194,14 @@ SubTransGetTopmostTransaction(TransactionId xid)
Size
SUBTRANSShmemSize(void)
{
- return SimpleLruShmemSize(NUM_SUBTRANS_BUFFERS, 0);
+ return SimpleLruShmemSize(subtrans_buffers, 0);
}
void
SUBTRANSShmemInit(void)
{
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
- SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0,
+ SimpleLruInit(SubTransCtl, "Subtrans", subtrans_buffers, 0,
SubtransSLRULock, "pg_subtrans",
LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
false);
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 8b24b22293..20a4dfec2a 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -116,7 +116,7 @@
* frontend during startup.) The above design guarantees that notifies from
* other backends will never be missed by ignoring self-notifies.
*
- * The amount of shared memory used for notify management (NUM_NOTIFY_BUFFERS)
+ * The amount of shared memory used for notify management (notify_buffers)
* can be varied without affecting anything but performance. The maximum
* amount of notification data that can be queued at one time is determined
* by max_notify_queue_pages GUC.
@@ -234,7 +234,7 @@ typedef struct QueuePosition
*
* Resist the temptation to make this really large. While that would save
* work in some places, it would add cost in others. In particular, this
- * should likely be less than NUM_NOTIFY_BUFFERS, to ensure that backends
+ * should likely be less than notify_buffers, to ensure that backends
* catch up before the pages they'll need to read fall out of SLRU cache.
*/
#define QUEUE_CLEANUP_DELAY 4
@@ -492,7 +492,7 @@ AsyncShmemSize(void)
size = mul_size(MaxBackends + 1, sizeof(QueueBackendStatus));
size = add_size(size, offsetof(AsyncQueueControl, backend));
- size = add_size(size, SimpleLruShmemSize(NUM_NOTIFY_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(notify_buffers, 0));
return size;
}
@@ -541,7 +541,7 @@ AsyncShmemInit(void)
* names are used in order to avoid wraparound.
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
- SimpleLruInit(NotifyCtl, "Notify", NUM_NOTIFY_BUFFERS, 0,
+ SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
SYNC_HANDLER_NONE, true);
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 30efcd554a..2924bae915 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -18,6 +18,8 @@
#include <ctype.h>
+#include "access/clog.h"
+#include "access/commit_ts.h"
#include "access/htup_details.h"
#include "access/parallel.h"
#include "access/xact.h"
@@ -400,6 +402,29 @@ show_timezone(void)
return "unknown";
}
+/*
+ * GUC show_hook for xact_buffers
+ */
+const char *
+show_xact_buffers(void)
+{
+ static char nbuf[16];
+
+ snprintf(nbuf, sizeof(nbuf), "%zu", CLOGShmemBuffers());
+ return nbuf;
+}
+
+/*
+ * GUC show_hook for commit_ts_buffers
+ */
+const char *
+show_commit_ts_buffers(void)
+{
+ static char nbuf[16];
+
+ snprintf(nbuf, sizeof(nbuf), "%zu", CommitTsShmemBuffers());
+ return nbuf;
+}
/*
* LOG_TIMEZONE
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index ee5ea1175c..7fc34720bf 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -808,7 +808,7 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial",
+ serial_buffers, 0, SerialSLRULock, "pg_serial",
LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
false);
#ifdef USE_ASSERT_CHECKING
@@ -1348,7 +1348,7 @@ PredicateLockShmemSize(void)
/* Shared memory structures for SLRU tracking of old committed xids. */
size = add_size(size, sizeof(SerialControlData));
- size = add_size(size, SimpleLruShmemSize(NUM_SERIAL_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(serial_buffers, 0));
return size;
}
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 88b03e8fa3..504293229c 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -156,3 +156,11 @@ int64 VacuumPageDirty = 0;
int VacuumCostBalance = 0; /* working state for vacuum */
bool VacuumCostActive = false;
+
+int multixact_offsets_buffers = 64;
+int multixact_members_buffers = 64;
+int subtrans_buffers = 64;
+int notify_buffers = 64;
+int serial_buffers = 64;
+int xact_buffers = 64;
+int commit_ts_buffers = 64;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 7fe58518d7..ea15f478fc 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -28,6 +28,7 @@
#include "access/commit_ts.h"
#include "access/gin.h"
+#include "access/slru.h"
#include "access/toast_compression.h"
#include "access/twophase.h"
#include "access/xlog_internal.h"
@@ -2320,6 +2321,82 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"multixact_offsets_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the MultiXact offset SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_offsets_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"multixact_members_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the MultiXact member SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_members_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"subtrans_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the sub-transaction SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &subtrans_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+ {
+ {"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the NOTIFY message SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ ¬ify_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"serial_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the serializable transaction SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &serial_buffers,
+ 64, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, NULL
+ },
+
+ {
+ {"xact_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the number of shared memory buffers used for the transaction status SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &xact_buffers,
+ 64, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, show_xact_buffers
+ },
+
+ {
+ {"commit_ts_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the commit timestamp SLRU cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &commit_ts_buffers,
+ 64, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ NULL, NULL, show_commit_ts_buffers
+ },
+
{
{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the maximum number of temporary buffers used by each session."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index da10b43dac..31e8f7fbf7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -50,6 +50,15 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+# - SLRU Buffers (change requires restart) -
+
+#xact_buffers = 0 # memory for pg_xact (0 = auto)
+#subtrans_buffers = 64 # memory for pg_subtrans
+#multixact_offsets_buffers = 64 # memory for pg_multixact/offsets
+#multixact_members_buffers = 64 # memory for pg_multixact/members
+#notify_buffers = 64 # memory for pg_notify
+#serial_buffers = 64 # memory for pg_serial
+#commit_ts_buffers = 0 # memory for pg_commit_ts (0 = auto)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 233f67dbcc..7ffd256c74 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -29,10 +29,6 @@
#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
-/* Number of SLRU buffers to use for multixact */
-#define NUM_MULTIXACTOFFSET_BUFFERS 8
-#define NUM_MULTIXACTMEMBER_BUFFERS 16
-
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index b05f6bc71d..72b30bba7f 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,11 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * To avoid overflowing internal arithmetic and the size_t data type, the
+ * number of buffers should not exceed this number.
+ */
+#define SLRU_MAX_ALLOWED_BUFFERS ((1024 * 1024 * 1024) / BLCKSZ)
/*
* Define SLRU segment size. A page is the same BLCKSZ as is used everywhere
diff --git a/src/include/access/subtrans.h b/src/include/access/subtrans.h
index b0d2ad57e5..e2213cf3fd 100644
--- a/src/include/access/subtrans.h
+++ b/src/include/access/subtrans.h
@@ -11,9 +11,6 @@
#ifndef SUBTRANS_H
#define SUBTRANS_H
-/* Number of SLRU buffers to use for subtrans */
-#define NUM_SUBTRANS_BUFFERS 32
-
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
extern TransactionId SubTransGetParent(TransactionId xid);
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 80b8583421..78daa25fa0 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -15,11 +15,6 @@
#include <signal.h>
-/*
- * The number of SLRU page buffers we use for the notification queue.
- */
-#define NUM_NOTIFY_BUFFERS 8
-
extern PGDLLIMPORT bool Trace_notify;
extern PGDLLIMPORT int max_notify_queue_pages;
extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0b01c1f093..6192365bc8 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -177,6 +177,13 @@ extern PGDLLIMPORT int MaxBackends;
extern PGDLLIMPORT int MaxConnections;
extern PGDLLIMPORT int max_worker_processes;
extern PGDLLIMPORT int max_parallel_workers;
+extern PGDLLIMPORT int multixact_offsets_buffers;
+extern PGDLLIMPORT int multixact_members_buffers;
+extern PGDLLIMPORT int subtrans_buffers;
+extern PGDLLIMPORT int notify_buffers;
+extern PGDLLIMPORT int serial_buffers;
+extern PGDLLIMPORT int xact_buffers;
+extern PGDLLIMPORT int commit_ts_buffers;
extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index a7edd38fa9..14ee9b94a2 100644
--- a/src/include/storage/predicate.h
+++ b/src/include/storage/predicate.h
@@ -26,10 +26,6 @@ extern PGDLLIMPORT int max_predicate_locks_per_xact;
extern PGDLLIMPORT int max_predicate_locks_per_relation;
extern PGDLLIMPORT int max_predicate_locks_per_page;
-
-/* Number of SLRU buffers to use for Serial SLRU */
-#define NUM_SERIAL_BUFFERS 16
-
/*
* A handle used for sharing SERIALIZABLEXACT objects between the participants
* in a parallel query.
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 5300c44f3b..a7bcb6b42a 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -163,4 +163,6 @@ extern void assign_wal_consistency_checking(const char *newval, void *extra);
extern bool check_wal_segment_size(int *newval, void **extra, GucSource source);
extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
+extern const char *show_xact_buffers(void);
+extern const char *show_commit_ts_buffers(void);
#endif /* GUC_HOOKS_H */
--
2.39.2 (Apple Git-143)
[application/octet-stream] v14-0003-Remove-the-centralized-control-lock-and-LRU-coun.patch (80.7K, ../../CAFiTN-tu7=EEL7sB5uFg5aHOeef9eeeE=_+-HbdzLgOYOuZwTg@mail.gmail.com/4-v14-0003-Remove-the-centralized-control-lock-and-LRU-coun.patch)
download | inline diff:
From 16f1d07e62e0487345b2e07b614fb8e406a729af Mon Sep 17 00:00:00 2001
From: Dilip Kumar <[email protected]>
Date: Tue, 23 Jan 2024 10:41:26 +0530
Subject: [PATCH v14 3/3] Remove the centralized control lock and LRU counter
The previous patch has divided SLRU buffer pool into associative
banks. This patch is further optimizing it by introducing
multiple SLRU locks instead of a common centralized lock this
will reduce the contention on the slru control lock. Basically,
we will have at max 128 bank locks and if the number of banks
is <= 128 then each lock will cover exactly one bank otherwise
they will cover multiple banks we will find the bank-to-lock
mapping by (bankno % 128). This patch also removes the
centralized lru counter and now we will have bank-wise lru
counters that will help in frequent cache invalidation while
modifying this counter.
Dilip Kumar based on design inputs from Robert Haas, Andrey M. Borodin,
and Alvaro Herrera
---
src/backend/access/transam/clog.c | 155 ++++++++---
src/backend/access/transam/commit_ts.c | 42 +--
src/backend/access/transam/multixact.c | 173 +++++++++----
src/backend/access/transam/slru.c | 245 +++++++++++++-----
src/backend/access/transam/subtrans.c | 58 ++++-
src/backend/commands/async.c | 43 ++-
src/backend/storage/lmgr/lwlock.c | 14 +
src/backend/storage/lmgr/lwlocknames.txt | 14 +-
src/backend/storage/lmgr/predicate.c | 34 +--
.../utils/activity/wait_event_names.txt | 15 +-
src/include/access/slru.h | 64 +++--
src/include/storage/lwlock.h | 7 +
src/test/modules/test_slru/test_slru.c | 35 +--
13 files changed, 647 insertions(+), 252 deletions(-)
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 7d349d2213..d9a18ad35c 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -285,15 +285,20 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
+ LWLock *lock;
+
/* Can't use group update when PGPROC overflows. */
StaticAssertDecl(THRESHOLD_SUBTRANS_CLOG_OPT <= PGPROC_MAX_CACHED_SUBXIDS,
"group clog threshold less than PGPROC cached subxids");
+ /* Get the SLRU bank lock for the page we are going to access. */
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+
/*
- * When there is contention on XactSLRULock, we try to group multiple
+ * When there is contention on Xact SLRU lock, we try to group multiple
* updates; a single leader process will perform transaction status
- * updates for multiple backends so that the number of times XactSLRULock
- * needs to be acquired is reduced.
+ * updates for multiple backends so that the number of times the Xact SLRU
+ * lock needs to be acquired is reduced.
*
* For this optimization to be safe, the XID and subxids in MyProc must be
* the same as the ones for which we're setting the status. Check that
@@ -311,17 +316,17 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
nsubxids * sizeof(TransactionId)) == 0))
{
/*
- * If we can immediately acquire XactSLRULock, we update the status of
+ * If we can immediately acquire SLRU lock, we update the status of
* our own XID and release the lock. If not, try use group XID
* update. If that doesn't work out, fall back to waiting for the
* lock to perform an update for this transaction only.
*/
- if (LWLockConditionalAcquire(XactSLRULock, LW_EXCLUSIVE))
+ if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
{
/* Got the lock without waiting! Do the update. */
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
return;
}
else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
@@ -334,10 +339,10 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
}
/* Group update not applicable, or couldn't accept this page number. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -356,7 +361,8 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
Assert(status == TRANSACTION_STATUS_COMMITTED ||
status == TRANSACTION_STATUS_ABORTED ||
(status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
- Assert(LWLockHeldByMeInMode(XactSLRULock, LW_EXCLUSIVE));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
+ LW_EXCLUSIVE));
/*
* If we're doing an async commit (ie, lsn is valid), then we must wait
@@ -407,14 +413,13 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
}
/*
- * When we cannot immediately acquire XactSLRULock in exclusive mode at
+ * When we cannot immediately acquire SLRU bank lock in exclusive mode at
* commit time, add ourselves to a list of processes that need their XIDs
* status update. The first process to add itself to the list will acquire
- * XactSLRULock in exclusive mode and set transaction status as required
- * on behalf of all group members. This avoids a great deal of contention
- * around XactSLRULock when many processes are trying to commit at once,
- * since the lock need not be repeatedly handed off from one committing
- * process to the next.
+ * the lock in exclusive mode and set transaction status as required on behalf
+ * of all group members. This avoids a great deal of contention when many
+ * processes are trying to commit at once, since the lock need not be
+ * repeatedly handed off from one committing process to the next.
*
* Returns true when transaction status has been updated in clog; returns
* false if we decided against applying the optimization because the page
@@ -428,6 +433,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
PGPROC *proc = MyProc;
uint32 nextidx;
uint32 wakeidx;
+ int prevpageno;
+ LWLock *prevlock = NULL;
/* We should definitely have an XID whose status needs to be updated. */
Assert(TransactionIdIsValid(xid));
@@ -442,6 +449,41 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
proc->clogGroupMemberPage = pageno;
proc->clogGroupMemberLsn = lsn;
+ /*
+ * The underlying SLRU is using bank-wise lock so it is possible that here
+ * we might get requesters who are contending on different SLRU-bank locks.
+ * But in the group, we try to only add the requesters who want to update
+ * the same page i.e. they would be requesting for the same SLRU-bank lock
+ * as well. The main reason for now allowing requesters of different pages
+ * together is 1) Once the leader acquires the lock they don't need to
+ * fetch multiple pages and do multiple I/O under the same lock 2) The
+ * leader need not switch the SLRU-bank lock if the different pages are
+ * from different SLRU banks 3) And the most important reason is that most
+ * of the time the contention will occur in high concurrent OLTP workload
+ * is going on and at that time most of the transactions would be generated
+ * during the same time and most of them would fall in same clog page as
+ * each page can hold status of 32k transactions. However, there is an
+ * exception where in some extreme conditions we might get different page
+ * requests added in the same group but we have handled that by switching
+ * the bank lock, although that is not the most performant way that's not
+ * the common case either so we are fine with that.
+ *
+ * Also to be noted that unless the leader of the current group does not
+ * get the lock we don't clear the 'procglobal->clogGroupFirst' that means
+ * concurrently if we get the requesters for different SLRU pages then
+ * those will have to go for the normal update instead of group update and
+ * that's fine as that is not the common case. As soon as the leader of
+ * the current group gets the lock for the required bank that time we clear
+ * this value and now other requesters (which might want to update a
+ * different page and that might fall into the different bank as well) are
+ * allowed to form a new group as the first group is now detached. So if
+ * the new group has a request for a different SLRU-bank lock then the
+ * group leader of this group might also get the lock while the first group
+ * is performing the update and these two groups can perform the group
+ * update concurrently but it is completely safe as these two leaders are
+ * operating on completely different SLRU pages and they both are holding
+ * their respective SLRU locks.
+ */
nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
while (true)
@@ -508,8 +550,17 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
return true;
}
- /* We are the leader. Acquire the lock on behalf of everyone. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ /*
+ * Acquire the SLRU bank lock for the first page in the group before we
+ * close this group by setting procglobal->clogGroupFirst as
+ * INVALID_PGPROCNO so that we do not close the new entries to the group
+ * even before getting the lock and losing whole purpose of the group
+ * update.
+ */
+ nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
+ prevpageno = ProcGlobal->allProcs[nextidx].clogGroupMemberPage;
+ prevlock = SimpleLruGetBankLock(XactCtl, prevpageno);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
/*
* Now that we've got the lock, clear the list of processes waiting for
@@ -526,6 +577,37 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
while (nextidx != INVALID_PGPROCNO)
{
PGPROC *nextproc = &ProcGlobal->allProcs[nextidx];
+ int thispageno = nextproc->clogGroupMemberPage;
+
+ /*
+ * If the SLRU bank lock for the current page is not the same as that
+ * of the last page then we need to release the lock on the previous
+ * bank and acquire the lock on the bank for the page we are going to
+ * update now.
+ *
+ * Although on the best effort basis we try that all the requests
+ * within a group are for the same clog page there are some
+ * possibilities that there are request for more than one page in the
+ * same group (for details refer to the comment in the previous while
+ * loop). That scenario might not be very performant because while
+ * switching the lock the group leader might need to wait on the new
+ * lock if the pages are from different SLRU bank but it is safe
+ * because a) we are releasing the old lock before acquiring the new
+ * lock so there is should not be any deadlock situation b) and, we
+ * are always modifying the page under the correct SLRU lock.
+ */
+ if (thispageno != prevpageno)
+ {
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, thispageno);
+
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ prevlock = lock;
+ prevpageno = thispageno;
+ }
/*
* Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs
@@ -545,7 +627,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
}
/* We're done with the lock now. */
- LWLockRelease(XactSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
/*
* Now that we've released the lock, go back and wake everybody up. We
@@ -574,7 +657,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
/*
* Sets the commit status of a single transaction.
*
- * Must be called with XactSLRULock held
+ * Must be called with slot specific SLRU bank's lock held
*/
static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
@@ -666,7 +749,7 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
lsnindex = GetLSNIndex(slotno, xid);
*lsn = XactCtl->shared->group_lsn[lsnindex];
- LWLockRelease(XactSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
return status;
}
@@ -700,8 +783,8 @@ CLOGShmemInit(void)
{
XactCtl->PagePrecedes = CLOGPagePrecedes;
SimpleLruInit(XactCtl, "Xact", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
- XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER,
- SYNC_HANDLER_CLOG, false);
+ "pg_xact", LWTRANCHE_XACT_BUFFER,
+ LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
}
@@ -715,8 +798,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, 0);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the commit log */
slotno = ZeroCLOGPage(0, false);
@@ -725,7 +809,7 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -760,14 +844,10 @@ StartupCLOG(void)
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
/*
* Initialize our idea of the latest page number.
*/
- XactCtl->shared->latest_page_number = pageno;
-
- LWLockRelease(XactSLRULock);
+ pg_atomic_init_u64(&XactCtl->shared->latest_page_number, pageno);
}
/*
@@ -778,8 +858,9 @@ TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* Zero out the remainder of the current clog page. Under normal
@@ -811,7 +892,7 @@ TrimCLOG(void)
XactCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -843,6 +924,7 @@ void
ExtendCLOG(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -853,13 +935,14 @@ ExtendCLOG(TransactionId newestXact)
return;
pageno = TransactionIdToPage(newestXact);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCLOGPage(pageno, true);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
@@ -997,16 +1080,18 @@ clog_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
else if (info == CLOG_TRUNCATE)
{
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 41337471e2..9e932a161b 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -228,8 +228,9 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
{
int slotno;
int i;
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid);
@@ -239,13 +240,13 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
CommitTsCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
* Sets the commit timestamp of a single transaction.
*
- * Must be called with CommitTsSLRULock held
+ * Must be called with slot specific SLRU bank's Lock held
*/
static void
TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
@@ -346,7 +347,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (nodeid)
*nodeid = entry.nodeid;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(CommitTsCtl, pageno));
return *ts != 0;
}
@@ -536,8 +537,8 @@ CommitTsShmemInit(void)
CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
SimpleLruInit(CommitTsCtl, "CommitTs", CommitTsShmemBuffers(), 0,
- CommitTsSLRULock, "pg_commit_ts",
- LWTRANCHE_COMMITTS_BUFFER,
+ "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
+ LWTRANCHE_COMMITTS_SLRU,
SYNC_HANDLER_COMMIT_TS,
false);
SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
@@ -695,9 +696,7 @@ ActivateCommitTs(void)
/*
* Re-Initialize our idea of the latest page number.
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
- CommitTsCtl->shared->latest_page_number = pageno;
- LWLockRelease(CommitTsSLRULock);
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number, pageno);
/*
* If CommitTs is enabled, but it wasn't in the previous server run, we
@@ -724,12 +723,13 @@ ActivateCommitTs(void)
if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno))
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/* Change the activation status in shared memory. */
@@ -778,9 +778,9 @@ DeactivateCommitTs(void)
* be overwritten anyway when we wrap around, but it seems better to be
* tidy.)
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ SimpleLruAcquireAllBankLock(CommitTsCtl, LW_EXCLUSIVE);
(void) SlruScanDirectory(CommitTsCtl, SlruScanDirCbDeleteAll, NULL);
- LWLockRelease(CommitTsSLRULock);
+ SimpleLruReleaseAllBankLock(CommitTsCtl);
}
/*
@@ -812,6 +812,7 @@ void
ExtendCommitTs(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -832,12 +833,14 @@ ExtendCommitTs(TransactionId newestXact)
pageno = TransactionIdToCTsPage(newestXact);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCommitTsPage(pageno, !InRecovery);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -991,16 +994,18 @@ commit_ts_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
else if (info == COMMIT_TS_TRUNCATE)
{
@@ -1012,7 +1017,8 @@ commit_ts_redo(XLogReaderState *record)
* During XLOG replay, latest_page_number isn't set up yet; insert a
* suitable value to bypass the sanity test in SimpleLruTruncate.
*/
- CommitTsCtl->shared->latest_page_number = trunc->pageno;
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number,
+ trunc->pageno);
SimpleLruTruncate(CommitTsCtl, trunc->pageno);
}
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index f8eceeac30..dbabc187b9 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -193,10 +193,10 @@ static SlruCtlData MultiXactMemberCtlData;
/*
* MultiXact state shared across all backends. All this state is protected
- * by MultiXactGenLock. (We also use MultiXactOffsetSLRULock and
- * MultiXactMemberSLRULock to guard accesses to the two sets of SLRU
- * buffers. For concurrency's sake, we avoid holding more than one of these
- * locks at a time.)
+ * by MultiXactGenLock. (We also use SLRU bank's lock of MultiXactOffset and
+ * MultiXactMember to guard accesses to the two sets of SLRU buffers. For
+ * concurrency's sake, we avoid holding more than one of these locks at a
+ * time.)
*/
typedef struct MultiXactStateData
{
@@ -871,12 +871,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int slotno;
MultiXactOffset *offptr;
int i;
-
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLock *lock;
+ LWLock *prevlock = NULL;
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+
/*
* Note: we pass the MultiXactId to SimpleLruReadPage as the "transaction"
* to complain about if there's any I/O error. This is kinda bogus, but
@@ -892,10 +895,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
- /* Exchange our lock */
- LWLockRelease(MultiXactOffsetSLRULock);
-
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ /* Release MultiXactOffset SLRU lock. */
+ LWLockRelease(lock);
prev_pageno = -1;
@@ -917,6 +918,20 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
if (pageno != prev_pageno)
{
+ /*
+ * MultiXactMember SLRU page is changed so check if this new page
+ * fall into the different SLRU bank then release the old bank's
+ * lock and acquire lock on the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -937,7 +952,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
}
/*
@@ -1240,6 +1256,8 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
+ LWLock *lock;
+ LWLock *prevlock = NULL;
debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
@@ -1343,11 +1361,22 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
* time on every multixact creation.
*/
retry:
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
-
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ /*
+ * If this page falls under a different bank, release the old bank's lock
+ * and acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1380,7 +1409,21 @@ retry:
entryno = MultiXactIdToOffsetEntry(tmpMXact);
if (pageno != prev_pageno)
+ {
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact);
+ }
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1389,7 +1432,8 @@ retry:
if (nextMXOffset == 0)
{
/* Corner case 2: next multixact is still being filled in */
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
CHECK_FOR_INTERRUPTS();
pg_usleep(1000L);
goto retry;
@@ -1398,13 +1442,11 @@ retry:
length = nextMXOffset - offset;
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
- /* Now get the members themselves. */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
-
truelength = 0;
prev_pageno = -1;
for (i = 0; i < length; i++, offset++)
@@ -1420,6 +1462,20 @@ retry:
if (pageno != prev_pageno)
{
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -1443,7 +1499,8 @@ retry:
truelength++;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock)
+ LWLockRelease(prevlock);
/* A multixid with zero members should not happen */
Assert(truelength > 0);
@@ -1853,15 +1910,15 @@ MultiXactShmemInit(void)
SimpleLruInit(MultiXactOffsetCtl,
"MultiXactOffset", multixact_offsets_buffers, 0,
- MultiXactOffsetSLRULock, "pg_multixact/offsets",
- LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
"MultiXactMember", multixact_members_buffers, 0,
- MultiXactMemberSLRULock, "pg_multixact/members",
- LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ "pg_multixact/members", LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
SYNC_HANDLER_MULTIXACT_MEMBER,
false);
/* doesn't call SimpleLruTruncate() or meet criteria for unit tests */
@@ -1897,8 +1954,10 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ LWLock *lock;
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the offsets log */
slotno = ZeroMultiXactOffsetPage(0, false);
@@ -1907,9 +1966,10 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the members log */
slotno = ZeroMultiXactMemberPage(0, false);
@@ -1918,7 +1978,7 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -1978,10 +2038,12 @@ static void
MaybeExtendOffsetSlru(void)
{
int64 pageno;
+ LWLock *lock;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno))
{
@@ -1996,7 +2058,7 @@ MaybeExtendOffsetSlru(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2018,13 +2080,15 @@ StartupMultiXact(void)
* Initialize offset's idea of the latest page number.
*/
pageno = MultiXactIdToOffsetPage(multi);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Initialize member's idea of the latest page number.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
}
/*
@@ -2049,13 +2113,13 @@ TrimMultiXact(void)
LWLockRelease(MultiXactGenLock);
/* Clean up offsets state */
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for offsets.
*/
pageno = MultiXactIdToOffsetPage(nextMXact);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Zero out the remainder of the current offsets page. See notes in
@@ -2070,7 +2134,9 @@ TrimMultiXact(void)
{
int slotno;
MultiXactOffset *offptr;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -2078,18 +2144,17 @@ TrimMultiXact(void)
MemSet(offptr, 0, BLCKSZ - (entryno * sizeof(MultiXactOffset)));
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactOffsetSLRULock);
-
/* And the same for members */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for members.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
/*
* Zero out the remainder of the current members page. See notes in
@@ -2101,7 +2166,9 @@ TrimMultiXact(void)
int slotno;
TransactionId *xidptr;
int memberoff;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
memberoff = MXOffsetToMemberOffset(offset);
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset);
xidptr = (TransactionId *)
@@ -2116,10 +2183,9 @@ TrimMultiXact(void)
*/
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactMemberSLRULock);
-
/* signal that we're officially up */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->finishedStartup = true;
@@ -2407,6 +2473,7 @@ static void
ExtendMultiXactOffset(MultiXactId multi)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2417,13 +2484,14 @@ ExtendMultiXactOffset(MultiXactId multi)
return;
pageno = MultiXactIdToOffsetPage(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactOffsetPage(pageno, true);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2456,15 +2524,17 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
if (flagsoff == 0 && flagsbit == 0)
{
int64 pageno;
+ LWLock *lock;
pageno = MXOffsetToMemberPage(offset);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactMemberPage(pageno, true);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2762,7 +2832,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
offset = *offptr;
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(MultiXactOffsetCtl, pageno));
*result = offset;
return true;
@@ -3244,31 +3314,35 @@ multixact_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactOffsetPage(pageno, false);
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactMemberPage(pageno, false);
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
@@ -3334,7 +3408,8 @@ multixact_redo(XLogReaderState *record)
* SimpleLruTruncate.
*/
pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);
LWLockRelease(MultiXactTruncationLock);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 211527b075..33670f7cfe 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -97,6 +97,21 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
*/
#define MAX_WRITEALL_BUFFERS 16
+/*
+ * Macro to get the index of lock for a given slotno in bank_lock array in
+ * SlruSharedData.
+ *
+ * Basically, the slru buffer pool is divided into banks of buffer and there is
+ * total SLRU_MAX_BANKLOCKS number of locks to protect access to buffer in the
+ * banks. Since we have max limit on the number of locks we can not always have
+ * one lock for each bank. So until the number of banks are
+ * <= SLRU_MAX_BANKLOCKS then there would be one lock protecting each bank
+ * otherwise one lock might protect multiple banks based on the number of
+ * banks.
+ */
+#define SLRU_SLOTNO_GET_BANKLOCKNO(slotno) \
+ (((slotno) / SLRU_BANK_SIZE) % SLRU_MAX_BANKLOCKS)
+
typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
@@ -118,34 +133,6 @@ typedef struct SlruWriteAllData *SlruWriteAll;
(a).segno = (xx_segno) \
)
-/*
- * Macro to mark a buffer slot "most recently used". Note multiple evaluation
- * of arguments!
- *
- * The reason for the if-test is that there are often many consecutive
- * accesses to the same page (particularly the latest page). By suppressing
- * useless increments of cur_lru_count, we reduce the probability that old
- * pages' counts will "wrap around" and make them appear recently used.
- *
- * We allow this code to be executed concurrently by multiple processes within
- * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
- * this should not cause any completely-bogus values to enter the computation.
- * However, it is possible for either cur_lru_count or individual
- * page_lru_count entries to be "reset" to lower values than they should have,
- * in case a process is delayed while it executes this macro. With care in
- * SlruSelectLRUPage(), this does little harm, and in any case the absolute
- * worst possible consequence is a nonoptimal choice of page to evict. The
- * gain from allowing concurrent reads of SLRU pages seems worth it.
- */
-#define SlruRecentlyUsed(shared, slotno) \
- do { \
- int new_lru_count = (shared)->cur_lru_count; \
- if (new_lru_count != (shared)->page_lru_count[slotno]) { \
- (shared)->cur_lru_count = ++new_lru_count; \
- (shared)->page_lru_count[slotno] = new_lru_count; \
- } \
- } while (0)
-
/* Saved info for SlruReportIOError */
typedef enum
{
@@ -173,6 +160,7 @@ static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
int64 segpage, void *data);
static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
/*
@@ -183,6 +171,8 @@ Size
SimpleLruShmemSize(int nslots, int nlsns)
{
Size sz;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
/* we assume nslots isn't so large as to risk overflow */
sz = MAXALIGN(sizeof(SlruSharedData));
@@ -192,6 +182,8 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
+ sz += MAXALIGN(nbanklocks * sizeof(LWLockPadded)); /* bank_locks[] */
+ sz += MAXALIGN(nbanks * sizeof(int)); /* bank_cur_lru_count[] */
if (nlsns > 0)
sz += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr)); /* group_lsn[] */
@@ -208,16 +200,19 @@ SimpleLruShmemSize(int nslots, int nlsns)
* nlsns: number of LSN groups per page (set to zero if not relevant).
* ctllock: LWLock to use to control access to the shared control structure.
* subdir: PGDATA-relative subdirectory that will contain the files.
- * tranche_id: LWLock tranche ID to use for the SLRU's per-buffer LWLocks.
+ * buffer_tranche_id: tranche ID to use for the SLRU's per-buffer LWLocks.
+ * bank_tranche_id: tranche ID to use for the bank LWLocks.
* sync_handler: which set of functions to use to handle sync requests
*/
void
SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
+ const char *subdir, int buffer_tranche_id, int bank_tranche_id,
SyncRequestHandler sync_handler, bool long_segment_names)
{
SlruShared shared;
bool found;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
shared = (SlruShared) ShmemInitStruct(name,
SimpleLruShmemSize(nslots, nlsns),
@@ -229,18 +224,16 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
char *ptr;
Size offset;
int slotno;
+ int bankno;
+ int banklockno;
Assert(!found);
memset(shared, 0, sizeof(SlruSharedData));
- shared->ControlLock = ctllock;
-
shared->num_slots = nslots;
shared->lsn_groups_per_page = nlsns;
- shared->cur_lru_count = 0;
-
/* shared->latest_page_number will be set later */
shared->slru_stats_idx = pgstat_get_slru_index(name);
@@ -261,6 +254,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
/* Initialize LWLocks */
shared->buffer_locks = (LWLockPadded *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(LWLockPadded));
+ shared->bank_locks = (LWLockPadded *) (ptr + offset);
+ offset += MAXALIGN(nbanklocks * sizeof(LWLockPadded));
+ shared->bank_cur_lru_count = (int *) (ptr + offset);
+ offset += MAXALIGN(nbanks * sizeof(int));
if (nlsns > 0)
{
@@ -272,7 +269,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
for (slotno = 0; slotno < nslots; slotno++)
{
LWLockInitialize(&shared->buffer_locks[slotno].lock,
- tranche_id);
+ buffer_tranche_id);
shared->page_buffer[slotno] = ptr;
shared->page_status[slotno] = SLRU_PAGE_EMPTY;
@@ -281,6 +278,15 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ptr += BLCKSZ;
}
+ /* Initialize the bank locks. */
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockInitialize(&shared->bank_locks[banklockno].lock,
+ bank_tranche_id);
+
+ /* Initialize the bank lru counters. */
+ for (bankno = 0; bankno < nbanks; bankno++)
+ shared->bank_cur_lru_count[bankno] = 0;
+
/* Should fit to estimated shmem size */
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
@@ -335,7 +341,7 @@ SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
SimpleLruZeroLSNs(ctl, slotno);
/* Assume this page is now the latest active page */
- shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&shared->latest_page_number, pageno);
/* update the stats counter of zeroed pages */
pgstat_count_slru_page_zeroed(shared->slru_stats_idx);
@@ -374,12 +380,13 @@ static void
SimpleLruWaitIO(SlruCtl ctl, int slotno)
{
SlruShared shared = ctl->shared;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* See notes at top of file */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);
LWLockRelease(&shared->buffer_locks[slotno].lock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
/*
* If the slot is still in an io-in-progress state, then either someone
@@ -430,10 +437,14 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
{
SlruShared shared = ctl->shared;
+ /* Caller must hold the bank lock for the input page. */
+ Assert(LWLockHeldByMe(SimpleLruGetBankLock(ctl, pageno)));
+
/* Outer loop handles restart if we must wait for someone else's I/O */
for (;;)
{
int slotno;
+ int banklockno;
bool ok;
/* See if page already is in memory; if not, pick victim slot */
@@ -476,9 +487,10 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
/* Acquire per-buffer lock (cannot deadlock, see notes at top) */
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
+ banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the read */
ok = SlruPhysicalReadPage(ctl, pageno, slotno);
@@ -487,7 +499,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
SimpleLruZeroLSNs(ctl, slotno);
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_READ_IN_PROGRESS &&
@@ -531,9 +543,10 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
int slotno;
int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
int bankend = bankstart + SLRU_BANK_SIZE;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(bankstart);
/* Try to find the page while holding only shared lock */
- LWLockAcquire(shared->ControlLock, LW_SHARED);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_SHARED);
/*
* See if the page is already in a buffer pool. The buffer pool is
@@ -557,8 +570,8 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
}
/* No luck, so switch to normal exclusive lock and do regular read */
- LWLockRelease(shared->ControlLock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
return SimpleLruReadPage(ctl, pageno, true, xid);
}
@@ -580,6 +593,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
SlruShared shared = ctl->shared;
int64 pageno = shared->page_number[slotno];
bool ok;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* If a write is in progress, wait for it to finish */
while (shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS &&
@@ -608,7 +622,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the write */
ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
@@ -623,7 +637,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
}
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS);
@@ -1067,13 +1081,14 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
- int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankno = pageno & ctl->bank_mask;
+ int bankstart = bankno * SLRU_BANK_SIZE;
int bankend = bankstart + SLRU_BANK_SIZE;
/*
* See if the page is already in a buffer pool. The buffer pool is
- * divided into banks of buffers and each pageno may reside only in one
- * bank so limit the search within the bank.
+ * divided into banks of buffers and each pageno may reside only in
+ * one bank so limit the search within the bank.
*/
for (slotno = bankstart; slotno < bankend; slotno++)
{
@@ -1109,7 +1124,7 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* That gets us back on the path to having good data when there are
* multiple pages with the same lru_count.
*/
- cur_count = (shared->cur_lru_count)++;
+ cur_count = (shared->bank_cur_lru_count[bankno])++;
for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
@@ -1131,7 +1146,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
this_delta = 0;
}
this_page_number = shared->page_number[slotno];
- if (this_page_number == shared->latest_page_number)
+ if (this_page_number ==
+ pg_atomic_read_u64(&shared->latest_page_number))
continue;
if (shared->page_status[slotno] == SLRU_PAGE_VALID)
{
@@ -1205,6 +1221,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
int slotno;
int64 pageno = 0;
int i;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
bool ok;
/* update the stats counter of flushes */
@@ -1215,10 +1232,23 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
*/
fdata.num_files = 0;
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
SlruInternalWritePage(ctl, slotno, &fdata);
/*
@@ -1232,7 +1262,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
!shared->page_dirty[slotno]));
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/*
* Now close any files that were open
@@ -1272,6 +1302,7 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
+ int prevlockno;
/* update the stats counter of truncates */
pgstat_count_slru_truncate(shared->slru_stats_idx);
@@ -1282,25 +1313,38 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
* or just after a checkpoint, any dirty pages should have been flushed
* already ... we're just being extra careful here.)
*/
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
-
restart:
/*
* While we are holding the lock, make an important safety check: the
* current endpoint page must not be eligible for removal.
*/
- if (ctl->PagePrecedes(shared->latest_page_number, cutoffPage))
+ if (ctl->PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
+ cutoffPage))
{
- LWLockRelease(shared->ControlLock);
ereport(LOG,
(errmsg("could not truncate directory \"%s\": apparent wraparound",
ctl->Dir)));
return;
}
+ prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
if (!ctl->PagePrecedes(shared->page_number[slotno], cutoffPage))
@@ -1330,10 +1374,12 @@ restart:
SlruInternalWritePage(ctl, slotno, NULL);
else
SimpleLruWaitIO(ctl, slotno);
+
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
goto restart;
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/* Now we can remove the old segment(s) */
(void) SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, &cutoffPage);
@@ -1374,15 +1420,29 @@ SlruDeleteSegment(SlruCtl ctl, int64 segno)
SlruShared shared = ctl->shared;
int slotno;
bool did_write;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
/* Clean out any possibly existing references to the segment. */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
restart:
did_write = false;
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
- int pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
+ int pagesegno;
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
+ pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
@@ -1416,7 +1476,7 @@ restart:
SlruInternalDeleteSegment(ctl, segno);
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
}
/*
@@ -1684,6 +1744,37 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
return result;
}
+/*
+ * Function to mark a buffer slot "most recently used".
+ *
+ * The reason for the if-test is that there are often many consecutive
+ * accesses to the same page (particularly the latest page). By suppressing
+ * useless increments of bank_cur_lru_count, we reduce the probability that old
+ * pages' counts will "wrap around" and make them appear recently used.
+ *
+ * We allow this code to be executed concurrently by multiple processes within
+ * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
+ * this should not cause any completely-bogus values to enter the computation.
+ * However, it is possible for either bank_cur_lru_count or individual
+ * page_lru_count entries to be "reset" to lower values than they should have,
+ * in case a process is delayed while it executes this function. With care in
+ * SlruSelectLRUPage(), this does little harm, and in any case the absolute
+ * worst possible consequence is a nonoptimal choice of page to evict. The
+ * gain from allowing concurrent reads of SLRU pages seems worth it.
+ */
+static inline void
+SlruRecentlyUsed(SlruShared shared, int slotno)
+{
+ int bankno = slotno / SLRU_BANK_SIZE;
+ int new_lru_count = shared->bank_cur_lru_count[bankno];
+
+ if (new_lru_count != shared->page_lru_count[slotno])
+ {
+ shared->bank_cur_lru_count[bankno] = ++new_lru_count;
+ shared->page_lru_count[slotno] = new_lru_count;
+ }
+}
+
/*
* Helper function for GUC check_hook to check whether slru buffers are in
* multiples of SLRU_BANK_SIZE.
@@ -1700,3 +1791,37 @@ check_slru_buffers(const char *name, int *newval)
SLRU_BANK_SIZE);
return false;
}
+
+/*
+ * Function to acquire all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, mode);
+}
+
+/*
+ * Function to release all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruReleaseAllBankLock(SlruCtl ctl)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index 82243c2728..c55d709846 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -87,12 +87,14 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
+ LWLock *lock;
TransactionId *ptr;
Assert(TransactionIdIsValid(parent));
Assert(TransactionIdFollows(xid, parent));
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
@@ -110,7 +112,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
SubTransCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -140,7 +142,7 @@ SubTransGetParent(TransactionId xid)
parent = *ptr;
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
return parent;
}
@@ -203,9 +205,8 @@ SUBTRANSShmemInit(void)
{
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
SimpleLruInit(SubTransCtl, "Subtrans", subtrans_buffers, 0,
- SubtransSLRULock, "pg_subtrans",
- LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
- false);
+ "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
+ LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
}
@@ -223,8 +224,9 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
slotno = ZeroSUBTRANSPage(0);
@@ -233,7 +235,7 @@ BootStrapSUBTRANS(void)
SimpleLruWritePage(SubTransCtl, slotno);
Assert(!SubTransCtl->shared->page_dirty[slotno]);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -263,6 +265,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
FullTransactionId nextXid;
int64 startPage;
int64 endPage;
+ LWLock *prevlock;
+ LWLock *lock;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -270,23 +274,47 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
* Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
* the new page without regard to whatever was previously on disk.
*/
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
-
startPage = TransactionIdToPage(oldestActiveXID);
nextXid = TransamVariables->nextXid;
endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
+ prevlock = SimpleLruGetBankLock(SubTransCtl, startPage);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
while (startPage != endPage)
{
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release
+ * the lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
(void) ZeroSUBTRANSPage(startPage);
startPage++;
/* must account for wraparound */
if (startPage > TransactionIdToPage(MaxTransactionId))
startPage = 0;
}
- (void) ZeroSUBTRANSPage(startPage);
- LWLockRelease(SubtransSLRULock);
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release the
+ * lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ (void) ZeroSUBTRANSPage(startPage);
+ LWLockRelease(lock);
}
/*
@@ -320,6 +348,7 @@ void
ExtendSUBTRANS(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -331,12 +360,13 @@ ExtendSUBTRANS(TransactionId newestXact)
pageno = TransactionIdToPage(newestXact);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page */
ZeroSUBTRANSPage(pageno);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 9059c0a202..0c2ac60946 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -267,9 +267,10 @@ typedef struct QueueBackendStatus
* both NotifyQueueLock and NotifyQueueTailLock in EXCLUSIVE mode, backends
* can change the tail pointers.
*
- * NotifySLRULock is used as the control lock for the pg_notify SLRU buffers.
+ * SLRU buffer pool is divided in banks and bank wise SLRU lock is used as
+ * the control lock for the pg_notify SLRU buffers.
* In order to avoid deadlocks, whenever we need multiple locks, we first get
- * NotifyQueueTailLock, then NotifyQueueLock, and lastly NotifySLRULock.
+ * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock.
*
* Each backend uses the backend[] array entry with index equal to its
* BackendId (which can range from 1 to MaxBackends). We rely on this to make
@@ -543,7 +544,7 @@ AsyncShmemInit(void)
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
- NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
+ "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
SYNC_HANDLER_NONE, true);
if (!found)
@@ -1357,7 +1358,7 @@ asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe)
* Eventually we will return NULL indicating all is done.
*
* We are holding NotifyQueueLock already from the caller and grab
- * NotifySLRULock locally in this function.
+ * page specific SLRU bank lock locally in this function.
*/
static ListCell *
asyncQueueAddEntries(ListCell *nextNotify)
@@ -1367,9 +1368,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
int64 pageno;
int offset;
int slotno;
-
- /* We hold both NotifyQueueLock and NotifySLRULock during this operation */
- LWLockAcquire(NotifySLRULock, LW_EXCLUSIVE);
+ LWLock *prevlock;
/*
* We work with a local copy of QUEUE_HEAD, which we write back to shared
@@ -1390,6 +1389,11 @@ asyncQueueAddEntries(ListCell *nextNotify)
* page should be initialized already, so just fetch it.
*/
pageno = QUEUE_POS_PAGE(queue_head);
+ prevlock = SimpleLruGetBankLock(NotifyCtl, pageno);
+
+ /* We hold both NotifyQueueLock and SLRU bank lock during this operation */
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
+
if (QUEUE_POS_IS_ZERO(queue_head))
slotno = SimpleLruZeroPage(NotifyCtl, pageno);
else
@@ -1435,6 +1439,17 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Advance queue_head appropriately, and detect if page is full */
if (asyncQueueAdvance(&(queue_head), qe.length))
{
+ LWLock *lock;
+
+ pageno = QUEUE_POS_PAGE(queue_head);
+ lock = SimpleLruGetBankLock(NotifyCtl, pageno);
+ if (lock != prevlock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
/*
* Page is full, so we're done here, but first fill the next page
* with zeroes. The reason to do this is to ensure that slru.c's
@@ -1461,7 +1476,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Success, so update the global QUEUE_HEAD */
QUEUE_HEAD = queue_head;
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(prevlock);
return nextNotify;
}
@@ -1932,9 +1947,9 @@ asyncQueueReadAllNotifications(void)
/*
* We copy the data from SLRU into a local buffer, so as to avoid
- * holding the NotifySLRULock while we are examining the entries
- * and possibly transmitting them to our frontend. Copy only the
- * part of the page we will actually inspect.
+ * holding the SLRU lock while we are examining the entries and
+ * possibly transmitting them to our frontend. Copy only the part
+ * of the page we will actually inspect.
*/
slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage,
InvalidTransactionId);
@@ -1954,7 +1969,7 @@ asyncQueueReadAllNotifications(void)
NotifyCtl->shared->page_buffer[slotno] + curoffset,
copysize);
/* Release lock that we got from SimpleLruReadPage_ReadOnly() */
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(SimpleLruGetBankLock(NotifyCtl, curpage));
/*
* Process messages up to the stop position, end of page, or an
@@ -1995,7 +2010,7 @@ asyncQueueReadAllNotifications(void)
*
* The current page must have been fetched into page_buffer from shared
* memory. (We could access the page right in shared memory, but that
- * would imply holding the NotifySLRULock throughout this routine.)
+ * would imply holding the SLRU bank lock throughout this routine.)
*
* We stop if we reach the "stop" position, or reach a notification from an
* uncommitted transaction, or reach the end of the page.
@@ -2148,7 +2163,7 @@ asyncQueueAdvanceTail(void)
if (asyncQueuePagePrecedes(oldtailpage, boundary))
{
/*
- * SimpleLruTruncate() will ask for NotifySLRULock but will also
+ * SimpleLruTruncate() will ask for SLRU bank locks but will also
* release the lock again.
*/
SimpleLruTruncate(NotifyCtl, newtailpage);
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 2f2de5a562..b2433b6f21 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -194,6 +194,20 @@ static const char *const BuiltinTrancheNames[] = {
"DSMRegistryDSA",
/* LWTRANCHE_DSM_REGISTRY_HASH: */
"DSMRegistryHash",
+ /* LWTRANCHE_XACT_SLRU: */
+ "XactSLRU",
+ /* LWTRANCHE_SUBTRANS_SLRU: */
+ "SubtransSLRU",
+ /* LWTRANCHE_COMMITTS_SLRU: */
+ "CommitTSSLRU",
+ /* LWTRANCHE_MULTIXACTOFFSET_SLRU: */
+ "MultixactOffsetSLRU",
+ /* LWTRANCHE_MULTIXACTMEMBER_SLRU: */
+ "MultixactMemberSLRU",
+ /* LWTRANCHE_NOTIFY_SLRU: */
+ "NotifySLRU",
+ /* LWTRANCHE_SERIAL_SLRU: */
+ "SerialSLRU",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index a0163b2187..e4aa3d91c6 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -16,11 +16,11 @@ WALBufMappingLock 7
WALWriteLock 8
ControlFileLock 9
# 10 was CheckpointLock
-XactSLRULock 11
-SubtransSLRULock 12
+# 11 was XactSLRULock
+# 12 was SubtransSLRULock
MultiXactGenLock 13
-MultiXactOffsetSLRULock 14
-MultiXactMemberSLRULock 15
+# 14 was MultiXactOffsetSLRULock
+# 15 was MultiXactMemberSLRULock
RelCacheInitLock 16
CheckpointerCommLock 17
TwoPhaseStateLock 18
@@ -31,19 +31,19 @@ AutovacuumLock 22
AutovacuumScheduleLock 23
SyncScanLock 24
RelationMappingLock 25
-NotifySLRULock 26
+#26 was NotifySLRULock
NotifyQueueLock 27
SerializableXactHashLock 28
SerializableFinishedListLock 29
SerializablePredicateListLock 30
-SerialSLRULock 31
+SerialControlLock 31
SyncRepLock 32
BackgroundWorkerLock 33
DynamicSharedMemoryControlLock 34
AutoFileLock 35
ReplicationSlotAllocationLock 36
ReplicationSlotControlLock 37
-CommitTsSLRULock 38
+#38 was CommitTsSLRULock
CommitTsLock 39
ReplicationOriginLock 40
MultiXactTruncationLock 41
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 10c51e2883..ea4392ab15 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -809,9 +809,9 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- serial_buffers, 0, SerialSLRULock, "pg_serial",
- LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
- false);
+ serial_buffers, 0, "pg_serial",
+ LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
+ SYNC_HANDLER_NONE, false);
#ifdef USE_ASSERT_CHECKING
SerialPagePrecedesLogicallyUnitTests();
#endif
@@ -848,12 +848,14 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
int slotno;
int64 firstZeroPage;
bool isNewPage;
+ LWLock *lock;
Assert(TransactionIdIsValid(xid));
targetPage = SerialPage(xid);
+ lock = SimpleLruGetBankLock(SerialSlruCtl, targetPage);
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* If no serializable transactions are active, there shouldn't be anything
@@ -903,7 +905,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
SerialValue(slotno, xid) = minConflictCommitSeqNo;
SerialSlruCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -921,10 +923,10 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
Assert(TransactionIdIsValid(xid));
- LWLockAcquire(SerialSLRULock, LW_SHARED);
+ LWLockAcquire(SerialControlLock, LW_SHARED);
headXid = serialControl->headXid;
tailXid = serialControl->tailXid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
if (!TransactionIdIsValid(headXid))
return 0;
@@ -936,13 +938,13 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
return 0;
/*
- * The following function must be called without holding SerialSLRULock,
+ * The following function must be called without holding SLRU bank lock,
* but will return with that lock held, which must then be released.
*/
slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
SerialPage(xid), xid);
val = SerialValue(slotno, xid);
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SerialSlruCtl, SerialPage(xid)));
return val;
}
@@ -955,7 +957,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
static void
SerialSetActiveSerXmin(TransactionId xid)
{
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/*
* When no sxacts are active, nothing overlaps, set the xid values to
@@ -967,7 +969,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = InvalidTransactionId;
serialControl->headXid = InvalidTransactionId;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -985,7 +987,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = xid;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -994,7 +996,7 @@ SerialSetActiveSerXmin(TransactionId xid)
serialControl->tailXid = xid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
}
/*
@@ -1008,12 +1010,12 @@ CheckPointPredicate(void)
{
int truncateCutoffPage;
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/* Exit quickly if the SLRU is currently not in use. */
if (serialControl->headPage < 0)
{
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -1073,7 +1075,7 @@ CheckPointPredicate(void)
serialControl->headPage = -1;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
/* Truncate away pages that are no longer required */
SimpleLruTruncate(SerialSlruCtl, truncateCutoffPage);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index a5df835dd4..e6235d5056 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -292,11 +292,7 @@ SInvalWrite "Waiting to add a message to the shared catalog invalidation queue."
WALBufMapping "Waiting to replace a page in WAL buffers."
WALWrite "Waiting for WAL buffers to be written to disk."
ControlFile "Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
-XactSLRU "Waiting to access the transaction status SLRU cache."
-SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
MultiXactGen "Waiting to read or update shared multixact state."
-MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
-MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
RelCacheInit "Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
CheckpointerComm "Waiting to manage fsync requests."
TwoPhaseState "Waiting to read or update the state of prepared transactions."
@@ -307,19 +303,17 @@ Autovacuum "Waiting to read or update the current state of autovacuum workers."
AutovacuumSchedule "Waiting to ensure that a table selected for autovacuum still needs vacuuming."
SyncScan "Waiting to select the starting location of a synchronized table scan."
RelationMapping "Waiting to read or update a <filename>pg_filenode.map</filename> file (used to track the filenode assignments of certain system catalogs)."
-NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
NotifyQueue "Waiting to read or update <command>NOTIFY</command> messages."
SerializableXactHash "Waiting to read or update information about serializable transactions."
SerializableFinishedList "Waiting to access the list of finished serializable transactions."
SerializablePredicateList "Waiting to access the list of predicate locks held by serializable transactions."
-SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SerialControl "Waiting to access the serializable transaction conflict SLRU cache."
SyncRep "Waiting to read or update information about the state of synchronous replication."
BackgroundWorker "Waiting to read or update background worker state."
DynamicSharedMemoryControl "Waiting to read or update dynamic shared memory allocation information."
AutoFile "Waiting to update the <filename>postgresql.auto.conf</filename> file."
ReplicationSlotAllocation "Waiting to allocate or free a replication slot."
ReplicationSlotControl "Waiting to read or update replication slot state."
-CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
CommitTs "Waiting to read or update the last value set for a transaction commit timestamp."
ReplicationOrigin "Waiting to create, drop or use a replication origin."
MultiXactTruncation "Waiting to read or truncate multixact information."
@@ -371,6 +365,13 @@ LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
+XactSLRU "Waiting to access the transaction status SLRU cache."
+SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
+CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
+MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
+MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
+NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
+SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
#
# Wait Events - Lock
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 2b74e11d42..46767f6f84 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -25,6 +25,14 @@
*/
#define SLRU_BANK_SIZE 16
+/*
+ * Number of bank locks to protect the in memory buffer slot access within a
+ * SLRU bank. If the number of banks are <= SLRU_MAX_BANKLOCKS then there will
+ * be one lock per bank otherwise each lock will protect multiple banks depends
+ * upon the number of banks.
+ */
+#define SLRU_MAX_BANKLOCKS 128
+
/*
* To avoid overflowing internal arithmetic and the size_t data type, the
* number of buffers should not exceed this number.
@@ -65,8 +73,6 @@ typedef enum
*/
typedef struct SlruSharedData
{
- LWLock *ControlLock;
-
/* Number of buffers managed by this SLRU structure */
int num_slots;
@@ -79,8 +85,30 @@ typedef struct SlruSharedData
bool *page_dirty;
int64 *page_number;
int *page_lru_count;
+
+ /* The buffer_locks protects the I/O on each buffer slots */
LWLockPadded *buffer_locks;
+ /* Locks to protect the in memory buffer slot access in SLRU bank. */
+ LWLockPadded *bank_locks;
+
+ /*----------
+ * A bank-wise LRU counter is maintained because we do a victim buffer
+ * search within a bank. Furthermore, manipulating an individual bank
+ * counter avoids frequent cache invalidation since we update it every time
+ * we access the page.
+ *
+ * We mark a page "most recently used" by setting
+ * page_lru_count[slotno] = ++bank_cur_lru_count[bankno];
+ * The oldest page in the bank is therefore the one with the highest value
+ * of
+ * bank_cur_lru_count[bankno] - page_lru_count[slotno]
+ * The counts will eventually wrap around, but this calculation still
+ * works as long as no page's age exceeds INT_MAX counts.
+ *----------
+ */
+ int *bank_cur_lru_count;
+
/*
* Optional array of WAL flush LSNs associated with entries in the SLRU
* pages. If not zero/NULL, we must flush WAL before writing pages (true
@@ -92,23 +120,12 @@ typedef struct SlruSharedData
XLogRecPtr *group_lsn;
int lsn_groups_per_page;
- /*----------
- * We mark a page "most recently used" by setting
- * page_lru_count[slotno] = ++cur_lru_count;
- * The oldest page is therefore the one with the highest value of
- * cur_lru_count - page_lru_count[slotno]
- * The counts will eventually wrap around, but this calculation still
- * works as long as no page's age exceeds INT_MAX counts.
- *----------
- */
- int cur_lru_count;
-
/*
* latest_page_number is the page number of the current end of the log;
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int64 latest_page_number;
+ pg_atomic_uint64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -165,11 +182,24 @@ typedef struct SlruCtlData
typedef SlruCtlData *SlruCtl;
+/*
+ * Get the SLRU bank lock for given SlruCtl and the pageno.
+ *
+ * This lock needs to be acquired to access the slru buffer slots in the
+ * respective bank.
+ */
+static inline LWLock *
+SimpleLruGetBankLock(SlruCtl ctl, int64 pageno)
+{
+ int banklockno = (pageno & ctl->bank_mask) % SLRU_MAX_BANKLOCKS;
+
+ return &(ctl->shared->bank_locks[banklockno].lock);
+}
extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
- SyncRequestHandler sync_handler,
+ const char *subdir, int buffer_tranche_id,
+ int bank_tranche_id, SyncRequestHandler sync_handler,
bool long_segment_names);
extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
@@ -199,5 +229,7 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
extern bool check_slru_buffers(const char *name, int *newval);
+extern void SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode);
+extern void SimpleLruReleaseAllBankLock(SlruCtl ctl);
#endif /* SLRU_H */
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 50a65e046d..408b5dd19a 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -209,6 +209,13 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_LAUNCHER_HASH,
LWTRANCHE_DSM_REGISTRY_DSA,
LWTRANCHE_DSM_REGISTRY_HASH,
+ LWTRANCHE_XACT_SLRU,
+ LWTRANCHE_COMMITTS_SLRU,
+ LWTRANCHE_SUBTRANS_SLRU,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
+ LWTRANCHE_NOTIFY_SLRU,
+ LWTRANCHE_SERIAL_SLRU,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 4b31f331ca..068a21f125 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -40,10 +40,6 @@ PG_FUNCTION_INFO_V1(test_slru_delete_all);
/* Number of SLRU page slots */
#define NUM_TEST_BUFFERS 16
-/* SLRU control lock */
-LWLock TestSLRULock;
-#define TestSLRULock (&TestSLRULock)
-
static SlruCtlData TestSlruCtlData;
#define TestSlruCtl (&TestSlruCtlData)
@@ -63,9 +59,9 @@ test_slru_page_write(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = text_to_cstring(PG_GETARG_TEXT_PP(1));
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
-
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruZeroPage(TestSlruCtl, pageno);
/* these should match */
@@ -80,7 +76,7 @@ test_slru_page_write(PG_FUNCTION_ARGS)
BLCKSZ - 1);
SimpleLruWritePage(TestSlruCtl, slotno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_VOID();
}
@@ -99,13 +95,14 @@ test_slru_page_read(PG_FUNCTION_ARGS)
bool write_ok = PG_GETARG_BOOL(1);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(TestSlruCtl, pageno,
write_ok, InvalidTransactionId);
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -116,14 +113,15 @@ test_slru_page_readonly(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
pageno,
InvalidTransactionId);
- Assert(LWLockHeldByMe(TestSLRULock));
+ Assert(LWLockHeldByMe(lock));
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -133,10 +131,11 @@ test_slru_page_exists(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool found;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
found = SimpleLruDoesPhysicalPageExist(TestSlruCtl, pageno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_BOOL(found);
}
@@ -221,6 +220,7 @@ test_slru_shmem_startup(void)
const bool long_segment_names = true;
const char slru_dir_name[] = "pg_test_slru";
int test_tranche_id;
+ int test_buffer_tranche_id;
if (prev_shmem_startup_hook)
prev_shmem_startup_hook();
@@ -234,12 +234,15 @@ test_slru_shmem_startup(void)
/* initialize the SLRU facility */
test_tranche_id = LWLockNewTrancheId();
LWLockRegisterTranche(test_tranche_id, "test_slru_tranche");
- LWLockInitialize(TestSLRULock, test_tranche_id);
+
+ test_buffer_tranche_id = LWLockNewTrancheId();
+ LWLockRegisterTranche(test_tranche_id, "test_buffer_tranche");
TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
SimpleLruInit(TestSlruCtl, "TestSLRU",
- NUM_TEST_BUFFERS, 0, TestSLRULock, slru_dir_name,
- test_tranche_id, SYNC_HANDLER_NONE, long_segment_names);
+ NUM_TEST_BUFFERS, 0, slru_dir_name,
+ test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
+ long_segment_names);
}
void
--
2.39.2 (Apple Git-143)
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-25 16:22 Alvaro Herrera <[email protected]>
parent: Dilip Kumar <[email protected]>
1 sibling, 2 replies; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-25 16:22 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
Here's a touched-up version of this patch.
First, PROC_GLOBAL->clogGroupFirst and SlruCtl->latest_page_number
change from being protected by locks to being atomics, but there's no
mention of considering memory barriers that they should have. Looking
at the code, I think the former doesn't need any additional barriers,
but latest_page_number is missing some, which I have added. This
deserves another careful look.
Second and most user visibly, the GUC names seem to have been chosen
based on the source-code variables, which have never meant to be user-
visible. So I renamed a few:
xact_buffers -> transaction_buffers
subtrans_buffers -> subtransaction_buffers
serial_buffers -> serializable_buffers
commit_ts_buffers -> commit_timestamp_buffers
(unchanged: multixact_offsets_buffers, multixact_members_buffers,
notify_buffers)
I did this explicitly trying to avoid using the term SLRU in a
user-visible manner, because what do users care? But immediately after
doing this I realized that we already have pg_stat_slru, so maybe the
cat is already out of the bag, and so perhaps we should name these GUCS
as, say slru_transaction_buffers? That may make the connection between
these things a little more explicit. (I do think we need to add
cross-links in the documentation of those GUCs to the pg_stat_slru
docs and vice-versa.)
Another thing that bothered me a bit is that we have auto-tuning for
transaction_buffers and commit_timestamp_buffers, but not for
subtransaction_buffers. (Autotuning means you set the GUC to 0 and it
scales with shared_buffers). I don't quite understand what's the reason
for the ommision, so I added it for subtrans too. I think it may make
sense to do likewise for the multixact ones too, not sure. It doesn't
seem worth having that for pg_serial and notify.
While messing about with these GUCs I realized that the usage of the
show_hook to print what the current number is, when autoturning is used,
was bogus: SHOW would print the number of blocks for (say)
transaction_buffers, but if you asked it to print (say)
multixact_offsets_buffers, it would give a size in MB or kB. I'm sure
such an inconsistency would bite us. So, digging around I found that a
good way to handle this is to remove the show_hook, and instead call
SetConfigOption() at the time when the ShmemInit function is called,
with the correct number of buffers determined. This is pretty much what
is used for XLOGbuffers, and it works correctly as far as my testing
shows.
Still with these auto-tuning GUCs, I noticed that the auto-tuning code
would continue to grow the buffer sizes with shared_buffers to
arbitrarily large values. I added an arbitrary maximum of 1024 (8 MB),
which is much higher than the current value of 128; but if you have
(say) 30 GB of shared_buffers (not uncommon these days), do you really
need 30MB of pg_clog cache? It seems mostly unnecessary ... and you can
still set it manually that way if you need it. So, largely I just
rewrote those small functions completely.
I also made the SGML documentation and postgresql.sample.conf all match
what the code actually docs. The whole thing wasn't particularly
consistent.
I rewrote a bunch of code comments and moved stuff around to appear in
alphabetical order, etc.
More comment rewriting still pending.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
Attachments:
[text/x-diff] v15-slru-optimization.patch (111.4K, ../../[email protected]/2-v15-slru-optimization.patch)
download | inline diff:
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 61038472c5..3e3119865a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2006,6 +2006,145 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-commit-timestamp-buffers" xreflabel="commit_timestamp_buffers">
+ <term><varname>commit_timestamp_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>commit_timestamp_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of memory to use to cache the contents of
+ <literal>pg_commit_ts</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-members-buffers" xreflabel="multixact_members_buffers">
+ <term><varname>multixact_members_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_members_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/members</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>32</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-offsets-buffers" xreflabel="multixact_offsets_buffers">
+ <term><varname>multixact_offsets_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_offsets_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/offsets</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>16</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-notify-buffers" xreflabel="notify_buffers">
+ <term><varname>notify_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>notify_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_notify</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>16</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-serializable-buffers" xreflabel="serializable_buffers">
+ <term><varname>serializable_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>serializable_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_serial</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>32</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-subtransaction-buffers" xreflabel="subtransaction_buffers">
+ <term><varname>subtransaction_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>subtransaction_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_subtrans</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-transaction-buffers" xreflabel="transaction_buffers">
+ <term><varname>transaction_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>transaction_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_xact</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
<term><varname>max_stack_depth</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index f6e7da7ffc..fb22bc2068 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -43,6 +43,7 @@
#include "pgstat.h"
#include "storage/proc.h"
#include "storage/sync.h"
+#include "utils/guc_hooks.h"
/*
* Defines for CLOG page sizes. A page is the same BLCKSZ as is used
@@ -62,6 +63,15 @@
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
#define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1)
+/*
+ * Because space used in CLOG by each transaction is so small, we place a
+ * smaller limit on the number of CLOG buffers than SLRU allows. No other
+ * SLRU needs this.
+ */
+#define CLOG_MAX_ALLOWED_BUFFERS \
+ Min(SLRU_MAX_ALLOWED_BUFFERS, \
+ (((MaxTransactionId / 2) + (CLOG_XACTS_PER_PAGE - 1)) / CLOG_XACTS_PER_PAGE))
+
/*
* Although we return an int64 the actual value can't currently exceed
@@ -284,14 +294,19 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
+ LWLock *lock;
+
/* Can't use group update when PGPROC overflows. */
StaticAssertDecl(THRESHOLD_SUBTRANS_CLOG_OPT <= PGPROC_MAX_CACHED_SUBXIDS,
"group clog threshold less than PGPROC cached subxids");
+ /* Get the SLRU bank lock for the page we are going to access. */
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+
/*
- * When there is contention on XactSLRULock, we try to group multiple
+ * When there is contention on the bank lock, we try to group multiple
* updates; a single leader process will perform transaction status
- * updates for multiple backends so that the number of times XactSLRULock
+ * updates for multiple backends so that the number of times the bank lock
* needs to be acquired is reduced.
*
* For this optimization to be safe, the XID and subxids in MyProc must be
@@ -310,17 +325,17 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
nsubxids * sizeof(TransactionId)) == 0))
{
/*
- * If we can immediately acquire XactSLRULock, we update the status of
- * our own XID and release the lock. If not, try use group XID
- * update. If that doesn't work out, fall back to waiting for the
- * lock to perform an update for this transaction only.
+ * If we can immediately acquire the lock, we update the status of our
+ * own XID and release the lock. If not, try use group XID update. If
+ * that doesn't work out, fall back to waiting for the lock to perform
+ * an update for this transaction only.
*/
- if (LWLockConditionalAcquire(XactSLRULock, LW_EXCLUSIVE))
+ if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
{
/* Got the lock without waiting! Do the update. */
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
return;
}
else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
@@ -333,10 +348,10 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
}
/* Group update not applicable, or couldn't accept this page number. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -355,7 +370,8 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
Assert(status == TRANSACTION_STATUS_COMMITTED ||
status == TRANSACTION_STATUS_ABORTED ||
(status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
- Assert(LWLockHeldByMeInMode(XactSLRULock, LW_EXCLUSIVE));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
+ LW_EXCLUSIVE));
/*
* If we're doing an async commit (ie, lsn is valid), then we must wait
@@ -406,14 +422,13 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
}
/*
- * When we cannot immediately acquire XactSLRULock in exclusive mode at
+ * When we cannot immediately acquire the SLRU bank lock in exclusive mode at
* commit time, add ourselves to a list of processes that need their XIDs
* status update. The first process to add itself to the list will acquire
- * XactSLRULock in exclusive mode and set transaction status as required
- * on behalf of all group members. This avoids a great deal of contention
- * around XactSLRULock when many processes are trying to commit at once,
- * since the lock need not be repeatedly handed off from one committing
- * process to the next.
+ * the lock in exclusive mode and set transaction status as required on behalf
+ * of all group members. This avoids a great deal of contention when many
+ * processes are trying to commit at once, since the lock need not be
+ * repeatedly handed off from one committing process to the next.
*
* Returns true when transaction status has been updated in clog; returns
* false if we decided against applying the optimization because the page
@@ -427,13 +442,15 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
PGPROC *proc = MyProc;
uint32 nextidx;
uint32 wakeidx;
+ int prevpageno;
+ LWLock *prevlock = NULL;
/* We should definitely have an XID whose status needs to be updated. */
Assert(TransactionIdIsValid(xid));
/*
- * Add ourselves to the list of processes needing a group XID status
- * update.
+ * Prepare to add ourselves to the list of processes needing a group XID
+ * status update.
*/
proc->clogGroupMember = true;
proc->clogGroupMemberXid = xid;
@@ -441,6 +458,41 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
proc->clogGroupMemberPage = pageno;
proc->clogGroupMemberLsn = lsn;
+ /*
+ * The underlying SLRU is using bank-wise lock so it is possible that here
+ * we might get requesters who are contending on different SLRU-bank
+ * locks. But in the group, we try to only add the requesters who want to
+ * update the same page i.e. they would be requesting for the same
+ * SLRU-bank lock as well. The main reason for now allowing requesters of
+ * different pages together is 1) Once the leader acquires the lock they
+ * don't need to fetch multiple pages and do multiple I/O under the same
+ * lock 2) The leader need not switch the SLRU-bank lock if the different
+ * pages are from different SLRU banks 3) And the most important reason is
+ * that most of the time the contention will occur in high concurrent OLTP
+ * workload is going on and at that time most of the transactions would be
+ * generated during the same time and most of them would fall in same clog
+ * page as each page can hold status of 32k transactions. However, there
+ * is an exception where in some extreme conditions we might get different
+ * page requests added in the same group but we have handled that by
+ * switching the bank lock, although that is not the most performant way
+ * that's not the common case either so we are fine with that.
+ *
+ * Also to be noted that unless the leader of the current group does not
+ * get the lock we don't clear the 'procglobal->clogGroupFirst' that means
+ * concurrently if we get the requesters for different SLRU pages then
+ * those will have to go for the normal update instead of group update and
+ * that's fine as that is not the common case. As soon as the leader of
+ * the current group gets the lock for the required bank that time we
+ * clear this value and now other requesters (which might want to update a
+ * different page and that might fall into the different bank as well) are
+ * allowed to form a new group as the first group is now detached. So if
+ * the new group has a request for a different SLRU-bank lock then the
+ * group leader of this group might also get the lock while the first
+ * group is performing the update and these two groups can perform the
+ * group update concurrently but it is completely safe as these two
+ * leaders are operating on completely different SLRU pages and they both
+ * are holding their respective SLRU locks.
+ */
nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
while (true)
@@ -507,8 +559,17 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
return true;
}
- /* We are the leader. Acquire the lock on behalf of everyone. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ /*
+ * Acquire the SLRU bank lock for the first page in the group before we
+ * close this group by setting procglobal->clogGroupFirst as
+ * INVALID_PGPROCNO so that we do not close the new entries to the group
+ * even before getting the lock and losing whole purpose of the group
+ * update.
+ */
+ nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
+ prevpageno = ProcGlobal->allProcs[nextidx].clogGroupMemberPage;
+ prevlock = SimpleLruGetBankLock(XactCtl, prevpageno);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
/*
* Now that we've got the lock, clear the list of processes waiting for
@@ -525,6 +586,37 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
while (nextidx != INVALID_PGPROCNO)
{
PGPROC *nextproc = &ProcGlobal->allProcs[nextidx];
+ int thispageno = nextproc->clogGroupMemberPage;
+
+ /*
+ * If the SLRU bank lock for the current page is not the same as that
+ * of the last page then we need to release the lock on the previous
+ * bank and acquire the lock on the bank for the page we are going to
+ * update now.
+ *
+ * Although on the best effort basis we try that all the requests
+ * within a group are for the same clog page there are some
+ * possibilities that there are request for more than one page in the
+ * same group (for details refer to the comment in the previous while
+ * loop). That scenario might not be very performant because while
+ * switching the lock the group leader might need to wait on the new
+ * lock if the pages are from different SLRU bank but it is safe
+ * because a) we are releasing the old lock before acquiring the new
+ * lock so there is should not be any deadlock situation b) and, we
+ * are always modifying the page under the correct SLRU lock.
+ */
+ if (thispageno != prevpageno)
+ {
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, thispageno);
+
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ prevlock = lock;
+ prevpageno = thispageno;
+ }
/*
* Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs
@@ -544,7 +636,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
}
/* We're done with the lock now. */
- LWLockRelease(XactSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
/*
* Now that we've released the lock, go back and wake everybody up. We
@@ -573,7 +666,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
/*
* Sets the commit status of a single transaction.
*
- * Must be called with XactSLRULock held
+ * Caller must hold the corresponding SLRU bank lock, will be held at exit.
*/
static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
@@ -584,6 +677,11 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
char byteval;
char curval;
+ Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(xid));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl,
+ XactCtl->shared->page_number[slotno]),
+ LW_EXCLUSIVE));
+
byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
curval = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
@@ -665,7 +763,7 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
lsnindex = GetLSNIndex(slotno, xid);
*lsn = XactCtl->shared->group_lsn[lsnindex];
- LWLockRelease(XactSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
return status;
}
@@ -673,23 +771,18 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
/*
* Number of shared CLOG buffers.
*
- * On larger multi-processor systems, it is possible to have many CLOG page
- * requests in flight at one time which could lead to disk access for CLOG
- * page if the required page is not found in memory. Testing revealed that we
- * can get the best performance by having 128 CLOG buffers, more than that it
- * doesn't improve performance.
- *
- * Unconditionally keeping the number of CLOG buffers to 128 did not seem like
- * a good idea, because it would increase the minimum amount of shared memory
- * required to start, which could be a problem for people running very small
- * configurations. The following formula seems to represent a reasonable
- * compromise: people with very low values for shared_buffers will get fewer
- * CLOG buffers as well, and everyone else will get 128.
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
*/
-Size
+static int
CLOGShmemBuffers(void)
{
- return Min(128, Max(4, NBuffers / 512));
+ /* auto-tune based on shared buffers */
+ if (transaction_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, transaction_buffers), CLOG_MAX_ALLOWED_BUFFERS);
}
/*
@@ -704,13 +797,36 @@ CLOGShmemSize(void)
void
CLOGShmemInit(void)
{
+ /* If auto-tuning is requested, now is the time to do it */
+ if (transaction_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", CLOGShmemBuffers());
+ SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (transaction_buffers == 0) /* failed to apply it? */
+ SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(transaction_buffers != 0);
+
XactCtl->PagePrecedes = CLOGPagePrecedes;
SimpleLruInit(XactCtl, "Xact", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
- XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER,
- SYNC_HANDLER_CLOG, false);
+ "pg_xact", LWTRANCHE_XACT_BUFFER,
+ LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
}
+/*
+ * GUC check_hook for transaction_buffers
+ */
+bool
+check_transaction_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("transaction_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates
* the initial CLOG segment. (The CLOG directory is assumed to
@@ -721,8 +837,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, 0);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the commit log */
slotno = ZeroCLOGPage(0, false);
@@ -731,7 +848,7 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -766,14 +883,8 @@ StartupCLOG(void)
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
- /*
- * Initialize our idea of the latest page number.
- */
- XactCtl->shared->latest_page_number = pageno;
-
- LWLockRelease(XactSLRULock);
+ /* Initialize our idea of the latest page number. */
+ pg_atomic_init_u64(&XactCtl->shared->latest_page_number, pageno);
}
/*
@@ -784,8 +895,9 @@ TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* Zero out the remainder of the current clog page. Under normal
@@ -817,7 +929,7 @@ TrimCLOG(void)
XactCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -849,6 +961,7 @@ void
ExtendCLOG(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -859,13 +972,14 @@ ExtendCLOG(TransactionId newestXact)
return;
pageno = TransactionIdToPage(newestXact);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCLOGPage(pageno, true);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
@@ -1003,16 +1117,18 @@ clog_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
else if (info == CLOG_TRUNCATE)
{
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 61b82385f3..69d359e0fa 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -33,6 +33,7 @@
#include "pg_trace.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"
@@ -225,10 +226,11 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
RepOriginId nodeid, int64 pageno)
{
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
int slotno;
int i;
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid);
@@ -238,22 +240,25 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
CommitTsCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
* Sets the commit timestamp of a single transaction.
*
- * Must be called with CommitTsSLRULock held
+ * Caller must hold the correct SLRU bank lock, will be held at exit
*/
static void
TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
RepOriginId nodeid, int slotno)
{
- int entryno = TransactionIdToCTsEntry(xid);
+ int entryno;
CommitTimestampEntry entry;
- Assert(TransactionIdIsNormal(xid));
+ if (!TransactionIdIsNormal(xid))
+ return;
+
+ entryno = TransactionIdToCTsEntry(xid);
entry.time = ts;
entry.nodeid = nodeid;
@@ -345,7 +350,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (nodeid)
*nodeid = entry.nodeid;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(CommitTsCtl, pageno));
return *ts != 0;
}
@@ -499,14 +504,18 @@ pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
/*
* Number of shared CommitTS buffers.
*
- * We use a very similar logic as for the number of CLOG buffers (except we
- * scale up twice as fast with shared buffers, and the maximum is twice as
- * high); see comments in CLOGShmemBuffers.
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
*/
-Size
+static int
CommitTsShmemBuffers(void)
{
- return Min(256, Max(4, NBuffers / 256));
+ /* auto-tune based on shared buffers */
+ if (commit_timestamp_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, commit_timestamp_buffers), SLRU_MAX_ALLOWED_BUFFERS);
}
/*
@@ -528,10 +537,24 @@ CommitTsShmemInit(void)
{
bool found;
+ /* If auto-tuning is requested, now is the time to do it */
+ if (commit_timestamp_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", CommitTsShmemBuffers());
+ SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (commit_timestamp_buffers == 0) /* failed to apply it? */
+ SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(commit_timestamp_buffers != 0);
+
CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
SimpleLruInit(CommitTsCtl, "CommitTs", CommitTsShmemBuffers(), 0,
- CommitTsSLRULock, "pg_commit_ts",
- LWTRANCHE_COMMITTS_BUFFER,
+ "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
+ LWTRANCHE_COMMITTS_SLRU,
SYNC_HANDLER_COMMIT_TS,
false);
SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
@@ -553,6 +576,15 @@ CommitTsShmemInit(void)
Assert(found);
}
+/*
+ * GUC check_hook for commit_timestamp_buffers
+ */
+bool
+check_commit_ts_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("commit_timestamp_buffers", newval);
+}
+
/*
* This function must be called ONCE on system install.
*
@@ -689,9 +721,7 @@ ActivateCommitTs(void)
/*
* Re-Initialize our idea of the latest page number.
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
- CommitTsCtl->shared->latest_page_number = pageno;
- LWLockRelease(CommitTsSLRULock);
+ pg_atomic_init_u64(&CommitTsCtl->shared->latest_page_number, pageno);
/*
* If CommitTs is enabled, but it wasn't in the previous server run, we
@@ -717,13 +747,14 @@ ActivateCommitTs(void)
/* Create the current segment file, if necessary */
if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno))
{
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
int slotno;
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/* Change the activation status in shared memory. */
@@ -772,9 +803,9 @@ DeactivateCommitTs(void)
* be overwritten anyway when we wrap around, but it seems better to be
* tidy.)
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ SimpleLruAcquireAllBankLock(CommitTsCtl, LW_EXCLUSIVE);
(void) SlruScanDirectory(CommitTsCtl, SlruScanDirCbDeleteAll, NULL);
- LWLockRelease(CommitTsSLRULock);
+ SimpleLruReleaseAllBankLock(CommitTsCtl);
}
/*
@@ -806,6 +837,7 @@ void
ExtendCommitTs(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -826,12 +858,14 @@ ExtendCommitTs(TransactionId newestXact)
pageno = TransactionIdToCTsPage(newestXact);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCommitTsPage(pageno, !InRecovery);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -985,16 +1019,18 @@ commit_ts_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
else if (info == COMMIT_TS_TRUNCATE)
{
@@ -1006,7 +1042,9 @@ commit_ts_redo(XLogReaderState *record)
* During XLOG replay, latest_page_number isn't set up yet; insert a
* suitable value to bypass the sanity test in SimpleLruTruncate.
*/
- CommitTsCtl->shared->latest_page_number = trunc->pageno;
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number,
+ trunc->pageno);
+ pg_write_barrier();
SimpleLruTruncate(CommitTsCtl, trunc->pageno);
}
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 59523be901..67b6a6b20a 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -88,6 +88,7 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@@ -192,10 +193,10 @@ static SlruCtlData MultiXactMemberCtlData;
/*
* MultiXact state shared across all backends. All this state is protected
- * by MultiXactGenLock. (We also use MultiXactOffsetSLRULock and
- * MultiXactMemberSLRULock to guard accesses to the two sets of SLRU
- * buffers. For concurrency's sake, we avoid holding more than one of these
- * locks at a time.)
+ * by MultiXactGenLock. (We also use SLRU bank's lock of MultiXactOffset and
+ * MultiXactMember to guard accesses to the two sets of SLRU buffers. For
+ * concurrency's sake, we avoid holding more than one of these locks at a
+ * time.)
*/
typedef struct MultiXactStateData
{
@@ -870,12 +871,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int slotno;
MultiXactOffset *offptr;
int i;
-
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLock *lock;
+ LWLock *prevlock = NULL;
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+
/*
* Note: we pass the MultiXactId to SimpleLruReadPage as the "transaction"
* to complain about if there's any I/O error. This is kinda bogus, but
@@ -891,10 +895,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
- /* Exchange our lock */
- LWLockRelease(MultiXactOffsetSLRULock);
-
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ /* Release MultiXactOffset SLRU lock. */
+ LWLockRelease(lock);
prev_pageno = -1;
@@ -916,6 +918,20 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
if (pageno != prev_pageno)
{
+ /*
+ * MultiXactMember SLRU page is changed so check if this new page
+ * fall into the different SLRU bank then release the old bank's
+ * lock and acquire lock on the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -936,7 +952,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
}
/*
@@ -1239,6 +1256,8 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
+ LWLock *lock;
+ LWLock *prevlock = NULL;
debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
@@ -1342,11 +1361,22 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
* time on every multixact creation.
*/
retry:
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
-
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ /*
+ * If this page falls under a different bank, release the old bank's lock
+ * and acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1379,7 +1409,21 @@ retry:
entryno = MultiXactIdToOffsetEntry(tmpMXact);
if (pageno != prev_pageno)
+ {
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact);
+ }
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1388,7 +1432,8 @@ retry:
if (nextMXOffset == 0)
{
/* Corner case 2: next multixact is still being filled in */
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
CHECK_FOR_INTERRUPTS();
pg_usleep(1000L);
goto retry;
@@ -1397,13 +1442,11 @@ retry:
length = nextMXOffset - offset;
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
- /* Now get the members themselves. */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
-
truelength = 0;
prev_pageno = -1;
for (i = 0; i < length; i++, offset++)
@@ -1419,6 +1462,20 @@ retry:
if (pageno != prev_pageno)
{
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -1442,7 +1499,8 @@ retry:
truelength++;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock)
+ LWLockRelease(prevlock);
/* A multixid with zero members should not happen */
Assert(truelength > 0);
@@ -1834,8 +1892,8 @@ MultiXactShmemSize(void)
mul_size(sizeof(MultiXactId) * 2, MaxOldestSlot))
size = SHARED_MULTIXACT_STATE_SIZE;
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTOFFSET_BUFFERS, 0));
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTMEMBER_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_offsets_buffers, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_members_buffers, 0));
return size;
}
@@ -1851,16 +1909,16 @@ MultiXactShmemInit(void)
MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
SimpleLruInit(MultiXactOffsetCtl,
- "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0,
- MultiXactOffsetSLRULock, "pg_multixact/offsets",
- LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ "MultiXactOffset", multixact_offsets_buffers, 0,
+ "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
- "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0,
- MultiXactMemberSLRULock, "pg_multixact/members",
- LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ "MultiXactMember", multixact_members_buffers, 0,
+ "pg_multixact/members", LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
SYNC_HANDLER_MULTIXACT_MEMBER,
false);
/* doesn't call SimpleLruTruncate() or meet criteria for unit tests */
@@ -1887,6 +1945,24 @@ MultiXactShmemInit(void)
OldestVisibleMXactId = OldestMemberMXactId + MaxOldestSlot;
}
+/*
+ * GUC check_hook for multixact_offsets_buffers
+ */
+bool
+check_multixact_offsets_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_offsets_buffers", newval);
+}
+
+/*
+ * GUC check_hook for multixact_members_buffers
+ */
+bool
+check_multixact_members_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_members_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates the initial
* MultiXact segments. (The MultiXacts directories are assumed to have been
@@ -1896,8 +1972,10 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ LWLock *lock;
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the offsets log */
slotno = ZeroMultiXactOffsetPage(0, false);
@@ -1906,9 +1984,10 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the members log */
slotno = ZeroMultiXactMemberPage(0, false);
@@ -1917,7 +1996,7 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -1977,10 +2056,12 @@ static void
MaybeExtendOffsetSlru(void)
{
int64 pageno;
+ LWLock *lock;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno))
{
@@ -1995,7 +2076,7 @@ MaybeExtendOffsetSlru(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2017,13 +2098,15 @@ StartupMultiXact(void)
* Initialize offset's idea of the latest page number.
*/
pageno = MultiXactIdToOffsetPage(multi);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Initialize member's idea of the latest page number.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
}
/*
@@ -2048,13 +2131,14 @@ TrimMultiXact(void)
LWLockRelease(MultiXactGenLock);
/* Clean up offsets state */
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for offsets.
*/
pageno = MultiXactIdToOffsetPage(nextMXact);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
/*
* Zero out the remainder of the current offsets page. See notes in
@@ -2069,7 +2153,9 @@ TrimMultiXact(void)
{
int slotno;
MultiXactOffset *offptr;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -2077,18 +2163,18 @@ TrimMultiXact(void)
MemSet(offptr, 0, BLCKSZ - (entryno * sizeof(MultiXactOffset)));
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactOffsetSLRULock);
-
/* And the same for members */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for members.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
/*
* Zero out the remainder of the current members page. See notes in
@@ -2100,7 +2186,9 @@ TrimMultiXact(void)
int slotno;
TransactionId *xidptr;
int memberoff;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
memberoff = MXOffsetToMemberOffset(offset);
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset);
xidptr = (TransactionId *)
@@ -2115,10 +2203,9 @@ TrimMultiXact(void)
*/
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactMemberSLRULock);
-
/* signal that we're officially up */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->finishedStartup = true;
@@ -2406,6 +2493,7 @@ static void
ExtendMultiXactOffset(MultiXactId multi)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2416,13 +2504,14 @@ ExtendMultiXactOffset(MultiXactId multi)
return;
pageno = MultiXactIdToOffsetPage(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactOffsetPage(pageno, true);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2455,15 +2544,17 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
if (flagsoff == 0 && flagsbit == 0)
{
int64 pageno;
+ LWLock *lock;
pageno = MXOffsetToMemberPage(offset);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactMemberPage(pageno, true);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2761,7 +2852,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
offset = *offptr;
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(MultiXactOffsetCtl, pageno));
*result = offset;
return true;
@@ -3243,31 +3334,35 @@ multixact_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactOffsetPage(pageno, false);
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactMemberPage(pageno, false);
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
@@ -3333,7 +3428,9 @@ multixact_redo(XLogReaderState *record)
* SimpleLruTruncate.
*/
pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);
LWLockRelease(MultiXactTruncationLock);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 9ac4790f16..ff3c2d7eec 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,6 +59,7 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/guc_hooks.h"
static inline int
SlruFileName(SlruCtl ctl, char *path, int64 segno)
@@ -96,6 +97,20 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
*/
#define MAX_WRITEALL_BUFFERS 16
+/*
+ * Macro to get the index of the lock for the given slot.
+ *
+ * Basically, the slru buffer pool is divided into banks of buffer and there is
+ * total SLRU_MAX_BANKLOCKS number of locks to protect access to buffer in the
+ * banks. Since we have max limit on the number of locks we can not always have
+ * one lock for each bank. So until the number of banks are
+ * <= SLRU_MAX_BANKLOCKS then there would be one lock protecting each bank
+ * otherwise one lock might protect multiple banks based on the number of
+ * banks.
+ */
+#define SLRU_SLOTNO_GET_BANKLOCKNO(slotno) \
+ (((slotno) / SLRU_BANK_SIZE) % SLRU_MAX_BANKLOCKS)
+
typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
@@ -117,34 +132,6 @@ typedef struct SlruWriteAllData *SlruWriteAll;
(a).segno = (xx_segno) \
)
-/*
- * Macro to mark a buffer slot "most recently used". Note multiple evaluation
- * of arguments!
- *
- * The reason for the if-test is that there are often many consecutive
- * accesses to the same page (particularly the latest page). By suppressing
- * useless increments of cur_lru_count, we reduce the probability that old
- * pages' counts will "wrap around" and make them appear recently used.
- *
- * We allow this code to be executed concurrently by multiple processes within
- * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
- * this should not cause any completely-bogus values to enter the computation.
- * However, it is possible for either cur_lru_count or individual
- * page_lru_count entries to be "reset" to lower values than they should have,
- * in case a process is delayed while it executes this macro. With care in
- * SlruSelectLRUPage(), this does little harm, and in any case the absolute
- * worst possible consequence is a nonoptimal choice of page to evict. The
- * gain from allowing concurrent reads of SLRU pages seems worth it.
- */
-#define SlruRecentlyUsed(shared, slotno) \
- do { \
- int new_lru_count = (shared)->cur_lru_count; \
- if (new_lru_count != (shared)->page_lru_count[slotno]) { \
- (shared)->cur_lru_count = ++new_lru_count; \
- (shared)->page_lru_count[slotno] = new_lru_count; \
- } \
- } while (0)
-
/* Saved info for SlruReportIOError */
typedef enum
{
@@ -172,6 +159,7 @@ static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
int64 segpage, void *data);
static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
/*
@@ -182,6 +170,10 @@ Size
SimpleLruShmemSize(int nslots, int nlsns)
{
Size sz;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
+
+ Assert(nslots <= SLRU_MAX_ALLOWED_BUFFERS);
/* we assume nslots isn't so large as to risk overflow */
sz = MAXALIGN(sizeof(SlruSharedData));
@@ -191,6 +183,8 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
+ sz += MAXALIGN(nbanklocks * sizeof(LWLockPadded)); /* bank_locks[] */
+ sz += MAXALIGN(nbanks * sizeof(int)); /* bank_cur_lru_count[] */
if (nlsns > 0)
sz += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr)); /* group_lsn[] */
@@ -207,16 +201,21 @@ SimpleLruShmemSize(int nslots, int nlsns)
* nlsns: number of LSN groups per page (set to zero if not relevant).
* ctllock: LWLock to use to control access to the shared control structure.
* subdir: PGDATA-relative subdirectory that will contain the files.
- * tranche_id: LWLock tranche ID to use for the SLRU's per-buffer LWLocks.
+ * buffer_tranche_id: tranche ID to use for the SLRU's per-buffer LWLocks.
+ * bank_tranche_id: tranche ID to use for the bank LWLocks.
* sync_handler: which set of functions to use to handle sync requests
*/
void
SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
+ const char *subdir, int buffer_tranche_id, int bank_tranche_id,
SyncRequestHandler sync_handler, bool long_segment_names)
{
SlruShared shared;
bool found;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
+
+ Assert(nslots <= SLRU_MAX_ALLOWED_BUFFERS);
shared = (SlruShared) ShmemInitStruct(name,
SimpleLruShmemSize(nslots, nlsns),
@@ -228,18 +227,16 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
char *ptr;
Size offset;
int slotno;
+ int bankno;
+ int banklockno;
Assert(!found);
memset(shared, 0, sizeof(SlruSharedData));
- shared->ControlLock = ctllock;
-
shared->num_slots = nslots;
shared->lsn_groups_per_page = nlsns;
- shared->cur_lru_count = 0;
-
/* shared->latest_page_number will be set later */
shared->slru_stats_idx = pgstat_get_slru_index(name);
@@ -260,6 +257,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
/* Initialize LWLocks */
shared->buffer_locks = (LWLockPadded *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(LWLockPadded));
+ shared->bank_locks = (LWLockPadded *) (ptr + offset);
+ offset += MAXALIGN(nbanklocks * sizeof(LWLockPadded));
+ shared->bank_cur_lru_count = (int *) (ptr + offset);
+ offset += MAXALIGN(nbanks * sizeof(int));
if (nlsns > 0)
{
@@ -271,7 +272,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
for (slotno = 0; slotno < nslots; slotno++)
{
LWLockInitialize(&shared->buffer_locks[slotno].lock,
- tranche_id);
+ buffer_tranche_id);
shared->page_buffer[slotno] = ptr;
shared->page_status[slotno] = SLRU_PAGE_EMPTY;
@@ -280,11 +281,23 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ptr += BLCKSZ;
}
+ /* Initialize the bank locks. */
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockInitialize(&shared->bank_locks[banklockno].lock,
+ bank_tranche_id);
+
+ /* Initialize the bank lru counters. */
+ for (bankno = 0; bankno < nbanks; bankno++)
+ shared->bank_cur_lru_count[bankno] = 0;
+
/* Should fit to estimated shmem size */
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
else
+ {
Assert(found);
+ Assert(shared->num_slots == nslots);
+ }
/*
* Initialize the unshared control struct, including directory path. We
@@ -293,6 +306,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ctl->shared = shared;
ctl->sync_handler = sync_handler;
ctl->long_segment_names = long_segment_names;
+ ctl->bank_mask = (nslots / SLRU_BANK_SIZE) - 1;
strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir));
}
@@ -330,7 +344,8 @@ SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
SimpleLruZeroLSNs(ctl, slotno);
/* Assume this page is now the latest active page */
- shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&shared->latest_page_number, pageno);
+ pg_write_barrier();
/* update the stats counter of zeroed pages */
pgstat_count_slru_page_zeroed(shared->slru_stats_idx);
@@ -369,12 +384,13 @@ static void
SimpleLruWaitIO(SlruCtl ctl, int slotno)
{
SlruShared shared = ctl->shared;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* See notes at top of file */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);
LWLockRelease(&shared->buffer_locks[slotno].lock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
/*
* If the slot is still in an io-in-progress state, then either someone
@@ -425,10 +441,14 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
{
SlruShared shared = ctl->shared;
+ /* Caller must hold the bank lock for the input page. */
+ Assert(LWLockHeldByMe(SimpleLruGetBankLock(ctl, pageno)));
+
/* Outer loop handles restart if we must wait for someone else's I/O */
for (;;)
{
int slotno;
+ int banklockno;
bool ok;
/* See if page already is in memory; if not, pick victim slot */
@@ -471,9 +491,10 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
/* Acquire per-buffer lock (cannot deadlock, see notes at top) */
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
+ banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the read */
ok = SlruPhysicalReadPage(ctl, pageno, slotno);
@@ -482,7 +503,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
SimpleLruZeroLSNs(ctl, slotno);
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_READ_IN_PROGRESS &&
@@ -524,12 +545,19 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(bankstart);
/* Try to find the page while holding only shared lock */
- LWLockAcquire(shared->ControlLock, LW_SHARED);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_SHARED);
- /* See if page is already in a buffer */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY &&
@@ -546,8 +574,8 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
}
/* No luck, so switch to normal exclusive lock and do regular read */
- LWLockRelease(shared->ControlLock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
return SimpleLruReadPage(ctl, pageno, true, xid);
}
@@ -569,6 +597,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
SlruShared shared = ctl->shared;
int64 pageno = shared->page_number[slotno];
bool ok;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* If a write is in progress, wait for it to finish */
while (shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS &&
@@ -597,7 +626,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the write */
ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
@@ -612,7 +641,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
}
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS);
@@ -1056,9 +1085,16 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
+ int bankno = pageno & ctl->bank_mask;
+ int bankstart = bankno * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
- /* See if page already has a buffer assigned */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in
+ * one bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY)
@@ -1092,8 +1128,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* That gets us back on the path to having good data when there are
* multiple pages with the same lru_count.
*/
- cur_count = (shared->cur_lru_count)++;
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ cur_count = (shared->bank_cur_lru_count[bankno])++;
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
int64 this_page_number;
@@ -1114,7 +1150,10 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
this_delta = 0;
}
this_page_number = shared->page_number[slotno];
- if (this_page_number == shared->latest_page_number)
+
+ pg_read_barrier();
+ if (this_page_number ==
+ pg_atomic_read_u64(&shared->latest_page_number))
continue;
if (shared->page_status[slotno] == SLRU_PAGE_VALID)
{
@@ -1188,6 +1227,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
int slotno;
int64 pageno = 0;
int i;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
bool ok;
/* update the stats counter of flushes */
@@ -1198,10 +1238,23 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
*/
fdata.num_files = 0;
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
SlruInternalWritePage(ctl, slotno, &fdata);
/*
@@ -1215,7 +1268,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
!shared->page_dirty[slotno]));
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/*
* Now close any files that were open
@@ -1255,6 +1308,7 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
+ int prevlockno;
/* update the stats counter of truncates */
pgstat_count_slru_truncate(shared->slru_stats_idx);
@@ -1265,25 +1319,39 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
* or just after a checkpoint, any dirty pages should have been flushed
* already ... we're just being extra careful here.)
*/
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
-
restart:
/*
- * While we are holding the lock, make an important safety check: the
- * current endpoint page must not be eligible for removal.
+ * An important safety check: the current endpoint page must not be
+ * eligible for removal.
*/
- if (ctl->PagePrecedes(shared->latest_page_number, cutoffPage))
+ pg_read_barrier();
+ if (ctl->PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
+ cutoffPage))
{
- LWLockRelease(shared->ControlLock);
ereport(LOG,
(errmsg("could not truncate directory \"%s\": apparent wraparound",
ctl->Dir)));
return;
}
+ prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
if (!ctl->PagePrecedes(shared->page_number[slotno], cutoffPage))
@@ -1313,10 +1381,12 @@ restart:
SlruInternalWritePage(ctl, slotno, NULL);
else
SimpleLruWaitIO(ctl, slotno);
+
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
goto restart;
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/* Now we can remove the old segment(s) */
(void) SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, &cutoffPage);
@@ -1357,15 +1427,29 @@ SlruDeleteSegment(SlruCtl ctl, int64 segno)
SlruShared shared = ctl->shared;
int slotno;
bool did_write;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
/* Clean out any possibly existing references to the segment. */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
restart:
did_write = false;
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
- int pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
+ int pagesegno;
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
+ pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
@@ -1399,7 +1483,7 @@ restart:
SlruInternalDeleteSegment(ctl, segno);
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
}
/*
@@ -1666,3 +1750,84 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
errno = save_errno;
return result;
}
+
+/*
+ * Function to mark a buffer slot "most recently used".
+ *
+ * The reason for the if-test is that there are often many consecutive
+ * accesses to the same page (particularly the latest page). By suppressing
+ * useless increments of bank_cur_lru_count, we reduce the probability that old
+ * pages' counts will "wrap around" and make them appear recently used.
+ *
+ * We allow this code to be executed concurrently by multiple processes within
+ * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
+ * this should not cause any completely-bogus values to enter the computation.
+ * However, it is possible for either bank_cur_lru_count or individual
+ * page_lru_count entries to be "reset" to lower values than they should have,
+ * in case a process is delayed while it executes this function. With care in
+ * SlruSelectLRUPage(), this does little harm, and in any case the absolute
+ * worst possible consequence is a nonoptimal choice of page to evict. The
+ * gain from allowing concurrent reads of SLRU pages seems worth it.
+ */
+static inline void
+SlruRecentlyUsed(SlruShared shared, int slotno)
+{
+ int bankno = slotno / SLRU_BANK_SIZE;
+ int new_lru_count = shared->bank_cur_lru_count[bankno];
+
+ if (new_lru_count != shared->page_lru_count[slotno])
+ {
+ shared->bank_cur_lru_count[bankno] = ++new_lru_count;
+ shared->page_lru_count[slotno] = new_lru_count;
+ }
+}
+
+/*
+ * Helper function for GUC check_hook to check whether slru buffers are in
+ * multiples of SLRU_BANK_SIZE.
+ */
+bool
+check_slru_buffers(const char *name, int *newval)
+{
+ /* Valid values are multiples of SLRU_BANK_SIZE */
+ if (*newval % SLRU_BANK_SIZE == 0)
+ return true;
+
+ GUC_check_errdetail("\"%s\" must be a multiple of %d", name,
+ SLRU_BANK_SIZE);
+ return false;
+}
+
+/*
+ * Function to acquire all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, mode);
+}
+
+/*
+ * Function to release all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruReleaseAllBankLock(SlruCtl ctl)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index b2ed82ac56..cee850c9f8 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -31,7 +31,9 @@
#include "access/slru.h"
#include "access/subtrans.h"
#include "access/transam.h"
+#include "miscadmin.h"
#include "pg_trace.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
@@ -85,12 +87,14 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
+ LWLock *lock;
TransactionId *ptr;
Assert(TransactionIdIsValid(parent));
Assert(TransactionIdFollows(xid, parent));
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
@@ -108,7 +112,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
SubTransCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -138,7 +142,7 @@ SubTransGetParent(TransactionId xid)
parent = *ptr;
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
return parent;
}
@@ -186,6 +190,22 @@ SubTransGetTopmostTransaction(TransactionId xid)
return previousXid;
}
+/*
+ * Number of shared SUBTRANS buffers.
+ *
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
+ */
+static int
+SUBTRANSShmemBuffers(void)
+{
+ /* auto-tune based on shared buffers */
+ if (subtransaction_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
+}
/*
* Initialization of shared memory for SUBTRANS
@@ -193,20 +213,42 @@ SubTransGetTopmostTransaction(TransactionId xid)
Size
SUBTRANSShmemSize(void)
{
- return SimpleLruShmemSize(NUM_SUBTRANS_BUFFERS, 0);
+ return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
}
void
SUBTRANSShmemInit(void)
{
+ /* If auto-tuning is requested, now is the time to do it */
+ if (subtransaction_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
+ SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (subtransaction_buffers == 0) /* failed to apply it? */
+ SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(subtransaction_buffers != 0);
+
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
- SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0,
- SubtransSLRULock, "pg_subtrans",
- LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
- false);
+ SimpleLruInit(SubTransCtl, "Subtrans", SUBTRANSShmemBuffers(), 0,
+ "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
+ LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
}
+/*
+ * GUC check_hook for subtransaction_buffers
+ */
+bool
+check_subtrans_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("subtransaction_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates
* the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
@@ -221,8 +263,9 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
slotno = ZeroSUBTRANSPage(0);
@@ -231,7 +274,7 @@ BootStrapSUBTRANS(void)
SimpleLruWritePage(SubTransCtl, slotno);
Assert(!SubTransCtl->shared->page_dirty[slotno]);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -261,6 +304,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
FullTransactionId nextXid;
int64 startPage;
int64 endPage;
+ LWLock *prevlock;
+ LWLock *lock;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -268,23 +313,47 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
* Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
* the new page without regard to whatever was previously on disk.
*/
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
-
startPage = TransactionIdToPage(oldestActiveXID);
nextXid = TransamVariables->nextXid;
endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
+ prevlock = SimpleLruGetBankLock(SubTransCtl, startPage);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
while (startPage != endPage)
{
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release
+ * the lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
(void) ZeroSUBTRANSPage(startPage);
startPage++;
/* must account for wraparound */
if (startPage > TransactionIdToPage(MaxTransactionId))
startPage = 0;
}
- (void) ZeroSUBTRANSPage(startPage);
- LWLockRelease(SubtransSLRULock);
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release the
+ * lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ (void) ZeroSUBTRANSPage(startPage);
+ LWLockRelease(lock);
}
/*
@@ -318,6 +387,7 @@ void
ExtendSUBTRANS(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -329,12 +399,13 @@ ExtendSUBTRANS(TransactionId newestXact)
pageno = TransactionIdToPage(newestXact);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page */
ZeroSUBTRANSPage(pageno);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 8b24b22293..0c2ac60946 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -116,7 +116,7 @@
* frontend during startup.) The above design guarantees that notifies from
* other backends will never be missed by ignoring self-notifies.
*
- * The amount of shared memory used for notify management (NUM_NOTIFY_BUFFERS)
+ * The amount of shared memory used for notify management (notify_buffers)
* can be varied without affecting anything but performance. The maximum
* amount of notification data that can be queued at one time is determined
* by max_notify_queue_pages GUC.
@@ -148,6 +148,7 @@
#include "storage/sinval.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
@@ -234,7 +235,7 @@ typedef struct QueuePosition
*
* Resist the temptation to make this really large. While that would save
* work in some places, it would add cost in others. In particular, this
- * should likely be less than NUM_NOTIFY_BUFFERS, to ensure that backends
+ * should likely be less than notify_buffers, to ensure that backends
* catch up before the pages they'll need to read fall out of SLRU cache.
*/
#define QUEUE_CLEANUP_DELAY 4
@@ -266,9 +267,10 @@ typedef struct QueueBackendStatus
* both NotifyQueueLock and NotifyQueueTailLock in EXCLUSIVE mode, backends
* can change the tail pointers.
*
- * NotifySLRULock is used as the control lock for the pg_notify SLRU buffers.
+ * SLRU buffer pool is divided in banks and bank wise SLRU lock is used as
+ * the control lock for the pg_notify SLRU buffers.
* In order to avoid deadlocks, whenever we need multiple locks, we first get
- * NotifyQueueTailLock, then NotifyQueueLock, and lastly NotifySLRULock.
+ * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock.
*
* Each backend uses the backend[] array entry with index equal to its
* BackendId (which can range from 1 to MaxBackends). We rely on this to make
@@ -492,7 +494,7 @@ AsyncShmemSize(void)
size = mul_size(MaxBackends + 1, sizeof(QueueBackendStatus));
size = add_size(size, offsetof(AsyncQueueControl, backend));
- size = add_size(size, SimpleLruShmemSize(NUM_NOTIFY_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(notify_buffers, 0));
return size;
}
@@ -541,8 +543,8 @@ AsyncShmemInit(void)
* names are used in order to avoid wraparound.
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
- SimpleLruInit(NotifyCtl, "Notify", NUM_NOTIFY_BUFFERS, 0,
- NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
+ SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
+ "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
SYNC_HANDLER_NONE, true);
if (!found)
@@ -1356,7 +1358,7 @@ asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe)
* Eventually we will return NULL indicating all is done.
*
* We are holding NotifyQueueLock already from the caller and grab
- * NotifySLRULock locally in this function.
+ * page specific SLRU bank lock locally in this function.
*/
static ListCell *
asyncQueueAddEntries(ListCell *nextNotify)
@@ -1366,9 +1368,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
int64 pageno;
int offset;
int slotno;
-
- /* We hold both NotifyQueueLock and NotifySLRULock during this operation */
- LWLockAcquire(NotifySLRULock, LW_EXCLUSIVE);
+ LWLock *prevlock;
/*
* We work with a local copy of QUEUE_HEAD, which we write back to shared
@@ -1389,6 +1389,11 @@ asyncQueueAddEntries(ListCell *nextNotify)
* page should be initialized already, so just fetch it.
*/
pageno = QUEUE_POS_PAGE(queue_head);
+ prevlock = SimpleLruGetBankLock(NotifyCtl, pageno);
+
+ /* We hold both NotifyQueueLock and SLRU bank lock during this operation */
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
+
if (QUEUE_POS_IS_ZERO(queue_head))
slotno = SimpleLruZeroPage(NotifyCtl, pageno);
else
@@ -1434,6 +1439,17 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Advance queue_head appropriately, and detect if page is full */
if (asyncQueueAdvance(&(queue_head), qe.length))
{
+ LWLock *lock;
+
+ pageno = QUEUE_POS_PAGE(queue_head);
+ lock = SimpleLruGetBankLock(NotifyCtl, pageno);
+ if (lock != prevlock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
/*
* Page is full, so we're done here, but first fill the next page
* with zeroes. The reason to do this is to ensure that slru.c's
@@ -1460,7 +1476,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Success, so update the global QUEUE_HEAD */
QUEUE_HEAD = queue_head;
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(prevlock);
return nextNotify;
}
@@ -1931,9 +1947,9 @@ asyncQueueReadAllNotifications(void)
/*
* We copy the data from SLRU into a local buffer, so as to avoid
- * holding the NotifySLRULock while we are examining the entries
- * and possibly transmitting them to our frontend. Copy only the
- * part of the page we will actually inspect.
+ * holding the SLRU lock while we are examining the entries and
+ * possibly transmitting them to our frontend. Copy only the part
+ * of the page we will actually inspect.
*/
slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage,
InvalidTransactionId);
@@ -1953,7 +1969,7 @@ asyncQueueReadAllNotifications(void)
NotifyCtl->shared->page_buffer[slotno] + curoffset,
copysize);
/* Release lock that we got from SimpleLruReadPage_ReadOnly() */
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(SimpleLruGetBankLock(NotifyCtl, curpage));
/*
* Process messages up to the stop position, end of page, or an
@@ -1994,7 +2010,7 @@ asyncQueueReadAllNotifications(void)
*
* The current page must have been fetched into page_buffer from shared
* memory. (We could access the page right in shared memory, but that
- * would imply holding the NotifySLRULock throughout this routine.)
+ * would imply holding the SLRU bank lock throughout this routine.)
*
* We stop if we reach the "stop" position, or reach a notification from an
* uncommitted transaction, or reach the end of the page.
@@ -2147,7 +2163,7 @@ asyncQueueAdvanceTail(void)
if (asyncQueuePagePrecedes(oldtailpage, boundary))
{
/*
- * SimpleLruTruncate() will ask for NotifySLRULock but will also
+ * SimpleLruTruncate() will ask for SLRU bank locks but will also
* release the lock again.
*/
SimpleLruTruncate(NotifyCtl, newtailpage);
@@ -2378,3 +2394,12 @@ ClearPendingActionsAndNotifies(void)
pendingActions = NULL;
pendingNotifies = NULL;
}
+
+/*
+ * GUC check_hook for notify_buffers
+ */
+bool
+check_notify_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("notify_buffers", newval);
+}
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 98fa6035cc..4a5e05d5e4 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -163,6 +163,13 @@ static const char *const BuiltinTrancheNames[] = {
[LWTRANCHE_LAUNCHER_HASH] = "LogicalRepLauncherHash",
[LWTRANCHE_DSM_REGISTRY_DSA] = "DSMRegistryDSA",
[LWTRANCHE_DSM_REGISTRY_HASH] = "DSMRegistryHash",
+ [LWTRANCHE_COMMITTS_SLRU] = "CommitTSSLRU",
+ [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultixactOffsetSLRU",
+ [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultixactMemberSLRU",
+ [LWTRANCHE_NOTIFY_SLRU] = "NotifySLRU",
+ [LWTRANCHE_SERIAL_SLRU] = "SerialSLRU"
+ [LWTRANCHE_SUBTRANS_SLRU] = "SubtransSLRU",
+ [LWTRANCHE_XACT_SLRU] = "XactSLRU",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index a0163b2187..e4aa3d91c6 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -16,11 +16,11 @@ WALBufMappingLock 7
WALWriteLock 8
ControlFileLock 9
# 10 was CheckpointLock
-XactSLRULock 11
-SubtransSLRULock 12
+# 11 was XactSLRULock
+# 12 was SubtransSLRULock
MultiXactGenLock 13
-MultiXactOffsetSLRULock 14
-MultiXactMemberSLRULock 15
+# 14 was MultiXactOffsetSLRULock
+# 15 was MultiXactMemberSLRULock
RelCacheInitLock 16
CheckpointerCommLock 17
TwoPhaseStateLock 18
@@ -31,19 +31,19 @@ AutovacuumLock 22
AutovacuumScheduleLock 23
SyncScanLock 24
RelationMappingLock 25
-NotifySLRULock 26
+#26 was NotifySLRULock
NotifyQueueLock 27
SerializableXactHashLock 28
SerializableFinishedListLock 29
SerializablePredicateListLock 30
-SerialSLRULock 31
+SerialControlLock 31
SyncRepLock 32
BackgroundWorkerLock 33
DynamicSharedMemoryControlLock 34
AutoFileLock 35
ReplicationSlotAllocationLock 36
ReplicationSlotControlLock 37
-CommitTsSLRULock 38
+#38 was CommitTsSLRULock
CommitTsLock 39
ReplicationOriginLock 40
MultiXactTruncationLock 41
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index ee5ea1175c..7fd1bca7f9 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -208,6 +208,7 @@
#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "utils/guc_hooks.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
@@ -808,9 +809,9 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial",
- LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
- false);
+ serializable_buffers, 0, "pg_serial",
+ LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
+ SYNC_HANDLER_NONE, false);
#ifdef USE_ASSERT_CHECKING
SerialPagePrecedesLogicallyUnitTests();
#endif
@@ -834,6 +835,15 @@ SerialInit(void)
}
}
+/*
+ * GUC check_hook for serializable_buffers
+ */
+bool
+check_serial_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("serializable_buffers", newval);
+}
+
/*
* Record a committed read write serializable xid and the minimum
* commitSeqNo of any transactions to which this xid had a rw-conflict out.
@@ -847,12 +857,14 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
int slotno;
int64 firstZeroPage;
bool isNewPage;
+ LWLock *lock;
Assert(TransactionIdIsValid(xid));
targetPage = SerialPage(xid);
+ lock = SimpleLruGetBankLock(SerialSlruCtl, targetPage);
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* If no serializable transactions are active, there shouldn't be anything
@@ -902,7 +914,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
SerialValue(slotno, xid) = minConflictCommitSeqNo;
SerialSlruCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -920,10 +932,10 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
Assert(TransactionIdIsValid(xid));
- LWLockAcquire(SerialSLRULock, LW_SHARED);
+ LWLockAcquire(SerialControlLock, LW_SHARED);
headXid = serialControl->headXid;
tailXid = serialControl->tailXid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
if (!TransactionIdIsValid(headXid))
return 0;
@@ -935,13 +947,13 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
return 0;
/*
- * The following function must be called without holding SerialSLRULock,
+ * The following function must be called without holding SLRU bank lock,
* but will return with that lock held, which must then be released.
*/
slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
SerialPage(xid), xid);
val = SerialValue(slotno, xid);
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SerialSlruCtl, SerialPage(xid)));
return val;
}
@@ -954,7 +966,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
static void
SerialSetActiveSerXmin(TransactionId xid)
{
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/*
* When no sxacts are active, nothing overlaps, set the xid values to
@@ -966,7 +978,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = InvalidTransactionId;
serialControl->headXid = InvalidTransactionId;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -984,7 +996,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = xid;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -993,7 +1005,7 @@ SerialSetActiveSerXmin(TransactionId xid)
serialControl->tailXid = xid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
}
/*
@@ -1007,12 +1019,12 @@ CheckPointPredicate(void)
{
int truncateCutoffPage;
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/* Exit quickly if the SLRU is currently not in use. */
if (serialControl->headPage < 0)
{
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -1072,7 +1084,7 @@ CheckPointPredicate(void)
serialControl->headPage = -1;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
/* Truncate away pages that are no longer required */
SimpleLruTruncate(SerialSlruCtl, truncateCutoffPage);
@@ -1348,7 +1360,7 @@ PredicateLockShmemSize(void)
/* Shared memory structures for SLRU tracking of old committed xids. */
size = add_size(size, sizeof(SerialControlData));
- size = add_size(size, SimpleLruShmemSize(NUM_SERIAL_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(serializable_buffers, 0));
return size;
}
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index a5df835dd4..f24950c0c1 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -292,11 +292,7 @@ SInvalWrite "Waiting to add a message to the shared catalog invalidation queue."
WALBufMapping "Waiting to replace a page in WAL buffers."
WALWrite "Waiting for WAL buffers to be written to disk."
ControlFile "Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
-XactSLRU "Waiting to access the transaction status SLRU cache."
-SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
MultiXactGen "Waiting to read or update shared multixact state."
-MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
-MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
RelCacheInit "Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
CheckpointerComm "Waiting to manage fsync requests."
TwoPhaseState "Waiting to read or update the state of prepared transactions."
@@ -307,19 +303,17 @@ Autovacuum "Waiting to read or update the current state of autovacuum workers."
AutovacuumSchedule "Waiting to ensure that a table selected for autovacuum still needs vacuuming."
SyncScan "Waiting to select the starting location of a synchronized table scan."
RelationMapping "Waiting to read or update a <filename>pg_filenode.map</filename> file (used to track the filenode assignments of certain system catalogs)."
-NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
NotifyQueue "Waiting to read or update <command>NOTIFY</command> messages."
SerializableXactHash "Waiting to read or update information about serializable transactions."
SerializableFinishedList "Waiting to access the list of finished serializable transactions."
SerializablePredicateList "Waiting to access the list of predicate locks held by serializable transactions."
-SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SerialControl "Waiting to access the serializable transaction conflict SLRU cache."
SyncRep "Waiting to read or update information about the state of synchronous replication."
BackgroundWorker "Waiting to read or update background worker state."
DynamicSharedMemoryControl "Waiting to read or update dynamic shared memory allocation information."
AutoFile "Waiting to update the <filename>postgresql.auto.conf</filename> file."
ReplicationSlotAllocation "Waiting to allocate or free a replication slot."
ReplicationSlotControl "Waiting to read or update replication slot state."
-CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
CommitTs "Waiting to read or update the last value set for a transaction commit timestamp."
ReplicationOrigin "Waiting to create, drop or use a replication origin."
MultiXactTruncation "Waiting to read or truncate multixact information."
@@ -371,6 +365,14 @@ LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
+CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
+MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
+MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
+NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
+SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
+XactSLRU "Waiting to access the transaction status SLRU cache."
+
#
# Wait Events - Lock
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 88b03e8fa3..7df342c70d 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -156,3 +156,12 @@ int64 VacuumPageDirty = 0;
int VacuumCostBalance = 0; /* working state for vacuum */
bool VacuumCostActive = false;
+
+/* configurable SLRU buffer sizes */
+int commit_timestamp_buffers = 0;
+int multixact_members_buffers = 32;
+int multixact_offsets_buffers = 16;
+int notify_buffers = 16;
+int serializable_buffers = 32;
+int subtransaction_buffers = 0;
+int transaction_buffers = 0;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 7fe58518d7..82d08647d0 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -28,6 +28,7 @@
#include "access/commit_ts.h"
#include "access/gin.h"
+#include "access/slru.h"
#include "access/toast_compression.h"
#include "access/twophase.h"
#include "access/xlog_internal.h"
@@ -2320,6 +2321,83 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"commit_timestamp_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the commit timestamp cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &commit_timestamp_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_commit_ts_buffers, NULL, NULL
+ },
+
+ {
+ {"multixact_members_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the MultiXact member cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_members_buffers,
+ 32, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ check_multixact_members_buffers, NULL, NULL
+ },
+
+ {
+ {"multixact_offsets_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the MultiXact offset cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_offsets_buffers,
+ 16, 8, SLRU_MAX_ALLOWED_BUFFERS,
+ check_multixact_offsets_buffers, NULL, NULL
+ },
+
+ {
+ {"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the LISTEN/NOTIFY message cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ ¬ify_buffers,
+ 16, 8, SLRU_MAX_ALLOWED_BUFFERS,
+ check_notify_buffers, NULL, NULL
+ },
+
+ {
+ {"serializable_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the serializable transaction cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &serializable_buffers,
+ 32, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ check_serial_buffers, NULL, NULL
+ },
+
+ {
+ {"subtransaction_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the sub-transaction cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &subtransaction_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_subtrans_buffers, NULL, NULL
+ },
+
+ {
+ {"transaction_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the transaction status cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &transaction_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_transaction_buffers, NULL, NULL
+ },
+
{
{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the maximum number of temporary buffers used by each session."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index da10b43dac..8b3a547a5e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -50,6 +50,15 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+# - SLRU Buffers (change requires restart) -
+
+#commit_timestamp_buffers = 0 # memory for pg_commit_ts (0 = auto)
+#multixact_offsets_buffers = 16 # memory for pg_multixact/offsets
+#multixact_members_buffers = 32 # memory for pg_multixact/members
+#notify_buffers = 16 # memory for pg_notify
+#serializable_buffers = 32 # memory for pg_serial
+#subtransaction_buffers = 0 # memory for pg_subtrans (0 = auto)
+#transaction_buffers = 0 # memory for pg_xact (0 = auto)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index becc365cb0..8e62917e49 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -40,7 +40,6 @@ extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status, XLogRecPtr lsn);
extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn);
-extern Size CLOGShmemBuffers(void);
extern Size CLOGShmemSize(void);
extern void CLOGShmemInit(void);
extern void BootStrapCLOG(void);
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index 9c6f3a35ca..82d3aa8627 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -27,7 +27,6 @@ extern bool TransactionIdGetCommitTsData(TransactionId xid,
extern TransactionId GetLatestCommitTsData(TimestampTz *ts,
RepOriginId *nodeid);
-extern Size CommitTsShmemBuffers(void);
extern Size CommitTsShmemSize(void);
extern void CommitTsShmemInit(void);
extern void BootStrapCommitTs(void);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 233f67dbcc..7ffd256c74 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -29,10 +29,6 @@
#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
-/* Number of SLRU buffers to use for multixact */
-#define NUM_MULTIXACTOFFSET_BUFFERS 8
-#define NUM_MULTIXACTMEMBER_BUFFERS 16
-
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index b05f6bc71d..3160980d04 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,27 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * SLRU bank size for slotno hash banks. Limit the bank size to 16 because we
+ * perform sequential search within a bank (while looking for a target page or
+ * while doing victim buffer search) and if we keep this size big then it may
+ * affect the performance.
+ */
+#define SLRU_BANK_SIZE 16
+
+/*
+ * Number of bank locks to protect the in memory buffer slot access within a
+ * SLRU bank. If the number of banks are <= SLRU_MAX_BANKLOCKS then there will
+ * be one lock per bank; otherwise each lock will protect multiple banks depends
+ * upon the number of banks.
+ */
+#define SLRU_MAX_BANKLOCKS 128
+
+/*
+ * To avoid overflowing internal arithmetic and the size_t data type, the
+ * number of buffers must not exceed this number.
+ */
+#define SLRU_MAX_ALLOWED_BUFFERS ((1024 * 1024 * 1024) / BLCKSZ)
/*
* Define SLRU segment size. A page is the same BLCKSZ as is used everywhere
@@ -52,8 +73,6 @@ typedef enum
*/
typedef struct SlruSharedData
{
- LWLock *ControlLock;
-
/* Number of buffers managed by this SLRU structure */
int num_slots;
@@ -66,8 +85,30 @@ typedef struct SlruSharedData
bool *page_dirty;
int64 *page_number;
int *page_lru_count;
+
+ /* The buffer_locks protects the I/O on each buffer slots */
LWLockPadded *buffer_locks;
+ /* Locks to protect the in memory buffer slot access in SLRU bank. */
+ LWLockPadded *bank_locks;
+
+ /*----------
+ * A bank-wise LRU counter is maintained because we do a victim buffer
+ * search within a bank. Furthermore, manipulating an individual bank
+ * counter avoids frequent cache invalidation since we update it every time
+ * we access the page.
+ *
+ * We mark a page "most recently used" by setting
+ * page_lru_count[slotno] = ++bank_cur_lru_count[bankno];
+ * The oldest page in the bank is therefore the one with the highest value
+ * of
+ * bank_cur_lru_count[bankno] - page_lru_count[slotno]
+ * The counts will eventually wrap around, but this calculation still
+ * works as long as no page's age exceeds INT_MAX counts.
+ *----------
+ */
+ int *bank_cur_lru_count;
+
/*
* Optional array of WAL flush LSNs associated with entries in the SLRU
* pages. If not zero/NULL, we must flush WAL before writing pages (true
@@ -79,23 +120,12 @@ typedef struct SlruSharedData
XLogRecPtr *group_lsn;
int lsn_groups_per_page;
- /*----------
- * We mark a page "most recently used" by setting
- * page_lru_count[slotno] = ++cur_lru_count;
- * The oldest page is therefore the one with the highest value of
- * cur_lru_count - page_lru_count[slotno]
- * The counts will eventually wrap around, but this calculation still
- * works as long as no page's age exceeds INT_MAX counts.
- *----------
- */
- int cur_lru_count;
-
/*
* latest_page_number is the page number of the current end of the log;
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int64 latest_page_number;
+ pg_atomic_uint64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -142,15 +172,35 @@ typedef struct SlruCtlData
* it's always the same, it doesn't need to be in shared memory.
*/
char Dir[64];
+
+ /*
+ * Mask for slotno banks, considering 1GB SLRU buffer pool size and the
+ * SLRU_BANK_SIZE bits16 should be sufficient for the bank mask.
+ */
+ bits16 bank_mask;
} SlruCtlData;
typedef SlruCtlData *SlruCtl;
+/*
+ * Get the SLRU bank lock for given SlruCtl and the pageno.
+ *
+ * This lock needs to be acquired to access the slru buffer slots in the
+ * respective bank.
+ */
+static inline LWLock *
+SimpleLruGetBankLock(SlruCtl ctl, int64 pageno)
+{
+ int banklockno;
+
+ banklockno = (pageno & ctl->bank_mask) % SLRU_MAX_BANKLOCKS;
+ return &(ctl->shared->bank_locks[banklockno].lock);
+}
extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
- SyncRequestHandler sync_handler,
+ const char *subdir, int buffer_tranche_id,
+ int bank_tranche_id, SyncRequestHandler sync_handler,
bool long_segment_names);
extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
@@ -179,5 +229,8 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
int64 segpage, void *data);
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
+extern bool check_slru_buffers(const char *name, int *newval);
+extern void SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode);
+extern void SimpleLruReleaseAllBankLock(SlruCtl ctl);
#endif /* SLRU_H */
diff --git a/src/include/access/subtrans.h b/src/include/access/subtrans.h
index b0d2ad57e5..e2213cf3fd 100644
--- a/src/include/access/subtrans.h
+++ b/src/include/access/subtrans.h
@@ -11,9 +11,6 @@
#ifndef SUBTRANS_H
#define SUBTRANS_H
-/* Number of SLRU buffers to use for subtrans */
-#define NUM_SUBTRANS_BUFFERS 32
-
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
extern TransactionId SubTransGetParent(TransactionId xid);
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 80b8583421..78daa25fa0 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -15,11 +15,6 @@
#include <signal.h>
-/*
- * The number of SLRU page buffers we use for the notification queue.
- */
-#define NUM_NOTIFY_BUFFERS 8
-
extern PGDLLIMPORT bool Trace_notify;
extern PGDLLIMPORT int max_notify_queue_pages;
extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0b01c1f093..39b8ed9425 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -178,6 +178,14 @@ extern PGDLLIMPORT int MaxConnections;
extern PGDLLIMPORT int max_worker_processes;
extern PGDLLIMPORT int max_parallel_workers;
+extern PGDLLIMPORT int commit_timestamp_buffers;
+extern PGDLLIMPORT int multixact_members_buffers;
+extern PGDLLIMPORT int multixact_offsets_buffers;
+extern PGDLLIMPORT int notify_buffers;
+extern PGDLLIMPORT int serializable_buffers;
+extern PGDLLIMPORT int subtransaction_buffers;
+extern PGDLLIMPORT int transaction_buffers;
+
extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT TimestampTz MyStartTimestamp;
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 50a65e046d..10bea8c595 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -209,6 +209,13 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_LAUNCHER_HASH,
LWTRANCHE_DSM_REGISTRY_DSA,
LWTRANCHE_DSM_REGISTRY_HASH,
+ LWTRANCHE_COMMITTS_SLRU,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
+ LWTRANCHE_NOTIFY_SLRU,
+ LWTRANCHE_SERIAL_SLRU,
+ LWTRANCHE_SUBTRANS_SLRU,
+ LWTRANCHE_XACT_SLRU,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index a7edd38fa9..14ee9b94a2 100644
--- a/src/include/storage/predicate.h
+++ b/src/include/storage/predicate.h
@@ -26,10 +26,6 @@ extern PGDLLIMPORT int max_predicate_locks_per_xact;
extern PGDLLIMPORT int max_predicate_locks_per_relation;
extern PGDLLIMPORT int max_predicate_locks_per_page;
-
-/* Number of SLRU buffers to use for Serial SLRU */
-#define NUM_SERIAL_BUFFERS 16
-
/*
* A handle used for sharing SERIALIZABLEXACT objects between the participants
* in a parallel query.
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 5300c44f3b..44b0cbf9a1 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -46,6 +46,8 @@ extern bool check_client_connection_check_interval(int *newval, void **extra,
extern bool check_client_encoding(char **newval, void **extra, GucSource source);
extern void assign_client_encoding(const char *newval, void *extra);
extern bool check_cluster_name(char **newval, void **extra, GucSource source);
+extern bool check_commit_ts_buffers(int *newval, void **extra,
+ GucSource source);
extern const char *show_data_directory_mode(void);
extern bool check_datestyle(char **newval, void **extra, GucSource source);
extern void assign_datestyle(const char *newval, void *extra);
@@ -91,6 +93,11 @@ extern bool check_max_worker_processes(int *newval, void **extra,
GucSource source);
extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
extern void assign_max_stack_depth(int newval, void *extra);
+extern bool check_multixact_members_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_multixact_offsets_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_notify_buffers(int *newval, void **extra, GucSource source);
extern bool check_primary_slot_name(char **newval, void **extra,
GucSource source);
extern bool check_random_seed(double *newval, void **extra, GucSource source);
@@ -122,12 +129,15 @@ extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
extern bool check_search_path(char **newval, void **extra, GucSource source);
extern void assign_search_path(const char *newval, void *extra);
+extern bool check_serial_buffers(int *newval, void **extra, GucSource source);
extern bool check_session_authorization(char **newval, void **extra, GucSource source);
extern void assign_session_authorization(const char *newval, void *extra);
extern void assign_session_replication_role(int newval, void *extra);
extern void assign_stats_fetch_consistency(int newval, void *extra);
extern bool check_ssl(bool *newval, void **extra, GucSource source);
extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
+extern bool check_subtrans_buffers(int *newval, void **extra,
+ GucSource source);
extern bool check_synchronous_standby_names(char **newval, void **extra,
GucSource source);
extern void assign_synchronous_standby_names(const char *newval, void *extra);
@@ -152,6 +162,7 @@ extern const char *show_timezone(void);
extern bool check_timezone_abbreviations(char **newval, void **extra,
GucSource source);
extern void assign_timezone_abbreviations(const char *newval, void *extra);
+extern bool check_transaction_buffers(int *newval, void **extra, GucSource source);
extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source);
extern bool check_transaction_isolation(int *newval, void **extra, GucSource source);
extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 4b31f331ca..068a21f125 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -40,10 +40,6 @@ PG_FUNCTION_INFO_V1(test_slru_delete_all);
/* Number of SLRU page slots */
#define NUM_TEST_BUFFERS 16
-/* SLRU control lock */
-LWLock TestSLRULock;
-#define TestSLRULock (&TestSLRULock)
-
static SlruCtlData TestSlruCtlData;
#define TestSlruCtl (&TestSlruCtlData)
@@ -63,9 +59,9 @@ test_slru_page_write(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = text_to_cstring(PG_GETARG_TEXT_PP(1));
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
-
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruZeroPage(TestSlruCtl, pageno);
/* these should match */
@@ -80,7 +76,7 @@ test_slru_page_write(PG_FUNCTION_ARGS)
BLCKSZ - 1);
SimpleLruWritePage(TestSlruCtl, slotno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_VOID();
}
@@ -99,13 +95,14 @@ test_slru_page_read(PG_FUNCTION_ARGS)
bool write_ok = PG_GETARG_BOOL(1);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(TestSlruCtl, pageno,
write_ok, InvalidTransactionId);
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -116,14 +113,15 @@ test_slru_page_readonly(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
pageno,
InvalidTransactionId);
- Assert(LWLockHeldByMe(TestSLRULock));
+ Assert(LWLockHeldByMe(lock));
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -133,10 +131,11 @@ test_slru_page_exists(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool found;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
found = SimpleLruDoesPhysicalPageExist(TestSlruCtl, pageno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_BOOL(found);
}
@@ -221,6 +220,7 @@ test_slru_shmem_startup(void)
const bool long_segment_names = true;
const char slru_dir_name[] = "pg_test_slru";
int test_tranche_id;
+ int test_buffer_tranche_id;
if (prev_shmem_startup_hook)
prev_shmem_startup_hook();
@@ -234,12 +234,15 @@ test_slru_shmem_startup(void)
/* initialize the SLRU facility */
test_tranche_id = LWLockNewTrancheId();
LWLockRegisterTranche(test_tranche_id, "test_slru_tranche");
- LWLockInitialize(TestSLRULock, test_tranche_id);
+
+ test_buffer_tranche_id = LWLockNewTrancheId();
+ LWLockRegisterTranche(test_tranche_id, "test_buffer_tranche");
TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
SimpleLruInit(TestSlruCtl, "TestSLRU",
- NUM_TEST_BUFFERS, 0, TestSLRULock, slru_dir_name,
- test_tranche_id, SYNC_HANDLER_NONE, long_segment_names);
+ NUM_TEST_BUFFERS, 0, slru_dir_name,
+ test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
+ long_segment_names);
}
void
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-25 16:33 Alvaro Herrera <[email protected]>
parent: Alvaro Herrera <[email protected]>
1 sibling, 1 reply; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-25 16:33 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2024-Jan-25, Alvaro Herrera wrote:
> Here's a touched-up version of this patch.
> diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
> index 98fa6035cc..4a5e05d5e4 100644
> --- a/src/backend/storage/lmgr/lwlock.c
> +++ b/src/backend/storage/lmgr/lwlock.c
> @@ -163,6 +163,13 @@ static const char *const BuiltinTrancheNames[] = {
> [LWTRANCHE_LAUNCHER_HASH] = "LogicalRepLauncherHash",
> [LWTRANCHE_DSM_REGISTRY_DSA] = "DSMRegistryDSA",
> [LWTRANCHE_DSM_REGISTRY_HASH] = "DSMRegistryHash",
> + [LWTRANCHE_COMMITTS_SLRU] = "CommitTSSLRU",
> + [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultixactOffsetSLRU",
> + [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultixactMemberSLRU",
> + [LWTRANCHE_NOTIFY_SLRU] = "NotifySLRU",
> + [LWTRANCHE_SERIAL_SLRU] = "SerialSLRU"
> + [LWTRANCHE_SUBTRANS_SLRU] = "SubtransSLRU",
> + [LWTRANCHE_XACT_SLRU] = "XactSLRU",
> };
Eeek. Last minute changes ... Fixed here.
--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"La primera ley de las demostraciones en vivo es: no trate de usar el sistema.
Escriba un guión que no toque nada para no causar daños." (Jakob Nielsen)
Attachments:
[text/x-diff] v16-slru-optimization.patch (111.4K, ../../[email protected]/2-v16-slru-optimization.patch)
download | inline diff:
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 61038472c5..3e3119865a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2006,6 +2006,145 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-commit-timestamp-buffers" xreflabel="commit_timestamp_buffers">
+ <term><varname>commit_timestamp_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>commit_timestamp_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of memory to use to cache the contents of
+ <literal>pg_commit_ts</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-members-buffers" xreflabel="multixact_members_buffers">
+ <term><varname>multixact_members_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_members_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/members</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>32</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-multixact-offsets-buffers" xreflabel="multixact_offsets_buffers">
+ <term><varname>multixact_offsets_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>multixact_offsets_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_multixact/offsets</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>16</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-notify-buffers" xreflabel="notify_buffers">
+ <term><varname>notify_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>notify_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_notify</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>16</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-serializable-buffers" xreflabel="serializable_buffers">
+ <term><varname>serializable_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>serializable_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_serial</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>32</literal>.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-subtransaction-buffers" xreflabel="subtransaction_buffers">
+ <term><varname>subtransaction_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>subtransaction_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_subtrans</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-transaction-buffers" xreflabel="transaction_buffers">
+ <term><varname>transaction_buffers</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>transaction_buffers</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the amount of shared memory to use to cache the contents
+ of <literal>pg_xact</literal> (see
+ <xref linkend="pgdata-contents-table"/>).
+ If this value is specified without units, it is taken as blocks,
+ that is <symbol>BLCKSZ</symbol> bytes, typically 8kB.
+ The default value is <literal>0</literal>, which requests
+ <varname>shared_buffers</varname>/512 up to 1024 blocks,
+ but not fewer than 16 blocks.
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
<term><varname>max_stack_depth</varname> (<type>integer</type>)
<indexterm>
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index f6e7da7ffc..fb22bc2068 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -43,6 +43,7 @@
#include "pgstat.h"
#include "storage/proc.h"
#include "storage/sync.h"
+#include "utils/guc_hooks.h"
/*
* Defines for CLOG page sizes. A page is the same BLCKSZ as is used
@@ -62,6 +63,15 @@
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
#define CLOG_XACT_BITMASK ((1 << CLOG_BITS_PER_XACT) - 1)
+/*
+ * Because space used in CLOG by each transaction is so small, we place a
+ * smaller limit on the number of CLOG buffers than SLRU allows. No other
+ * SLRU needs this.
+ */
+#define CLOG_MAX_ALLOWED_BUFFERS \
+ Min(SLRU_MAX_ALLOWED_BUFFERS, \
+ (((MaxTransactionId / 2) + (CLOG_XACTS_PER_PAGE - 1)) / CLOG_XACTS_PER_PAGE))
+
/*
* Although we return an int64 the actual value can't currently exceed
@@ -284,14 +294,19 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
XLogRecPtr lsn, int64 pageno,
bool all_xact_same_page)
{
+ LWLock *lock;
+
/* Can't use group update when PGPROC overflows. */
StaticAssertDecl(THRESHOLD_SUBTRANS_CLOG_OPT <= PGPROC_MAX_CACHED_SUBXIDS,
"group clog threshold less than PGPROC cached subxids");
+ /* Get the SLRU bank lock for the page we are going to access. */
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+
/*
- * When there is contention on XactSLRULock, we try to group multiple
+ * When there is contention on the bank lock, we try to group multiple
* updates; a single leader process will perform transaction status
- * updates for multiple backends so that the number of times XactSLRULock
+ * updates for multiple backends so that the number of times the bank lock
* needs to be acquired is reduced.
*
* For this optimization to be safe, the XID and subxids in MyProc must be
@@ -310,17 +325,17 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
nsubxids * sizeof(TransactionId)) == 0))
{
/*
- * If we can immediately acquire XactSLRULock, we update the status of
- * our own XID and release the lock. If not, try use group XID
- * update. If that doesn't work out, fall back to waiting for the
- * lock to perform an update for this transaction only.
+ * If we can immediately acquire the lock, we update the status of our
+ * own XID and release the lock. If not, try use group XID update. If
+ * that doesn't work out, fall back to waiting for the lock to perform
+ * an update for this transaction only.
*/
- if (LWLockConditionalAcquire(XactSLRULock, LW_EXCLUSIVE))
+ if (LWLockConditionalAcquire(lock, LW_EXCLUSIVE))
{
/* Got the lock without waiting! Do the update. */
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
return;
}
else if (TransactionGroupUpdateXidStatus(xid, status, lsn, pageno))
@@ -333,10 +348,10 @@ TransactionIdSetPageStatus(TransactionId xid, int nsubxids,
}
/* Group update not applicable, or couldn't accept this page number. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
TransactionIdSetPageStatusInternal(xid, nsubxids, subxids, status,
lsn, pageno);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -355,7 +370,8 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
Assert(status == TRANSACTION_STATUS_COMMITTED ||
status == TRANSACTION_STATUS_ABORTED ||
(status == TRANSACTION_STATUS_SUB_COMMITTED && !TransactionIdIsValid(xid)));
- Assert(LWLockHeldByMeInMode(XactSLRULock, LW_EXCLUSIVE));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl, pageno),
+ LW_EXCLUSIVE));
/*
* If we're doing an async commit (ie, lsn is valid), then we must wait
@@ -406,14 +422,13 @@ TransactionIdSetPageStatusInternal(TransactionId xid, int nsubxids,
}
/*
- * When we cannot immediately acquire XactSLRULock in exclusive mode at
+ * When we cannot immediately acquire the SLRU bank lock in exclusive mode at
* commit time, add ourselves to a list of processes that need their XIDs
* status update. The first process to add itself to the list will acquire
- * XactSLRULock in exclusive mode and set transaction status as required
- * on behalf of all group members. This avoids a great deal of contention
- * around XactSLRULock when many processes are trying to commit at once,
- * since the lock need not be repeatedly handed off from one committing
- * process to the next.
+ * the lock in exclusive mode and set transaction status as required on behalf
+ * of all group members. This avoids a great deal of contention when many
+ * processes are trying to commit at once, since the lock need not be
+ * repeatedly handed off from one committing process to the next.
*
* Returns true when transaction status has been updated in clog; returns
* false if we decided against applying the optimization because the page
@@ -427,13 +442,15 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
PGPROC *proc = MyProc;
uint32 nextidx;
uint32 wakeidx;
+ int prevpageno;
+ LWLock *prevlock = NULL;
/* We should definitely have an XID whose status needs to be updated. */
Assert(TransactionIdIsValid(xid));
/*
- * Add ourselves to the list of processes needing a group XID status
- * update.
+ * Prepare to add ourselves to the list of processes needing a group XID
+ * status update.
*/
proc->clogGroupMember = true;
proc->clogGroupMemberXid = xid;
@@ -441,6 +458,41 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
proc->clogGroupMemberPage = pageno;
proc->clogGroupMemberLsn = lsn;
+ /*
+ * The underlying SLRU is using bank-wise lock so it is possible that here
+ * we might get requesters who are contending on different SLRU-bank
+ * locks. But in the group, we try to only add the requesters who want to
+ * update the same page i.e. they would be requesting for the same
+ * SLRU-bank lock as well. The main reason for now allowing requesters of
+ * different pages together is 1) Once the leader acquires the lock they
+ * don't need to fetch multiple pages and do multiple I/O under the same
+ * lock 2) The leader need not switch the SLRU-bank lock if the different
+ * pages are from different SLRU banks 3) And the most important reason is
+ * that most of the time the contention will occur in high concurrent OLTP
+ * workload is going on and at that time most of the transactions would be
+ * generated during the same time and most of them would fall in same clog
+ * page as each page can hold status of 32k transactions. However, there
+ * is an exception where in some extreme conditions we might get different
+ * page requests added in the same group but we have handled that by
+ * switching the bank lock, although that is not the most performant way
+ * that's not the common case either so we are fine with that.
+ *
+ * Also to be noted that unless the leader of the current group does not
+ * get the lock we don't clear the 'procglobal->clogGroupFirst' that means
+ * concurrently if we get the requesters for different SLRU pages then
+ * those will have to go for the normal update instead of group update and
+ * that's fine as that is not the common case. As soon as the leader of
+ * the current group gets the lock for the required bank that time we
+ * clear this value and now other requesters (which might want to update a
+ * different page and that might fall into the different bank as well) are
+ * allowed to form a new group as the first group is now detached. So if
+ * the new group has a request for a different SLRU-bank lock then the
+ * group leader of this group might also get the lock while the first
+ * group is performing the update and these two groups can perform the
+ * group update concurrently but it is completely safe as these two
+ * leaders are operating on completely different SLRU pages and they both
+ * are holding their respective SLRU locks.
+ */
nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
while (true)
@@ -507,8 +559,17 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
return true;
}
- /* We are the leader. Acquire the lock on behalf of everyone. */
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ /*
+ * Acquire the SLRU bank lock for the first page in the group before we
+ * close this group by setting procglobal->clogGroupFirst as
+ * INVALID_PGPROCNO so that we do not close the new entries to the group
+ * even before getting the lock and losing whole purpose of the group
+ * update.
+ */
+ nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst);
+ prevpageno = ProcGlobal->allProcs[nextidx].clogGroupMemberPage;
+ prevlock = SimpleLruGetBankLock(XactCtl, prevpageno);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
/*
* Now that we've got the lock, clear the list of processes waiting for
@@ -525,6 +586,37 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
while (nextidx != INVALID_PGPROCNO)
{
PGPROC *nextproc = &ProcGlobal->allProcs[nextidx];
+ int thispageno = nextproc->clogGroupMemberPage;
+
+ /*
+ * If the SLRU bank lock for the current page is not the same as that
+ * of the last page then we need to release the lock on the previous
+ * bank and acquire the lock on the bank for the page we are going to
+ * update now.
+ *
+ * Although on the best effort basis we try that all the requests
+ * within a group are for the same clog page there are some
+ * possibilities that there are request for more than one page in the
+ * same group (for details refer to the comment in the previous while
+ * loop). That scenario might not be very performant because while
+ * switching the lock the group leader might need to wait on the new
+ * lock if the pages are from different SLRU bank but it is safe
+ * because a) we are releasing the old lock before acquiring the new
+ * lock so there is should not be any deadlock situation b) and, we
+ * are always modifying the page under the correct SLRU lock.
+ */
+ if (thispageno != prevpageno)
+ {
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, thispageno);
+
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ prevlock = lock;
+ prevpageno = thispageno;
+ }
/*
* Transactions with more than THRESHOLD_SUBTRANS_CLOG_OPT sub-XIDs
@@ -544,7 +636,8 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
}
/* We're done with the lock now. */
- LWLockRelease(XactSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
/*
* Now that we've released the lock, go back and wake everybody up. We
@@ -573,7 +666,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status,
/*
* Sets the commit status of a single transaction.
*
- * Must be called with XactSLRULock held
+ * Caller must hold the corresponding SLRU bank lock, will be held at exit.
*/
static void
TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
@@ -584,6 +677,11 @@ TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, i
char byteval;
char curval;
+ Assert(XactCtl->shared->page_number[slotno] == TransactionIdToPage(xid));
+ Assert(LWLockHeldByMeInMode(SimpleLruGetBankLock(XactCtl,
+ XactCtl->shared->page_number[slotno]),
+ LW_EXCLUSIVE));
+
byteptr = XactCtl->shared->page_buffer[slotno] + byteno;
curval = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
@@ -665,7 +763,7 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
lsnindex = GetLSNIndex(slotno, xid);
*lsn = XactCtl->shared->group_lsn[lsnindex];
- LWLockRelease(XactSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(XactCtl, pageno));
return status;
}
@@ -673,23 +771,18 @@ TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
/*
* Number of shared CLOG buffers.
*
- * On larger multi-processor systems, it is possible to have many CLOG page
- * requests in flight at one time which could lead to disk access for CLOG
- * page if the required page is not found in memory. Testing revealed that we
- * can get the best performance by having 128 CLOG buffers, more than that it
- * doesn't improve performance.
- *
- * Unconditionally keeping the number of CLOG buffers to 128 did not seem like
- * a good idea, because it would increase the minimum amount of shared memory
- * required to start, which could be a problem for people running very small
- * configurations. The following formula seems to represent a reasonable
- * compromise: people with very low values for shared_buffers will get fewer
- * CLOG buffers as well, and everyone else will get 128.
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
*/
-Size
+static int
CLOGShmemBuffers(void)
{
- return Min(128, Max(4, NBuffers / 512));
+ /* auto-tune based on shared buffers */
+ if (transaction_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, transaction_buffers), CLOG_MAX_ALLOWED_BUFFERS);
}
/*
@@ -704,13 +797,36 @@ CLOGShmemSize(void)
void
CLOGShmemInit(void)
{
+ /* If auto-tuning is requested, now is the time to do it */
+ if (transaction_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", CLOGShmemBuffers());
+ SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (transaction_buffers == 0) /* failed to apply it? */
+ SetConfigOption("transaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(transaction_buffers != 0);
+
XactCtl->PagePrecedes = CLOGPagePrecedes;
SimpleLruInit(XactCtl, "Xact", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
- XactSLRULock, "pg_xact", LWTRANCHE_XACT_BUFFER,
- SYNC_HANDLER_CLOG, false);
+ "pg_xact", LWTRANCHE_XACT_BUFFER,
+ LWTRANCHE_XACT_SLRU, SYNC_HANDLER_CLOG, false);
SlruPagePrecedesUnitTests(XactCtl, CLOG_XACTS_PER_PAGE);
}
+/*
+ * GUC check_hook for transaction_buffers
+ */
+bool
+check_transaction_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("transaction_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates
* the initial CLOG segment. (The CLOG directory is assumed to
@@ -721,8 +837,9 @@ void
BootStrapCLOG(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, 0);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the commit log */
slotno = ZeroCLOGPage(0, false);
@@ -731,7 +848,7 @@ BootStrapCLOG(void)
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -766,14 +883,8 @@ StartupCLOG(void)
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
-
- /*
- * Initialize our idea of the latest page number.
- */
- XactCtl->shared->latest_page_number = pageno;
-
- LWLockRelease(XactSLRULock);
+ /* Initialize our idea of the latest page number. */
+ pg_atomic_init_u64(&XactCtl->shared->latest_page_number, pageno);
}
/*
@@ -784,8 +895,9 @@ TrimCLOG(void)
{
TransactionId xid = XidFromFullTransactionId(TransamVariables->nextXid);
int64 pageno = TransactionIdToPage(xid);
+ LWLock *lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* Zero out the remainder of the current clog page. Under normal
@@ -817,7 +929,7 @@ TrimCLOG(void)
XactCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -849,6 +961,7 @@ void
ExtendCLOG(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -859,13 +972,14 @@ ExtendCLOG(TransactionId newestXact)
return;
pageno = TransactionIdToPage(newestXact);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCLOGPage(pageno, true);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
@@ -1003,16 +1117,18 @@ clog_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(XactCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCLOGPage(pageno, false);
SimpleLruWritePage(XactCtl, slotno);
Assert(!XactCtl->shared->page_dirty[slotno]);
- LWLockRelease(XactSLRULock);
+ LWLockRelease(lock);
}
else if (info == CLOG_TRUNCATE)
{
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 61b82385f3..69d359e0fa 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -33,6 +33,7 @@
#include "pg_trace.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
#include "utils/timestamp.h"
@@ -225,10 +226,11 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
TransactionId *subxids, TimestampTz ts,
RepOriginId nodeid, int64 pageno)
{
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
int slotno;
int i;
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(CommitTsCtl, pageno, true, xid);
@@ -238,22 +240,25 @@ SetXidCommitTsInPage(TransactionId xid, int nsubxids,
CommitTsCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
* Sets the commit timestamp of a single transaction.
*
- * Must be called with CommitTsSLRULock held
+ * Caller must hold the correct SLRU bank lock, will be held at exit
*/
static void
TransactionIdSetCommitTs(TransactionId xid, TimestampTz ts,
RepOriginId nodeid, int slotno)
{
- int entryno = TransactionIdToCTsEntry(xid);
+ int entryno;
CommitTimestampEntry entry;
- Assert(TransactionIdIsNormal(xid));
+ if (!TransactionIdIsNormal(xid))
+ return;
+
+ entryno = TransactionIdToCTsEntry(xid);
entry.time = ts;
entry.nodeid = nodeid;
@@ -345,7 +350,7 @@ TransactionIdGetCommitTsData(TransactionId xid, TimestampTz *ts,
if (nodeid)
*nodeid = entry.nodeid;
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(CommitTsCtl, pageno));
return *ts != 0;
}
@@ -499,14 +504,18 @@ pg_xact_commit_timestamp_origin(PG_FUNCTION_ARGS)
/*
* Number of shared CommitTS buffers.
*
- * We use a very similar logic as for the number of CLOG buffers (except we
- * scale up twice as fast with shared buffers, and the maximum is twice as
- * high); see comments in CLOGShmemBuffers.
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
*/
-Size
+static int
CommitTsShmemBuffers(void)
{
- return Min(256, Max(4, NBuffers / 256));
+ /* auto-tune based on shared buffers */
+ if (commit_timestamp_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, commit_timestamp_buffers), SLRU_MAX_ALLOWED_BUFFERS);
}
/*
@@ -528,10 +537,24 @@ CommitTsShmemInit(void)
{
bool found;
+ /* If auto-tuning is requested, now is the time to do it */
+ if (commit_timestamp_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", CommitTsShmemBuffers());
+ SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (commit_timestamp_buffers == 0) /* failed to apply it? */
+ SetConfigOption("commit_timestamp_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(commit_timestamp_buffers != 0);
+
CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
SimpleLruInit(CommitTsCtl, "CommitTs", CommitTsShmemBuffers(), 0,
- CommitTsSLRULock, "pg_commit_ts",
- LWTRANCHE_COMMITTS_BUFFER,
+ "pg_commit_ts", LWTRANCHE_COMMITTS_BUFFER,
+ LWTRANCHE_COMMITTS_SLRU,
SYNC_HANDLER_COMMIT_TS,
false);
SlruPagePrecedesUnitTests(CommitTsCtl, COMMIT_TS_XACTS_PER_PAGE);
@@ -553,6 +576,15 @@ CommitTsShmemInit(void)
Assert(found);
}
+/*
+ * GUC check_hook for commit_timestamp_buffers
+ */
+bool
+check_commit_ts_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("commit_timestamp_buffers", newval);
+}
+
/*
* This function must be called ONCE on system install.
*
@@ -689,9 +721,7 @@ ActivateCommitTs(void)
/*
* Re-Initialize our idea of the latest page number.
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
- CommitTsCtl->shared->latest_page_number = pageno;
- LWLockRelease(CommitTsSLRULock);
+ pg_atomic_init_u64(&CommitTsCtl->shared->latest_page_number, pageno);
/*
* If CommitTs is enabled, but it wasn't in the previous server run, we
@@ -717,13 +747,14 @@ ActivateCommitTs(void)
/* Create the current segment file, if necessary */
if (!SimpleLruDoesPhysicalPageExist(CommitTsCtl, pageno))
{
+ LWLock *lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
int slotno;
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/* Change the activation status in shared memory. */
@@ -772,9 +803,9 @@ DeactivateCommitTs(void)
* be overwritten anyway when we wrap around, but it seems better to be
* tidy.)
*/
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ SimpleLruAcquireAllBankLock(CommitTsCtl, LW_EXCLUSIVE);
(void) SlruScanDirectory(CommitTsCtl, SlruScanDirCbDeleteAll, NULL);
- LWLockRelease(CommitTsSLRULock);
+ SimpleLruReleaseAllBankLock(CommitTsCtl);
}
/*
@@ -806,6 +837,7 @@ void
ExtendCommitTs(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* Nothing to do if module not enabled. Note we do an unlocked read of
@@ -826,12 +858,14 @@ ExtendCommitTs(TransactionId newestXact)
pageno = TransactionIdToCTsPage(newestXact);
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroCommitTsPage(pageno, !InRecovery);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -985,16 +1019,18 @@ commit_ts_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(CommitTsSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(CommitTsCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroCommitTsPage(pageno, false);
SimpleLruWritePage(CommitTsCtl, slotno);
Assert(!CommitTsCtl->shared->page_dirty[slotno]);
- LWLockRelease(CommitTsSLRULock);
+ LWLockRelease(lock);
}
else if (info == COMMIT_TS_TRUNCATE)
{
@@ -1006,7 +1042,9 @@ commit_ts_redo(XLogReaderState *record)
* During XLOG replay, latest_page_number isn't set up yet; insert a
* suitable value to bypass the sanity test in SimpleLruTruncate.
*/
- CommitTsCtl->shared->latest_page_number = trunc->pageno;
+ pg_atomic_write_u64(&CommitTsCtl->shared->latest_page_number,
+ trunc->pageno);
+ pg_write_barrier();
SimpleLruTruncate(CommitTsCtl, trunc->pageno);
}
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 59523be901..67b6a6b20a 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -88,6 +88,7 @@
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
@@ -192,10 +193,10 @@ static SlruCtlData MultiXactMemberCtlData;
/*
* MultiXact state shared across all backends. All this state is protected
- * by MultiXactGenLock. (We also use MultiXactOffsetSLRULock and
- * MultiXactMemberSLRULock to guard accesses to the two sets of SLRU
- * buffers. For concurrency's sake, we avoid holding more than one of these
- * locks at a time.)
+ * by MultiXactGenLock. (We also use SLRU bank's lock of MultiXactOffset and
+ * MultiXactMember to guard accesses to the two sets of SLRU buffers. For
+ * concurrency's sake, we avoid holding more than one of these locks at a
+ * time.)
*/
typedef struct MultiXactStateData
{
@@ -870,12 +871,15 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int slotno;
MultiXactOffset *offptr;
int i;
-
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLock *lock;
+ LWLock *prevlock = NULL;
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+
/*
* Note: we pass the MultiXactId to SimpleLruReadPage as the "transaction"
* to complain about if there's any I/O error. This is kinda bogus, but
@@ -891,10 +895,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
- /* Exchange our lock */
- LWLockRelease(MultiXactOffsetSLRULock);
-
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ /* Release MultiXactOffset SLRU lock. */
+ LWLockRelease(lock);
prev_pageno = -1;
@@ -916,6 +918,20 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
if (pageno != prev_pageno)
{
+ /*
+ * MultiXactMember SLRU page is changed so check if this new page
+ * fall into the different SLRU bank then release the old bank's
+ * lock and acquire lock on the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -936,7 +952,8 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
}
/*
@@ -1239,6 +1256,8 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
MultiXactId tmpMXact;
MultiXactOffset nextOffset;
MultiXactMember *ptr;
+ LWLock *lock;
+ LWLock *prevlock = NULL;
debug_elog3(DEBUG2, "GetMembers: asked for %u", multi);
@@ -1342,11 +1361,22 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
* time on every multixact creation.
*/
retry:
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
-
pageno = MultiXactIdToOffsetPage(multi);
entryno = MultiXactIdToOffsetEntry(multi);
+ /*
+ * If this page falls under a different bank, release the old bank's lock
+ * and acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock != NULL)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1379,7 +1409,21 @@ retry:
entryno = MultiXactIdToOffsetEntry(tmpMXact);
if (pageno != prev_pageno)
+ {
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, tmpMXact);
+ }
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -1388,7 +1432,8 @@ retry:
if (nextMXOffset == 0)
{
/* Corner case 2: next multixact is still being filled in */
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
CHECK_FOR_INTERRUPTS();
pg_usleep(1000L);
goto retry;
@@ -1397,13 +1442,11 @@ retry:
length = nextMXOffset - offset;
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(prevlock);
+ prevlock = NULL;
ptr = (MultiXactMember *) palloc(length * sizeof(MultiXactMember));
- /* Now get the members themselves. */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
-
truelength = 0;
prev_pageno = -1;
for (i = 0; i < length; i++, offset++)
@@ -1419,6 +1462,20 @@ retry:
if (pageno != prev_pageno)
{
+ /*
+ * Since we're going to access a different SLRU page, if this page
+ * falls under a different bank, release the old bank's lock and
+ * acquire the lock of the new bank.
+ */
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ if (lock != prevlock)
+ {
+ if (prevlock)
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi);
prev_pageno = pageno;
}
@@ -1442,7 +1499,8 @@ retry:
truelength++;
}
- LWLockRelease(MultiXactMemberSLRULock);
+ if (prevlock)
+ LWLockRelease(prevlock);
/* A multixid with zero members should not happen */
Assert(truelength > 0);
@@ -1834,8 +1892,8 @@ MultiXactShmemSize(void)
mul_size(sizeof(MultiXactId) * 2, MaxOldestSlot))
size = SHARED_MULTIXACT_STATE_SIZE;
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTOFFSET_BUFFERS, 0));
- size = add_size(size, SimpleLruShmemSize(NUM_MULTIXACTMEMBER_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_offsets_buffers, 0));
+ size = add_size(size, SimpleLruShmemSize(multixact_members_buffers, 0));
return size;
}
@@ -1851,16 +1909,16 @@ MultiXactShmemInit(void)
MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;
SimpleLruInit(MultiXactOffsetCtl,
- "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0,
- MultiXactOffsetSLRULock, "pg_multixact/offsets",
- LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ "MultiXactOffset", multixact_offsets_buffers, 0,
+ "pg_multixact/offsets", LWTRANCHE_MULTIXACTOFFSET_BUFFER,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
SYNC_HANDLER_MULTIXACT_OFFSET,
false);
SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
SimpleLruInit(MultiXactMemberCtl,
- "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0,
- MultiXactMemberSLRULock, "pg_multixact/members",
- LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ "MultiXactMember", multixact_members_buffers, 0,
+ "pg_multixact/members", LWTRANCHE_MULTIXACTMEMBER_BUFFER,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
SYNC_HANDLER_MULTIXACT_MEMBER,
false);
/* doesn't call SimpleLruTruncate() or meet criteria for unit tests */
@@ -1887,6 +1945,24 @@ MultiXactShmemInit(void)
OldestVisibleMXactId = OldestMemberMXactId + MaxOldestSlot;
}
+/*
+ * GUC check_hook for multixact_offsets_buffers
+ */
+bool
+check_multixact_offsets_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_offsets_buffers", newval);
+}
+
+/*
+ * GUC check_hook for multixact_members_buffers
+ */
+bool
+check_multixact_members_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("multixact_members_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates the initial
* MultiXact segments. (The MultiXacts directories are assumed to have been
@@ -1896,8 +1972,10 @@ void
BootStrapMultiXact(void)
{
int slotno;
+ LWLock *lock;
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the offsets log */
slotno = ZeroMultiXactOffsetPage(0, false);
@@ -1906,9 +1984,10 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, 0);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the members log */
slotno = ZeroMultiXactMemberPage(0, false);
@@ -1917,7 +1996,7 @@ BootStrapMultiXact(void)
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -1977,10 +2056,12 @@ static void
MaybeExtendOffsetSlru(void)
{
int64 pageno;
+ LWLock *lock;
pageno = MultiXactIdToOffsetPage(MultiXactState->nextMXact);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno))
{
@@ -1995,7 +2076,7 @@ MaybeExtendOffsetSlru(void)
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
}
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2017,13 +2098,15 @@ StartupMultiXact(void)
* Initialize offset's idea of the latest page number.
*/
pageno = MultiXactIdToOffsetPage(multi);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
/*
* Initialize member's idea of the latest page number.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_init_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
}
/*
@@ -2048,13 +2131,14 @@ TrimMultiXact(void)
LWLockRelease(MultiXactGenLock);
/* Clean up offsets state */
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for offsets.
*/
pageno = MultiXactIdToOffsetPage(nextMXact);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
/*
* Zero out the remainder of the current offsets page. See notes in
@@ -2069,7 +2153,9 @@ TrimMultiXact(void)
{
int slotno;
MultiXactOffset *offptr;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
@@ -2077,18 +2163,18 @@ TrimMultiXact(void)
MemSet(offptr, 0, BLCKSZ - (entryno * sizeof(MultiXactOffset)));
MultiXactOffsetCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactOffsetSLRULock);
-
/* And the same for members */
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
/*
* (Re-)Initialize our idea of the latest page number for members.
*/
pageno = MXOffsetToMemberPage(offset);
- MultiXactMemberCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactMemberCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
/*
* Zero out the remainder of the current members page. See notes in
@@ -2100,7 +2186,9 @@ TrimMultiXact(void)
int slotno;
TransactionId *xidptr;
int memberoff;
+ LWLock *lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
memberoff = MXOffsetToMemberOffset(offset);
slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, offset);
xidptr = (TransactionId *)
@@ -2115,10 +2203,9 @@ TrimMultiXact(void)
*/
MultiXactMemberCtl->shared->page_dirty[slotno] = true;
+ LWLockRelease(lock);
}
- LWLockRelease(MultiXactMemberSLRULock);
-
/* signal that we're officially up */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->finishedStartup = true;
@@ -2406,6 +2493,7 @@ static void
ExtendMultiXactOffset(MultiXactId multi)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first MultiXactId of a page. But beware: just after
@@ -2416,13 +2504,14 @@ ExtendMultiXactOffset(MultiXactId multi)
return;
pageno = MultiXactIdToOffsetPage(multi);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactOffsetPage(pageno, true);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2455,15 +2544,17 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers)
if (flagsoff == 0 && flagsbit == 0)
{
int64 pageno;
+ LWLock *lock;
pageno = MXOffsetToMemberPage(offset);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page and make an XLOG entry about it */
ZeroMultiXactMemberPage(pageno, true);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -2761,7 +2852,7 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
offptr += entryno;
offset = *offptr;
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(MultiXactOffsetCtl, pageno));
*result = offset;
return true;
@@ -3243,31 +3334,35 @@ multixact_redo(XLogReaderState *record)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactOffsetSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactOffsetPage(pageno, false);
SimpleLruWritePage(MultiXactOffsetCtl, slotno);
Assert(!MultiXactOffsetCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactOffsetSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_ZERO_MEM_PAGE)
{
int64 pageno;
int slotno;
+ LWLock *lock;
memcpy(&pageno, XLogRecGetData(record), sizeof(pageno));
- LWLockAcquire(MultiXactMemberSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(MultiXactMemberCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = ZeroMultiXactMemberPage(pageno, false);
SimpleLruWritePage(MultiXactMemberCtl, slotno);
Assert(!MultiXactMemberCtl->shared->page_dirty[slotno]);
- LWLockRelease(MultiXactMemberSLRULock);
+ LWLockRelease(lock);
}
else if (info == XLOG_MULTIXACT_CREATE_ID)
{
@@ -3333,7 +3428,9 @@ multixact_redo(XLogReaderState *record)
* SimpleLruTruncate.
*/
pageno = MultiXactIdToOffsetPage(xlrec.endTruncOff);
- MultiXactOffsetCtl->shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&MultiXactOffsetCtl->shared->latest_page_number,
+ pageno);
+ pg_write_barrier();
PerformOffsetsTruncation(xlrec.startTruncOff, xlrec.endTruncOff);
LWLockRelease(MultiXactTruncationLock);
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 9ac4790f16..ff3c2d7eec 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -59,6 +59,7 @@
#include "pgstat.h"
#include "storage/fd.h"
#include "storage/shmem.h"
+#include "utils/guc_hooks.h"
static inline int
SlruFileName(SlruCtl ctl, char *path, int64 segno)
@@ -96,6 +97,20 @@ SlruFileName(SlruCtl ctl, char *path, int64 segno)
*/
#define MAX_WRITEALL_BUFFERS 16
+/*
+ * Macro to get the index of the lock for the given slot.
+ *
+ * Basically, the slru buffer pool is divided into banks of buffer and there is
+ * total SLRU_MAX_BANKLOCKS number of locks to protect access to buffer in the
+ * banks. Since we have max limit on the number of locks we can not always have
+ * one lock for each bank. So until the number of banks are
+ * <= SLRU_MAX_BANKLOCKS then there would be one lock protecting each bank
+ * otherwise one lock might protect multiple banks based on the number of
+ * banks.
+ */
+#define SLRU_SLOTNO_GET_BANKLOCKNO(slotno) \
+ (((slotno) / SLRU_BANK_SIZE) % SLRU_MAX_BANKLOCKS)
+
typedef struct SlruWriteAllData
{
int num_files; /* # files actually open */
@@ -117,34 +132,6 @@ typedef struct SlruWriteAllData *SlruWriteAll;
(a).segno = (xx_segno) \
)
-/*
- * Macro to mark a buffer slot "most recently used". Note multiple evaluation
- * of arguments!
- *
- * The reason for the if-test is that there are often many consecutive
- * accesses to the same page (particularly the latest page). By suppressing
- * useless increments of cur_lru_count, we reduce the probability that old
- * pages' counts will "wrap around" and make them appear recently used.
- *
- * We allow this code to be executed concurrently by multiple processes within
- * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
- * this should not cause any completely-bogus values to enter the computation.
- * However, it is possible for either cur_lru_count or individual
- * page_lru_count entries to be "reset" to lower values than they should have,
- * in case a process is delayed while it executes this macro. With care in
- * SlruSelectLRUPage(), this does little harm, and in any case the absolute
- * worst possible consequence is a nonoptimal choice of page to evict. The
- * gain from allowing concurrent reads of SLRU pages seems worth it.
- */
-#define SlruRecentlyUsed(shared, slotno) \
- do { \
- int new_lru_count = (shared)->cur_lru_count; \
- if (new_lru_count != (shared)->page_lru_count[slotno]) { \
- (shared)->cur_lru_count = ++new_lru_count; \
- (shared)->page_lru_count[slotno] = new_lru_count; \
- } \
- } while (0)
-
/* Saved info for SlruReportIOError */
typedef enum
{
@@ -172,6 +159,7 @@ static int SlruSelectLRUPage(SlruCtl ctl, int64 pageno);
static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
int64 segpage, void *data);
static void SlruInternalDeleteSegment(SlruCtl ctl, int64 segno);
+static inline void SlruRecentlyUsed(SlruShared shared, int slotno);
/*
@@ -182,6 +170,10 @@ Size
SimpleLruShmemSize(int nslots, int nlsns)
{
Size sz;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
+
+ Assert(nslots <= SLRU_MAX_ALLOWED_BUFFERS);
/* we assume nslots isn't so large as to risk overflow */
sz = MAXALIGN(sizeof(SlruSharedData));
@@ -191,6 +183,8 @@ SimpleLruShmemSize(int nslots, int nlsns)
sz += MAXALIGN(nslots * sizeof(int64)); /* page_number[] */
sz += MAXALIGN(nslots * sizeof(int)); /* page_lru_count[] */
sz += MAXALIGN(nslots * sizeof(LWLockPadded)); /* buffer_locks[] */
+ sz += MAXALIGN(nbanklocks * sizeof(LWLockPadded)); /* bank_locks[] */
+ sz += MAXALIGN(nbanks * sizeof(int)); /* bank_cur_lru_count[] */
if (nlsns > 0)
sz += MAXALIGN(nslots * nlsns * sizeof(XLogRecPtr)); /* group_lsn[] */
@@ -207,16 +201,21 @@ SimpleLruShmemSize(int nslots, int nlsns)
* nlsns: number of LSN groups per page (set to zero if not relevant).
* ctllock: LWLock to use to control access to the shared control structure.
* subdir: PGDATA-relative subdirectory that will contain the files.
- * tranche_id: LWLock tranche ID to use for the SLRU's per-buffer LWLocks.
+ * buffer_tranche_id: tranche ID to use for the SLRU's per-buffer LWLocks.
+ * bank_tranche_id: tranche ID to use for the bank LWLocks.
* sync_handler: which set of functions to use to handle sync requests
*/
void
SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
+ const char *subdir, int buffer_tranche_id, int bank_tranche_id,
SyncRequestHandler sync_handler, bool long_segment_names)
{
SlruShared shared;
bool found;
+ int nbanks = nslots / SLRU_BANK_SIZE;
+ int nbanklocks = Min(nbanks, SLRU_MAX_BANKLOCKS);
+
+ Assert(nslots <= SLRU_MAX_ALLOWED_BUFFERS);
shared = (SlruShared) ShmemInitStruct(name,
SimpleLruShmemSize(nslots, nlsns),
@@ -228,18 +227,16 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
char *ptr;
Size offset;
int slotno;
+ int bankno;
+ int banklockno;
Assert(!found);
memset(shared, 0, sizeof(SlruSharedData));
- shared->ControlLock = ctllock;
-
shared->num_slots = nslots;
shared->lsn_groups_per_page = nlsns;
- shared->cur_lru_count = 0;
-
/* shared->latest_page_number will be set later */
shared->slru_stats_idx = pgstat_get_slru_index(name);
@@ -260,6 +257,10 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
/* Initialize LWLocks */
shared->buffer_locks = (LWLockPadded *) (ptr + offset);
offset += MAXALIGN(nslots * sizeof(LWLockPadded));
+ shared->bank_locks = (LWLockPadded *) (ptr + offset);
+ offset += MAXALIGN(nbanklocks * sizeof(LWLockPadded));
+ shared->bank_cur_lru_count = (int *) (ptr + offset);
+ offset += MAXALIGN(nbanks * sizeof(int));
if (nlsns > 0)
{
@@ -271,7 +272,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
for (slotno = 0; slotno < nslots; slotno++)
{
LWLockInitialize(&shared->buffer_locks[slotno].lock,
- tranche_id);
+ buffer_tranche_id);
shared->page_buffer[slotno] = ptr;
shared->page_status[slotno] = SLRU_PAGE_EMPTY;
@@ -280,11 +281,23 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ptr += BLCKSZ;
}
+ /* Initialize the bank locks. */
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockInitialize(&shared->bank_locks[banklockno].lock,
+ bank_tranche_id);
+
+ /* Initialize the bank lru counters. */
+ for (bankno = 0; bankno < nbanks; bankno++)
+ shared->bank_cur_lru_count[bankno] = 0;
+
/* Should fit to estimated shmem size */
Assert(ptr - (char *) shared <= SimpleLruShmemSize(nslots, nlsns));
}
else
+ {
Assert(found);
+ Assert(shared->num_slots == nslots);
+ }
/*
* Initialize the unshared control struct, including directory path. We
@@ -293,6 +306,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
ctl->shared = shared;
ctl->sync_handler = sync_handler;
ctl->long_segment_names = long_segment_names;
+ ctl->bank_mask = (nslots / SLRU_BANK_SIZE) - 1;
strlcpy(ctl->Dir, subdir, sizeof(ctl->Dir));
}
@@ -330,7 +344,8 @@ SimpleLruZeroPage(SlruCtl ctl, int64 pageno)
SimpleLruZeroLSNs(ctl, slotno);
/* Assume this page is now the latest active page */
- shared->latest_page_number = pageno;
+ pg_atomic_write_u64(&shared->latest_page_number, pageno);
+ pg_write_barrier();
/* update the stats counter of zeroed pages */
pgstat_count_slru_page_zeroed(shared->slru_stats_idx);
@@ -369,12 +384,13 @@ static void
SimpleLruWaitIO(SlruCtl ctl, int slotno)
{
SlruShared shared = ctl->shared;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* See notes at top of file */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_SHARED);
LWLockRelease(&shared->buffer_locks[slotno].lock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
/*
* If the slot is still in an io-in-progress state, then either someone
@@ -425,10 +441,14 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
{
SlruShared shared = ctl->shared;
+ /* Caller must hold the bank lock for the input page. */
+ Assert(LWLockHeldByMe(SimpleLruGetBankLock(ctl, pageno)));
+
/* Outer loop handles restart if we must wait for someone else's I/O */
for (;;)
{
int slotno;
+ int banklockno;
bool ok;
/* See if page already is in memory; if not, pick victim slot */
@@ -471,9 +491,10 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
/* Acquire per-buffer lock (cannot deadlock, see notes at top) */
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
+ banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the read */
ok = SlruPhysicalReadPage(ctl, pageno, slotno);
@@ -482,7 +503,7 @@ SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
SimpleLruZeroLSNs(ctl, slotno);
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_READ_IN_PROGRESS &&
@@ -524,12 +545,19 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
{
SlruShared shared = ctl->shared;
int slotno;
+ int bankstart = (pageno & ctl->bank_mask) * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(bankstart);
/* Try to find the page while holding only shared lock */
- LWLockAcquire(shared->ControlLock, LW_SHARED);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_SHARED);
- /* See if page is already in a buffer */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in one
+ * bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY &&
@@ -546,8 +574,8 @@ SimpleLruReadPage_ReadOnly(SlruCtl ctl, int64 pageno, TransactionId xid)
}
/* No luck, so switch to normal exclusive lock and do regular read */
- LWLockRelease(shared->ControlLock);
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
return SimpleLruReadPage(ctl, pageno, true, xid);
}
@@ -569,6 +597,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
SlruShared shared = ctl->shared;
int64 pageno = shared->page_number[slotno];
bool ok;
+ int banklockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
/* If a write is in progress, wait for it to finish */
while (shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS &&
@@ -597,7 +626,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
LWLockAcquire(&shared->buffer_locks[slotno].lock, LW_EXCLUSIVE);
/* Release control lock while doing I/O */
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
/* Do the write */
ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
@@ -612,7 +641,7 @@ SlruInternalWritePage(SlruCtl ctl, int slotno, SlruWriteAll fdata)
}
/* Re-acquire control lock and update page state */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, LW_EXCLUSIVE);
Assert(shared->page_number[slotno] == pageno &&
shared->page_status[slotno] == SLRU_PAGE_WRITE_IN_PROGRESS);
@@ -1056,9 +1085,16 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
int bestinvalidslot = 0; /* keep compiler quiet */
int best_invalid_delta = -1;
int64 best_invalid_page_number = 0; /* keep compiler quiet */
+ int bankno = pageno & ctl->bank_mask;
+ int bankstart = bankno * SLRU_BANK_SIZE;
+ int bankend = bankstart + SLRU_BANK_SIZE;
- /* See if page already has a buffer assigned */
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ /*
+ * See if the page is already in a buffer pool. The buffer pool is
+ * divided into banks of buffers and each pageno may reside only in
+ * one bank so limit the search within the bank.
+ */
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
if (shared->page_number[slotno] == pageno &&
shared->page_status[slotno] != SLRU_PAGE_EMPTY)
@@ -1092,8 +1128,8 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
* That gets us back on the path to having good data when there are
* multiple pages with the same lru_count.
*/
- cur_count = (shared->cur_lru_count)++;
- for (slotno = 0; slotno < shared->num_slots; slotno++)
+ cur_count = (shared->bank_cur_lru_count[bankno])++;
+ for (slotno = bankstart; slotno < bankend; slotno++)
{
int this_delta;
int64 this_page_number;
@@ -1114,7 +1150,10 @@ SlruSelectLRUPage(SlruCtl ctl, int64 pageno)
this_delta = 0;
}
this_page_number = shared->page_number[slotno];
- if (this_page_number == shared->latest_page_number)
+
+ pg_read_barrier();
+ if (this_page_number ==
+ pg_atomic_read_u64(&shared->latest_page_number))
continue;
if (shared->page_status[slotno] == SLRU_PAGE_VALID)
{
@@ -1188,6 +1227,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
int slotno;
int64 pageno = 0;
int i;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
bool ok;
/* update the stats counter of flushes */
@@ -1198,10 +1238,23 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
*/
fdata.num_files = 0;
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
SlruInternalWritePage(ctl, slotno, &fdata);
/*
@@ -1215,7 +1268,7 @@ SimpleLruWriteAll(SlruCtl ctl, bool allow_redirtied)
!shared->page_dirty[slotno]));
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/*
* Now close any files that were open
@@ -1255,6 +1308,7 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
{
SlruShared shared = ctl->shared;
int slotno;
+ int prevlockno;
/* update the stats counter of truncates */
pgstat_count_slru_truncate(shared->slru_stats_idx);
@@ -1265,25 +1319,39 @@ SimpleLruTruncate(SlruCtl ctl, int64 cutoffPage)
* or just after a checkpoint, any dirty pages should have been flushed
* already ... we're just being extra careful here.)
*/
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
-
restart:
/*
- * While we are holding the lock, make an important safety check: the
- * current endpoint page must not be eligible for removal.
+ * An important safety check: the current endpoint page must not be
+ * eligible for removal.
*/
- if (ctl->PagePrecedes(shared->latest_page_number, cutoffPage))
+ pg_read_barrier();
+ if (ctl->PagePrecedes(pg_atomic_read_u64(&shared->latest_page_number),
+ cutoffPage))
{
- LWLockRelease(shared->ControlLock);
ereport(LOG,
(errmsg("could not truncate directory \"%s\": apparent wraparound",
ctl->Dir)));
return;
}
+ prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
if (!ctl->PagePrecedes(shared->page_number[slotno], cutoffPage))
@@ -1313,10 +1381,12 @@ restart:
SlruInternalWritePage(ctl, slotno, NULL);
else
SimpleLruWaitIO(ctl, slotno);
+
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
goto restart;
}
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
/* Now we can remove the old segment(s) */
(void) SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, &cutoffPage);
@@ -1357,15 +1427,29 @@ SlruDeleteSegment(SlruCtl ctl, int64 segno)
SlruShared shared = ctl->shared;
int slotno;
bool did_write;
+ int prevlockno = SLRU_SLOTNO_GET_BANKLOCKNO(0);
/* Clean out any possibly existing references to the segment. */
- LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+ LWLockAcquire(&shared->bank_locks[prevlockno].lock, LW_EXCLUSIVE);
restart:
did_write = false;
for (slotno = 0; slotno < shared->num_slots; slotno++)
{
- int pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
+ int pagesegno;
+ int curlockno = SLRU_SLOTNO_GET_BANKLOCKNO(slotno);
+ /*
+ * If the current bank lock is not same as the previous bank lock then
+ * release the previous lock and acquire the new lock.
+ */
+ if (curlockno != prevlockno)
+ {
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
+ LWLockAcquire(&shared->bank_locks[curlockno].lock, LW_EXCLUSIVE);
+ prevlockno = curlockno;
+ }
+
+ pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
continue;
@@ -1399,7 +1483,7 @@ restart:
SlruInternalDeleteSegment(ctl, segno);
- LWLockRelease(shared->ControlLock);
+ LWLockRelease(&shared->bank_locks[prevlockno].lock);
}
/*
@@ -1666,3 +1750,84 @@ SlruSyncFileTag(SlruCtl ctl, const FileTag *ftag, char *path)
errno = save_errno;
return result;
}
+
+/*
+ * Function to mark a buffer slot "most recently used".
+ *
+ * The reason for the if-test is that there are often many consecutive
+ * accesses to the same page (particularly the latest page). By suppressing
+ * useless increments of bank_cur_lru_count, we reduce the probability that old
+ * pages' counts will "wrap around" and make them appear recently used.
+ *
+ * We allow this code to be executed concurrently by multiple processes within
+ * SimpleLruReadPage_ReadOnly(). As long as int reads and writes are atomic,
+ * this should not cause any completely-bogus values to enter the computation.
+ * However, it is possible for either bank_cur_lru_count or individual
+ * page_lru_count entries to be "reset" to lower values than they should have,
+ * in case a process is delayed while it executes this function. With care in
+ * SlruSelectLRUPage(), this does little harm, and in any case the absolute
+ * worst possible consequence is a nonoptimal choice of page to evict. The
+ * gain from allowing concurrent reads of SLRU pages seems worth it.
+ */
+static inline void
+SlruRecentlyUsed(SlruShared shared, int slotno)
+{
+ int bankno = slotno / SLRU_BANK_SIZE;
+ int new_lru_count = shared->bank_cur_lru_count[bankno];
+
+ if (new_lru_count != shared->page_lru_count[slotno])
+ {
+ shared->bank_cur_lru_count[bankno] = ++new_lru_count;
+ shared->page_lru_count[slotno] = new_lru_count;
+ }
+}
+
+/*
+ * Helper function for GUC check_hook to check whether slru buffers are in
+ * multiples of SLRU_BANK_SIZE.
+ */
+bool
+check_slru_buffers(const char *name, int *newval)
+{
+ /* Valid values are multiples of SLRU_BANK_SIZE */
+ if (*newval % SLRU_BANK_SIZE == 0)
+ return true;
+
+ GUC_check_errdetail("\"%s\" must be a multiple of %d", name,
+ SLRU_BANK_SIZE);
+ return false;
+}
+
+/*
+ * Function to acquire all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockAcquire(&shared->bank_locks[banklockno].lock, mode);
+}
+
+/*
+ * Function to release all bank's lock of the given SlruCtl
+ */
+void
+SimpleLruReleaseAllBankLock(SlruCtl ctl)
+{
+ SlruShared shared = ctl->shared;
+ int banklockno;
+ int nbanklocks;
+
+ /* Compute number of bank locks. */
+ nbanklocks = Min(shared->num_slots / SLRU_BANK_SIZE, SLRU_MAX_BANKLOCKS);
+
+ for (banklockno = 0; banklockno < nbanklocks; banklockno++)
+ LWLockRelease(&shared->bank_locks[banklockno].lock);
+}
diff --git a/src/backend/access/transam/subtrans.c b/src/backend/access/transam/subtrans.c
index b2ed82ac56..cee850c9f8 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -31,7 +31,9 @@
#include "access/slru.h"
#include "access/subtrans.h"
#include "access/transam.h"
+#include "miscadmin.h"
#include "pg_trace.h"
+#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
@@ -85,12 +87,14 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
int64 pageno = TransactionIdToPage(xid);
int entryno = TransactionIdToEntry(xid);
int slotno;
+ LWLock *lock;
TransactionId *ptr;
Assert(TransactionIdIsValid(parent));
Assert(TransactionIdFollows(xid, parent));
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(SubTransCtl, pageno, true, xid);
ptr = (TransactionId *) SubTransCtl->shared->page_buffer[slotno];
@@ -108,7 +112,7 @@ SubTransSetParent(TransactionId xid, TransactionId parent)
SubTransCtl->shared->page_dirty[slotno] = true;
}
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -138,7 +142,7 @@ SubTransGetParent(TransactionId xid)
parent = *ptr;
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SubTransCtl, pageno));
return parent;
}
@@ -186,6 +190,22 @@ SubTransGetTopmostTransaction(TransactionId xid)
return previousXid;
}
+/*
+ * Number of shared SUBTRANS buffers.
+ *
+ * If asked to autotune, we use 2MB for every 1GB of shared buffers, up to 8MB,
+ * but always at least 16 buffers. Otherwise just cap the configured amount to
+ * be between 16 and the maximum allowed.
+ */
+static int
+SUBTRANSShmemBuffers(void)
+{
+ /* auto-tune based on shared buffers */
+ if (subtransaction_buffers == 0)
+ return Min(1024, Max(16, NBuffers / 512));
+
+ return Min(Max(16, subtransaction_buffers), SLRU_MAX_ALLOWED_BUFFERS);
+}
/*
* Initialization of shared memory for SUBTRANS
@@ -193,20 +213,42 @@ SubTransGetTopmostTransaction(TransactionId xid)
Size
SUBTRANSShmemSize(void)
{
- return SimpleLruShmemSize(NUM_SUBTRANS_BUFFERS, 0);
+ return SimpleLruShmemSize(SUBTRANSShmemBuffers(), 0);
}
void
SUBTRANSShmemInit(void)
{
+ /* If auto-tuning is requested, now is the time to do it */
+ if (subtransaction_buffers == 0)
+ {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%d", SUBTRANSShmemBuffers());
+ SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_DYNAMIC_DEFAULT);
+ if (subtransaction_buffers == 0) /* failed to apply it? */
+ SetConfigOption("subtransaction_buffers", buf, PGC_POSTMASTER,
+ PGC_S_OVERRIDE);
+ }
+ Assert(subtransaction_buffers != 0);
+
SubTransCtl->PagePrecedes = SubTransPagePrecedes;
- SimpleLruInit(SubTransCtl, "Subtrans", NUM_SUBTRANS_BUFFERS, 0,
- SubtransSLRULock, "pg_subtrans",
- LWTRANCHE_SUBTRANS_BUFFER, SYNC_HANDLER_NONE,
- false);
+ SimpleLruInit(SubTransCtl, "Subtrans", SUBTRANSShmemBuffers(), 0,
+ "pg_subtrans", LWTRANCHE_SUBTRANS_BUFFER,
+ LWTRANCHE_SUBTRANS_SLRU, SYNC_HANDLER_NONE, false);
SlruPagePrecedesUnitTests(SubTransCtl, SUBTRANS_XACTS_PER_PAGE);
}
+/*
+ * GUC check_hook for subtransaction_buffers
+ */
+bool
+check_subtrans_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("subtransaction_buffers", newval);
+}
+
/*
* This func must be called ONCE on system install. It creates
* the initial SUBTRANS segment. (The SUBTRANS directory is assumed to
@@ -221,8 +263,9 @@ void
BootStrapSUBTRANS(void)
{
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(SubTransCtl, 0);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Create and zero the first page of the subtrans log */
slotno = ZeroSUBTRANSPage(0);
@@ -231,7 +274,7 @@ BootStrapSUBTRANS(void)
SimpleLruWritePage(SubTransCtl, slotno);
Assert(!SubTransCtl->shared->page_dirty[slotno]);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -261,6 +304,8 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
FullTransactionId nextXid;
int64 startPage;
int64 endPage;
+ LWLock *prevlock;
+ LWLock *lock;
/*
* Since we don't expect pg_subtrans to be valid across crashes, we
@@ -268,23 +313,47 @@ StartupSUBTRANS(TransactionId oldestActiveXID)
* Whenever we advance into a new page, ExtendSUBTRANS will likewise zero
* the new page without regard to whatever was previously on disk.
*/
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
-
startPage = TransactionIdToPage(oldestActiveXID);
nextXid = TransamVariables->nextXid;
endPage = TransactionIdToPage(XidFromFullTransactionId(nextXid));
+ prevlock = SimpleLruGetBankLock(SubTransCtl, startPage);
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
while (startPage != endPage)
{
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release
+ * the lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
(void) ZeroSUBTRANSPage(startPage);
startPage++;
/* must account for wraparound */
if (startPage > TransactionIdToPage(MaxTransactionId))
startPage = 0;
}
- (void) ZeroSUBTRANSPage(startPage);
- LWLockRelease(SubtransSLRULock);
+ lock = SimpleLruGetBankLock(SubTransCtl, startPage);
+
+ /*
+ * Check if we need to acquire the lock on the new bank then release the
+ * lock on the old bank and acquire on the new bank.
+ */
+ if (prevlock != lock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ }
+ (void) ZeroSUBTRANSPage(startPage);
+ LWLockRelease(lock);
}
/*
@@ -318,6 +387,7 @@ void
ExtendSUBTRANS(TransactionId newestXact)
{
int64 pageno;
+ LWLock *lock;
/*
* No work except at first XID of a page. But beware: just after
@@ -329,12 +399,13 @@ ExtendSUBTRANS(TransactionId newestXact)
pageno = TransactionIdToPage(newestXact);
- LWLockAcquire(SubtransSLRULock, LW_EXCLUSIVE);
+ lock = SimpleLruGetBankLock(SubTransCtl, pageno);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/* Zero the page */
ZeroSUBTRANSPage(pageno);
- LWLockRelease(SubtransSLRULock);
+ LWLockRelease(lock);
}
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 8b24b22293..0c2ac60946 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -116,7 +116,7 @@
* frontend during startup.) The above design guarantees that notifies from
* other backends will never be missed by ignoring self-notifies.
*
- * The amount of shared memory used for notify management (NUM_NOTIFY_BUFFERS)
+ * The amount of shared memory used for notify management (notify_buffers)
* can be varied without affecting anything but performance. The maximum
* amount of notification data that can be queued at one time is determined
* by max_notify_queue_pages GUC.
@@ -148,6 +148,7 @@
#include "storage/sinval.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/snapmgr.h"
@@ -234,7 +235,7 @@ typedef struct QueuePosition
*
* Resist the temptation to make this really large. While that would save
* work in some places, it would add cost in others. In particular, this
- * should likely be less than NUM_NOTIFY_BUFFERS, to ensure that backends
+ * should likely be less than notify_buffers, to ensure that backends
* catch up before the pages they'll need to read fall out of SLRU cache.
*/
#define QUEUE_CLEANUP_DELAY 4
@@ -266,9 +267,10 @@ typedef struct QueueBackendStatus
* both NotifyQueueLock and NotifyQueueTailLock in EXCLUSIVE mode, backends
* can change the tail pointers.
*
- * NotifySLRULock is used as the control lock for the pg_notify SLRU buffers.
+ * SLRU buffer pool is divided in banks and bank wise SLRU lock is used as
+ * the control lock for the pg_notify SLRU buffers.
* In order to avoid deadlocks, whenever we need multiple locks, we first get
- * NotifyQueueTailLock, then NotifyQueueLock, and lastly NotifySLRULock.
+ * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock.
*
* Each backend uses the backend[] array entry with index equal to its
* BackendId (which can range from 1 to MaxBackends). We rely on this to make
@@ -492,7 +494,7 @@ AsyncShmemSize(void)
size = mul_size(MaxBackends + 1, sizeof(QueueBackendStatus));
size = add_size(size, offsetof(AsyncQueueControl, backend));
- size = add_size(size, SimpleLruShmemSize(NUM_NOTIFY_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(notify_buffers, 0));
return size;
}
@@ -541,8 +543,8 @@ AsyncShmemInit(void)
* names are used in order to avoid wraparound.
*/
NotifyCtl->PagePrecedes = asyncQueuePagePrecedes;
- SimpleLruInit(NotifyCtl, "Notify", NUM_NOTIFY_BUFFERS, 0,
- NotifySLRULock, "pg_notify", LWTRANCHE_NOTIFY_BUFFER,
+ SimpleLruInit(NotifyCtl, "Notify", notify_buffers, 0,
+ "pg_notify", LWTRANCHE_NOTIFY_BUFFER, LWTRANCHE_NOTIFY_SLRU,
SYNC_HANDLER_NONE, true);
if (!found)
@@ -1356,7 +1358,7 @@ asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe)
* Eventually we will return NULL indicating all is done.
*
* We are holding NotifyQueueLock already from the caller and grab
- * NotifySLRULock locally in this function.
+ * page specific SLRU bank lock locally in this function.
*/
static ListCell *
asyncQueueAddEntries(ListCell *nextNotify)
@@ -1366,9 +1368,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
int64 pageno;
int offset;
int slotno;
-
- /* We hold both NotifyQueueLock and NotifySLRULock during this operation */
- LWLockAcquire(NotifySLRULock, LW_EXCLUSIVE);
+ LWLock *prevlock;
/*
* We work with a local copy of QUEUE_HEAD, which we write back to shared
@@ -1389,6 +1389,11 @@ asyncQueueAddEntries(ListCell *nextNotify)
* page should be initialized already, so just fetch it.
*/
pageno = QUEUE_POS_PAGE(queue_head);
+ prevlock = SimpleLruGetBankLock(NotifyCtl, pageno);
+
+ /* We hold both NotifyQueueLock and SLRU bank lock during this operation */
+ LWLockAcquire(prevlock, LW_EXCLUSIVE);
+
if (QUEUE_POS_IS_ZERO(queue_head))
slotno = SimpleLruZeroPage(NotifyCtl, pageno);
else
@@ -1434,6 +1439,17 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Advance queue_head appropriately, and detect if page is full */
if (asyncQueueAdvance(&(queue_head), qe.length))
{
+ LWLock *lock;
+
+ pageno = QUEUE_POS_PAGE(queue_head);
+ lock = SimpleLruGetBankLock(NotifyCtl, pageno);
+ if (lock != prevlock)
+ {
+ LWLockRelease(prevlock);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
+ prevlock = lock;
+ }
+
/*
* Page is full, so we're done here, but first fill the next page
* with zeroes. The reason to do this is to ensure that slru.c's
@@ -1460,7 +1476,7 @@ asyncQueueAddEntries(ListCell *nextNotify)
/* Success, so update the global QUEUE_HEAD */
QUEUE_HEAD = queue_head;
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(prevlock);
return nextNotify;
}
@@ -1931,9 +1947,9 @@ asyncQueueReadAllNotifications(void)
/*
* We copy the data from SLRU into a local buffer, so as to avoid
- * holding the NotifySLRULock while we are examining the entries
- * and possibly transmitting them to our frontend. Copy only the
- * part of the page we will actually inspect.
+ * holding the SLRU lock while we are examining the entries and
+ * possibly transmitting them to our frontend. Copy only the part
+ * of the page we will actually inspect.
*/
slotno = SimpleLruReadPage_ReadOnly(NotifyCtl, curpage,
InvalidTransactionId);
@@ -1953,7 +1969,7 @@ asyncQueueReadAllNotifications(void)
NotifyCtl->shared->page_buffer[slotno] + curoffset,
copysize);
/* Release lock that we got from SimpleLruReadPage_ReadOnly() */
- LWLockRelease(NotifySLRULock);
+ LWLockRelease(SimpleLruGetBankLock(NotifyCtl, curpage));
/*
* Process messages up to the stop position, end of page, or an
@@ -1994,7 +2010,7 @@ asyncQueueReadAllNotifications(void)
*
* The current page must have been fetched into page_buffer from shared
* memory. (We could access the page right in shared memory, but that
- * would imply holding the NotifySLRULock throughout this routine.)
+ * would imply holding the SLRU bank lock throughout this routine.)
*
* We stop if we reach the "stop" position, or reach a notification from an
* uncommitted transaction, or reach the end of the page.
@@ -2147,7 +2163,7 @@ asyncQueueAdvanceTail(void)
if (asyncQueuePagePrecedes(oldtailpage, boundary))
{
/*
- * SimpleLruTruncate() will ask for NotifySLRULock but will also
+ * SimpleLruTruncate() will ask for SLRU bank locks but will also
* release the lock again.
*/
SimpleLruTruncate(NotifyCtl, newtailpage);
@@ -2378,3 +2394,12 @@ ClearPendingActionsAndNotifies(void)
pendingActions = NULL;
pendingNotifies = NULL;
}
+
+/*
+ * GUC check_hook for notify_buffers
+ */
+bool
+check_notify_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("notify_buffers", newval);
+}
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 98fa6035cc..accbe82a8c 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -163,6 +163,13 @@ static const char *const BuiltinTrancheNames[] = {
[LWTRANCHE_LAUNCHER_HASH] = "LogicalRepLauncherHash",
[LWTRANCHE_DSM_REGISTRY_DSA] = "DSMRegistryDSA",
[LWTRANCHE_DSM_REGISTRY_HASH] = "DSMRegistryHash",
+ [LWTRANCHE_COMMITTS_SLRU] = "CommitTSSLRU",
+ [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultixactOffsetSLRU",
+ [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultixactMemberSLRU",
+ [LWTRANCHE_NOTIFY_SLRU] = "NotifySLRU",
+ [LWTRANCHE_SERIAL_SLRU] = "SerialSLRU",
+ [LWTRANCHE_SUBTRANS_SLRU] = "SubtransSLRU",
+ [LWTRANCHE_XACT_SLRU] = "XactSLRU",
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index a0163b2187..e4aa3d91c6 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -16,11 +16,11 @@ WALBufMappingLock 7
WALWriteLock 8
ControlFileLock 9
# 10 was CheckpointLock
-XactSLRULock 11
-SubtransSLRULock 12
+# 11 was XactSLRULock
+# 12 was SubtransSLRULock
MultiXactGenLock 13
-MultiXactOffsetSLRULock 14
-MultiXactMemberSLRULock 15
+# 14 was MultiXactOffsetSLRULock
+# 15 was MultiXactMemberSLRULock
RelCacheInitLock 16
CheckpointerCommLock 17
TwoPhaseStateLock 18
@@ -31,19 +31,19 @@ AutovacuumLock 22
AutovacuumScheduleLock 23
SyncScanLock 24
RelationMappingLock 25
-NotifySLRULock 26
+#26 was NotifySLRULock
NotifyQueueLock 27
SerializableXactHashLock 28
SerializableFinishedListLock 29
SerializablePredicateListLock 30
-SerialSLRULock 31
+SerialControlLock 31
SyncRepLock 32
BackgroundWorkerLock 33
DynamicSharedMemoryControlLock 34
AutoFileLock 35
ReplicationSlotAllocationLock 36
ReplicationSlotControlLock 37
-CommitTsSLRULock 38
+#38 was CommitTsSLRULock
CommitTsLock 39
ReplicationOriginLock 40
MultiXactTruncationLock 41
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index ee5ea1175c..7fd1bca7f9 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -208,6 +208,7 @@
#include "storage/predicate_internals.h"
#include "storage/proc.h"
#include "storage/procarray.h"
+#include "utils/guc_hooks.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
@@ -808,9 +809,9 @@ SerialInit(void)
*/
SerialSlruCtl->PagePrecedes = SerialPagePrecedesLogically;
SimpleLruInit(SerialSlruCtl, "Serial",
- NUM_SERIAL_BUFFERS, 0, SerialSLRULock, "pg_serial",
- LWTRANCHE_SERIAL_BUFFER, SYNC_HANDLER_NONE,
- false);
+ serializable_buffers, 0, "pg_serial",
+ LWTRANCHE_SERIAL_BUFFER, LWTRANCHE_SERIAL_SLRU,
+ SYNC_HANDLER_NONE, false);
#ifdef USE_ASSERT_CHECKING
SerialPagePrecedesLogicallyUnitTests();
#endif
@@ -834,6 +835,15 @@ SerialInit(void)
}
}
+/*
+ * GUC check_hook for serializable_buffers
+ */
+bool
+check_serial_buffers(int *newval, void **extra, GucSource source)
+{
+ return check_slru_buffers("serializable_buffers", newval);
+}
+
/*
* Record a committed read write serializable xid and the minimum
* commitSeqNo of any transactions to which this xid had a rw-conflict out.
@@ -847,12 +857,14 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
int slotno;
int64 firstZeroPage;
bool isNewPage;
+ LWLock *lock;
Assert(TransactionIdIsValid(xid));
targetPage = SerialPage(xid);
+ lock = SimpleLruGetBankLock(SerialSlruCtl, targetPage);
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
/*
* If no serializable transactions are active, there shouldn't be anything
@@ -902,7 +914,7 @@ SerialAdd(TransactionId xid, SerCommitSeqNo minConflictCommitSeqNo)
SerialValue(slotno, xid) = minConflictCommitSeqNo;
SerialSlruCtl->shared->page_dirty[slotno] = true;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(lock);
}
/*
@@ -920,10 +932,10 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
Assert(TransactionIdIsValid(xid));
- LWLockAcquire(SerialSLRULock, LW_SHARED);
+ LWLockAcquire(SerialControlLock, LW_SHARED);
headXid = serialControl->headXid;
tailXid = serialControl->tailXid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
if (!TransactionIdIsValid(headXid))
return 0;
@@ -935,13 +947,13 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
return 0;
/*
- * The following function must be called without holding SerialSLRULock,
+ * The following function must be called without holding SLRU bank lock,
* but will return with that lock held, which must then be released.
*/
slotno = SimpleLruReadPage_ReadOnly(SerialSlruCtl,
SerialPage(xid), xid);
val = SerialValue(slotno, xid);
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SimpleLruGetBankLock(SerialSlruCtl, SerialPage(xid)));
return val;
}
@@ -954,7 +966,7 @@ SerialGetMinConflictCommitSeqNo(TransactionId xid)
static void
SerialSetActiveSerXmin(TransactionId xid)
{
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/*
* When no sxacts are active, nothing overlaps, set the xid values to
@@ -966,7 +978,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = InvalidTransactionId;
serialControl->headXid = InvalidTransactionId;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -984,7 +996,7 @@ SerialSetActiveSerXmin(TransactionId xid)
{
serialControl->tailXid = xid;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -993,7 +1005,7 @@ SerialSetActiveSerXmin(TransactionId xid)
serialControl->tailXid = xid;
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
}
/*
@@ -1007,12 +1019,12 @@ CheckPointPredicate(void)
{
int truncateCutoffPage;
- LWLockAcquire(SerialSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(SerialControlLock, LW_EXCLUSIVE);
/* Exit quickly if the SLRU is currently not in use. */
if (serialControl->headPage < 0)
{
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
return;
}
@@ -1072,7 +1084,7 @@ CheckPointPredicate(void)
serialControl->headPage = -1;
}
- LWLockRelease(SerialSLRULock);
+ LWLockRelease(SerialControlLock);
/* Truncate away pages that are no longer required */
SimpleLruTruncate(SerialSlruCtl, truncateCutoffPage);
@@ -1348,7 +1360,7 @@ PredicateLockShmemSize(void)
/* Shared memory structures for SLRU tracking of old committed xids. */
size = add_size(size, sizeof(SerialControlData));
- size = add_size(size, SimpleLruShmemSize(NUM_SERIAL_BUFFERS, 0));
+ size = add_size(size, SimpleLruShmemSize(serializable_buffers, 0));
return size;
}
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index a5df835dd4..f24950c0c1 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -292,11 +292,7 @@ SInvalWrite "Waiting to add a message to the shared catalog invalidation queue."
WALBufMapping "Waiting to replace a page in WAL buffers."
WALWrite "Waiting for WAL buffers to be written to disk."
ControlFile "Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
-XactSLRU "Waiting to access the transaction status SLRU cache."
-SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
MultiXactGen "Waiting to read or update shared multixact state."
-MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
-MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
RelCacheInit "Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
CheckpointerComm "Waiting to manage fsync requests."
TwoPhaseState "Waiting to read or update the state of prepared transactions."
@@ -307,19 +303,17 @@ Autovacuum "Waiting to read or update the current state of autovacuum workers."
AutovacuumSchedule "Waiting to ensure that a table selected for autovacuum still needs vacuuming."
SyncScan "Waiting to select the starting location of a synchronized table scan."
RelationMapping "Waiting to read or update a <filename>pg_filenode.map</filename> file (used to track the filenode assignments of certain system catalogs)."
-NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
NotifyQueue "Waiting to read or update <command>NOTIFY</command> messages."
SerializableXactHash "Waiting to read or update information about serializable transactions."
SerializableFinishedList "Waiting to access the list of finished serializable transactions."
SerializablePredicateList "Waiting to access the list of predicate locks held by serializable transactions."
-SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SerialControl "Waiting to access the serializable transaction conflict SLRU cache."
SyncRep "Waiting to read or update information about the state of synchronous replication."
BackgroundWorker "Waiting to read or update background worker state."
DynamicSharedMemoryControl "Waiting to read or update dynamic shared memory allocation information."
AutoFile "Waiting to update the <filename>postgresql.auto.conf</filename> file."
ReplicationSlotAllocation "Waiting to allocate or free a replication slot."
ReplicationSlotControl "Waiting to read or update replication slot state."
-CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
CommitTs "Waiting to read or update the last value set for a transaction commit timestamp."
ReplicationOrigin "Waiting to create, drop or use a replication origin."
MultiXactTruncation "Waiting to read or truncate multixact information."
@@ -371,6 +365,14 @@ LogicalRepLauncherDSA "Waiting to access logical replication launcher's dynamic
LogicalRepLauncherHash "Waiting to access logical replication launcher's shared hash table."
DSMRegistryDSA "Waiting to access dynamic shared memory registry's dynamic shared memory allocator."
DSMRegistryHash "Waiting to access dynamic shared memory registry's shared hash table."
+CommitTsSLRU "Waiting to access the commit timestamp SLRU cache."
+MultiXactOffsetSLRU "Waiting to access the multixact offset SLRU cache."
+MultiXactMemberSLRU "Waiting to access the multixact member SLRU cache."
+NotifySLRU "Waiting to access the <command>NOTIFY</command> message SLRU cache."
+SerialSLRU "Waiting to access the serializable transaction conflict SLRU cache."
+SubtransSLRU "Waiting to access the sub-transaction SLRU cache."
+XactSLRU "Waiting to access the transaction status SLRU cache."
+
#
# Wait Events - Lock
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 88b03e8fa3..7df342c70d 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -156,3 +156,12 @@ int64 VacuumPageDirty = 0;
int VacuumCostBalance = 0; /* working state for vacuum */
bool VacuumCostActive = false;
+
+/* configurable SLRU buffer sizes */
+int commit_timestamp_buffers = 0;
+int multixact_members_buffers = 32;
+int multixact_offsets_buffers = 16;
+int notify_buffers = 16;
+int serializable_buffers = 32;
+int subtransaction_buffers = 0;
+int transaction_buffers = 0;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 7fe58518d7..82d08647d0 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -28,6 +28,7 @@
#include "access/commit_ts.h"
#include "access/gin.h"
+#include "access/slru.h"
#include "access/toast_compression.h"
#include "access/twophase.h"
#include "access/xlog_internal.h"
@@ -2320,6 +2321,83 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"commit_timestamp_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the commit timestamp cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &commit_timestamp_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_commit_ts_buffers, NULL, NULL
+ },
+
+ {
+ {"multixact_members_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the MultiXact member cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_members_buffers,
+ 32, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ check_multixact_members_buffers, NULL, NULL
+ },
+
+ {
+ {"multixact_offsets_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the MultiXact offset cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_offsets_buffers,
+ 16, 8, SLRU_MAX_ALLOWED_BUFFERS,
+ check_multixact_offsets_buffers, NULL, NULL
+ },
+
+ {
+ {"notify_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the LISTEN/NOTIFY message cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ ¬ify_buffers,
+ 16, 8, SLRU_MAX_ALLOWED_BUFFERS,
+ check_notify_buffers, NULL, NULL
+ },
+
+ {
+ {"serializable_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the serializable transaction cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &serializable_buffers,
+ 32, 16, SLRU_MAX_ALLOWED_BUFFERS,
+ check_serial_buffers, NULL, NULL
+ },
+
+ {
+ {"subtransaction_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the sub-transaction cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &subtransaction_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_subtrans_buffers, NULL, NULL
+ },
+
+ {
+ {"transaction_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for the transaction status cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &transaction_buffers,
+ 0, 0, SLRU_MAX_ALLOWED_BUFFERS,
+ check_transaction_buffers, NULL, NULL
+ },
+
{
{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the maximum number of temporary buffers used by each session."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index da10b43dac..8b3a547a5e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -50,6 +50,15 @@
#external_pid_file = '' # write an extra PID file
# (change requires restart)
+# - SLRU Buffers (change requires restart) -
+
+#commit_timestamp_buffers = 0 # memory for pg_commit_ts (0 = auto)
+#multixact_offsets_buffers = 16 # memory for pg_multixact/offsets
+#multixact_members_buffers = 32 # memory for pg_multixact/members
+#notify_buffers = 16 # memory for pg_notify
+#serializable_buffers = 32 # memory for pg_serial
+#subtransaction_buffers = 0 # memory for pg_subtrans (0 = auto)
+#transaction_buffers = 0 # memory for pg_xact (0 = auto)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index becc365cb0..8e62917e49 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -40,7 +40,6 @@ extern void TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
TransactionId *subxids, XidStatus status, XLogRecPtr lsn);
extern XidStatus TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn);
-extern Size CLOGShmemBuffers(void);
extern Size CLOGShmemSize(void);
extern void CLOGShmemInit(void);
extern void BootStrapCLOG(void);
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index 9c6f3a35ca..82d3aa8627 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -27,7 +27,6 @@ extern bool TransactionIdGetCommitTsData(TransactionId xid,
extern TransactionId GetLatestCommitTsData(TimestampTz *ts,
RepOriginId *nodeid);
-extern Size CommitTsShmemBuffers(void);
extern Size CommitTsShmemSize(void);
extern void CommitTsShmemInit(void);
extern void BootStrapCommitTs(void);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 233f67dbcc..7ffd256c74 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -29,10 +29,6 @@
#define MaxMultiXactOffset ((MultiXactOffset) 0xFFFFFFFF)
-/* Number of SLRU buffers to use for multixact */
-#define NUM_MULTIXACTOFFSET_BUFFERS 8
-#define NUM_MULTIXACTMEMBER_BUFFERS 16
-
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index b05f6bc71d..3160980d04 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -17,6 +17,27 @@
#include "storage/lwlock.h"
#include "storage/sync.h"
+/*
+ * SLRU bank size for slotno hash banks. Limit the bank size to 16 because we
+ * perform sequential search within a bank (while looking for a target page or
+ * while doing victim buffer search) and if we keep this size big then it may
+ * affect the performance.
+ */
+#define SLRU_BANK_SIZE 16
+
+/*
+ * Number of bank locks to protect the in memory buffer slot access within a
+ * SLRU bank. If the number of banks are <= SLRU_MAX_BANKLOCKS then there will
+ * be one lock per bank; otherwise each lock will protect multiple banks depends
+ * upon the number of banks.
+ */
+#define SLRU_MAX_BANKLOCKS 128
+
+/*
+ * To avoid overflowing internal arithmetic and the size_t data type, the
+ * number of buffers must not exceed this number.
+ */
+#define SLRU_MAX_ALLOWED_BUFFERS ((1024 * 1024 * 1024) / BLCKSZ)
/*
* Define SLRU segment size. A page is the same BLCKSZ as is used everywhere
@@ -52,8 +73,6 @@ typedef enum
*/
typedef struct SlruSharedData
{
- LWLock *ControlLock;
-
/* Number of buffers managed by this SLRU structure */
int num_slots;
@@ -66,8 +85,30 @@ typedef struct SlruSharedData
bool *page_dirty;
int64 *page_number;
int *page_lru_count;
+
+ /* The buffer_locks protects the I/O on each buffer slots */
LWLockPadded *buffer_locks;
+ /* Locks to protect the in memory buffer slot access in SLRU bank. */
+ LWLockPadded *bank_locks;
+
+ /*----------
+ * A bank-wise LRU counter is maintained because we do a victim buffer
+ * search within a bank. Furthermore, manipulating an individual bank
+ * counter avoids frequent cache invalidation since we update it every time
+ * we access the page.
+ *
+ * We mark a page "most recently used" by setting
+ * page_lru_count[slotno] = ++bank_cur_lru_count[bankno];
+ * The oldest page in the bank is therefore the one with the highest value
+ * of
+ * bank_cur_lru_count[bankno] - page_lru_count[slotno]
+ * The counts will eventually wrap around, but this calculation still
+ * works as long as no page's age exceeds INT_MAX counts.
+ *----------
+ */
+ int *bank_cur_lru_count;
+
/*
* Optional array of WAL flush LSNs associated with entries in the SLRU
* pages. If not zero/NULL, we must flush WAL before writing pages (true
@@ -79,23 +120,12 @@ typedef struct SlruSharedData
XLogRecPtr *group_lsn;
int lsn_groups_per_page;
- /*----------
- * We mark a page "most recently used" by setting
- * page_lru_count[slotno] = ++cur_lru_count;
- * The oldest page is therefore the one with the highest value of
- * cur_lru_count - page_lru_count[slotno]
- * The counts will eventually wrap around, but this calculation still
- * works as long as no page's age exceeds INT_MAX counts.
- *----------
- */
- int cur_lru_count;
-
/*
* latest_page_number is the page number of the current end of the log;
* this is not critical data, since we use it only to avoid swapping out
* the latest page.
*/
- int64 latest_page_number;
+ pg_atomic_uint64 latest_page_number;
/* SLRU's index for statistics purposes (might not be unique) */
int slru_stats_idx;
@@ -142,15 +172,35 @@ typedef struct SlruCtlData
* it's always the same, it doesn't need to be in shared memory.
*/
char Dir[64];
+
+ /*
+ * Mask for slotno banks, considering 1GB SLRU buffer pool size and the
+ * SLRU_BANK_SIZE bits16 should be sufficient for the bank mask.
+ */
+ bits16 bank_mask;
} SlruCtlData;
typedef SlruCtlData *SlruCtl;
+/*
+ * Get the SLRU bank lock for given SlruCtl and the pageno.
+ *
+ * This lock needs to be acquired to access the slru buffer slots in the
+ * respective bank.
+ */
+static inline LWLock *
+SimpleLruGetBankLock(SlruCtl ctl, int64 pageno)
+{
+ int banklockno;
+
+ banklockno = (pageno & ctl->bank_mask) % SLRU_MAX_BANKLOCKS;
+ return &(ctl->shared->bank_locks[banklockno].lock);
+}
extern Size SimpleLruShmemSize(int nslots, int nlsns);
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
- LWLock *ctllock, const char *subdir, int tranche_id,
- SyncRequestHandler sync_handler,
+ const char *subdir, int buffer_tranche_id,
+ int bank_tranche_id, SyncRequestHandler sync_handler,
bool long_segment_names);
extern int SimpleLruZeroPage(SlruCtl ctl, int64 pageno);
extern int SimpleLruReadPage(SlruCtl ctl, int64 pageno, bool write_ok,
@@ -179,5 +229,8 @@ extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,
int64 segpage, void *data);
extern bool SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int64 segpage,
void *data);
+extern bool check_slru_buffers(const char *name, int *newval);
+extern void SimpleLruAcquireAllBankLock(SlruCtl ctl, LWLockMode mode);
+extern void SimpleLruReleaseAllBankLock(SlruCtl ctl);
#endif /* SLRU_H */
diff --git a/src/include/access/subtrans.h b/src/include/access/subtrans.h
index b0d2ad57e5..e2213cf3fd 100644
--- a/src/include/access/subtrans.h
+++ b/src/include/access/subtrans.h
@@ -11,9 +11,6 @@
#ifndef SUBTRANS_H
#define SUBTRANS_H
-/* Number of SLRU buffers to use for subtrans */
-#define NUM_SUBTRANS_BUFFERS 32
-
extern void SubTransSetParent(TransactionId xid, TransactionId parent);
extern TransactionId SubTransGetParent(TransactionId xid);
extern TransactionId SubTransGetTopmostTransaction(TransactionId xid);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 80b8583421..78daa25fa0 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -15,11 +15,6 @@
#include <signal.h>
-/*
- * The number of SLRU page buffers we use for the notification queue.
- */
-#define NUM_NOTIFY_BUFFERS 8
-
extern PGDLLIMPORT bool Trace_notify;
extern PGDLLIMPORT int max_notify_queue_pages;
extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0b01c1f093..39b8ed9425 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -178,6 +178,14 @@ extern PGDLLIMPORT int MaxConnections;
extern PGDLLIMPORT int max_worker_processes;
extern PGDLLIMPORT int max_parallel_workers;
+extern PGDLLIMPORT int commit_timestamp_buffers;
+extern PGDLLIMPORT int multixact_members_buffers;
+extern PGDLLIMPORT int multixact_offsets_buffers;
+extern PGDLLIMPORT int notify_buffers;
+extern PGDLLIMPORT int serializable_buffers;
+extern PGDLLIMPORT int subtransaction_buffers;
+extern PGDLLIMPORT int transaction_buffers;
+
extern PGDLLIMPORT int MyProcPid;
extern PGDLLIMPORT pg_time_t MyStartTime;
extern PGDLLIMPORT TimestampTz MyStartTimestamp;
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 50a65e046d..10bea8c595 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -209,6 +209,13 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_LAUNCHER_HASH,
LWTRANCHE_DSM_REGISTRY_DSA,
LWTRANCHE_DSM_REGISTRY_HASH,
+ LWTRANCHE_COMMITTS_SLRU,
+ LWTRANCHE_MULTIXACTMEMBER_SLRU,
+ LWTRANCHE_MULTIXACTOFFSET_SLRU,
+ LWTRANCHE_NOTIFY_SLRU,
+ LWTRANCHE_SERIAL_SLRU,
+ LWTRANCHE_SUBTRANS_SLRU,
+ LWTRANCHE_XACT_SLRU,
LWTRANCHE_FIRST_USER_DEFINED,
} BuiltinTrancheIds;
diff --git a/src/include/storage/predicate.h b/src/include/storage/predicate.h
index a7edd38fa9..14ee9b94a2 100644
--- a/src/include/storage/predicate.h
+++ b/src/include/storage/predicate.h
@@ -26,10 +26,6 @@ extern PGDLLIMPORT int max_predicate_locks_per_xact;
extern PGDLLIMPORT int max_predicate_locks_per_relation;
extern PGDLLIMPORT int max_predicate_locks_per_page;
-
-/* Number of SLRU buffers to use for Serial SLRU */
-#define NUM_SERIAL_BUFFERS 16
-
/*
* A handle used for sharing SERIALIZABLEXACT objects between the participants
* in a parallel query.
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 5300c44f3b..44b0cbf9a1 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -46,6 +46,8 @@ extern bool check_client_connection_check_interval(int *newval, void **extra,
extern bool check_client_encoding(char **newval, void **extra, GucSource source);
extern void assign_client_encoding(const char *newval, void *extra);
extern bool check_cluster_name(char **newval, void **extra, GucSource source);
+extern bool check_commit_ts_buffers(int *newval, void **extra,
+ GucSource source);
extern const char *show_data_directory_mode(void);
extern bool check_datestyle(char **newval, void **extra, GucSource source);
extern void assign_datestyle(const char *newval, void *extra);
@@ -91,6 +93,11 @@ extern bool check_max_worker_processes(int *newval, void **extra,
GucSource source);
extern bool check_max_stack_depth(int *newval, void **extra, GucSource source);
extern void assign_max_stack_depth(int newval, void *extra);
+extern bool check_multixact_members_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_multixact_offsets_buffers(int *newval, void **extra,
+ GucSource source);
+extern bool check_notify_buffers(int *newval, void **extra, GucSource source);
extern bool check_primary_slot_name(char **newval, void **extra,
GucSource source);
extern bool check_random_seed(double *newval, void **extra, GucSource source);
@@ -122,12 +129,15 @@ extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
extern bool check_search_path(char **newval, void **extra, GucSource source);
extern void assign_search_path(const char *newval, void *extra);
+extern bool check_serial_buffers(int *newval, void **extra, GucSource source);
extern bool check_session_authorization(char **newval, void **extra, GucSource source);
extern void assign_session_authorization(const char *newval, void *extra);
extern void assign_session_replication_role(int newval, void *extra);
extern void assign_stats_fetch_consistency(int newval, void *extra);
extern bool check_ssl(bool *newval, void **extra, GucSource source);
extern bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
+extern bool check_subtrans_buffers(int *newval, void **extra,
+ GucSource source);
extern bool check_synchronous_standby_names(char **newval, void **extra,
GucSource source);
extern void assign_synchronous_standby_names(const char *newval, void *extra);
@@ -152,6 +162,7 @@ extern const char *show_timezone(void);
extern bool check_timezone_abbreviations(char **newval, void **extra,
GucSource source);
extern void assign_timezone_abbreviations(const char *newval, void *extra);
+extern bool check_transaction_buffers(int *newval, void **extra, GucSource source);
extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source);
extern bool check_transaction_isolation(int *newval, void **extra, GucSource source);
extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
diff --git a/src/test/modules/test_slru/test_slru.c b/src/test/modules/test_slru/test_slru.c
index 4b31f331ca..068a21f125 100644
--- a/src/test/modules/test_slru/test_slru.c
+++ b/src/test/modules/test_slru/test_slru.c
@@ -40,10 +40,6 @@ PG_FUNCTION_INFO_V1(test_slru_delete_all);
/* Number of SLRU page slots */
#define NUM_TEST_BUFFERS 16
-/* SLRU control lock */
-LWLock TestSLRULock;
-#define TestSLRULock (&TestSLRULock)
-
static SlruCtlData TestSlruCtlData;
#define TestSlruCtl (&TestSlruCtlData)
@@ -63,9 +59,9 @@ test_slru_page_write(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = text_to_cstring(PG_GETARG_TEXT_PP(1));
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
-
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruZeroPage(TestSlruCtl, pageno);
/* these should match */
@@ -80,7 +76,7 @@ test_slru_page_write(PG_FUNCTION_ARGS)
BLCKSZ - 1);
SimpleLruWritePage(TestSlruCtl, slotno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_VOID();
}
@@ -99,13 +95,14 @@ test_slru_page_read(PG_FUNCTION_ARGS)
bool write_ok = PG_GETARG_BOOL(1);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(TestSlruCtl, pageno,
write_ok, InvalidTransactionId);
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -116,14 +113,15 @@ test_slru_page_readonly(PG_FUNCTION_ARGS)
int64 pageno = PG_GETARG_INT64(0);
char *data = NULL;
int slotno;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
pageno,
InvalidTransactionId);
- Assert(LWLockHeldByMe(TestSLRULock));
+ Assert(LWLockHeldByMe(lock));
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
@@ -133,10 +131,11 @@ test_slru_page_exists(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool found;
+ LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
- LWLockAcquire(TestSLRULock, LW_EXCLUSIVE);
+ LWLockAcquire(lock, LW_EXCLUSIVE);
found = SimpleLruDoesPhysicalPageExist(TestSlruCtl, pageno);
- LWLockRelease(TestSLRULock);
+ LWLockRelease(lock);
PG_RETURN_BOOL(found);
}
@@ -221,6 +220,7 @@ test_slru_shmem_startup(void)
const bool long_segment_names = true;
const char slru_dir_name[] = "pg_test_slru";
int test_tranche_id;
+ int test_buffer_tranche_id;
if (prev_shmem_startup_hook)
prev_shmem_startup_hook();
@@ -234,12 +234,15 @@ test_slru_shmem_startup(void)
/* initialize the SLRU facility */
test_tranche_id = LWLockNewTrancheId();
LWLockRegisterTranche(test_tranche_id, "test_slru_tranche");
- LWLockInitialize(TestSLRULock, test_tranche_id);
+
+ test_buffer_tranche_id = LWLockNewTrancheId();
+ LWLockRegisterTranche(test_tranche_id, "test_buffer_tranche");
TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
SimpleLruInit(TestSlruCtl, "TestSLRU",
- NUM_TEST_BUFFERS, 0, TestSLRULock, slru_dir_name,
- test_tranche_id, SYNC_HANDLER_NONE, long_segment_names);
+ NUM_TEST_BUFFERS, 0, slru_dir_name,
+ test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
+ long_segment_names);
}
void
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-25 17:28 Robert Haas <[email protected]>
parent: Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 79+ messages in thread
From: Robert Haas @ 2024-01-25 17:28 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Dilip Kumar <[email protected]>; tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Jan 25, 2024 at 11:22 AM Alvaro Herrera <[email protected]> wrote:
> Still with these auto-tuning GUCs, I noticed that the auto-tuning code
> would continue to grow the buffer sizes with shared_buffers to
> arbitrarily large values. I added an arbitrary maximum of 1024 (8 MB),
> which is much higher than the current value of 128; but if you have
> (say) 30 GB of shared_buffers (not uncommon these days), do you really
> need 30MB of pg_clog cache? It seems mostly unnecessary ... and you can
> still set it manually that way if you need it. So, largely I just
> rewrote those small functions completely.
Yeah, I think that if we're going to scale with shared_buffers, it
should be capped.
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-26 17:38 Alvaro Herrera <[email protected]>
parent: Dilip Kumar <[email protected]>
1 sibling, 2 replies; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-26 17:38 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
I've continued to review this and decided that I don't like the mess
this patch proposes in order to support pg_commit_ts's deletion of all
files. (Yes, I know that I was the one that proposed this idea. It's
still a bad one). I'd like to change that code by removing the limit
that we can only have 128 bank locks in a SLRU. To recap, the reason we
did this is that commit_ts sometimes wants to delete all files while
running (DeactivateCommitTs), and for this it needs to acquire all bank
locks. Since going above the MAX_SIMUL_LWLOCKS limit is disallowed, we
added the SLRU limit making multiple banks share lwlocks.
I propose two alternative solutions:
1. The easiest is to have DeactivateCommitTs continue to hold
CommitTsLock until the end, including while SlruScanDirectory does its
thing. This sounds terrible, but considering that this code only runs
when the module is being deactivated, I don't think it's really all that
bad in practice. I mean, if you deactivate the commit_ts module and
then try to read commit timestamp data, you deserve to wait for a few
seconds just as a punishment for your stupidity. AFAICT the cases where
anything is done in the module mostly check without locking that
commitTsActive is set, so we're not slowing down any critical
operations. Also, if you don't like to be delayed for a couple of
seconds, just don't deactivate the module.
2. If we want some refinement, the other idea is to change
SlruScanDirCbDeleteAll (the callback that SlruScanDirectory uses in this
case) so that it acquires the bank lock appropriate for all the slots
used by the file that's going to be deleted. This is OK because in the
default compilation each file only has 32 segments, so that requires
only 32 lwlocks held at once while the file is being deleted. I think
we don't need to bother with this, but it's an option if we see the
above as unworkable for whatever reason.
The only other user of SlruScanDirCbDeleteAll is async.c (the LISTEN/
NOTIFY code), and what that does is delete all the files at server
start, where nothing is running concurrently anyway. So whatever we do
for commit_ts, it won't affect async.c.
So, if we do away with the SLRU_MAX_BANKLOCKS idea, then we're assured
one LWLock per bank (instead of sharing some lwlocks to multiple banks),
and that makes the code simpler to reason about.
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"In fact, the basic problem with Perl 5's subroutines is that they're not
crufty enough, so the cruft leaks out into user-defined code instead, by
the Conservation of Cruft Principle." (Larry Wall, Apocalypse 6)
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-26 18:21 Andrey Borodin <[email protected]>
parent: Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 79+ messages in thread
From: Andrey Borodin @ 2024-01-26 18:21 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Dilip Kumar <[email protected]>; tender wang <[email protected]>; pgsql-hackers mailing list <[email protected]>
> On 26 Jan 2024, at 22:38, Alvaro Herrera <[email protected]> wrote:
>
> This is OK because in the
> default compilation each file only has 32 segments, so that requires
> only 32 lwlocks held at once while the file is being deleted.
Do we account somehow that different subsystems do not accumulate MAX_SIMUL_LWLOCKS together?
E.g. GiST during split can combine 75 locks, and somehow commit_ts will be deactivated by this backend at the same moment and add 32 locks more :)
I understand that this sounds fantastic, these subsystems do not interfere. But this is fantastic only until something like that actually happens.
If possible, I'd prefer one lock at a time, any maybe sometimes two-three with some guarantees that this is safe.
So, from my POV first solution that you proposed seems much better to me.
Thanks for working on this!
Best regard, Andrey Borodin.
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-29 05:56 Dilip Kumar <[email protected]>
parent: Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 79+ messages in thread
From: Dilip Kumar @ 2024-01-29 05:56 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Jan 25, 2024 at 10:03 PM Alvaro Herrera <[email protected]> wrote:
>
> On 2024-Jan-25, Alvaro Herrera wrote:
>
> > Here's a touched-up version of this patch.
>
> > diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
> > index 98fa6035cc..4a5e05d5e4 100644
> > --- a/src/backend/storage/lmgr/lwlock.c
> > +++ b/src/backend/storage/lmgr/lwlock.c
> > @@ -163,6 +163,13 @@ static const char *const BuiltinTrancheNames[] = {
> > [LWTRANCHE_LAUNCHER_HASH] = "LogicalRepLauncherHash",
> > [LWTRANCHE_DSM_REGISTRY_DSA] = "DSMRegistryDSA",
> > [LWTRANCHE_DSM_REGISTRY_HASH] = "DSMRegistryHash",
> > + [LWTRANCHE_COMMITTS_SLRU] = "CommitTSSLRU",
> > + [LWTRANCHE_MULTIXACTOFFSET_SLRU] = "MultixactOffsetSLRU",
> > + [LWTRANCHE_MULTIXACTMEMBER_SLRU] = "MultixactMemberSLRU",
> > + [LWTRANCHE_NOTIFY_SLRU] = "NotifySLRU",
> > + [LWTRANCHE_SERIAL_SLRU] = "SerialSLRU"
> > + [LWTRANCHE_SUBTRANS_SLRU] = "SubtransSLRU",
> > + [LWTRANCHE_XACT_SLRU] = "XactSLRU",
> > };
>
> Eeek. Last minute changes ... Fixed here.
Thank you for working on this. There is one thing that I feel is
problematic. We have kept the allowed values for these GUCs to be in
multiple of SLRU_BANK_SIZE i.e. 16 and that's the reason the min
values were changed to 16 but in this refactoring patch for some of
the buffers you have changed that to 8 so I think that's not good.
+ {
+ {"multixact_offsets_buffers", PGC_POSTMASTER, RESOURCES_MEM,
+ gettext_noop("Sets the size of the dedicated buffer pool used for
the MultiXact offset cache."),
+ NULL,
+ GUC_UNIT_BLOCKS
+ },
+ &multixact_offsets_buffers,
+ 16, 8, SLRU_MAX_ALLOWED_BUFFERS,
+ check_multixact_offsets_buffers, NULL, NULL
+ },
Other than this patch looks good to me.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-29 06:01 Dilip Kumar <[email protected]>
parent: Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 79+ messages in thread
From: Dilip Kumar @ 2024-01-29 06:01 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Jan 26, 2024 at 11:08 PM Alvaro Herrera <[email protected]> wrote:
>
> I've continued to review this and decided that I don't like the mess
> this patch proposes in order to support pg_commit_ts's deletion of all
> files. (Yes, I know that I was the one that proposed this idea. It's
> still a bad one). I'd like to change that code by removing the limit
> that we can only have 128 bank locks in a SLRU. To recap, the reason we
> did this is that commit_ts sometimes wants to delete all files while
> running (DeactivateCommitTs), and for this it needs to acquire all bank
> locks. Since going above the MAX_SIMUL_LWLOCKS limit is disallowed, we
> added the SLRU limit making multiple banks share lwlocks.
>
> I propose two alternative solutions:
>
> 1. The easiest is to have DeactivateCommitTs continue to hold
> CommitTsLock until the end, including while SlruScanDirectory does its
> thing. This sounds terrible, but considering that this code only runs
> when the module is being deactivated, I don't think it's really all that
> bad in practice. I mean, if you deactivate the commit_ts module and
> then try to read commit timestamp data, you deserve to wait for a few
> seconds just as a punishment for your stupidity.
I think this idea looks reasonable. I agree that if we are trying to
read commit_ts after deactivating it then it's fine to make it wait.
--
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 79+ messages in thread
* Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock
@ 2024-01-31 23:31 Alvaro Herrera <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 0 replies; 79+ messages in thread
From: Alvaro Herrera @ 2024-01-31 23:31 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: tender wang <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2024-Jan-29, Dilip Kumar wrote:
> Thank you for working on this. There is one thing that I feel is
> problematic. We have kept the allowed values for these GUCs to be in
> multiple of SLRU_BANK_SIZE i.e. 16 and that's the reason the min
> values were changed to 16 but in this refactoring patch for some of
> the buffers you have changed that to 8 so I think that's not good.
Oh, absolutely, you're right. Restored the minimum to 16.
So, here's the patchset as two pieces. 0001 converts
SlruSharedData->latest_page_number to use atomics. I don't see any
reason to mix this in with the rest of the patch, and though it likely
won't have any performance advantage by itself (since the lock
acquisition is pretty much the same), it seems better to get it in ahead
of the rest -- I think that simplifies matters for the second patch,
which is large enough.
So, 0002 introduces the rest of the feature. I removed use of banklocks
in a different amount as banks, and I made commit_ts use a longer
lwlock acquisition at truncation time, rather than forcing all-lwlock
acquisition.
The more I look at 0002, the more I notice that some comments need badly
updated, so please don't read too much into it yet. But I wanted to
post it anyway for archives and cfbot purposes.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
^ permalink raw reply [nested|flat] 79+ messages in thread
end of thread, other threads:[~2024-01-31 23:31 UTC | newest]
Thread overview: 79+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v3] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v2] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2021-01-21 16:06 [PATCH v1] Allow HEAP_XMAX_LOCK_ONLY and HEAP_KEYS_UPDATED combination. Julien Rouhaud <[email protected]>
2024-01-08 11:25 Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-08 13:18 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Dilip Kumar <[email protected]>
2024-01-08 15:42 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-10 13:20 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Dilip Kumar <[email protected]>
2024-01-23 05:35 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Dilip Kumar <[email protected]>
2024-01-25 16:22 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-25 16:33 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-29 05:56 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Dilip Kumar <[email protected]>
2024-01-31 23:31 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-25 17:28 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Robert Haas <[email protected]>
2024-01-26 17:38 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Alvaro Herrera <[email protected]>
2024-01-26 18:21 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Andrey Borodin <[email protected]>
2024-01-29 06:01 ` Re: SLRU optimization - configurable buffer pool and partitioning the SLRU lock Dilip Kumar <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox