openGauss-server/src/gausskernel/storage/access/transam/double_write.cpp

1201 lines
41 KiB
C++

/*
* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* ---------------------------------------------------------------------------------------
*
* double_write.cpp
* Before flush dirty pages to data file, flush them to double write file,
* in case of half-flushed pages. Recover those half-flushed data file pages
* before replaying xlog when starting.
*
* IDENTIFICATION
* src/gausskernel/storage/access/transam/double_write.cpp
*
* ---------------------------------------------------------------------------------------
*/
#include <unistd.h>
#include "utils/elog.h"
#include "utils/builtins.h"
#include "access/double_write.h"
#include "pgstat.h"
#include "utils/palloc.h"
#include "gstrace/gstrace_infra.h"
#include "gstrace/access_gstrace.h"
#ifdef ENABLE_UT
#define static
#endif
Datum dw_get_node_name()
{
if (g_instance.attr.attr_common.PGXCNodeName == NULL || g_instance.attr.attr_common.PGXCNodeName[0] == '\0') {
return CStringGetTextDatum("not define");
} else {
return CStringGetTextDatum(g_instance.attr.attr_common.PGXCNodeName);
}
}
Datum dw_get_dw_number()
{
if (dw_enabled()) {
return UInt64GetDatum((uint64)g_instance.dw_cxt.file_head->head.dwn);
}
return UInt64GetDatum(0);
}
Datum dw_get_start_page()
{
if (dw_enabled()) {
return UInt64GetDatum((uint64)g_instance.dw_cxt.file_head->start);
}
return UInt64GetDatum(0);
}
Datum dw_get_file_trunc_num()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.file_trunc_num);
}
Datum dw_get_file_reset_num()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.file_reset_num);
}
Datum dw_get_total_writes()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.total_writes);
}
Datum dw_get_low_threshold_writes()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.low_threshold_writes);
}
Datum dw_get_high_threshold_writes()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.high_threshold_writes);
}
Datum dw_get_total_pages()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.total_pages);
}
Datum dw_get_low_threshold_pages()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.low_threshold_pages);
}
Datum dw_get_high_threshold_pages()
{
return UInt64GetDatum(g_instance.dw_cxt.stat_info.high_threshold_pages);
}
/* double write statistic view */
const dw_view_col_t g_dw_view_col_arr[DW_VIEW_COL_NUM] = {
{"node_name", TEXTOID, dw_get_node_name},
{"curr_dwn", INT8OID, dw_get_dw_number},
{"curr_start_page", INT8OID, dw_get_start_page},
{"file_trunc_num", INT8OID, dw_get_file_trunc_num},
{"file_reset_num", INT8OID, dw_get_file_reset_num},
{"total_writes", INT8OID, dw_get_total_writes},
{"low_threshold_writes", INT8OID, dw_get_low_threshold_writes},
{"high_threshold_writes", INT8OID, dw_get_high_threshold_writes},
{"total_pages", INT8OID, dw_get_total_pages},
{"low_threshold_pages", INT8OID, dw_get_low_threshold_pages},
{"high_threshold_pages", INT8OID, dw_get_high_threshold_pages}
};
void dw_pread_file(int fd, void* buf, int size, int64 offset)
{
int32 curr_size, total_size;
total_size = 0;
do {
curr_size = pread64(fd, ((char*)buf + total_size), (size - total_size), offset);
if (curr_size == -1) {
ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("Read file error")));
}
total_size += curr_size;
offset += curr_size;
} while (curr_size > 0);
if (total_size != size) {
ereport(PANIC,
(errcode_for_file_access(),
errmodule(MOD_DW),
errmsg("Read file size mismatch: expected %d, read %d", size, total_size)));
}
}
void dw_pwrite_file(int fd, const void* buf, int size, int64 offset)
{
int write_size = 0;
uint32 try_times = 0;
if ((offset + size) > DW_FILE_SIZE) {
ereport(PANIC,
(errmodule(MOD_DW),
errmsg(
"DW write file failed, dw_file_size %ld, offset %ld, write_size %d", DW_FILE_SIZE, offset, size)));
}
while (try_times < DW_TRY_WRITE_TIMES) {
write_size = pwrite64(fd, buf, size, offset);
if (write_size == 0) {
try_times++;
pg_usleep(DW_SLEEP_US);
continue;
} else if (write_size < 0) {
ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("Write file error")));
} else {
break;
}
}
if (write_size != size) {
ereport(PANIC,
(errcode_for_file_access(),
errmodule(MOD_DW),
errmsg("Write file size mismatch: expected %d, written %d", size, write_size)));
}
}
int64 dw_seek_file(int fd, int64 offset, int32 origin)
{
return (int64)lseek64(fd, (off64_t)offset, origin);
}
static void dw_extend_file(int fd, const void* buf, int buf_size, int64 size)
{
int64 offset, remain_size;
offset = dw_seek_file(fd, 0, SEEK_END);
if (offset == -1) {
ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("Seek file error")));
}
if ((offset + size) > DW_FILE_SIZE) {
ereport(PANIC,
(errmodule(MOD_DW),
errmsg("DW extend file failed, expected_file_size %ld, offset %ld, extend_size %ld",
DW_FILE_SIZE,
offset,
size)));
}
remain_size = size;
while (remain_size > 0) {
size = (remain_size > buf_size) ? buf_size : remain_size;
dw_pwrite_file(fd, buf, size, offset);
offset += size;
remain_size -= size;
}
}
static void dw_set_pg_checksum(char* page, BlockNumber blockNum)
{
if (PageIsNew(page)) {
return;
}
/* set page->pd_flags mark using FNV1A for checksum */
PageSetChecksumByFNV1A(page);
((PageHeader)page)->pd_checksum = pg_checksum_page(page, blockNum);
}
static bool dw_verify_pg_checksum(PageHeader page_header, BlockNumber blockNum)
{
if (PageIsNew(page_header)) {
return true;
}
if (PageIsChecksumByFNV1A(page_header)) {
uint16 checksum = pg_checksum_page((char*)page_header, blockNum);
return checksum == page_header->pd_checksum;
} else {
ereport(FATAL, (errmodule(MOD_DW), errmsg("DW verify checksum: page checksum flag is wrong")));
return false;
}
}
inline void dw_prepare_page(dw_batch_t* batch, uint16 page_num, uint16 page_id, uint16 dwn)
{
if (g_instance.ckpt_cxt_ctl->buffers_contain_hashbucket == true) {
page_num = page_num | IS_HASH_BKT_MASK;
}
batch->page_num = page_num;
batch->head.page_id = page_id;
batch->head.dwn = dwn;
DW_PAGE_TAIL(batch)->dwn = dwn;
dw_calc_batch_checksum(batch);
}
static void dw_prepare_file_head(char* file_head, uint16 start, uint16 dwn)
{
uint32 i;
uint32 id;
dw_file_head_t* curr_head = NULL;
for (i = 0; i < DW_FILE_HEAD_ID_NUM; i++) {
id = g_dw_file_head_ids[i];
curr_head = (dw_file_head_t*)(file_head + sizeof(dw_file_head_t) * id);
curr_head->head.page_id = 0;
curr_head->head.dwn = dwn;
curr_head->start = start;
curr_head->tail.dwn = dwn;
dw_calc_file_head_checksum(curr_head);
}
}
static void dw_generate_file(int fd, char* file_head, int buf_size)
{
dw_batch_t* batch_head = NULL;
/* file head and first batch head will be writen */
int64 extend_size = (DW_FILE_PAGE * BLCKSZ) - BLCKSZ - BLCKSZ;
dw_prepare_file_head(file_head, DW_BATCH_FILE_START, 0);
batch_head = (dw_batch_t*)(file_head + BLCKSZ);
batch_head->head.page_id = DW_BATCH_FILE_START;
dw_calc_batch_checksum(batch_head);
pgstat_report_waitevent(WAIT_EVENT_DW_WRITE);
dw_pwrite_file(fd, file_head, (BLCKSZ + BLCKSZ), 0);
dw_extend_file(fd, file_head, buf_size, extend_size);
pgstat_report_waitevent(WAIT_EVENT_END);
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write file created successfully")));
}
static void dw_recover_file_head(dw_context_t* ctx)
{
uint32 i;
uint16 id = DW_FILE_HEAD_ID_MAX;
errno_t rc;
dw_file_head_t* curr_head = NULL;
dw_file_head_t* working_head = NULL;
char* file_head = (char*)ctx->file_head;
pgstat_report_waitevent(WAIT_EVENT_DW_READ);
dw_pread_file(ctx->fd, ctx->file_head, BLCKSZ, 0);
pgstat_report_waitevent(WAIT_EVENT_END);
int64 offset = dw_seek_file(ctx->fd, 0, SEEK_END);
if (offset != DW_FILE_SIZE) {
ereport(PANIC,
(errmodule(MOD_DW),
errmsg("DW check file size failed, expected_size %ld, actual_size %ld", DW_FILE_SIZE, offset)));
}
for (i = 0; i < DW_FILE_HEAD_ID_NUM; i++) {
id = g_dw_file_head_ids[i];
curr_head = (dw_file_head_t*)(file_head + sizeof(dw_file_head_t) * id);
if (dw_verify_file_head(curr_head)) {
working_head = curr_head;
break;
}
}
if (working_head == NULL) {
ereport(FATAL, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("File header is broken")));
/* we should not get here, since FATAL will do abort. But for ut, return is needed */
return;
}
ereport(LOG,
(errmodule(MOD_DW),
errmsg("Found a valid file header: id %hu, file_head[dwn %hu, start %hu]",
id,
working_head->head.dwn,
working_head->start)));
for (i = 0; i < DW_FILE_HEAD_ID_NUM; i++) {
id = g_dw_file_head_ids[i];
curr_head = (dw_file_head_t*)(file_head + sizeof(dw_file_head_t) * id);
if (curr_head != working_head) {
rc = memcpy_s(curr_head, sizeof(dw_file_head_t), working_head, sizeof(dw_file_head_t));
securec_check(rc, "\0", "\0");
}
}
pgstat_report_waitevent(WAIT_EVENT_DW_WRITE);
dw_pwrite_file(ctx->fd, file_head, BLCKSZ, 0);
pgstat_report_waitevent(WAIT_EVENT_END);
}
static inline void dw_log_page_header(PageHeader page)
{
ereport(DW_LOG_LEVEL,
(errmodule(MOD_DW),
errmsg("Page header info: pd_lsn %lu, pd_checksum %hu, "
"pd_lower %hu(%s), pd_upper %hu(%s), max_offset %hu",
PageGetLSN(page),
page->pd_checksum,
page->pd_lower,
PageIsEmpty(page) ? "empty" : "non-empty",
page->pd_upper,
PageIsNew(page) ? "new" : "old",
PageGetMaxOffsetNumber((char*)page))));
}
template <typename T>
static inline void dw_log_data_page(int elevel, const char* state, T* buf_tag)
{
ereport(elevel,
(errmodule(MOD_DW),
errmsg("%s: buf_tag[rel %u/%u/%u blk %u fork %d]",
state,
buf_tag->rnode.spcNode,
buf_tag->rnode.dbNode,
buf_tag->rnode.relNode,
buf_tag->blockNum,
buf_tag->forkNum)));
}
template <typename T1, typename T2>
static void dw_recover_pages(T1* batch, T2* buf_tag, PageHeader data_page, bool is_hashbucket)
{
uint16 i;
PageHeader dw_page;
SMgrRelation relation;
BlockNumber blk_num;
RelFileNode relnode;
for (i = 0; i < GET_REL_PGAENUM(batch->page_num); i++) {
buf_tag = &batch->buf_tag[i];
if (is_hashbucket) {
relnode.dbNode = buf_tag->rnode.dbNode;
relnode.spcNode = buf_tag->rnode.spcNode;
relnode.relNode = buf_tag->rnode.relNode;
relnode.bucketNode = ((BufferTag*)buf_tag)->rnode.bucketNode;
} else {
relnode.dbNode = buf_tag->rnode.dbNode;
relnode.spcNode = buf_tag->rnode.spcNode;
relnode.relNode = buf_tag->rnode.relNode;
relnode.bucketNode = InvalidBktId;
}
relation = smgropen(relnode, InvalidBackendId, GetColumnNum(buf_tag->forkNum));
if (!smgrexists(relation, buf_tag->forkNum)) {
dw_log_data_page(WARNING, "Data file deleted", buf_tag);
continue;
}
blk_num = smgrnblocks(relation, buf_tag->forkNum);
if (blk_num <= buf_tag->blockNum) {
dw_log_data_page(WARNING, "Data page deleted", buf_tag);
continue;
}
smgrread(relation, buf_tag->forkNum, buf_tag->blockNum, (char*)data_page);
dw_page = (PageHeader)((char*)batch + (i + 1) * BLCKSZ);
if (!dw_verify_pg_checksum(dw_page, buf_tag->blockNum)) {
dw_log_data_page(WARNING, "DW page broken", buf_tag);
dw_log_page_header(dw_page);
continue;
}
dw_log_data_page(DW_LOG_LEVEL, "DW page fine", buf_tag);
dw_log_page_header(dw_page);
if (!dw_verify_pg_checksum(data_page, buf_tag->blockNum) ||
XLByteLT(PageGetLSN(data_page), PageGetLSN(dw_page))) {
smgrwrite(relation, buf_tag->forkNum, buf_tag->blockNum, (const char*)dw_page, false);
dw_log_data_page(LOG, "Date page recovered", buf_tag);
dw_log_page_header(data_page);
}
}
}
/*
* Basically, dw_reset_if_need calls smgrsync and then reuse dw file to some extent:
* 1. truncate dw file start position to last flush postition, before which all dirty buffers are garanteed
* to be smgr-synced, in order to avoid redundant dw file check during crash recovery.
* 2. fully recycle dw file and set dw file start position to the first page, when dw file is out of space.
*
* On entry, caller should hold dw flush lock. For truncate purpose, which is currently considered as
* an rto optimization, dw flush lock is released during performing smgrsync and is only conditionally re-acquired.
* Callers for dw truncate, i.e. checkpointer and startup, should take care of lock failure.
*
* We do not allow dw truncate and full recycle at the same time. In fact, full dw recycle, which blocks
* all concurrent pagewriters, should be removed for higher performance in the future, as long as dw file
* is reused as a ring file while file sync and dw truncate jobs are solely taken care of by checkpointer.
*
* Return FALSE if we can not grab conditional dw flush lock after smgrsync for truncate.
*/
static bool dw_reset_if_need(dw_context_t* ctx, uint16 pages_to_write, bool trunc_file)
{
bool file_full = false;
dw_file_head_t* file_head = ctx->file_head;
volatile uint16 org_start = file_head->start;
volatile uint16 org_dwn = file_head->head.dwn;
uint16 last_flush_page;
file_full = (file_head->start + ctx->flush_page + pages_to_write >= DW_FILE_PAGE);
Assert(!(file_full && trunc_file));
if (!file_full && !trunc_file) {
return true;
}
if (trunc_file) {
/* record last flush position for truncate because flush lock is not held during smgrsync */
last_flush_page = ctx->last_flush_page;
LWLockRelease(ctx->flush_lock);
} else {
/* no need to reserve last flush position for full recycle */
last_flush_page = ctx->flush_page;
}
smgrsync_for_dw();
if (trunc_file) {
if (!LWLockConditionalAcquire(ctx->flush_lock, LW_EXCLUSIVE)) {
ereport(LOG,
(errmodule(MOD_DW), errmsg("Can not get dw flush lock and skip dw truncate after sync for this time")));
return false;
} else if (org_start != file_head->start || org_dwn != file_head->head.dwn) {
/*
* Even if there are concurrent dw truncate/reset during the above smgrsync,
* the possibility of same start and dwn value should be small enough.
*/
ereport(LOG,
(errmodule(MOD_DW),
errmsg("Skip dw truncate after sync due to concurrent dw truncate/reset, "
"original[dwn %hu, start %hu], current[dwn %hu, start %hu]",
org_dwn,
org_start,
file_head->head.dwn,
file_head->start)));
return true;
}
}
ereport(DW_LOG_LEVEL,
(errmodule(MOD_DW),
errmsg("Reset DW file: file_head[dwn %hu, start %hu], total_pages %hu, "
"file_full %d, trunc_file %d, pages_to_write %hu",
file_head->head.dwn,
file_head->start,
ctx->flush_page,
file_full,
trunc_file,
pages_to_write)));
if (file_full) {
Assert(AmStartupProcess() || AmPageWriterProcess());
file_head->start = DW_BATCH_FILE_START;
ctx->last_flush_page = ctx->flush_page;
} else {
Assert(AmStartupProcess() || AmCheckpointerProcess() || AmBootstrapProcess() || !IsUnderPostmaster);
/*
* we can only discard all the batches till last dw flush.
* For pages recorded in current dw flush, they may be concurrently
* flushed by pagewriters and we might have not absorbed their fsync request.
*/
file_head->start += last_flush_page;
}
ctx->flush_page -= last_flush_page;
ctx->last_flush_page -= last_flush_page;
/*
* if truncate file and flush_page is not 0, the dwn can not plus,
* otherwise verify will failed when recovery the data form dw file.
*/
if (ctx->flush_page > 0) {
Assert(trunc_file);
dw_prepare_file_head((char*)file_head, file_head->start, file_head->head.dwn);
} else {
dw_prepare_file_head((char*)file_head, file_head->start, file_head->head.dwn + 1);
}
Assert(file_head->head.dwn == file_head->tail.dwn);
pgstat_report_waitevent(WAIT_EVENT_DW_WRITE);
dw_pwrite_file(ctx->fd, file_head, BLCKSZ, 0);
pgstat_report_waitevent(WAIT_EVENT_END);
pg_atomic_add_fetch_u64(&ctx->stat_info.file_trunc_num, 1);
if (file_full) {
pg_atomic_add_fetch_u64(&ctx->stat_info.file_reset_num, 1);
}
return true;
}
static void dw_read_pages(dw_read_asst_t* read_asst, uint16 reading_pages)
{
if (reading_pages > 0) {
Assert(read_asst->buf_end + reading_pages <= read_asst->buf_capacity);
Assert(read_asst->file_start + reading_pages <= read_asst->file_capacity);
pgstat_report_waitevent(WAIT_EVENT_DW_READ);
dw_pread_file(read_asst->fd,
(read_asst->buf + read_asst->buf_end * BLCKSZ),
(reading_pages * BLCKSZ),
(read_asst->file_start * BLCKSZ));
pgstat_report_waitevent(WAIT_EVENT_END);
read_asst->buf_end += reading_pages;
read_asst->file_start += reading_pages;
}
Assert(read_asst->buf_end >= read_asst->buf_start);
}
static inline void dw_discard_pages(dw_read_asst_t* read_asst, uint16 page_num)
{
read_asst->buf_start += page_num;
Assert(read_asst->buf_end >= read_asst->buf_start);
}
static uint16 dw_calc_reading_pages(dw_read_asst_t* read_asst)
{
dw_batch_t* curr_head;
uint16 remain_pages, batch_pages, reading_pages;
errno_t rc;
remain_pages = read_asst->buf_end - read_asst->buf_start;
curr_head = (dw_batch_t*)(read_asst->buf + read_asst->buf_start * BLCKSZ);
batch_pages = (GET_REL_PGAENUM(curr_head->page_num) + DW_EXTRA_FOR_ONE_BATCH);
/* batch is already im memory, no need read */
if (remain_pages >= batch_pages) {
reading_pages = 0;
} else {
/* if buf size not enough for the batch, move the read pages to the start */
/* all 3 variable less than DW_BUF_MAX, sum not overflow uint32 */
if (read_asst->buf_start + batch_pages >= read_asst->buf_capacity) {
rc = memmove_s(read_asst->buf, (remain_pages * BLCKSZ), curr_head, (remain_pages * BLCKSZ));
securec_check(rc, "\0", "\0");
read_asst->buf_start = 0;
read_asst->buf_end = remain_pages;
curr_head = (dw_batch_t*)read_asst->buf;
}
reading_pages = batch_pages - remain_pages;
}
Assert(
(char*)curr_head + (remain_pages + reading_pages) * BLCKSZ < read_asst->buf + read_asst->buf_capacity * BLCKSZ);
Assert(read_asst->file_start + reading_pages <= DW_FILE_PAGE);
return reading_pages;
}
static void dw_recover_batch_head(dw_context_t* ctx, dw_batch_t* curr_head)
{
errno_t rc;
rc = memset_s(curr_head, BLCKSZ, 0, BLCKSZ);
securec_check(rc, "\0", "\0");
dw_prepare_page(curr_head, 0, ctx->file_head->start, ctx->file_head->head.dwn);
pgstat_report_waitevent(WAIT_EVENT_DW_WRITE);
dw_pwrite_file(ctx->fd, curr_head, BLCKSZ, (curr_head->head.page_id * BLCKSZ));
pgstat_report_waitevent(WAIT_EVENT_END);
}
static inline void dw_log_recover_state(dw_context_t* ctx, int elevel, const char* state, dw_batch_t* batch)
{
ereport(elevel,
(errmodule(MOD_DW),
errmsg("DW recovery state: \"%s\", file start page[dwn %hu, start %hu], now access page %hu, "
"current [page_id %hu, dwn %hu, checksum verify res is %d, page_num orig %hu, page_num fixed %hu]",
state,
ctx->file_head->head.dwn,
ctx->file_head->start,
ctx->flush_page,
batch->head.page_id,
batch->head.dwn,
dw_verify_batch_checksum(batch),
batch->page_num,
GET_REL_PGAENUM(batch->page_num))));
}
static bool dw_batch_head_broken(dw_context_t* ctx, dw_batch_t* curr_head)
{
bool broken = false;
dw_batch_t* curr_tail = dw_batch_tail_page(curr_head);
if (dw_verify_page(curr_head)) {
if (GET_REL_PGAENUM(curr_head->page_num) == 0) {
dw_log_recover_state(ctx, LOG, "Empty", curr_head);
} else if (curr_head->head.page_id == DW_BATCH_FILE_START) {
dw_log_recover_state(ctx, LOG, "File reset", curr_head);
} else {
dw_log_recover_state(ctx, WARNING, "Head info", curr_head);
dw_log_recover_state(ctx, WARNING, "Tail broken", curr_tail);
broken = true;
}
} else {
dw_log_recover_state(ctx, WARNING, "Head broken", curr_head);
dw_log_recover_state(ctx, WARNING, "Tail unknown", curr_tail);
broken = true;
}
return broken;
}
static void dw_recover_partial_write(dw_context_t* ctx)
{
dw_read_asst_t read_asst;
dw_batch_t* curr_head = NULL;
uint16 reading_pages;
uint16 remain_pages;
bool dw_file_broken = false;
char* data_page = NULL;
MemoryContext old_mem_ctx;
read_asst.fd = ctx->fd;
read_asst.file_start = ctx->file_head->start;
read_asst.file_capacity = DW_FILE_PAGE;
read_asst.buf_start = 0;
read_asst.buf_end = 0;
read_asst.buf_capacity = GET_DW_BUF_MAX;
read_asst.buf = ctx->buf;
reading_pages = Min(GET_DW_BATCH_MAX, (DW_FILE_PAGE - ctx->file_head->start));
old_mem_ctx = MemoryContextSwitchTo(ctx->mem_ctx);
data_page = (char*)palloc0(BLCKSZ);
for (;;) {
dw_read_pages(&read_asst, reading_pages);
curr_head = (dw_batch_t*)(read_asst.buf + (read_asst.buf_start * BLCKSZ));
bool is_hashbucket = ((curr_head->page_num & IS_HASH_BKT_MASK) != 0);
if (!dw_verify_batch(curr_head, ctx->file_head->head.dwn)) {
dw_file_broken = dw_batch_head_broken(ctx, curr_head);
break;
}
remain_pages = read_asst.buf_end - read_asst.buf_start;
Assert(curr_head->head.page_id + remain_pages == read_asst.file_start);
Assert(remain_pages >= GET_REL_PGAENUM(curr_head->page_num) + DW_EXTRA_FOR_ONE_BATCH);
dw_log_recover_state(ctx, DW_LOG_LEVEL, "Batch fine", curr_head);
if (is_hashbucket) {
BufferTag* tmp = NULL;
dw_recover_pages<dw_batch_t, BufferTag>(curr_head, tmp, (PageHeader)data_page, is_hashbucket);
} else {
BufferTagNoHBkt* tmp = NULL;
dw_recover_pages<dw_batch_nohbkt_t, BufferTagNoHBkt>(
(dw_batch_nohbkt_t*)curr_head, tmp, (PageHeader)data_page, is_hashbucket);
}
/* discard the first batch. including head page and data pages */
ctx->flush_page += (1 + GET_REL_PGAENUM(curr_head->page_num));
dw_discard_pages(&read_asst, (1 + GET_REL_PGAENUM(curr_head->page_num)));
curr_head = dw_batch_tail_page(curr_head);
if (GET_REL_PGAENUM(curr_head->page_num) == 0) {
dw_log_recover_state(ctx, LOG, "Batch end", curr_head);
break;
}
reading_pages = dw_calc_reading_pages(&read_asst);
}
/* Truncate to all flushed page is safe since there is no concurrent flush-buffer at this stage */
ctx->last_flush_page = ctx->flush_page;
/* if free space not enough for one batch, reuse file. Otherwise, just do a truncate */
if ((ctx->file_head->start + ctx->flush_page + GET_DW_BUF_MAX) >= DW_FILE_PAGE) {
(void)dw_reset_if_need(ctx, GET_DW_BUF_MAX, false);
} else if (ctx->flush_page > 0) {
if (!dw_reset_if_need(ctx, 0, true)) {
ereport(PANIC,
(errcode_for_file_access(), errmodule(MOD_DW), errmsg("Could not truncate dw file during startup!")));
}
}
if (dw_file_broken) {
dw_recover_batch_head(ctx, curr_head);
}
dw_log_recover_state(ctx, LOG, "Finish", curr_head);
pfree(data_page);
MemoryContextSwitchTo(old_mem_ctx);
}
void dw_bootstrap()
{
char* unaligned_buf = NULL;
char* file_head = NULL;
int fd = -1; /* resource fd should be initialized any way */
int extend_buf_size = DW_FILE_EXTEND_SIZE + BLCKSZ; /* one more BLCKSZ for alignment */
if (file_exists(DW_FILE_NAME)) {
ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), "DW file already exists"));
}
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write bootstrap")));
/* Open file with O_SYNC, to make sure the data and file system control info on file after block writing. */
fd = open(DW_FILE_NAME, (DW_FILE_FLAG | O_CREAT), DW_FILE_PERM);
if (fd == -1) {
ereport(PANIC,
(errcode_for_file_access(), errmodule(MOD_DW), errmsg("Could not create file \"%s\"", DW_FILE_NAME)));
}
unaligned_buf = (char*)palloc0(extend_buf_size);
/* alignment for O_DIRECT */
file_head = (char*)TYPEALIGN(BLCKSZ, unaligned_buf);
dw_generate_file(fd, file_head, DW_FILE_EXTEND_SIZE);
(void)close(fd);
pfree(unaligned_buf);
}
static void dw_init_memory(dw_context_t* ctx)
{
uint32 buf_size;
char* buf = NULL;
MemoryContext old_mem_ctx;
buf_size = DW_MEM_CTX_MAX_BLOCK_SIZE_FOR_NOHBK;
ctx->mem_ctx = AllocSetContextCreate(
g_instance.instance_context, "double write", buf_size, buf_size, buf_size, SHARED_CONTEXT);
old_mem_ctx = MemoryContextSwitchTo(ctx->mem_ctx);
/* one BLCKSZ left for partial write recovering's data file reading buffer */
ctx->unaligned_buf = (char*)palloc0(buf_size - BLCKSZ);
/* alignment for O_DIRECT */
buf = (char*)TYPEALIGN(BLCKSZ, ctx->unaligned_buf);
ctx->file_head = (dw_file_head_t*)buf;
buf += BLCKSZ;
ctx->buf = buf;
ctx->write_pos = 0;
ctx->flush_page = 0;
ctx->last_flush_page = 0;
(void)MemoryContextSwitchTo(old_mem_ctx);
}
static void dw_free_resource(dw_context_t* ctx)
{
int rc = close(ctx->fd);
if (rc == -1) {
ereport(ERROR, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("DW file close failed")));
}
pfree(ctx->unaligned_buf);
ctx->unaligned_buf = NULL;
ctx->file_head = NULL;
ctx->buf = NULL;
MemoryContextDelete(ctx->mem_ctx);
}
void dw_shmem_init()
{
/* LWLock Should be reset when postmaster inits shmem. */
if (!IsUnderPostmaster) {
g_instance.dw_cxt.flush_lock = NULL;
}
}
void dw_init()
{
dw_context_t* ctx = &g_instance.dw_cxt;
#ifndef ENABLE_THREAD_CHECK
if (TAS(&ctx->initialized)) {
#else
if (__sync_lock_test_and_set(&ctx->initialized, 1)) {
#endif
ereport(WARNING, (errmodule(MOD_DW), errmsg("Double write already initialized")));
return;
}
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write init")));
ctx->closed = 0;
if (file_exists(DW_BUILD_FILE_NAME)) {
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write initializing after build")));
if (file_exists(DW_FILE_NAME)) {
/*
* Probably the gaussdb was killed during the first time startup after build, resulting in a half-written
* DW file. So, log a warning message and remove the residual DW file.
*/
ereport(WARNING, (errcode_for_file_access(), errmodule(MOD_DW), "Residual DW file exists, deleting it"));
if (unlink(DW_FILE_NAME) != 0) {
ereport(PANIC,
(errcode_for_file_access(), errmodule(MOD_DW), errmsg("Could not remove the residual DW file")));
}
}
/* Create the DW file. */
dw_bootstrap();
/* Remove the DW build file. */
if (unlink(DW_BUILD_FILE_NAME) != 0) {
ereport(
PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("Could not remove the DW build file")));
}
}
if (!file_exists(DW_FILE_NAME)) {
ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("DW file does not exist")));
}
/* double write file disk space pre-allocated, O_DSYNC for less IO */
ctx->fd = open(DW_FILE_NAME, DW_FILE_FLAG, DW_FILE_PERM);
if (ctx->fd == -1) {
ereport(
PANIC, (errcode_for_file_access(), errmodule(MOD_DW), errmsg("Could not open file \"%s\"", DW_FILE_NAME)));
}
/* LWLock has no free method, so only assign once when first init */
/* fail_over and switch_over will dw_exit and dw_init multiple times */
if (ctx->flush_lock == NULL) {
ctx->flush_lock = LWLockAssign(LWTRANCHE_DOUBLE_WRITE);
}
LWLockAcquire(ctx->flush_lock, LW_EXCLUSIVE);
ctx->flush_page = 0;
dw_init_memory(ctx);
dw_recover_file_head(ctx);
dw_recover_partial_write(ctx);
LWLockRelease(ctx->flush_lock);
/*
* After recovering partially written pages (if any), we will un-initialize, if the double write is disabled.
*/
if (!dw_enabled()) {
dw_free_resource(ctx);
ctx->initialized = 0;
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write exit after recovering partial write")));
}
smgrcloseall();
}
static void dw_encrypt_page(char* dest_addr)
{
size_t plain_len, cipher_len;
if (isEncryptedCluster() && !PageIsNew(dest_addr)) {
plain_len = BLCKSZ - GetPageHeaderSize(dest_addr);
encryptBlockOrCUData(PageGetContents(dest_addr), plain_len, PageGetContents(dest_addr), &cipher_len);
Assert(plain_len == cipher_len);
PageSetEncrypt((Page)dest_addr);
}
}
static XLogRecPtr dw_copy_page(dw_context_t* dw_ctx, int buf_desc_id, bool* is_skipped)
{
/* we write different struct depend on hashbucket */
dw_batch_t* batch = NULL;
dw_batch_nohbkt_t* batch_nohbkt = NULL;
char* dest_addr = NULL;
BufferDesc* buf_desc = NULL;
XLogRecPtr page_lsn = InvalidXLogRecPtr;
Block block;
uint16 page_num;
uint32 buf_state;
errno_t rc;
*is_skipped = false;
buf_desc = GetBufferDescriptor(buf_desc_id);
buf_state = LockBufHdr(buf_desc);
if (!dw_buf_ckpt_needed(buf_state)) {
UnlockBufHdr(buf_desc, buf_state);
return page_lsn;
}
PinBuffer_Locked(buf_desc);
/* We must use a conditional lock acquisition here to avoid deadlock. If
* page_writer and double_write are enabled, only page_writer is allowed to
* flush the buffers. So the backends (BufferAlloc, FlushRelationBuffers,
* FlushDatabaseBuffers) are not allowed to flush the buffers, instead they
* will just wait for page_writer to flush the required buffer. In some cases
* (for example, btree split, heap_multi_insert), BufferAlloc will be called
* with holding exclusive lock on another buffer. So if we try to acquire
* the shared lock directly here (page_writer), it will block unconditionally
* and the backends will be blocked on the page_writer to flush the buffer,
* resulting in deadlock.
*/
if (!LWLockConditionalAcquire(buf_desc->content_lock, LW_SHARED)) {
UnpinBuffer(buf_desc, true);
*is_skipped = true;
return page_lsn;
}
dw_ctx->write_pos++;
if (dw_ctx->write_pos <= GET_DW_BATCH_DATA_PAGE_MAX) {
batch = (dw_batch_t*)dw_ctx->buf;
page_num = dw_ctx->write_pos;
} else {
batch = (dw_batch_t*)(dw_ctx->buf + (GET_DW_BATCH_DATA_PAGE_MAX + 1) * BLCKSZ);
page_num = dw_ctx->write_pos - GET_DW_BATCH_DATA_PAGE_MAX;
}
if (g_instance.ckpt_cxt_ctl->buffers_contain_hashbucket) {
batch->buf_tag[page_num - 1] = buf_desc->tag;
} else {
/* change struct to nohash bucket */
batch_nohbkt = (dw_batch_nohbkt_t*)batch;
batch_nohbkt->buf_tag[page_num - 1].blockNum = buf_desc->tag.blockNum;
batch_nohbkt->buf_tag[page_num - 1].forkNum = buf_desc->tag.forkNum;
batch_nohbkt->buf_tag[page_num - 1].rnode.dbNode = buf_desc->tag.rnode.dbNode;
batch_nohbkt->buf_tag[page_num - 1].rnode.spcNode = buf_desc->tag.rnode.spcNode;
batch_nohbkt->buf_tag[page_num - 1].rnode.relNode = buf_desc->tag.rnode.relNode;
}
dest_addr = (char*)batch + page_num * BLCKSZ;
block = BufHdrGetBlock(buf_desc);
rc = memcpy_s(dest_addr, BLCKSZ, block, BLCKSZ);
securec_check(rc, "\0", "\0");
LWLockRelease(buf_desc->content_lock);
UnpinBuffer(buf_desc, true);
page_lsn = PageGetLSN(dest_addr);
dw_encrypt_page(dest_addr);
dw_set_pg_checksum(dest_addr, buf_desc->tag.blockNum);
return page_lsn;
}
inline uint16 dw_batch_add_extra(uint16 page_num)
{
Assert(page_num <= GET_DW_DIRTY_PAGE_MAX);
if (page_num <= GET_DW_BATCH_DATA_PAGE_MAX) {
return page_num + DW_EXTRA_FOR_ONE_BATCH;
} else {
return page_num + DW_EXTRA_FOR_TWO_BATCH;
}
}
static void dw_assemble_batch(dw_context_t* dw_ctx, uint16 page_id, uint16 dwn)
{
dw_batch_t* batch = NULL;
uint16 first_batch_pages;
uint16 second_batch_pages;
if (dw_ctx->write_pos > GET_DW_BATCH_DATA_PAGE_MAX) {
first_batch_pages = GET_DW_BATCH_DATA_PAGE_MAX;
second_batch_pages = dw_ctx->write_pos - GET_DW_BATCH_DATA_PAGE_MAX;
} else {
first_batch_pages = dw_ctx->write_pos;
second_batch_pages = 0;
}
batch = (dw_batch_t*)dw_ctx->buf;
dw_prepare_page(batch, first_batch_pages, page_id, dwn);
/* tail of the first batch */
page_id = page_id + 1 + GET_REL_PGAENUM(batch->page_num);
batch = dw_batch_tail_page(batch);
dw_prepare_page(batch, second_batch_pages, page_id, dwn);
if (second_batch_pages == 0) {
return;
}
/* also head of the second batch, if second batch not empty, prepare its tail */
page_id = page_id + 1 + GET_REL_PGAENUM(batch->page_num);
batch = dw_batch_tail_page(batch);
dw_prepare_page(batch, 0, page_id, dwn);
}
static inline void dw_stat_flush(dw_stat_info* stat_info, uint32 page_to_write)
{
(void)pg_atomic_add_fetch_u64(&stat_info->total_writes, 1);
(void)pg_atomic_add_fetch_u64(&stat_info->total_pages, page_to_write);
if (page_to_write < DW_WRITE_STAT_LOWER_LIMIT) {
(void)pg_atomic_add_fetch_u64(&stat_info->low_threshold_writes, 1);
(void)pg_atomic_add_fetch_u64(&stat_info->low_threshold_pages, page_to_write);
} else if (page_to_write > GET_DW_BATCH_MAX) {
(void)pg_atomic_add_fetch_u64(&stat_info->high_threshold_writes, 1);
(void)pg_atomic_add_fetch_u64(&stat_info->high_threshold_pages, page_to_write);
}
}
static inline void dw_log_perform(dw_context_t* ctx, const char* phase, uint32 write_id, uint16 size)
{
ereport(DW_LOG_LEVEL,
(errmodule(MOD_DW),
errmsg("DW perform %s: write_id %u, file_head[dwn %hu, start %hu], total_pages %hu, size %hu",
phase,
write_id,
ctx->file_head->head.dwn,
ctx->file_head->start,
ctx->flush_page,
size)));
}
/**
* flush the copied page in the buffer into dw file, allocate the token for outside data file flushing
* @param dw_ctx double write context
* @param latest_lsn the latest lsn in the copied pages
*/
static void dw_flush(dw_context_t* dw_ctx, XLogRecPtr latest_lsn)
{
uint16 offset_page;
uint16 pages_to_write = 0;
uint16 write_pos = 0;
dw_file_head_t* file_head = NULL;
(void)LWLockAcquire(dw_ctx->flush_lock, LW_EXCLUSIVE);
Assert(dw_ctx->write_pos > 0);
file_head = dw_ctx->file_head;
pages_to_write = dw_batch_add_extra(dw_ctx->write_pos);
(void)dw_reset_if_need(dw_ctx, pages_to_write, false);
/* calculate it after checking file space, in case of updated by sync */
offset_page = file_head->start + dw_ctx->flush_page;
dw_assemble_batch(dw_ctx, offset_page, file_head->head.dwn);
if (!XLogRecPtrIsInvalid(latest_lsn)) {
XLogFlush(latest_lsn);
}
pgstat_report_waitevent(WAIT_EVENT_DW_WRITE);
dw_pwrite_file(dw_ctx->fd, dw_ctx->buf, (pages_to_write * BLCKSZ), (offset_page * BLCKSZ));
pgstat_report_waitevent(WAIT_EVENT_END);
dw_stat_flush(&dw_ctx->stat_info, pages_to_write);
dw_ctx->last_flush_page = dw_ctx->flush_page;
/* the tail of this flushed batch is the head of the next batch */
dw_ctx->flush_page += (pages_to_write - 1);
write_pos = dw_ctx->write_pos;
dw_ctx->write_pos = 0;
LWLockRelease(dw_ctx->flush_lock);
ereport(DW_LOG_LEVEL,
(errmodule(MOD_DW),
errmsg("DW flush: file_head[dwn %hu, start %hu], total_pages %hu, data_pages %hu, flushed_pages %hu",
dw_ctx->file_head->head.dwn,
dw_ctx->file_head->start,
dw_ctx->flush_page,
write_pos,
pages_to_write)));
}
void dw_perform(uint32 size)
{
uint16 batch_size;
dw_context_t* dw_ctx = &g_instance.dw_cxt;
XLogRecPtr latest_lsn = InvalidXLogRecPtr;
XLogRecPtr page_lsn;
uint32 write_id;
if (!dw_enabled()) {
/* Double write is not enabled, nothing to do. */
return;
}
if (SECUREC_UNLIKELY(!dw_ctx->initialized)) {
ereport(PANIC, (errmodule(MOD_DW), errmsg("Double write not initialized")));
}
if (SECUREC_UNLIKELY(dw_ctx->closed)) {
ereport(ERROR, (errmodule(MOD_DW), errmsg("Double write already closed")));
}
Assert(size > 0 && size <= GET_DW_DIRTY_PAGE_MAX);
batch_size = (uint16)size;
write_id = dw_ctx->stat_info.total_writes;
dw_log_perform(dw_ctx, "start", write_id, batch_size);
if (dw_ctx->write_pos != 0) {
ereport(WARNING, (errmodule(MOD_DW), errmsg("Double write exception ignored")));
}
dw_ctx->write_pos = 0;
for (uint16 i = 0; i < batch_size; i++) {
bool is_skipped = false;
page_lsn = dw_copy_page(dw_ctx, g_instance.ckpt_cxt_ctl->CkptBufferIds[i].buf_id, &is_skipped);
if (is_skipped) {
/*
* We couldn't acquire conditional lock on the buffer content_lock.
* So we mark it in buf_id_arr.
*/
g_instance.ckpt_cxt_ctl->CkptBufferIds[i].buf_id = DW_INVALID_BUFFER_ID;
continue;
}
if (XLByteLT(latest_lsn, page_lsn)) {
latest_lsn = page_lsn;
}
}
if (dw_ctx->write_pos > 0) {
dw_flush(dw_ctx, latest_lsn);
}
dw_log_perform(dw_ctx, "end", write_id, batch_size);
}
void dw_truncate()
{
dw_context_t* ctx = &g_instance.dw_cxt;
if (!dw_enabled()) {
/* Double write is not enabled, nothing to do. */
return;
}
gstrace_entry(GS_TRC_ID_dw_truncate);
ereport(DW_LOG_LEVEL,
(errmodule(MOD_DW),
errmsg("DW truncate start: file_head[dwn %hu, start %hu], total_pages %hu",
ctx->file_head->head.dwn,
ctx->file_head->start,
ctx->flush_page)));
/*
* If we can grab dw flush lock, truncate dw file for faster recovery.
*
* Note: This is only for recovery optimization. we can not block on
* dw flush lock, because, if we are checkpointer, pagewriter may be
* waiting for us to finish smgrsync before it can do a full recycle of dw file.
*/
if (!LWLockConditionalAcquire(ctx->flush_lock, LW_EXCLUSIVE)) {
ereport(LOG, (errmodule(MOD_DW), errmsg("Can not get dw flush lock and skip dw truncate for this time")));
return;
}
if (dw_reset_if_need(ctx, 0, true)) {
LWLockRelease(ctx->flush_lock);
}
gstrace_exit(GS_TRC_ID_dw_truncate);
ereport(LOG,
(errmodule(MOD_DW),
errmsg("DW truncate end: file_head[dwn %hu, start %hu], total_pages %hu",
ctx->file_head->head.dwn,
ctx->file_head->start,
ctx->flush_page)));
}
void dw_exit()
{
dw_context_t* ctx = &g_instance.dw_cxt;
if (!dw_enabled()) {
/* Double write is not enabled, nothing to do. */
return;
}
if (SECUREC_UNLIKELY(!ctx->initialized)) {
ereport(WARNING, (errmodule(MOD_DW), errmsg("Double write not initialized")));
return;
}
Assert(pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) == 0);
if (TAS(&ctx->closed)) {
ereport(WARNING, (errmodule(MOD_DW), errmsg("Double write already closed")));
return;
}
ereport(LOG, (errmodule(MOD_DW), errmsg("Double write exit")));
/* Do a final truncate before free resource. */
dw_truncate();
dw_free_resource(ctx);
ctx->initialized = 0;
}