public inbox for [email protected]help / color / mirror / Atom feed
[PATCH] hint squash commit 46+ messages / 3 participants [nested] [flat]
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 03:25 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 03:25 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +++++- src/backend/access/transam/xlog.c | 4 ++++ src/backend/access/transam/xloginsert.c | 26 ++++++++++++++++++++++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 18 ++++++++++++++++ src/include/access/xloginsert.h | 2 ++ src/include/catalog/pg_control.h | 1 + 7 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..eebca0fc16 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,32 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + * + * Callable while holding just share lock on the buffer content. + * + * The LSN of the inserted wal record is returned if we had to write, + * InvalidXLogRecPtr otherwise. + * + * It is possible that multiple concurrent backends could attempt to write WAL + * records. In that case, multiple copies of the same block would be recorded + * in separate WAL records by different backends, though that is still OK from + * a correctness perspective. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..fd59f9fc1d 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3865,6 +3865,24 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) { dirtied = true; /* Means "will be dirtied by this action" */ + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page. + */ + if (XLogRecPtrIsInvalid(lsn) /* XXX && file_encryption_method != 0 */) + lsn = XLogLSNForHint(); + /* * Set the page LSN if we wrote a backup block. We aren't supposed * to set this when only holding a share lock but as long as we diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --ikeVEW9yuYc//A+q-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* [PATCH] hint squash commit @ 2021-02-04 18:43 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Bruce Momjian @ 2021-02-04 18:43 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xlogdesc.c | 6 +- src/backend/access/transam/xlog.c | 4 ++ src/backend/access/transam/xloginsert.c | 16 +++++ src/backend/replication/logical/decode.c | 1 + src/backend/storage/buffer/bufmgr.c | 89 ++++++++++++++++-------- src/include/access/xloginsert.h | 2 + src/include/catalog/pg_control.h | 1 + 7 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/backend/access/rmgrdesc/xlogdesc.c b/src/backend/access/rmgrdesc/xlogdesc.c index 92cc7ea073..16a967bb71 100644 --- a/src/backend/access/rmgrdesc/xlogdesc.c +++ b/src/backend/access/rmgrdesc/xlogdesc.c @@ -80,7 +80,8 @@ xlog_desc(StringInfo buf, XLogReaderState *record) appendStringInfoString(buf, xlrec->rp_name); } - else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT) + else if (info == XLOG_FPI || info == XLOG_FPI_FOR_HINT || + info == XLOG_HINT_LSN) { /* no further information to print */ } @@ -185,6 +186,9 @@ xlog_identify(uint8 info) case XLOG_FPI_FOR_HINT: id = "FPI_FOR_HINT"; break; + case XLOG_HINT_LSN: + id = "HINT_LSN"; + break; } return id; diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f03bd473e2..f67316ee07 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -10260,6 +10260,10 @@ xlog_redo(XLogReaderState *record) UnlockReleaseBuffer(buffer); } } + else if (info == XLOG_HINT_LSN) + { + /* nothing to do here */ + } else if (info == XLOG_BACKUP_END) { XLogRecPtr startpoint; diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index 7052dc245e..7635a3d44d 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -980,6 +980,22 @@ XLogSaveBufferForHint(Buffer buffer, bool buffer_std) return recptr; } +/* + * On the first hint bit change during a checkpoint, XLogSaveBufferForHint() + * writes a full page image to the WAL and returns a new LSN which can be + * assigned to the page. Cluster file encryption needs a new LSN for + * every hint bit page write because the LSN is used in the nonce, and + * the nonce must be unique. Therefore, this routine just advances the LSN + * so the page can be assigned a new LSN. + */ +XLogRecPtr +XLogLSNForHint(void) +{ + XLogBeginInsert(); + + return XLogInsert(RM_XLOG_ID, XLOG_HINT_LSN); +} + /* * Write a WAL record containing a full image of a page. Caller is responsible * for writing the page to disk after calling this routine. diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index afa1df00d0..2ada89c6b1 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -223,6 +223,7 @@ DecodeXLogOp(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_FPW_CHANGE: case XLOG_FPI_FOR_HINT: case XLOG_FPI: + case XLOG_HINT_LSN: break; default: elog(ERROR, "unexpected RM_XLOG_ID record type: %u", info); diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 561c212092..b9f3f76798 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -3810,13 +3810,14 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * If we need to protect hint bit updates from torn writes, WAL-log a * full page image of the page. This full page image is only necessary * if the hint bit update is the first change to the page since the - * last checkpoint. + * last checkpoint. If cluster file encryption is enabled, we also + * need to generate new page LSNs for non-first-page writes during + * a checkpoint. * * We don't check full_page_writes here because that logic is included * when we call XLogInsert() since the value changes dynamically. */ - if (XLogHintBitIsNeeded() && - (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT)) + if (XLogHintBitIsNeeded()) { /* * If we must not write WAL, due to a relfilenode-specific @@ -3826,35 +3827,67 @@ MarkBufferDirtyHint(Buffer buffer, bool buffer_std) * * See src/backend/storage/page/README for longer discussion. */ - if (RecoveryInProgress() || - RelFileNodeSkippingWAL(bufHdr->tag.rnode)) + if (((pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + /* XXX || file_encryption_method != 0 */) && + (RecoveryInProgress() || + RelFileNodeSkippingWAL(bufHdr->tag.rnode))) return; + if (pg_atomic_read_u32(&bufHdr->state) & BM_PERMANENT) + { + /* + * If the block is already dirty because we either made a change + * or set a hint already, then we don't need to write a full page + * image. Note that aggressive cleaning of blocks dirtied by hint + * bit setting would increase the call rate. Bulk setting of hint + * bits would reduce the call rate... + * + * We must issue the WAL record before we mark the buffer dirty. + * Otherwise we might write the page before we write the WAL. That + * causes a race condition, since a checkpoint might occur between + * writing the WAL record and marking the buffer dirty. We solve + * that with a kluge, but one that is already in use during + * transaction commit to prevent race conditions. Basically, we + * simply prevent the checkpoint WAL record from being written + * until we have marked the buffer dirty. We don't start the + * checkpoint flush until we have marked dirty, so our checkpoint + * must flush the change to disk successfully or the checkpoint + * never gets written, so crash recovery will fix. + * + * It's possible we may enter here without an xid, so it is + * essential that CreateCheckpoint waits for virtual transactions + * rather than full transactionids. + */ + MyProc->delayChkpt = delayChkpt = true; + lsn = XLogSaveBufferForHint(buffer, buffer_std); + } + /* - * If the block is already dirty because we either made a change - * or set a hint already, then we don't need to write a full page - * image. Note that aggressive cleaning of blocks dirtied by hint - * bit setting would increase the call rate. Bulk setting of hint - * bits would reduce the call rate... - * - * We must issue the WAL record before we mark the buffer dirty. - * Otherwise we might write the page before we write the WAL. That - * causes a race condition, since a checkpoint might occur between - * writing the WAL record and marking the buffer dirty. We solve - * that with a kluge, but one that is already in use during - * transaction commit to prevent race conditions. Basically, we - * simply prevent the checkpoint WAL record from being written - * until we have marked the buffer dirty. We don't start the - * checkpoint flush until we have marked dirty, so our checkpoint - * must flush the change to disk successfully or the checkpoint - * never gets written, so crash recovery will fix. - * - * It's possible we may enter here without an xid, so it is - * essential that CreateCheckpoint waits for virtual transactions - * rather than full transactionids. + * For cluster file encryption, we generate a new page LSN + * for hint-bit non-permanent and non-first page writes. */ - MyProc->delayChkpt = delayChkpt = true; - lsn = XLogSaveBufferForHint(buffer, buffer_std); + if (/* XXX file_encryption_method != 0 && */ + XLogRecPtrIsInvalid(lsn) && + /* XXX can we check BM_DIRTY without a page lock? */ + !(pg_atomic_read_u32(&bufHdr->state) & BM_DIRTY)) + { + /* + * If the buffer is clean, and lsn is valid, it must be the + * first hint bit change of the checkpoint (the old page lsn + * is earlier than the RedoRecPtr). If the page is clean and + * the lsn is invalid, the page must have been already written + * during this checkpoint, and this is a new hint bit change. + * Such page changes usually don't create new page lsns, but we + * need one for cluster file encryption because the lsn is used as + * part of the nonce, and we don't want to reencrypt the page + * with the hint bit changes using the same nonce as the previous + * writes during this checkpoint. (It would expose the hint bit + * change locations.) Therefore we create a simple WAL record + * to advance the lsn, which can can then be assigned to the + * page below. + */ + lsn = XLogLSNForHint(); + } } buf_state = LockBufHdr(bufHdr); diff --git a/src/include/access/xloginsert.h b/src/include/access/xloginsert.h index f1d8c39edf..a066f65229 100644 --- a/src/include/access/xloginsert.h +++ b/src/include/access/xloginsert.h @@ -61,6 +61,8 @@ extern void log_newpage_range(Relation rel, ForkNumber forkNum, BlockNumber startblk, BlockNumber endblk, bool page_std); extern XLogRecPtr XLogSaveBufferForHint(Buffer buffer, bool buffer_std); +extern XLogRecPtr XLogLSNForHint(void); + extern void InitXLogInsert(void); #endif /* XLOGINSERT_H */ diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index e3f48158ce..36ac7dfbb8 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -76,6 +76,7 @@ typedef struct CheckPoint #define XLOG_END_OF_RECOVERY 0x90 #define XLOG_FPI_FOR_HINT 0xA0 #define XLOG_FPI 0xB0 +#define XLOG_HINT_LSN 0xC0 /* -- 2.20.1 --T4sUOijqQbZv57TR-- ^ permalink raw reply [nested|flat] 46+ messages in thread
* Re: [PATCH] pg_stat_toast v6 @ 2022-01-03 21:23 Alvaro Herrera <[email protected]> 2022-01-04 11:29 ` Re: [PATCH] pg_stat_toast v6 Gunnar "Nick" Bluth <[email protected]> 0 siblings, 1 reply; 46+ messages in thread From: Alvaro Herrera @ 2022-01-03 21:23 UTC (permalink / raw) To: Gunnar "Nick" Bluth <[email protected]>; +Cc: [email protected]; [email protected] <[email protected]>; Justin Pryzby <[email protected]> Overall I think this is a good feature to have; assessing the need for compression is important for tuning, so +1 for the goal of the patch. I didn't look into the patch carefully, but here are some minor comments: On 2022-Jan-03, Gunnar "Nick" Bluth wrote: > @@ -229,7 +230,9 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) > Datum *value = &ttc->ttc_values[attribute]; > Datum new_value; > ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; > + instr_time start_time; > > + INSTR_TIME_SET_CURRENT(start_time); > new_value = toast_compress_datum(*value, attr->tai_compression); > > if (DatumGetPointer(new_value) != NULL) Don't INSTR_TIME_SET_CURRENT unconditionally; in some systems it's an expensive syscall. Find a way to only do it if the feature is enabled. This also suggests that perhaps it'd be a good idea to allow this to be enabled for specific tables only, rather than system-wide. (Maybe in order for stats to be collected, the user should have to both set the GUC option *and* set a per-table option? Not sure.) > @@ -82,10 +82,12 @@ typedef enum StatMsgType > PGSTAT_MTYPE_DEADLOCK, > PGSTAT_MTYPE_CHECKSUMFAILURE, > PGSTAT_MTYPE_REPLSLOT, > + PGSTAT_MTYPE_CONNECTION, I think this new enum value doesn't belong in this patch. > +/* ---------- > + * PgStat_ToastEntry Per-TOAST-column info in a MsgFuncstat > + * ---------- Not in "a MsgFuncstat", right? > +-- function to wait for counters to advance > +create function wait_for_stats() returns void as $$ I don't think we want a separate copy of wait_for_stats; see commit fe60b67250a3 and the discussion leading to it. Maybe you'll want to move the test to stats.sql. I'm not sure what to say about relying on LZ4; maybe you'll want to leave that part out, and just verify in an LZ4-enabled build that some 'l' entry exists. BTW, don't we have any decent way to turn that 'l' into a more reasonable, descriptive string? Also, perhaps make the view-defining query turn an empty compression method into whatever the default is. Or even better, stats collection should store the real compression method used rather than empty string, to avoid confusing things when some stats are collected, then the default is changed, then some more stats are collected. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Para tener más hay que desear menos" ^ permalink raw reply [nested|flat] 46+ messages in thread
* Re: [PATCH] pg_stat_toast v6 2022-01-03 21:23 Re: [PATCH] pg_stat_toast v6 Alvaro Herrera <[email protected]> @ 2022-01-04 11:29 ` Gunnar "Nick" Bluth <[email protected]> 0 siblings, 0 replies; 46+ messages in thread From: Gunnar "Nick" Bluth @ 2022-01-04 11:29 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: [email protected]; [email protected] <[email protected]>; Justin Pryzby <[email protected]> Am 03.01.22 um 22:23 schrieb Alvaro Herrera: > Overall I think this is a good feature to have; assessing the need for > compression is important for tuning, so +1 for the goal of the patch. Much appreciated! > I didn't look into the patch carefully, but here are some minor > comments: > > On 2022-Jan-03, Gunnar "Nick" Bluth wrote: > >> @@ -229,7 +230,9 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) >> Datum *value = &ttc->ttc_values[attribute]; >> Datum new_value; >> ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; >> + instr_time start_time; >> >> + INSTR_TIME_SET_CURRENT(start_time); >> new_value = toast_compress_datum(*value, attr->tai_compression); >> >> if (DatumGetPointer(new_value) != NULL) > > Don't INSTR_TIME_SET_CURRENT unconditionally; in some systems it's an > expensive syscall. Find a way to only do it if the feature is enabled. Yeah, I was worried about that (and asking if it would be required) already. Adding the check was easier than I expected, though I'm absolutely clueless if I did it right! #include "pgstat.h" extern PGDLLIMPORT bool pgstat_track_toast; > This also suggests that perhaps it'd be a good idea to allow this to be > enabled for specific tables only, rather than system-wide. (Maybe in > order for stats to be collected, the user should have to both set the > GUC option *and* set a per-table option? Not sure.) That would of course be nice, but I seriously doubt the required additional logic would be justified. The patch currently tampers with as few internal structures as possible, and for good reason... ;-) >> @@ -82,10 +82,12 @@ typedef enum StatMsgType >> PGSTAT_MTYPE_DEADLOCK, >> PGSTAT_MTYPE_CHECKSUMFAILURE, >> PGSTAT_MTYPE_REPLSLOT, >> + PGSTAT_MTYPE_CONNECTION, > > I think this new enum value doesn't belong in this patch. Yeah, did I mention I'm struggling with rebasing? ;-| >> +/* ---------- >> + * PgStat_ToastEntry Per-TOAST-column info in a MsgFuncstat >> + * ---------- > > Not in "a MsgFuncstat", right? Obviously... fixed! > >> +-- function to wait for counters to advance >> +create function wait_for_stats() returns void as $$ > > I don't think we want a separate copy of wait_for_stats; see commit > fe60b67250a3 and the discussion leading to it. Maybe you'll want to > move the test to stats.sql. I'm not sure what to say about relying on Did so. > LZ4; maybe you'll want to leave that part out, and just verify in an > LZ4-enabled build that some 'l' entry exists. BTW, don't we have any > decent way to turn that 'l' into a more reasonable, descriptive string? > Also, perhaps make the view-defining query turn an empty compression > method into whatever the default is. I'm not even sure that having it in there is useful at all. It's simply JOINed in from pg_attribute. Which is where I'd see that "make it look nicer" change happening, tbth. ;-) > Or even better, stats collection > should store the real compression method used rather than empty string, > to avoid confusing things when some stats are collected, then the > default is changed, then some more stats are collected. I was thinking about that already, but came to the conclusion that it a) would blow up the size of these statistics by quite a bit and b) would be quite tricky to display in a useful way. I mean, the use case of track_toast is pretty limited anyway; you'll probably turn this feature on with a specific column in mind, of which you'll probably know which compression method is used at the time. Thanks for the feedback! v7 attached. -- Gunnar "Nick" Bluth Eimermacherweg 106 D-48159 Münster Mobil +49 172 8853339 Email: [email protected] __________________________________________________________________________ "Ceterum censeo SystemD esse delendam" - Cato Attachments: [text/x-patch] pg_stat_toast_v7.patch (46.5K, ../../[email protected]/2-pg_stat_toast_v7.patch) download | inline diff: From f07213d68c646ba64757e551e3587aab0ff221df Mon Sep 17 00:00:00 2001 From: "Gunnar \"Nick\" Bluth" <[email protected]> Date: Tue, 4 Jan 2022 12:08:32 +0100 Subject: [PATCH v7] pg_stat_toast v7 --- doc/src/sgml/config.sgml | 26 ++ doc/src/sgml/monitoring.sgml | 163 ++++++++++ doc/src/sgml/storage.sgml | 12 +- src/backend/access/table/toast_helper.c | 44 ++- src/backend/catalog/system_views.sql | 20 ++ src/backend/postmaster/pgstat.c | 305 +++++++++++++++++- src/backend/utils/adt/pgstatfuncs.c | 72 +++++ src/backend/utils/misc/guc.c | 9 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/catalog/pg_proc.dat | 25 ++ src/include/pgstat.h | 107 ++++++ src/test/regress/expected/rules.out | 17 + src/test/regress/expected/stats.out | 62 ++++ src/test/regress/sql/stats.sql | 28 ++ 14 files changed, 882 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index afbb6c35e3..b2617cc941 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -7668,6 +7668,32 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv; </listitem> </varlistentry> + <varlistentry id="guc-track-toast" xreflabel="track_toast"> + <term><varname>track_toast</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>track_toast</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Enables tracking of <link linkend="storage-toast">TOAST</link> activities. + Compressions and externalizations are tracked. + The default is <literal>off</literal>. + Only superusers can change this setting. + </para> + + <note> + <para> + Be aware that this feature, depending on the amount of TOASTable columns in + your databases, may significantly increase the size of the statistics files + and the workload of the statistics collector. It is recommended to only + temporarily activate this to assess the right compression and storage method + for a column. + </para> + </note> + </listitem> + </varlistentry> + <varlistentry id="guc-stats-temp-directory" xreflabel="stats_temp_directory"> <term><varname>stats_temp_directory</varname> (<type>string</type>) <indexterm> diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 62f2a3332b..d2ed4e8e7d 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -610,6 +610,17 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser yet included in <structname>pg_stat_user_functions</structname>).</entry> </row> + <row> + <entry><structname>pg_stat_toast</structname><indexterm><primary>pg_stat_toast</primary></indexterm></entry> + <entry> + One row for each column that has ever been TOASTed (compressed and/or externalized). + Showing the number of externalizations, compression attempts / successes, compressed and + uncompressed sizes etc. + <link linkend="monitoring-pg-stat-toast-view"> + <structname>pg_stat_toast</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_slru</structname><indexterm><primary>pg_stat_slru</primary></indexterm></entry> <entry>One row per SLRU, showing statistics of operations. See @@ -4969,6 +4980,158 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i </sect2> + <sect2 id="monitoring-pg-stat-toast-view"> + <title><structname>pg_stat_toast</structname></title> + + <indexterm> + <primary>pg_stat_toast</primary> + </indexterm> + + <para> + The <structname>pg_stat_toast</structname> view will contain + one row for each column of variable size that has been TOASTed since + the last statistics reset. The <xref linkend="guc-track-toast"/> parameter + controls whether TOAST activities are tracked or not. + </para> + + <table id="pg-stat-toast-view" xreflabel="pg_stat_toast"> + <title><structname>pg_stat_toast</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + Column Type + </para> + <para> + Description + </para></entry> + </row> + </thead> + + <tbody> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>schemaname</structfield> <type>name</type> + </para> + <para> + Name of the schema the relation is in + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>reloid</structfield> <type>oid</type> + </para> + <para> + OID of the relation + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>attnum</structfield> <type>int</type> + </para> + <para> + Attribute (column) number in the relation + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>relname</structfield> <type>name</type> + </para> + <para> + Name of the relation + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>attname</structfield> <type>name</type> + </para> + <para> + Name of the attribute (column) + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>storagemethod</structfield> <type>char</type> + </para> + <para> + Storage method of the attribute + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>externalized</structfield> <type>bigint</type> + </para> + <para> + Number of times this attribute was externalized (pushed to TOAST relation) + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>compressmethod</structfield> <type>char</type> + </para> + <para> + Current compression method of the attribute (empty means default) + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>compressattempts</structfield> <type>bigint</type> + </para> + <para> + Number of times compression of this attribute was attempted + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>compresssuccesses</structfield> <type>bigint</type> + </para> + <para> + Number of times compression of this attribute was successful + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>compressedsize</structfield> <type>bigint</type> + </para> + <para> + Total size of all compressed datums + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>originalsize</structfield> <type>bigint</type> + </para> + <para> + Total size of all compressed datums before compression + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>total_time</structfield> <type>double precision</type> + </para> + <para> + Total time spent TOASTing this attribute, in microseconds. + </para></entry> + </row> + + </tbody> + </tgroup> + </table> + + </sect2> + <sect2 id="monitoring-pg-stat-slru-view"> <title><structname>pg_stat_slru</structname></title> diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml index 7136bbe7a3..2a47922573 100644 --- a/doc/src/sgml/storage.sgml +++ b/doc/src/sgml/storage.sgml @@ -517,6 +517,15 @@ pages). There was no run time difference compared to an un-<acronym>TOAST</acron comparison table, in which all the HTML pages were cut down to 7 kB to fit. </para> +<para> +When you enable <xref linkend="guc-track-toast"/>, the system view +<link linkend="monitoring-pg-stat-toast-view"><structname>pg_stat_toast</structname> +</link> provides details on the number and effect of compression attempts, +number of externalizations and some more useful information that enables you +to decide if a different storage method and/or compression method would suite a +column better. +</para> + </sect2> <sect2 id="storage-toast-inmemory"> @@ -1068,7 +1077,8 @@ data. Empty in ordinary tables.</entry> <type>struct varlena</type>, which includes the total length of the stored value and some flag bits. Depending on the flags, the data can be either inline or in a <acronym>TOAST</acronym> table; - it might be compressed, too (see <xref linkend="storage-toast"/>). + it might be compressed, too (see <xref linkend="storage-toast"/> and + <xref linkend="monitoring-pg-stat-toast-view"/>). </para> </sect2> diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c index 013236b73d..d0a8ca4a1c 100644 --- a/src/backend/access/table/toast_helper.c +++ b/src/backend/access/table/toast_helper.c @@ -19,7 +19,9 @@ #include "access/toast_helper.h" #include "access/toast_internals.h" #include "catalog/pg_type_d.h" +#include "pgstat.h" +extern PGDLLIMPORT bool pgstat_track_toast; /* * Prepare to TOAST a tuple. @@ -226,10 +228,15 @@ toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) { - Datum *value = &ttc->ttc_values[attribute]; - Datum new_value; + Datum *value = &ttc->ttc_values[attribute]; + Datum new_value; ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; + instr_time start_time; + if (pgstat_track_toast) + { + INSTR_TIME_SET_CURRENT(start_time); + } new_value = toast_compress_datum(*value, attr->tai_compression); if (DatumGetPointer(new_value) != NULL) @@ -239,6 +246,15 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) pfree(DatumGetPointer(*value)); *value = new_value; attr->tai_colflags |= TOASTCOL_NEEDS_FREE; + if (pgstat_track_toast) + { + pgstat_report_toast_activity(ttc->ttc_rel->rd_rel->oid, attribute, + false, + true, + attr->tai_size, + VARSIZE(DatumGetPointer(*value)), + start_time); + } attr->tai_size = VARSIZE(DatumGetPointer(*value)); ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } @@ -246,6 +262,15 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) { /* incompressible, ignore on subsequent compression passes */ attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; + if (pgstat_track_toast) + { + pgstat_report_toast_activity(ttc->ttc_rel->rd_rel->oid, attribute, + false, + true, + 0, + 0, + start_time); + } } } @@ -258,6 +283,12 @@ toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) Datum *value = &ttc->ttc_values[attribute]; Datum old_value = *value; ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; + instr_time start_time; + + if (pgstat_track_toast) + { + INSTR_TIME_SET_CURRENT(start_time); + } attr->tai_colflags |= TOASTCOL_IGNORE; *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, @@ -266,6 +297,15 @@ toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) pfree(DatumGetPointer(old_value)); attr->tai_colflags |= TOASTCOL_NEEDS_FREE; ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); + if (pgstat_track_toast) + { + pgstat_report_toast_activity(ttc->ttc_rel->rd_rel->oid, attribute, + true, + false, + 0, + 0, + start_time); +} } /* diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 61b515cdb8..3de3025488 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1039,6 +1039,26 @@ CREATE VIEW pg_stat_user_functions AS WHERE P.prolang != 12 -- fast check to eliminate built-in functions AND pg_stat_get_function_calls(P.oid) IS NOT NULL; + +CREATE OR REPLACE VIEW pg_stat_toast AS + SELECT + n.nspname AS schemaname, + a.attrelid AS reloid, + a.attnum AS attnum, + c.relname AS relname, + a.attname AS attname, + attstorage AS storagemethod, + pg_stat_get_toast_externalizations(a.attrelid,a.attnum) AS externalized, + attcompression AS compressmethod, + pg_stat_get_toast_compressions(a.attrelid,a.attnum) AS compressattempts, + pg_stat_get_toast_compressionsuccesses(a.attrelid,a.attnum) AS compresssuccesses, + pg_stat_get_toast_compressedsizesum(a.attrelid,a.attnum) AS compressedsize, + pg_stat_get_toast_originalsizesum(a.attrelid,a.attnum) AS originalsize, + pg_stat_get_toast_total_time(a.attrelid,a.attnum) AS total_time + FROM pg_attribute a + JOIN pg_class c ON c.oid = a.attrelid + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE pg_stat_get_toast_externalizations(a.attrelid,a.attnum) IS NOT NULL; CREATE VIEW pg_stat_xact_user_functions AS SELECT P.oid AS funcid, diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index 7264d2c727..68aa770d21 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -106,6 +106,7 @@ #define PGSTAT_DB_HASH_SIZE 16 #define PGSTAT_TAB_HASH_SIZE 512 #define PGSTAT_FUNCTION_HASH_SIZE 512 +#define PGSTAT_TOAST_HASH_SIZE 64 #define PGSTAT_SUBWORKER_HASH_SIZE 32 #define PGSTAT_REPLSLOT_HASH_SIZE 32 @@ -116,6 +117,7 @@ */ bool pgstat_track_counts = false; int pgstat_track_functions = TRACK_FUNC_OFF; +bool pgstat_track_toast = false; /* ---------- * Built from GUC parameter @@ -228,6 +230,19 @@ static HTAB *pgStatFunctions = NULL; */ static bool have_function_stats = false; +/* + * Backends store per-toast-column info that's waiting to be sent to the collector + * in this hash table (indexed by column's PgStat_BackendAttrIdentifier). + */ +static HTAB *pgStatToastActions = NULL; + + +/* + * Indicates if backend has some toast stats that it hasn't yet + * sent to the collector. + */ +static bool have_toast_stats = false; + /* * Tuple insertion/deletion counts for an open transaction can't be propagated * into PgStat_TableStatus counters until we know if it is going to commit @@ -328,7 +343,7 @@ static PgStat_StatSubWorkerEntry *pgstat_get_subworker_entry(PgStat_StatDBEntry static void pgstat_write_statsfiles(bool permanent, bool allDbs); static void pgstat_write_db_statsfile(PgStat_StatDBEntry *dbentry, bool permanent); static HTAB *pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep); -static void pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, +static void pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, HTAB *toasthash, HTAB *subworkerhash, bool permanent); static void backend_read_statsfile(void); @@ -340,6 +355,7 @@ static void pgstat_reset_replslot(PgStat_StatReplSlotEntry *slotstats, Timestamp static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg, TimestampTz now); static void pgstat_send_funcstats(void); +static void pgstat_send_toaststats(void); static void pgstat_send_slru(void); static void pgstat_send_subscription_purge(PgStat_MsgSubscriptionPurge *msg); static HTAB *pgstat_collect_oids(Oid catalogid, AttrNumber anum_oid); @@ -373,6 +389,7 @@ static void pgstat_recv_wal(PgStat_MsgWal *msg, int len); static void pgstat_recv_slru(PgStat_MsgSLRU *msg, int len); static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len); static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len); +static void pgstat_recv_toaststat(PgStat_MsgToaststat *msg, int len); static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len); static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len); static void pgstat_recv_checksum_failure(PgStat_MsgChecksumFailure *msg, int len); @@ -891,7 +908,7 @@ pgstat_report_stat(bool disconnect) pgStatXactCommit == 0 && pgStatXactRollback == 0 && pgWalUsage.wal_records == prevWalUsage.wal_records && WalStats.m_wal_write == 0 && WalStats.m_wal_sync == 0 && - !have_function_stats && !disconnect) + !have_function_stats && !have_toast_stats && !disconnect) return; /* @@ -983,6 +1000,9 @@ pgstat_report_stat(bool disconnect) /* Now, send function statistics */ pgstat_send_funcstats(); + /* Now, send TOAST statistics */ + pgstat_send_toaststats(); + /* Send WAL statistics */ pgstat_send_wal(true); @@ -1116,6 +1136,63 @@ pgstat_send_funcstats(void) have_function_stats = false; } +/* + * Subroutine for pgstat_report_stat: populate and send a toast stat message + */ +static void +pgstat_send_toaststats(void) +{ + static const PgStat_ToastCounts all_zeroes; + + PgStat_MsgToaststat msg; + PgStat_BackendToastEntry *entry; + HASH_SEQ_STATUS tstat; + + if (pgStatToastActions == NULL) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TOASTSTAT); + msg.m_databaseid = MyDatabaseId; + msg.m_nentries = 0; + + hash_seq_init(&tstat, pgStatToastActions); + while ((entry = (PgStat_BackendToastEntry *) hash_seq_search(&tstat)) != NULL) + { + PgStat_ToastEntry *m_ent; + + /* Skip it if no counts accumulated since last time */ + if (memcmp(&entry->t_counts, &all_zeroes, + sizeof(PgStat_ToastCounts)) == 0) + continue; + + /* need to convert format of time accumulators */ + m_ent = &msg.m_entry[msg.m_nentries]; + m_ent->attr = entry->attr; + m_ent->t_numexternalized = entry->t_counts.t_numexternalized; + m_ent->t_numcompressed = entry->t_counts.t_numcompressed; + m_ent->t_numcompressionsuccess = entry->t_counts.t_numcompressionsuccess; + m_ent->t_size_orig = entry->t_counts.t_size_orig; + m_ent->t_size_compressed = entry->t_counts.t_size_compressed; + m_ent->t_comp_time = INSTR_TIME_GET_MICROSEC(entry->t_counts.t_comp_time); + + if (++msg.m_nentries >= PGSTAT_NUM_TOASTENTRIES) + { + pgstat_send(&msg, offsetof(PgStat_MsgToaststat, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_ToastEntry)); + msg.m_nentries = 0; + } + + /* reset the entry's counts */ + MemSet(&entry->t_counts, 0, sizeof(PgStat_ToastCounts)); + } + + if (msg.m_nentries > 0) + pgstat_send(&msg, offsetof(PgStat_MsgToaststat, m_entry[0]) + + msg.m_nentries * sizeof(PgStat_ToastEntry)); + + have_toast_stats = false; +} + /* ---------- * pgstat_vacuum_stat() - @@ -2151,6 +2228,72 @@ pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize) have_function_stats = true; } +/* + * Report TOAST activity + * Called by toast_helper functions. + */ +void +pgstat_report_toast_activity(Oid relid, int attr, + bool externalized, + bool compressed, + int32 old_size, + int32 new_size, + instr_time start_time) +{ + PgStat_BackendAttrIdentifier toastattr = { relid, attr }; + PgStat_BackendToastEntry *htabent; + instr_time time_spent; + bool found; + + if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_toast) + return; + + INSTR_TIME_SET_CURRENT(time_spent); + INSTR_TIME_SUBTRACT(time_spent, start_time); + + if (!pgStatToastActions) + { + /* First time through - initialize toast stat table */ + HASHCTL hash_ctl; + + hash_ctl.keysize = sizeof(PgStat_BackendAttrIdentifier); + hash_ctl.entrysize = sizeof(PgStat_BackendToastEntry); + pgStatToastActions = hash_create("TOAST stat entries", + PGSTAT_TOAST_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); + } + + /* Get the stats entry for this TOAST attribute, create if necessary */ + htabent = hash_search(pgStatToastActions, &toastattr, + HASH_ENTER, &found); + if (!found) + { + MemSet(&htabent->t_counts, 0, sizeof(PgStat_ToastCounts)); + } + + /* update counters */ + if (externalized) + { + htabent->t_counts.t_numexternalized++; + } + if (compressed) + { + htabent->t_counts.t_numcompressed++; + htabent->t_counts.t_size_orig+=old_size; + if (new_size) + { + htabent->t_counts.t_numcompressionsuccess++; + htabent->t_counts.t_size_compressed+=new_size; + } + } + /* record time spent */ + INSTR_TIME_ADD(htabent->t_counts.t_comp_time, time_spent); + + /* indicate that we have something to send */ + have_toast_stats = true; +} + /* ---------- * pgstat_initstats() - @@ -3028,6 +3171,35 @@ pgstat_fetch_stat_subworker_entry(Oid subid, Oid subrelid) return wentry; } +/* ---------- + * pgstat_fetch_stat_toastentry() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one TOAST attribute or NULL. + * ---------- + */ +PgStat_StatToastEntry * +pgstat_fetch_stat_toastentry(Oid rel_id, int attr) +{ + PgStat_StatDBEntry *dbentry; + PgStat_BackendAttrIdentifier toast_id = { rel_id, attr }; + PgStat_StatToastEntry *toastentry = NULL; + + /* load the stats file if needed */ + backend_read_statsfile(); + + /* Lookup our database, then find the requested TOAST activity stats. */ + dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId); + if (dbentry != NULL && dbentry->toastactivity != NULL) + { + toastentry = (PgStat_StatToastEntry *) hash_search(dbentry->toastactivity, + (void *) &toast_id, + HASH_FIND, NULL); + } + + return toastentry; +} + /* * --------- * pgstat_fetch_stat_archiver() - @@ -3708,6 +3880,10 @@ PgstatCollectorMain(int argc, char *argv[]) pgstat_recv_funcpurge(&msg.msg_funcpurge, len); break; + case PGSTAT_MTYPE_TOASTSTAT: + pgstat_recv_toaststat(&msg.msg_toaststat, len); + break; + case PGSTAT_MTYPE_RECOVERYCONFLICT: pgstat_recv_recoveryconflict(&msg.msg_recoveryconflict, len); @@ -3852,6 +4028,14 @@ reset_dbentry_counters(PgStat_StatDBEntry *dbentry) PGSTAT_SUBWORKER_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_BLOBS); + + hash_ctl.keysize = sizeof(PgStat_BackendAttrIdentifier); + hash_ctl.entrysize = sizeof(PgStat_StatToastEntry); + dbentry->toastactivity = hash_create("Per-database TOAST", + PGSTAT_TOAST_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS); + } /* @@ -4059,8 +4243,8 @@ pgstat_write_statsfiles(bool permanent, bool allDbs) while ((dbentry = (PgStat_StatDBEntry *) hash_seq_search(&hstat)) != NULL) { /* - * Write out the table, function, and subscription-worker stats for - * this DB into the appropriate per-DB stat file, if required. + * Write out the table, function, TOAST and subscription-worker stats for this DB into the + * appropriate per-DB stat file, if required. */ if (allDbs || pgstat_db_requested(dbentry->databaseid)) { @@ -4175,9 +4359,11 @@ pgstat_write_db_statsfile(PgStat_StatDBEntry *dbentry, bool permanent) HASH_SEQ_STATUS tstat; HASH_SEQ_STATUS fstat; HASH_SEQ_STATUS sstat; + HASH_SEQ_STATUS ostat; PgStat_StatTabEntry *tabentry; PgStat_StatFuncEntry *funcentry; PgStat_StatSubWorkerEntry *subwentry; + PgStat_StatToastEntry *toastentry; FILE *fpout; int32 format_id; Oid dbid = dbentry->databaseid; @@ -4243,6 +4429,17 @@ pgstat_write_db_statsfile(PgStat_StatDBEntry *dbentry, bool permanent) (void) rc; /* we'll check for error with ferror */ } + /* + * Walk through the database's TOAST stats table. + */ + hash_seq_init(&ostat, dbentry->toastactivity); + while ((toastentry = (PgStat_StatToastEntry *) hash_seq_search(&ostat)) != NULL) + { + fputc('O', fpout); + rc = fwrite(toastentry, sizeof(PgStat_StatToastEntry), 1, fpout); + (void) rc; /* we'll check for error with ferror */ + } + /* * No more output to be done. Close the temp file and replace the old * pgstat.stat with it. The ferror() check replaces testing for error @@ -4483,6 +4680,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) dbentry->tables = NULL; dbentry->functions = NULL; dbentry->subworkers = NULL; + dbentry->toastactivity = NULL; /* * In the collector, disregard the timestamp we read from the @@ -4528,6 +4726,14 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + hash_ctl.keysize = sizeof(PgStat_BackendAttrIdentifier); + hash_ctl.entrysize = sizeof(PgStat_StatToastEntry); + hash_ctl.hcxt = pgStatLocalContext; + dbentry->toastactivity = hash_create("Per-database toast information", + PGSTAT_TOAST_HASH_SIZE, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + /* * If requested, read the data from the database-specific * file. Otherwise we just leave the hashtables empty. @@ -4536,6 +4742,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) pgstat_read_db_statsfile(dbentry->databaseid, dbentry->tables, dbentry->functions, + dbentry->toastactivity, dbentry->subworkers, permanent); @@ -4620,7 +4827,7 @@ done: * ---------- */ static void -pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, +pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, HTAB *toasthash, HTAB *subworkerhash, bool permanent) { PgStat_StatTabEntry *tabentry; @@ -4629,6 +4836,8 @@ pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, PgStat_StatFuncEntry *funcentry; PgStat_StatSubWorkerEntry subwbuf; PgStat_StatSubWorkerEntry *subwentry; + PgStat_StatToastEntry toastbuf; + PgStat_StatToastEntry *toastentry; FILE *fpin; int32 format_id; bool found; @@ -4777,6 +4986,32 @@ pgstat_read_db_statsfile(Oid databaseid, HTAB *tabhash, HTAB *funchash, memcpy(subwentry, &subwbuf, sizeof(subwbuf)); break; + + /* + * 'O' A PgStat_StatToastEntry follows (tOast) + */ + case 'O': + if (fread(&toastbuf, 1, sizeof(PgStat_StatToastEntry), + fpin) != sizeof(PgStat_StatToastEntry)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted statistics file \"%s\"", + statfile))); + goto done; + } + + /* + * Skip if TOAST data not wanted. + */ + if (toasthash == NULL) + break; + + toastentry = (PgStat_StatToastEntry *) hash_search(toasthash, + (void *) &toastbuf.t_id, + HASH_ENTER, &found); + memcpy(toastentry, &toastbuf, sizeof(toastbuf)); + break; + /* * 'E' The EOF marker of a complete stats file. */ @@ -5452,6 +5687,8 @@ pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len) hash_destroy(dbentry->functions); if (dbentry->subworkers != NULL) hash_destroy(dbentry->subworkers); + if (dbentry->toastactivity != NULL) + hash_destroy(dbentry->toastactivity); if (hash_search(pgStatDBHash, (void *) &dbid, @@ -5491,10 +5728,12 @@ pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len) hash_destroy(dbentry->functions); if (dbentry->subworkers != NULL) hash_destroy(dbentry->subworkers); - + if (dbentry->toastactivity != NULL) + hash_destroy(dbentry->toastactivity); dbentry->tables = NULL; dbentry->functions = NULL; dbentry->subworkers = NULL; + dbentry->toastactivity = NULL; /* * Reset database-level stats, too. This creates empty hash tables for @@ -6152,6 +6391,60 @@ pgstat_recv_subscription_purge(PgStat_MsgSubscriptionPurge *msg, int len) } } +/* ---------- + * pgstat_recv_toaststat() - + * + * Count what the backend has done. + * ---------- + */ +static void +pgstat_recv_toaststat(PgStat_MsgToaststat *msg, int len) +{ + PgStat_ToastEntry *toastmsg = &(msg->m_entry[0]); + PgStat_StatDBEntry *dbentry; + PgStat_StatToastEntry *toastentry; + int i; + bool found; + + dbentry = pgstat_get_db_entry(msg->m_databaseid, true); + + /* + * Process all TOAST entries in the message. + */ + for (i = 0; i < msg->m_nentries; i++, toastmsg++) + { + toastentry = (PgStat_StatToastEntry *) hash_search(dbentry->toastactivity, + (void *) &(toastmsg->attr), + HASH_ENTER, &found); + + if (!found) + { + /* + * If it's a new entry, initialize counters to the values + * we just got. + */ + toastentry->t_numexternalized = toastmsg->t_numexternalized; + toastentry->t_numcompressed = toastmsg->t_numcompressed; + toastentry->t_numcompressionsuccess = toastmsg->t_numcompressionsuccess; + toastentry->t_size_orig = toastmsg->t_size_orig; + toastentry->t_size_compressed = toastmsg->t_size_compressed; + toastentry->t_comp_time = toastmsg->t_comp_time; + } + else + { + /* + * Otherwise add the values to the existing entry. + */ + toastentry->t_numexternalized += toastmsg->t_numexternalized; + toastentry->t_numcompressed += toastmsg->t_numcompressed; + toastentry->t_numcompressionsuccess += toastmsg->t_numcompressionsuccess; + toastentry->t_size_orig += toastmsg->t_size_orig; + toastentry->t_size_compressed += toastmsg->t_size_compressed; + toastentry->t_comp_time += toastmsg->t_comp_time; + } + } +} + /* ---------- * pgstat_recv_subworker_error() - * diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index f529c1561a..8b40c43626 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -410,6 +410,78 @@ pg_stat_get_function_self_time(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(((double) funcentry->f_self_time) / 1000.0); } +Datum +pg_stat_get_toast_externalizations(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_numexternalized); +} + +Datum +pg_stat_get_toast_compressions(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_numcompressed); +} + +Datum +pg_stat_get_toast_compressionsuccesses(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_numcompressionsuccess); +} + +Datum +pg_stat_get_toast_originalsizesum(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_size_orig); +} + +Datum +pg_stat_get_toast_compressedsizesum(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_size_compressed); +} + +Datum +pg_stat_get_toast_total_time(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int attr = PG_GETARG_INT16(1); + PgStat_StatToastEntry *toastentry; + + if ((toastentry = pgstat_fetch_stat_toastentry(relid,attr - 1)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_INT64(toastentry->t_comp_time); +} + Datum pg_stat_get_backend_idset(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index f9504d3aec..f7ff934d4e 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1538,6 +1538,15 @@ static struct config_bool ConfigureNamesBool[] = true, NULL, NULL, NULL }, + { + {"track_toast", PGC_SUSET, STATS_COLLECTOR, + gettext_noop("Collects statistics on TOAST activity."), + NULL + }, + &pgstat_track_toast, + false, + NULL, NULL, NULL + }, { {"track_io_timing", PGC_SUSET, STATS_COLLECTOR, gettext_noop("Collects timing statistics for database I/O activity."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index a1acd46b61..12f4bb1a38 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -607,6 +607,7 @@ #track_io_timing = off #track_wal_io_timing = off #track_functions = none # none, pl, all +#track_toast = off #stats_temp_directory = 'pg_stat_tmp' diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4d992dc224..22c34b96a6 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5668,6 +5668,31 @@ proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_function_self_time' }, +{ oid => '9700', descr => 'statistics: number of TOAST externalizations', + proname => 'pg_stat_get_toast_externalizations', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_externalizations' }, +{ oid => '9701', descr => 'statistics: number of TOAST compressions', + proname => 'pg_stat_get_toast_compressions', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_compressions' }, + { oid => '9702', descr => 'statistics: number of successful TOAST compressions', + proname => 'pg_stat_get_toast_compressionsuccesses', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_compressionsuccesses' }, +{ oid => '9703', descr => 'statistics: total original size of compressed TOAST data', + proname => 'pg_stat_get_toast_originalsizesum', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_originalsizesum' }, +{ oid => '9704', descr => 'statistics: total compressed size of compressed TOAST data', + proname => 'pg_stat_get_toast_compressedsizesum', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_compressedsizesum' }, +{ oid => '9705', descr => 'statistics: total time spend TOASTing data', + proname => 'pg_stat_get_toast_total_time', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid int4', + prosrc => 'pg_stat_get_toast_total_time' }, + { oid => '3037', descr => 'statistics: number of scans done for table/index in current transaction', proname => 'pg_stat_get_xact_numscans', provolatile => 'v', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 5b51b58e5a..b65f08de40 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -86,6 +86,7 @@ typedef enum StatMsgType PGSTAT_MTYPE_DISCONNECT, PGSTAT_MTYPE_SUBSCRIPTIONPURGE, PGSTAT_MTYPE_SUBWORKERERROR, + PGSTAT_MTYPE_TOASTSTAT, } StatMsgType; /* ---------- @@ -733,6 +734,80 @@ typedef struct PgStat_MsgDisconnect SessionEndType m_cause; } PgStat_MsgDisconnect; +/* ---------- + * PgStat_BackendAttrIdentifier Identifier for a single attribute/column (OID + attr) + * Used as a hashable identifier for (e.g.) TOAST columns + * ---------- + */ +typedef struct PgStat_BackendAttrIdentifier +{ + Oid relid; + int attr; +} PgStat_BackendAttrIdentifier; + +/* ---------- + * PgStat_ToastCounts The actual per-TOAST counts kept by a backend + * + * This struct should contain only actual event counters, because we memcmp + * it against zeroes to detect whether there are any counts to transmit. + * + * Note that the time counters are in instr_time format here. We convert to + * microseconds in PgStat_Counter format when transmitting to the collector. + * ---------- + */ +typedef struct PgStat_ToastCounts +{ + PgStat_Counter t_numexternalized; + PgStat_Counter t_numcompressed; + PgStat_Counter t_numcompressionsuccess; + uint64 t_size_orig; + uint64 t_size_compressed; + instr_time t_comp_time; +} PgStat_ToastCounts; + +/* ---------- + * PgStat_BackendToastEntry Entry in backend's per-toast-attr hash table + * ---------- + */ +typedef struct PgStat_BackendToastEntry +{ + PgStat_BackendAttrIdentifier attr; + PgStat_ToastCounts t_counts; +} PgStat_BackendToastEntry; + +/* ---------- + * PgStat_ToastEntry Per-TOAST-column info in a MsgToaststat + * ---------- + */ +typedef struct PgStat_ToastEntry +{ + PgStat_BackendAttrIdentifier attr; + PgStat_Counter t_numexternalized; + PgStat_Counter t_numcompressed; + PgStat_Counter t_numcompressionsuccess; + uint64 t_size_orig; + uint64 t_size_compressed; + PgStat_Counter t_comp_time; /* time in microseconds */ +} PgStat_ToastEntry; + +/* ---------- + * PgStat_MsgToaststat Sent by the backend to report function + * usage statistics. + * ---------- + */ +#define PGSTAT_NUM_TOASTENTRIES \ + ((PGSTAT_MSG_PAYLOAD - sizeof(Oid) - sizeof(int)) \ + / sizeof(PgStat_ToastEntry)) + +typedef struct PgStat_MsgToaststat +{ + PgStat_MsgHdr m_hdr; + Oid m_databaseid; + int m_nentries; + PgStat_ToastEntry m_entry[PGSTAT_NUM_TOASTENTRIES]; +} PgStat_MsgToaststat; + + /* ---------- * PgStat_Msg Union over all possible messages. * ---------- @@ -760,6 +835,7 @@ typedef union PgStat_Msg PgStat_MsgSLRU msg_slru; PgStat_MsgFuncstat msg_funcstat; PgStat_MsgFuncpurge msg_funcpurge; + PgStat_MsgToaststat msg_toaststat; PgStat_MsgRecoveryConflict msg_recoveryconflict; PgStat_MsgDeadlock msg_deadlock; PgStat_MsgTempFile msg_tempfile; @@ -833,6 +909,7 @@ typedef struct PgStat_StatDBEntry HTAB *tables; HTAB *functions; HTAB *subworkers; + HTAB *toastactivity; } PgStat_StatDBEntry; @@ -1022,6 +1099,22 @@ typedef struct PgStat_StatSubWorkerEntry char last_error_message[PGSTAT_SUBWORKERERROR_MSGLEN]; } PgStat_StatSubWorkerEntry; +/* ---------- + * PgStat_StatToastEntry The collector's data per TOAST attribute + * ---------- + */ +typedef struct PgStat_StatToastEntry +{ + PgStat_BackendAttrIdentifier t_id; + PgStat_Counter t_numexternalized; + PgStat_Counter t_numcompressed; + PgStat_Counter t_numcompressionsuccess; + uint64 t_size_orig; + uint64 t_size_compressed; + PgStat_Counter t_comp_time; /* time in microseconds */ +} PgStat_StatToastEntry; + + /* * Working state needed to accumulate per-function-call timing statistics. */ @@ -1045,6 +1138,7 @@ typedef struct PgStat_FunctionCallUsage */ extern PGDLLIMPORT bool pgstat_track_counts; extern PGDLLIMPORT int pgstat_track_functions; +extern PGDLLIMPORT bool pgstat_track_toast; extern char *pgstat_stat_directory; extern char *pgstat_stat_tmpname; extern char *pgstat_stat_filename; @@ -1196,12 +1290,22 @@ extern void pgstat_count_heap_delete(Relation rel); extern void pgstat_count_truncate(Relation rel); extern void pgstat_update_heap_dead_tuples(Relation rel, int delta); +extern void pgstat_count_toast_insert(Relation rel, PgStat_Counter n); + struct FunctionCallInfoBaseData; extern void pgstat_init_function_usage(struct FunctionCallInfoBaseData *fcinfo, PgStat_FunctionCallUsage *fcu); extern void pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize); +extern void +pgstat_report_toast_activity(Oid relid, int attr, + bool externalized, + bool compressed, + int32 old_size, + int32 new_size, + instr_time start_time); + extern void AtEOXact_PgStat(bool isCommit, bool parallel); extern void AtEOSubXact_PgStat(bool isCommit, int nestDepth); @@ -1228,9 +1332,12 @@ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid); extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid); extern PgStat_StatSubWorkerEntry *pgstat_fetch_stat_subworker_entry(Oid subid, Oid subrelid); +extern PgStat_StatToastEntry *pgstat_fetch_stat_toastentry(Oid rel_id, int attr); +extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void); extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void); extern PgStat_BgWriterStats *pgstat_fetch_stat_bgwriter(void); extern PgStat_CheckpointerStats *pgstat_fetch_stat_checkpointer(void); + extern PgStat_GlobalStats *pgstat_fetch_global(void); extern PgStat_WalStats *pgstat_fetch_stat_wal(void); extern PgStat_SLRUStats *pgstat_fetch_slru(void); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index b58b062b10..2b49bebcb8 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2147,6 +2147,23 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid, pg_stat_all_tables.autoanalyze_count FROM pg_stat_all_tables WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text)); +pg_stat_toast| SELECT n.nspname AS schemaname, + a.attrelid AS reloid, + a.attnum, + c.relname, + a.attname, + a.attstorage AS storagemethod, + pg_stat_get_toast_externalizations(a.attrelid, (a.attnum)::integer) AS externalized, + a.attcompression AS compressmethod, + pg_stat_get_toast_compressions(a.attrelid, (a.attnum)::integer) AS compressattempts, + pg_stat_get_toast_compressionsuccesses(a.attrelid, (a.attnum)::integer) AS compresssuccesses, + pg_stat_get_toast_compressedsizesum(a.attrelid, (a.attnum)::integer) AS compressedsize, + pg_stat_get_toast_originalsizesum(a.attrelid, (a.attnum)::integer) AS originalsize, + pg_stat_get_toast_total_time(a.attrelid, (a.attnum)::integer) AS total_time + FROM ((pg_attribute a + JOIN pg_class c ON ((c.oid = a.attrelid))) + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + WHERE (pg_stat_get_toast_externalizations(a.attrelid, (a.attnum)::integer) IS NOT NULL); pg_stat_user_functions| SELECT p.oid AS funcid, n.nspname AS schemaname, p.proname AS funcname, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 3e9ab0915f..880380aafa 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -11,6 +11,32 @@ SHOW track_counts; -- must be on on (1 row) +-- prepare and fill the pg_stat_toast table now: +SHOW track_toast; + track_toast +------------- + off +(1 row) + +SET track_toast TO on; +SHOW track_toast; + track_toast +------------- + on +(1 row) + +TABLE pg_stat_toast; -- view exists + schemaname | reloid | attnum | relname | attname | storagemethod | externalized | compressmethod | compressattempts | compresssuccesses | compressedsize | originalsize | total_time +------------+--------+--------+---------+---------+---------------+--------------+----------------+------------------+-------------------+----------------+--------------+------------ +(0 rows) + +CREATE TABLE toast_test (cola TEXT, colb TEXT, colc TEXT , cold TEXT); +ALTER TABLE toast_test ALTER colb SET STORAGE EXTERNAL; +ALTER TABLE toast_test ALTER colc SET STORAGE MAIN; +ALTER TABLE toast_test ALTER cold SET STORAGE PLAIN; +INSERT INTO toast_test VALUES (repeat(md5('a'),100), repeat(md5('a'),100), repeat(md5('a'),100), repeat(md5('a'),100) ); +-- make sure we don't interfere with the other tests: +SET track_toast TO off; -- ensure that both seqscan and indexscan plans are allowed SET enable_seqscan TO on; SET enable_indexscan TO on; @@ -252,4 +278,40 @@ SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid); DROP TABLE brin_hot; DROP FUNCTION wait_for_hot_stats(); +-- now check that the track_toast stuff worked: +SELECT attname + ,storagemethod + ,externalized + ,compressmethod + ,compressattempts + ,compresssuccesses + ,compressedsize < originalsize AS compression_works + , total_time > 0 AS takes_time +FROM pg_stat_toast WHERE relname = 'toast_test' ORDER BY attname; + attname | storagemethod | externalized | compressmethod | compressattempts | compresssuccesses | compression_works | takes_time +---------+---------------+--------------+----------------+------------------+-------------------+-------------------+------------ + cola | x | 1 | | 1 | 1 | t | t + colb | e | 1 | | 0 | 0 | f | t + colc | m | 0 | | 1 | 1 | t | t +(3 rows) + +SELECT compressattempts=0 AS external_doesnt_compress FROM pg_stat_toast WHERE relname = 'toast_test' AND storagemethod = 'e'; + external_doesnt_compress +-------------------------- + t +(1 row) + +SELECT externalized=0 AS main_doesnt_externalize FROM pg_stat_toast WHERE relname = 'toast_test' AND storagemethod = 'm'; + main_doesnt_externalize +------------------------- + t +(1 row) + +DROP TABLE toast_test; +SELECT count(*) FROM pg_stat_toast WHERE relname = 'toast_test'; + count +------- + 0 +(1 row) + -- End of Stats Test diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 82e6f24c39..ba462537f6 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -8,6 +8,20 @@ -- conditio sine qua non SHOW track_counts; -- must be on +-- prepare and fill the pg_stat_toast table now: +SHOW track_toast; +SET track_toast TO on; +SHOW track_toast; +TABLE pg_stat_toast; -- view exists + +CREATE TABLE toast_test (cola TEXT, colb TEXT, colc TEXT , cold TEXT); +ALTER TABLE toast_test ALTER colb SET STORAGE EXTERNAL; +ALTER TABLE toast_test ALTER colc SET STORAGE MAIN; +ALTER TABLE toast_test ALTER cold SET STORAGE PLAIN; +INSERT INTO toast_test VALUES (repeat(md5('a'),100), repeat(md5('a'),100), repeat(md5('a'),100), repeat(md5('a'),100) ); +-- make sure we don't interfere with the other tests: +SET track_toast TO off; + -- ensure that both seqscan and indexscan plans are allowed SET enable_seqscan TO on; SET enable_indexscan TO on; @@ -225,5 +239,19 @@ SELECT pg_stat_get_tuples_hot_updated('brin_hot'::regclass::oid); DROP TABLE brin_hot; DROP FUNCTION wait_for_hot_stats(); +-- now check that the track_toast stuff worked: +SELECT attname + ,storagemethod + ,externalized + ,compressmethod + ,compressattempts + ,compresssuccesses + ,compressedsize < originalsize AS compression_works + , total_time > 0 AS takes_time +FROM pg_stat_toast WHERE relname = 'toast_test' ORDER BY attname; +SELECT compressattempts=0 AS external_doesnt_compress FROM pg_stat_toast WHERE relname = 'toast_test' AND storagemethod = 'e'; +SELECT externalized=0 AS main_doesnt_externalize FROM pg_stat_toast WHERE relname = 'toast_test' AND storagemethod = 'm'; +DROP TABLE toast_test; +SELECT count(*) FROM pg_stat_toast WHERE relname = 'toast_test'; -- End of Stats Test -- 2.32.0 ^ permalink raw reply [nested|flat] 46+ messages in thread
end of thread, other threads:[~2022-01-04 11:29 UTC | newest] Thread overview: 46+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 03:25 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2021-02-04 18:43 [PATCH] hint squash commit Bruce Momjian <[email protected]> 2022-01-03 21:23 Re: [PATCH] pg_stat_toast v6 Alvaro Herrera <[email protected]> 2022-01-04 11:29 ` Re: [PATCH] pg_stat_toast v6 Gunnar "Nick" Bluth <[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