forked from huawei/openGauss-server
1201 lines
44 KiB
C++
1201 lines
44 KiB
C++
/*
|
|
*
|
|
* bgwriter.cpp
|
|
*
|
|
* The background writer (bgwriter) is new as of Postgres 8.0. It attempts
|
|
* to keep regular backends from having to write out dirty shared buffers
|
|
* (which they would only do when needing to free a shared buffer to read in
|
|
* another page). In the best scenario all writes from shared buffers will
|
|
* be issued by the background writer process. However, regular backends are
|
|
* still empowered to issue writes if the bgwriter fails to maintain enough
|
|
* clean shared buffers.
|
|
*
|
|
* As of Postgres 9.2 the bgwriter no longer handles checkpoints.
|
|
*
|
|
* The bgwriter is started by the postmaster as soon as the startup subprocess
|
|
* finishes, or as soon as recovery begins if we are doing archive recovery.
|
|
* It remains alive until the postmaster commands it to terminate.
|
|
* Normal termination is by SIGTERM, which instructs the bgwriter to exit(0).
|
|
* Emergency termination is by SIGQUIT; like any backend, the bgwriter will
|
|
* simply abort and exit on SIGQUIT.
|
|
*
|
|
* If the bgwriter exits unexpectedly, the postmaster treats that the same
|
|
* as a backend crash: shared memory may be corrupted, so remaining backends
|
|
* should be killed by SIGQUIT and then a recovery cycle started.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/process/postmaster/bgwriter.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
|
|
#include "access/xlog_internal.h"
|
|
#include "access/double_write.h"
|
|
#include "libpq/pqsignal.h"
|
|
#include "miscadmin.h"
|
|
#include "pgstat.h"
|
|
#include "postmaster/bgwriter.h"
|
|
#include "postmaster/pagewriter.h"
|
|
#include "storage/buf/bufmgr.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/lock/lwlock.h"
|
|
#include "storage/proc.h"
|
|
#include "storage/shmem.h"
|
|
#include "storage/smgr.h"
|
|
#include "storage/spin.h"
|
|
#include "storage/standby.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/resowner.h"
|
|
#include "utils/timestamp.h"
|
|
#include "gssignal/gs_signal.h"
|
|
#include "replication/slot.h"
|
|
|
|
#define MIN(A, B) ((B) < (A) ? (B) : (A))
|
|
#define MAX(A, B) ((B) > (A) ? (B) : (A))
|
|
|
|
|
|
/*
|
|
* Multiplier to apply to BgWriterDelay when we decide to hibernate.
|
|
* (Perhaps this needs to be configurable?)
|
|
*/
|
|
#define HIBERNATE_FACTOR 50
|
|
|
|
/*
|
|
* Interval in which standby snapshots are logged into the WAL stream, in
|
|
* milliseconds.
|
|
*/
|
|
#define LOG_SNAPSHOT_INTERVAL_MS 15000
|
|
|
|
/*
|
|
* LSN and timestamp at which we last issued a LogStandbySnapshot(), to avoid
|
|
* doing so too often or repeatedly if there has been no other write activity
|
|
* in the system.
|
|
*/
|
|
static TimestampTz last_snapshot_ts;
|
|
static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr;
|
|
|
|
/* Signal handlers */
|
|
static void bgwriter_quickdie(SIGNAL_ARGS);
|
|
static void bgwriter_sighup_handler(SIGNAL_ARGS);
|
|
static void bgwriter_request_shutdown_handler(SIGNAL_ARGS);
|
|
static void bgwriter_sigusr1_handler(SIGNAL_ARGS);
|
|
extern void write_term_log(uint32 term);
|
|
|
|
/* incremental checkpoint bgwriter thread function */
|
|
const int MAX_THREAD_NAME_LEN = 128;
|
|
const int MILLISECOND_TO_MICROSECOND = 1000;
|
|
#define FULL_CKPT g_instance.ckpt_cxt_ctl->flush_all_dirty_page
|
|
|
|
static void candidate_buf_push(int buf_id, int thread_id);
|
|
static int64 get_thread_candidate_nums(int thread_id);
|
|
static uint32 get_candidate_buf(bool *contain_hashbucket);
|
|
static uint32 get_buf_form_dirty_queue(bool *contain_hashbucket);
|
|
|
|
static void setup_bgwriter_signalhook(void)
|
|
{
|
|
/*
|
|
* Reset some signals that are accepted by postmaster but not here
|
|
*/
|
|
(void)gspqsignal(SIGHUP, bgwriter_sighup_handler); /* set flag to read config file */
|
|
(void)gspqsignal(SIGINT, SIG_IGN);
|
|
(void)gspqsignal(SIGTERM, bgwriter_request_shutdown_handler); /* shutdown */
|
|
(void)gspqsignal(SIGQUIT, bgwriter_quickdie); /* hard crash time */
|
|
(void)gspqsignal(SIGALRM, SIG_IGN);
|
|
(void)gspqsignal(SIGPIPE, SIG_IGN);
|
|
(void)gspqsignal(SIGUSR1, bgwriter_sigusr1_handler);
|
|
(void)gspqsignal(SIGUSR2, SIG_IGN);
|
|
|
|
/*
|
|
* Reset some signals that are accepted by postmaster but not here
|
|
*/
|
|
(void)gspqsignal(SIGCHLD, SIG_DFL);
|
|
(void)gspqsignal(SIGTTIN, SIG_DFL);
|
|
(void)gspqsignal(SIGTTOU, SIG_DFL);
|
|
(void)gspqsignal(SIGCONT, SIG_DFL);
|
|
(void)gspqsignal(SIGWINCH, SIG_DFL);
|
|
|
|
/* We allow SIGQUIT (quickdie) at all times */
|
|
sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
|
|
}
|
|
|
|
static void bgwriter_handle_exceptions(WritebackContext wb_context, MemoryContext bgwriter_cxt)
|
|
{
|
|
/* Since not using PG_TRY, must reset error stack by hand */
|
|
t_thrd.log_cxt.error_context_stack = NULL;
|
|
|
|
/* Prevent interrupts while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/* Report the error to the server log */
|
|
EmitErrorReport();
|
|
|
|
/* abort async io, must before LWlock release */
|
|
AbortAsyncListIO();
|
|
|
|
/*
|
|
* These operations are really just a minimal subset of
|
|
* AbortTransaction(). We don't have very many resources to worry
|
|
* about in bgwriter, but we do have LWLocks, buffers, and temp files.
|
|
*/
|
|
LWLockReleaseAll();
|
|
AbortBufferIO();
|
|
UnlockBuffers();
|
|
/* buffer pins are released here: */
|
|
ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true);
|
|
/* we needn't bother with the other ResourceOwnerRelease phases */
|
|
AtEOXact_Buffers(false);
|
|
AtEOXact_SMgr();
|
|
AtEOXact_Files();
|
|
AtEOXact_HashTables(false);
|
|
|
|
/*
|
|
* Now return to normal top-level context and clear ErrorContext for
|
|
* next time.
|
|
*/
|
|
(void)MemoryContextSwitchTo(bgwriter_cxt);
|
|
FlushErrorState();
|
|
|
|
/* Flush any leaked data in the top-level context */
|
|
MemoryContextResetAndDeleteChildren(bgwriter_cxt);
|
|
|
|
/* re-initialize to avoid repeated errors causing problems */
|
|
WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after);
|
|
|
|
/* Now we can allow interrupts again */
|
|
RESUME_INTERRUPTS();
|
|
|
|
/*
|
|
* Sleep at least 1 second after any error. A write error is likely
|
|
* to be repeated, and we don't want to be filling the error logs as
|
|
* fast as we can.
|
|
*/
|
|
pg_usleep(1000000L);
|
|
|
|
/*
|
|
* Close all open files after any error. This is helpful on Windows,
|
|
* where holding deleted files open causes various strange errors.
|
|
* It's not clear we need it elsewhere, but shouldn't hurt.
|
|
*/
|
|
smgrcloseall();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Main entry point for bgwriter process
|
|
*
|
|
* This is invoked from AuxiliaryProcessMain, which has already created the
|
|
* basic execution environment, but not enabled signals yet.
|
|
*/
|
|
void BackgroundWriterMain(void)
|
|
{
|
|
sigjmp_buf local_sigjmp_buf;
|
|
MemoryContext bgwriter_context;
|
|
bool prev_hibernate = false;
|
|
WritebackContext wb_context;
|
|
|
|
t_thrd.role = BGWRITER;
|
|
|
|
ereport(LOG, (errmsg("bgwriter started")));
|
|
|
|
setup_bgwriter_signalhook();
|
|
|
|
/*
|
|
* We just started, assume there has been either a shutdown or
|
|
* end-of-recovery snapshot.
|
|
*/
|
|
last_snapshot_ts = GetCurrentTimestamp();
|
|
|
|
/*
|
|
* Create a resource owner to keep track of our resources (currently only
|
|
* buffer pins).
|
|
*/
|
|
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer", MEMORY_CONTEXT_STORAGE);
|
|
|
|
/*
|
|
* Create a memory context that we will do all our work in. We do this so
|
|
* that we can reset the context during error recovery and thereby avoid
|
|
* possible memory leaks. Formerly this code just ran in
|
|
* t_thrd.top_mem_cxt, but resetting that would be a really bad idea.
|
|
*/
|
|
bgwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt,
|
|
"Background Writer",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
MemoryContextSwitchTo(bgwriter_context);
|
|
|
|
WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after);
|
|
|
|
/*
|
|
* If an exception is encountered, processing resumes here.
|
|
*
|
|
* See notes in postgres.c about the design of this coding.
|
|
*/
|
|
int curTryCounter;
|
|
int* oldTryCounter = NULL;
|
|
if (sigsetjmp(local_sigjmp_buf, 1) != 0) {
|
|
gstrace_tryblock_exit(true, oldTryCounter);
|
|
bgwriter_handle_exceptions(wb_context, bgwriter_context);
|
|
|
|
/* Report wait end here, when there is no further possibility of wait */
|
|
pgstat_report_waitevent(WAIT_EVENT_END);
|
|
}
|
|
oldTryCounter = gstrace_tryblock_entry(&curTryCounter);
|
|
|
|
/* We can now handle ereport(ERROR) */
|
|
t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf;
|
|
|
|
/*
|
|
* Unblock signals (they were blocked when the postmaster forked us)
|
|
*/
|
|
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
|
|
(void)gs_signal_unblock_sigusr2();
|
|
|
|
/*
|
|
* Use the recovery target timeline ID during recovery
|
|
*/
|
|
if (RecoveryInProgress())
|
|
t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI();
|
|
|
|
/*
|
|
* Reset hibernation state after any error.
|
|
*/
|
|
prev_hibernate = false;
|
|
|
|
pgstat_report_appname("Background writer");
|
|
pgstat_report_activity(STATE_IDLE, NULL);
|
|
|
|
/*
|
|
* Loop forever
|
|
*/
|
|
for (;;) {
|
|
bool can_hibernate = false;
|
|
int rc;
|
|
|
|
/* Clear any already-pending wakeups */
|
|
ResetLatch(&t_thrd.proc->procLatch);
|
|
|
|
pgstat_report_activity(STATE_RUNNING, NULL);
|
|
|
|
if (t_thrd.bgwriter_cxt.got_SIGHUP) {
|
|
t_thrd.bgwriter_cxt.got_SIGHUP = false;
|
|
ProcessConfigFile(PGC_SIGHUP);
|
|
}
|
|
|
|
if (t_thrd.bgwriter_cxt.shutdown_requested) {
|
|
/*
|
|
* From here on, elog(ERROR) should end with exit(1), not send
|
|
* control back to the sigsetjmp block above
|
|
*/
|
|
u_sess->attr.attr_common.ExitOnAnyError = true;
|
|
/* Normal exit from the bgwriter is here */
|
|
proc_exit(0); /* done */
|
|
}
|
|
|
|
/*
|
|
* Do one cycle of dirty-buffer writing.
|
|
*/
|
|
can_hibernate = BgBufferSync(&wb_context);
|
|
|
|
/*
|
|
* Send off activity statistics to the stats collector
|
|
*/
|
|
pgstat_send_bgwriter();
|
|
|
|
if (FirstCallSinceLastCheckpoint()) {
|
|
/*
|
|
* After any checkpoint, close all smgr files. This is so we
|
|
* won't hang onto smgr references to deleted files indefinitely.
|
|
*/
|
|
smgrcloseall();
|
|
}
|
|
/*
|
|
* Log a new xl_running_xacts every now and then so replication can get
|
|
* into a consistent state faster (think of suboverflowed snapshots)
|
|
* and clean up resources (locks, KnownXids*) more frequently. The
|
|
* costs of this are relatively low, so doing it 4 times
|
|
* (LOG_SNAPSHOT_INTERVAL_MS) a minute seems fine.
|
|
*
|
|
* We assume the interval for writing xl_running_xacts is
|
|
* significantly bigger than BgWriterDelay, so we don't complicate the
|
|
* overall timeout handling but just assume we're going to get called
|
|
* often enough even if hibernation mode is active. It's not that
|
|
* important that log_snap_interval_ms is met strictly. To make sure
|
|
* we're not waking the disk up unneccesarily on an idle system we
|
|
* check whether there has been any WAL inserted since the last time
|
|
* we've logged a running xacts.
|
|
*
|
|
* We do this logging in the bgwriter as its the only process thats
|
|
* run regularly and returns to its mainloop all the
|
|
* time. E.g. Checkpointer, when active, is barely ever in its
|
|
* mainloop and thus makes it hard to log regularly.
|
|
*/
|
|
if (XLogStandbyInfoActive() && !RecoveryInProgress()) {
|
|
TimestampTz timeout = 0;
|
|
TimestampTz now = GetCurrentTimestamp();
|
|
timeout = TimestampTzPlusMilliseconds(last_snapshot_ts, LOG_SNAPSHOT_INTERVAL_MS);
|
|
|
|
/*
|
|
* only log if enough time has passed and some xlog record has been
|
|
* inserted.
|
|
*/
|
|
if (now >= timeout && !XLByteEQ(last_snapshot_lsn, GetXLogInsertRecPtr())) {
|
|
last_snapshot_lsn = LogStandbySnapshot();
|
|
last_snapshot_ts = now;
|
|
}
|
|
if (now >= timeout) {
|
|
LogCheckSlot();
|
|
}
|
|
|
|
if (now >= timeout) {
|
|
uint32 term_cur = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file,
|
|
g_instance.comm_cxt.localinfo_cxt.term_from_xlog);
|
|
write_term_log(term_cur);
|
|
g_instance.comm_cxt.localinfo_cxt.set_term = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sleep until we are signaled or BgWriterDelay has elapsed.
|
|
*
|
|
* Note: the feedback control loop in BgBufferSync() expects that we
|
|
* will call it every BgWriterDelay msec. While it's not critical for
|
|
* correctness that that be exact, the feedback loop might misbehave
|
|
* if we stray too far from that. Hence, avoid loading this process
|
|
* down with latch events that are likely to happen frequently during
|
|
* normal operation.
|
|
*/
|
|
pgstat_report_activity(STATE_IDLE, NULL);
|
|
rc = WaitLatch(&t_thrd.proc->procLatch,
|
|
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
|
|
u_sess->attr.attr_storage.BgWriterDelay /* ms */);
|
|
|
|
/*
|
|
* If no latch event and BgBufferSync says nothing's happening, extend
|
|
* the sleep in "hibernation" mode, where we sleep for much longer
|
|
* than bgwriter_delay says. Fewer wakeups save electricity. When a
|
|
* backend starts using buffers again, it will wake us up by setting
|
|
* our latch. Because the extra sleep will persist only as long as no
|
|
* buffer allocations happen, this should not distort the behavior of
|
|
* BgBufferSync's control loop too badly; essentially, it will think
|
|
* that the system-wide idle interval didn't exist.
|
|
*
|
|
* There is a race condition here, in that a backend might allocate a
|
|
* buffer between the time BgBufferSync saw the alloc count as zero
|
|
* and the time we call StrategyNotifyBgWriter. While it's not
|
|
* critical that we not hibernate anyway, we try to reduce the odds of
|
|
* that by only hibernating when BgBufferSync says nothing's happening
|
|
* for two consecutive cycles. Also, we mitigate any possible
|
|
* consequences of a missed wakeup by not hibernating forever.
|
|
*/
|
|
if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate) {
|
|
/* Ask for notification at next buffer allocation */
|
|
StrategyNotifyBgWriter(t_thrd.proc->pgprocno);
|
|
/* Sleep ... */
|
|
rc = WaitLatch(&t_thrd.proc->procLatch,
|
|
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
|
|
u_sess->attr.attr_storage.BgWriterDelay * HIBERNATE_FACTOR);
|
|
/* Reset the notification request in case we timed out */
|
|
StrategyNotifyBgWriter(-1);
|
|
}
|
|
|
|
/*
|
|
* Emergency bailout if postmaster has died. This is to avoid the
|
|
* necessity for manual cleanup of all postmaster children.
|
|
*/
|
|
if (rc & WL_POSTMASTER_DEATH)
|
|
gs_thread_exit(1);
|
|
|
|
prev_hibernate = can_hibernate;
|
|
}
|
|
}
|
|
|
|
/* --------------------------------
|
|
* signal handler routines
|
|
* --------------------------------
|
|
*/
|
|
/*
|
|
* bg_quickdie() occurs when signalled SIGQUIT by the postmaster.
|
|
*
|
|
* Some backend has bought the farm,
|
|
* so we need to stop what we're doing and exit.
|
|
*/
|
|
static void bgwriter_quickdie(SIGNAL_ARGS)
|
|
{
|
|
gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL);
|
|
|
|
/*
|
|
* We DO NOT want to run proc_exit() callbacks -- we're here because
|
|
* shared memory may be corrupted, so we don't want to try to clean up our
|
|
* transaction. Just nail the windows shut and get out of town. Now that
|
|
* there's an atexit callback to prevent third-party code from breaking
|
|
* things by calling exit() directly, we have to reset the callbacks
|
|
* explicitly to make this work as intended.
|
|
*/
|
|
on_exit_reset();
|
|
|
|
/*
|
|
* Note we do exit(2) not exit(0). This is to force the postmaster into a
|
|
* system reset cycle if some idiot DBA sends a manual SIGQUIT to a random
|
|
* backend. This is necessary precisely because we don't clean up our
|
|
* shared memory state. (The "dead man switch" mechanism in pmsignal.c
|
|
* should ensure the postmaster sees this as a crash, too, but no harm in
|
|
* being doubly sure.)
|
|
*/
|
|
exit(2);
|
|
}
|
|
|
|
/* SIGHUP: set flag to re-read config file at next convenient time */
|
|
static void bgwriter_sighup_handler(SIGNAL_ARGS)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
t_thrd.bgwriter_cxt.got_SIGHUP = true;
|
|
|
|
if (t_thrd.proc)
|
|
SetLatch(&t_thrd.proc->procLatch);
|
|
|
|
errno = save_errno;
|
|
}
|
|
|
|
/* SIGTERM: set flag to shutdown and exit */
|
|
static void bgwriter_request_shutdown_handler(SIGNAL_ARGS)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
t_thrd.bgwriter_cxt.shutdown_requested = true;
|
|
|
|
if (t_thrd.proc)
|
|
SetLatch(&t_thrd.proc->procLatch);
|
|
|
|
errno = save_errno;
|
|
}
|
|
|
|
/* SIGUSR1: used for latch wakeups */
|
|
static void bgwriter_sigusr1_handler(SIGNAL_ARGS)
|
|
{
|
|
int save_errno = errno;
|
|
|
|
latch_sigusr1_handler();
|
|
|
|
errno = save_errno;
|
|
}
|
|
|
|
bool IsBgwriterProcess(void)
|
|
{
|
|
return (t_thrd.role == BGWRITER);
|
|
}
|
|
|
|
/* bgwriter view function */
|
|
Datum bgwriter_view_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 bgwriter_view_get_actual_flush_num()
|
|
{
|
|
return Int64GetDatum(g_instance.bgwriter_cxt.bgwriter_actual_total_flush);
|
|
}
|
|
|
|
Datum bgwriter_view_get_last_flush_num()
|
|
{
|
|
int last_flush_num = 0;
|
|
for (int i = 0; i < g_instance.bgwriter_cxt.bgwriter_num; i++) {
|
|
if (g_instance.bgwriter_cxt.bgwriter_procs[i].proc != NULL) {
|
|
last_flush_num += g_instance.bgwriter_cxt.bgwriter_procs[i].thread_last_flush;
|
|
}
|
|
}
|
|
return Int32GetDatum(last_flush_num);
|
|
}
|
|
|
|
Datum bgwriter_view_get_candidate_nums()
|
|
{
|
|
int candidate_num = get_curr_candidate_nums();
|
|
return Int32GetDatum(candidate_num);
|
|
}
|
|
|
|
Datum bgwriter_view_get_num_candidate_list()
|
|
{
|
|
return Int64GetDatum(g_instance.bgwriter_cxt.get_buf_num_candidate_list);
|
|
}
|
|
|
|
Datum bgwriter_view_get_num_clock_sweep()
|
|
{
|
|
return Int64GetDatum(g_instance.bgwriter_cxt.get_buf_num_clock_sweep);
|
|
}
|
|
|
|
const incre_ckpt_view_col g_bgwriter_view_col[INCRE_CKPT_BGWRITER_VIEW_COL_NUM] = {
|
|
{"node_name", TEXTOID, bgwriter_view_get_node_name},
|
|
{"bgwr_actual_flush_total_num", INT8OID, bgwriter_view_get_actual_flush_num},
|
|
{"bgwr_last_flush_num", INT4OID, bgwriter_view_get_last_flush_num},
|
|
{"candidate_slots", INT4OID, bgwriter_view_get_candidate_nums},
|
|
{"get_buffer_from_list", INT8OID, bgwriter_view_get_num_candidate_list},
|
|
{"get_buf_clock_sweep", INT8OID, bgwriter_view_get_num_clock_sweep}};
|
|
|
|
uint32 incre_ckpt_bgwriter_flush_dirty_page(WritebackContext wb_context, int thread_id,
|
|
const CkptSortItem *dirty_buf_list, int start, int batch_num)
|
|
{
|
|
uint32 num_actual_flush = 0;
|
|
uint32 candidates = 0;
|
|
int buf_id_start = g_instance.bgwriter_cxt.bgwriter_procs[thread_id].buf_id_start;
|
|
int buf_id_end = buf_id_start + g_instance.bgwriter_cxt.bgwriter_procs[thread_id].cand_list_size;
|
|
|
|
for (int i = start; i < start + batch_num; i++) {
|
|
uint32 buf_state;
|
|
uint32 sync_state;
|
|
BufferDesc *buf_desc = NULL;
|
|
int buf_id = dirty_buf_list[i].buf_id;
|
|
|
|
if (buf_id == DW_INVALID_BUFFER_ID) {
|
|
continue;
|
|
}
|
|
buf_desc = GetBufferDescriptor(buf_id);
|
|
buf_state = LockBufHdr(buf_desc);
|
|
|
|
if ((buf_state & BM_CHECKPOINT_NEEDED) && (buf_state & BM_DIRTY)) {
|
|
UnlockBufHdr(buf_desc, buf_state);
|
|
|
|
sync_state = SyncOneBuffer(buf_id, false, &wb_context, true);
|
|
if (!(sync_state & BUF_WRITTEN)) {
|
|
clean_buf_need_flush_flag(buf_desc);
|
|
} else {
|
|
num_actual_flush++;
|
|
}
|
|
|
|
if (buf_id >= buf_id_start && buf_id < buf_id_end) {
|
|
buf_state = pg_atomic_read_u32(&buf_desc->state);
|
|
if (BUF_STATE_GET_REFCOUNT(buf_state) > 0) {
|
|
continue;
|
|
}
|
|
if (g_instance.bgwriter_cxt.candidate_free_map[buf_id] == false) {
|
|
buf_state = LockBufHdr(buf_desc);
|
|
if (g_instance.bgwriter_cxt.candidate_free_map[buf_id] == false) {
|
|
if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
|
|
!(buf_state & BM_DIRTY)) {
|
|
candidate_buf_push(buf_id, thread_id);
|
|
candidates++;
|
|
g_instance.bgwriter_cxt.candidate_free_map[buf_id] = true;
|
|
}
|
|
}
|
|
UnlockBufHdr(buf_desc, buf_state);
|
|
}
|
|
}
|
|
} else {
|
|
buf_state &= (~BM_CHECKPOINT_NEEDED);
|
|
UnlockBufHdr(buf_desc, buf_state);
|
|
}
|
|
}
|
|
return num_actual_flush;
|
|
}
|
|
|
|
void incre_ckpt_bgwriter_flush_page_batch(WritebackContext wb_context, uint32 need_flush_num,
|
|
bool contain_hashbucket)
|
|
{
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
CkptSortItem *dirty_buf_list = bgwriter->dirty_buf_list;
|
|
int dw_batch_page_max = GET_DW_DIRTY_PAGE_MAX(contain_hashbucket);
|
|
int runs = (need_flush_num + dw_batch_page_max - 1) / dw_batch_page_max;
|
|
int num_actual_flush = 0;
|
|
|
|
qsort(dirty_buf_list, need_flush_num, sizeof(CkptSortItem), ckpt_buforder_comparator);
|
|
ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner);
|
|
|
|
/* Double write can only handle at most DW_DIRTY_PAGE_MAX at one time. */
|
|
for (int i = 0; i < runs; i++) {
|
|
/* Last batch, take the rest of the buffers */
|
|
int offset = i * dw_batch_page_max;
|
|
int batch_num = (i == runs - 1) ? (need_flush_num - offset) : dw_batch_page_max;
|
|
uint32 flush_num;
|
|
|
|
bgwriter->thrd_dw_cxt.contain_hashbucket = contain_hashbucket;
|
|
bgwriter->thrd_dw_cxt.dw_page_idx = -1;
|
|
dw_perform_batch_flush(batch_num, dirty_buf_list + offset, &bgwriter->thrd_dw_cxt);
|
|
flush_num = incre_ckpt_bgwriter_flush_dirty_page(wb_context, thread_id, dirty_buf_list, offset, batch_num);
|
|
bgwriter->thrd_dw_cxt.dw_page_idx = -1;
|
|
num_actual_flush += flush_num;
|
|
}
|
|
bgwriter->thread_last_flush = num_actual_flush;
|
|
(void)pg_atomic_fetch_add_u64(&g_instance.bgwriter_cxt.bgwriter_actual_total_flush, num_actual_flush);
|
|
smgrcloseall();
|
|
}
|
|
|
|
void candidate_buf_init(void)
|
|
{
|
|
bool found_candidate_buf = false;
|
|
bool found_candidate_fm = false;
|
|
int buffer_num = g_instance.attr.attr_storage.NBuffers;
|
|
|
|
/*
|
|
* Each thread manages a part of the buffer. Several slots are reserved to
|
|
* prevent the thread first and last slots equals.
|
|
*/
|
|
g_instance.bgwriter_cxt.candidate_buffers = (Buffer *)
|
|
ShmemInitStruct("CandidateBuffers", buffer_num * sizeof(Buffer), &found_candidate_buf);
|
|
g_instance.bgwriter_cxt.candidate_free_map = (bool *)
|
|
ShmemInitStruct("CandidateFreeMap", buffer_num * sizeof(bool), &found_candidate_fm);
|
|
|
|
if (found_candidate_buf || found_candidate_fm) {
|
|
Assert(found_candidate_buf && found_candidate_fm);
|
|
} else {
|
|
MemsetHugeMem((char *)g_instance.bgwriter_cxt.candidate_buffers, buffer_num * sizeof(Buffer), -1);
|
|
MemsetHugeMem((char *)g_instance.bgwriter_cxt.candidate_free_map, buffer_num * sizeof(bool));
|
|
|
|
if (g_instance.bgwriter_cxt.bgwriter_procs != NULL) {
|
|
int thread_num = g_instance.bgwriter_cxt.bgwriter_num;
|
|
int avg_num = g_instance.attr.attr_storage.NBuffers / thread_num;
|
|
for (int i = 0; i < thread_num; i++) {
|
|
int start = avg_num * i;
|
|
int end = start + avg_num;
|
|
if (i == thread_num - 1) {
|
|
end += g_instance.attr.attr_storage.NBuffers % thread_num;
|
|
}
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].buf_id_start = start;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].cand_list_size = end - start;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].cand_buf_list =
|
|
&g_instance.bgwriter_cxt.candidate_buffers[start];
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].head = 0;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].tail = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const int MAX_BGWRITER_FLUSH_NUM = 1000 * DW_DIRTY_PAGE_MAX_FOR_NOHBK;
|
|
void incre_ckpt_bgwriter_cxt_init()
|
|
{
|
|
MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.increCheckPoint_context);
|
|
int thread_num = g_instance.attr.attr_storage.bgwriter_thread_num;
|
|
|
|
thread_num = thread_num > 0 ? thread_num : 1;
|
|
g_instance.bgwriter_cxt.bgwriter_num = thread_num;
|
|
g_instance.bgwriter_cxt.bgwriter_procs = (BgWriterProc *)palloc0(sizeof(BgWriterProc) * thread_num);
|
|
|
|
uint32 dirty_list_size = MAX_BGWRITER_FLUSH_NUM / thread_num;
|
|
int avg_num = g_instance.attr.attr_storage.NBuffers / thread_num;
|
|
for (int i = 0; i < thread_num; i++) {
|
|
int start = avg_num * i;
|
|
int end = start + avg_num;
|
|
if (i == thread_num - 1) {
|
|
end += g_instance.attr.attr_storage.NBuffers % thread_num;
|
|
}
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].buf_id_start = start;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].cand_list_size = end - start;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].cand_buf_list = &g_instance.bgwriter_cxt.candidate_buffers[start];
|
|
|
|
/* bgwriter thread dw cxt init */
|
|
char *unaligned_buf = (char*)palloc0((DW_BUF_MAX_FOR_NOHBK + 1) * BLCKSZ);
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].thrd_dw_cxt.dw_buf = (char*)TYPEALIGN(BLCKSZ, unaligned_buf);
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].thrd_dw_cxt.dw_page_idx = -1;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].thrd_dw_cxt.contain_hashbucket = false;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].dirty_list_size = dirty_list_size;
|
|
g_instance.bgwriter_cxt.bgwriter_procs[i].dirty_buf_list =
|
|
(CkptSortItem *)palloc0(dirty_list_size * sizeof(CkptSortItem));
|
|
}
|
|
(void)MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
static void incre_ckpt_bgwriter_kill(int code, Datum arg)
|
|
{
|
|
int id = t_thrd.bgwriter_cxt.thread_id;
|
|
Assert(id >= 0 && id < g_instance.bgwriter_cxt.bgwriter_num);
|
|
|
|
/* Making sure that we mark our exit status */
|
|
g_instance.bgwriter_cxt.bgwriter_procs[id].thrd_dw_cxt.dw_page_idx = -1;
|
|
|
|
/* Decrements the current number of active bgwriter and reset it's PROC pointer. */
|
|
(void)pg_atomic_fetch_sub_u32(&g_instance.bgwriter_cxt.curr_bgwriter_num, 1);
|
|
g_instance.bgwriter_cxt.bgwriter_procs[id].proc = NULL;
|
|
return;
|
|
}
|
|
|
|
static int64 get_bgwriter_sleep_time()
|
|
{
|
|
uint64 now;
|
|
int64 time_diff;
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
|
|
/* If primary instance do full checkpoint and not the first bgwriter thread, can scan the dirty
|
|
* page queue, help the pagewriter thread finish the dirty page flush.
|
|
*/
|
|
if (FULL_CKPT && !RecoveryInProgress() && thread_id > 0) {
|
|
return 0;
|
|
}
|
|
|
|
now = get_time_ms();
|
|
if (t_thrd.bgwriter_cxt.next_flush_time > now) {
|
|
time_diff = t_thrd.bgwriter_cxt.next_flush_time - now;
|
|
} else {
|
|
time_diff = 0;
|
|
}
|
|
time_diff = MIN(time_diff, u_sess->attr.attr_storage.BgWriterDelay);
|
|
return time_diff;
|
|
}
|
|
|
|
/**
|
|
* @Description: incremental checkpoint buffer writer main function
|
|
*/
|
|
void incre_ckpt_background_writer_main(void)
|
|
{
|
|
sigjmp_buf localSigjmpBuf;
|
|
MemoryContext bgwriter_context;
|
|
char name[MAX_THREAD_NAME_LEN] = {0};
|
|
WritebackContext wb_context;
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
uint64 now;
|
|
|
|
t_thrd.role = BGWRITER;
|
|
|
|
setup_bgwriter_signalhook();
|
|
ereport(LOG, (errmodule(MOD_INCRE_BG), errmsg("bgwriter started, thread id is %d", thread_id)));
|
|
|
|
Assert(thread_id >= 0);
|
|
errno_t err_rc = snprintf_s(name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s%d", "bgwriter", thread_id);
|
|
securec_check_ss(err_rc, "", "");
|
|
|
|
/*
|
|
* Create a resource owner to keep track of our resources (currently only buffer pins).
|
|
*/
|
|
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, name, MEMORY_CONTEXT_STORAGE);
|
|
|
|
/*
|
|
* Create a memory context that we will do all our work in. We do this so
|
|
* that we can reset the context during error recovery and thereby avoid
|
|
* possible memory leaks. Formerly this code just ran in
|
|
* t_thrd.top_mem_cxt, but resetting that would be a really bad idea.
|
|
*/
|
|
bgwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt, name, ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
|
MemoryContextSwitchTo(bgwriter_context);
|
|
|
|
WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after);
|
|
on_shmem_exit(incre_ckpt_bgwriter_kill, (Datum)0);
|
|
|
|
if (sigsetjmp(localSigjmpBuf, 1) != 0) {
|
|
ereport(WARNING, (errmodule(MOD_INCRE_BG), errmsg("bgwriter exception occured.")));
|
|
bgwriter->thrd_dw_cxt.dw_page_idx = -1;
|
|
bgwriter_handle_exceptions(wb_context, bgwriter_context);
|
|
}
|
|
|
|
/* We can now handle ereport(ERROR) */
|
|
t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf;
|
|
|
|
/*
|
|
* Unblock signals (they were blocked when the postmaster forked us)
|
|
*/
|
|
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
|
|
(void)gs_signal_unblock_sigusr2();
|
|
|
|
/*
|
|
* Use the recovery target timeline ID during recovery
|
|
*/
|
|
if (RecoveryInProgress()) {
|
|
t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI();
|
|
}
|
|
|
|
now = get_time_ms();
|
|
t_thrd.bgwriter_cxt.next_flush_time = now + u_sess->attr.attr_storage.BgWriterDelay;
|
|
|
|
pgstat_report_appname("IncrBgWriter");
|
|
pgstat_report_activity(STATE_IDLE, NULL);
|
|
|
|
/* Loop forever */
|
|
for (;;) {
|
|
int rc;
|
|
bool contain_hashbucket = false;
|
|
uint32 need_flush_num = 0;
|
|
int64 sleep_time = 0;
|
|
|
|
if (t_thrd.bgwriter_cxt.got_SIGHUP) {
|
|
t_thrd.bgwriter_cxt.got_SIGHUP = false;
|
|
ProcessConfigFile(PGC_SIGHUP);
|
|
}
|
|
|
|
if (t_thrd.bgwriter_cxt.shutdown_requested) {
|
|
/* the first thread should exit last */
|
|
if (thread_id == 0) {
|
|
while (pg_atomic_read_u32(&g_instance.bgwriter_cxt.curr_bgwriter_num) > 1) {
|
|
pg_usleep(MILLISECOND_TO_MICROSECOND);
|
|
continue;
|
|
}
|
|
/*
|
|
* From here on, elog(ERROR) should end with exit(1), not send
|
|
* control back to the sigsetjmp block above
|
|
*/
|
|
ereport(LOG, (errmodule(MOD_INCRE_BG), errmsg("bgwriter thread shut down, id is %d", thread_id)));
|
|
u_sess->attr.attr_common.ExitOnAnyError = true;
|
|
/* Normal exit from the bgwriter is here */
|
|
proc_exit(0); /* done */
|
|
} else {
|
|
ereport(LOG, (errmodule(MOD_INCRE_BG), errmsg("bgwriter thread shut down, id is %d", thread_id)));
|
|
u_sess->attr.attr_common.ExitOnAnyError = true;
|
|
proc_exit(0);
|
|
}
|
|
}
|
|
|
|
pgstat_report_activity(STATE_IDLE, NULL);
|
|
sleep_time = get_bgwriter_sleep_time();
|
|
rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleep_time);
|
|
if (rc & WL_POSTMASTER_DEATH) {
|
|
gs_thread_exit(1);
|
|
}
|
|
|
|
/* Clear any already-pending wakeups */
|
|
ResetLatch(&t_thrd.proc->procLatch);
|
|
|
|
pgstat_report_activity(STATE_RUNNING, NULL);
|
|
|
|
now = get_time_ms();
|
|
t_thrd.bgwriter_cxt.next_flush_time = now + u_sess->attr.attr_storage.BgWriterDelay;
|
|
|
|
if (get_curr_candidate_nums() == (uint32)g_instance.attr.attr_storage.NBuffers) {
|
|
continue;
|
|
}
|
|
/*
|
|
* When the primary instance do full checkpoint, the first thread remain scan the
|
|
* buffer pool to maintain the candidate buffer list, other threads scan the dirty
|
|
* page queue and flush pages in sequence.
|
|
* Standby redo the checkpoint xlog, which is similar to full checkpoint, so the
|
|
* bgwirter thread only scan the buffer pool to maintain the candidate buffer list.
|
|
*/
|
|
if (FULL_CKPT && !RecoveryInProgress() && thread_id > 0) {
|
|
need_flush_num = get_buf_form_dirty_queue(&contain_hashbucket);
|
|
} else {
|
|
need_flush_num = get_candidate_buf(&contain_hashbucket);
|
|
}
|
|
|
|
if (need_flush_num == 0) {
|
|
continue;
|
|
}
|
|
|
|
incre_ckpt_bgwriter_flush_page_batch(wb_context, need_flush_num, contain_hashbucket);
|
|
}
|
|
}
|
|
|
|
int get_bgwriter_thread_id(void)
|
|
{
|
|
if (t_thrd.bgwriter_cxt.thread_id != -1) {
|
|
return t_thrd.bgwriter_cxt.thread_id;
|
|
}
|
|
|
|
/*
|
|
* The first bgwriter thread start, will be placed in the writer_proc slot in order. Some
|
|
* condition, some bgwriter thread exit, It must be placed in the corresponding slot.
|
|
*/
|
|
int id = pg_atomic_fetch_add_u32(&g_instance.bgwriter_cxt.curr_bgwriter_num, 1);
|
|
if (g_instance.bgwriter_cxt.bgwriter_procs[id].proc == NULL) {
|
|
g_instance.bgwriter_cxt.bgwriter_procs[id].proc = t_thrd.proc;
|
|
t_thrd.bgwriter_cxt.thread_id = id;
|
|
} else {
|
|
for (int i = 0; i < g_instance.bgwriter_cxt.bgwriter_num; i++) {
|
|
void *expected = NULL;
|
|
if (pg_atomic_compare_exchange_uintptr(
|
|
(uintptr_t *)&g_instance.bgwriter_cxt.bgwriter_procs[i].proc,
|
|
(uintptr_t *)&expected, (uintptr_t)t_thrd.proc)) {
|
|
t_thrd.bgwriter_cxt.thread_id = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert(t_thrd.bgwriter_cxt.thread_id >= 0 && t_thrd.bgwriter_cxt.thread_id < g_instance.bgwriter_cxt.bgwriter_num);
|
|
return t_thrd.bgwriter_cxt.thread_id;
|
|
}
|
|
|
|
const float GAP_PERCENT = 0.15;
|
|
static uint32 get_bgwriter_flush_num()
|
|
{
|
|
int buffer_num = g_instance.attr.attr_storage.NBuffers;
|
|
double percent_target = u_sess->attr.attr_storage.candidate_buf_percent_target;
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
uint32 dirty_list_size = g_instance.bgwriter_cxt.bgwriter_procs[thread_id].dirty_list_size;
|
|
uint32 cur_candidate_num;
|
|
uint32 total_target;
|
|
uint32 high_water_mark;
|
|
uint32 flush_num = 0;
|
|
uint32 min_io = DW_DIRTY_PAGE_MAX_FOR_NOHBK;
|
|
uint32 max_io = calculate_thread_max_flush_num(false);
|
|
|
|
total_target = buffer_num * percent_target;
|
|
high_water_mark = buffer_num * (percent_target + GAP_PERCENT);
|
|
cur_candidate_num = get_curr_candidate_nums();
|
|
|
|
/* If the slots are sufficient, the standby DN does not need to flush too many pages. */
|
|
if (RecoveryInProgress() && cur_candidate_num >= total_target / 2) {
|
|
max_io = max_io / 2;
|
|
}
|
|
|
|
/* max_io need greater than one batch flush num, and need less than the dirty list size */
|
|
max_io = MAX(max_io / g_instance.bgwriter_cxt.bgwriter_num, DW_DIRTY_PAGE_MAX_FOR_NOHBK);
|
|
max_io = MIN(max_io, dirty_list_size);
|
|
|
|
if (cur_candidate_num >= high_water_mark) {
|
|
flush_num = min_io; /* only flush one batch dirty page */
|
|
} else if (cur_candidate_num >= total_target) {
|
|
flush_num = min_io + (float)(high_water_mark - cur_candidate_num) /
|
|
(float)(high_water_mark - total_target) * (max_io - min_io);
|
|
} else {
|
|
/* every time only flush max_io dirty pages */
|
|
flush_num = max_io;
|
|
}
|
|
|
|
ereport(DEBUG1, (errmodule(MOD_INCRE_BG),
|
|
errmsg("bgwriter flush_num num is %u, now candidate buf is %u", flush_num, cur_candidate_num)));
|
|
return flush_num;
|
|
}
|
|
|
|
const int MAX_SCAN_BATCH_NUM = 131072 * 10; /* 10GB buffers */
|
|
/**
|
|
* @Description: Scan n buffers in the BufferPool from start, put
|
|
* the unreferenced and not dirty page into the candidate list.
|
|
* @in: bgwirter thread dirty buf list pointer
|
|
* @in: bgwriter thread id
|
|
* @out: Return the number of dirty buffers and dirty buffer list and this batch buffer whether hashbucket is included.
|
|
*/
|
|
static uint32 get_candidate_buf(bool *contain_hashbucket)
|
|
{
|
|
uint32 need_flush_num = 0;
|
|
uint32 candidates = 0;
|
|
BufferDesc *buf_desc = NULL;
|
|
uint32 local_buf_state;
|
|
CkptSortItem* item = NULL;
|
|
uint32 max_flush_num = get_bgwriter_flush_num();
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
CkptSortItem *dirty_buf_list = bgwriter->dirty_buf_list;
|
|
int batch_scan_num = MIN(bgwriter->cand_list_size, MAX_SCAN_BATCH_NUM);
|
|
int start = MAX(bgwriter->buf_id_start, bgwriter->next_scan_loc);
|
|
int end = bgwriter->buf_id_start + bgwriter->cand_list_size;
|
|
|
|
end = MIN(start + batch_scan_num, end);
|
|
|
|
for (int buf_id = start; buf_id < end; buf_id++) {
|
|
if (FULL_CKPT && !RecoveryInProgress() && thread_id > 0) {
|
|
break;
|
|
}
|
|
|
|
buf_desc = GetBufferDescriptor(buf_id);
|
|
local_buf_state = pg_atomic_read_u32(&buf_desc->state);
|
|
|
|
/* Dirty read, pinned buffer, skip */
|
|
if (BUF_STATE_GET_REFCOUNT(local_buf_state) > 0) {
|
|
continue;
|
|
}
|
|
|
|
local_buf_state = LockBufHdr(buf_desc);
|
|
if (BUF_STATE_GET_REFCOUNT(local_buf_state) > 0) {
|
|
goto UNLOCK;
|
|
}
|
|
|
|
/* Not dirty, put directly into flushed candidates */
|
|
if (!(local_buf_state & BM_DIRTY)) {
|
|
if (g_instance.bgwriter_cxt.candidate_free_map[buf_id] == false) {
|
|
candidate_buf_push(buf_id, thread_id);
|
|
g_instance.bgwriter_cxt.candidate_free_map[buf_id] = true;
|
|
candidates++;
|
|
}
|
|
goto UNLOCK;
|
|
}
|
|
|
|
if ((!RecoveryInProgress() && XLogNeedsFlush(BufferGetLSN(buf_desc))) ||
|
|
need_flush_num >= max_flush_num) {
|
|
goto UNLOCK;
|
|
}
|
|
|
|
if (!(local_buf_state & BM_CHECKPOINT_NEEDED)) {
|
|
local_buf_state |= BM_CHECKPOINT_NEEDED;
|
|
item = &dirty_buf_list[need_flush_num++];
|
|
item->buf_id = buf_id;
|
|
item->tsId = buf_desc->tag.rnode.spcNode;
|
|
item->relNode = buf_desc->tag.rnode.relNode;
|
|
item->bucketNode = buf_desc->tag.rnode.bucketNode;
|
|
item->forkNum = buf_desc->tag.forkNum;
|
|
item->blockNum = buf_desc->tag.blockNum;
|
|
if (buf_desc->tag.rnode.bucketNode != InvalidBktId) {
|
|
*contain_hashbucket = true;
|
|
}
|
|
}
|
|
UNLOCK:
|
|
UnlockBufHdr(buf_desc, local_buf_state);
|
|
}
|
|
|
|
if (end >= bgwriter->buf_id_start + bgwriter->cand_list_size) {
|
|
bgwriter->next_scan_loc = bgwriter->buf_id_start;
|
|
} else {
|
|
bgwriter->next_scan_loc = end;
|
|
}
|
|
|
|
ereport(DEBUG1, (errmodule(MOD_INCRE_BG),
|
|
errmsg("get_candidate_buf %u buf, total candidate num is %u, thread num is %ld, sent %u to flush",
|
|
candidates, get_curr_candidate_nums(), get_thread_candidate_nums(thread_id), need_flush_num)));
|
|
return need_flush_num;
|
|
}
|
|
|
|
static uint32 get_buf_form_dirty_queue(bool *contain_hashbucket)
|
|
{
|
|
uint32 need_flush_num = 0;
|
|
BufferDesc *buf_desc = NULL;
|
|
uint32 local_buf_state;
|
|
CkptSortItem* item = NULL;
|
|
int thread_id = t_thrd.bgwriter_cxt.thread_id;
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
CkptSortItem *dirty_buf_list = bgwriter->dirty_buf_list;
|
|
XLogRecPtr redo = g_instance.ckpt_cxt_ctl->full_ckpt_redo_ptr;
|
|
uint64 dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head);
|
|
|
|
for (int i = 0; ;i++) {
|
|
Buffer buffer;
|
|
uint64 temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size;
|
|
volatile DirtyPageQueueSlot* slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc];
|
|
|
|
/* slot location is pre-occupied, but the buffer not set finish, need break. */
|
|
if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) {
|
|
break;
|
|
}
|
|
pg_read_barrier();
|
|
buffer = slot->buffer;
|
|
/* slot state is valid, buffer is invalid, the slot buffer set 0 when BufferAlloc or InvalidateBuffer */
|
|
if (BufferIsInvalid(buffer)) {
|
|
continue; /* this tempLoc maybe set 0 when remove dirty page */
|
|
}
|
|
buf_desc = GetBufferDescriptor(buffer - 1);
|
|
local_buf_state = LockBufHdr(buf_desc);
|
|
|
|
if (XLByteLT(redo, buf_desc->rec_lsn)) {
|
|
UnlockBufHdr(buf_desc, local_buf_state);
|
|
break;
|
|
}
|
|
|
|
if ((local_buf_state & BM_DIRTY) && !(local_buf_state & BM_CHECKPOINT_NEEDED)) {
|
|
local_buf_state |= BM_CHECKPOINT_NEEDED;
|
|
item = &dirty_buf_list[need_flush_num++];
|
|
item->buf_id = buffer - 1;
|
|
item->tsId = buf_desc->tag.rnode.spcNode;
|
|
item->relNode = buf_desc->tag.rnode.relNode;
|
|
item->bucketNode = buf_desc->tag.rnode.bucketNode;
|
|
item->forkNum = buf_desc->tag.forkNum;
|
|
item->blockNum = buf_desc->tag.blockNum;
|
|
if (buf_desc->tag.rnode.bucketNode != InvalidBktId) {
|
|
*contain_hashbucket = true;
|
|
}
|
|
}
|
|
UnlockBufHdr(buf_desc, local_buf_state);
|
|
if (need_flush_num >= GET_DW_DIRTY_PAGE_MAX(*contain_hashbucket)) {
|
|
break;
|
|
}
|
|
}
|
|
ereport(DEBUG1, (errmodule(MOD_INCRE_BG),
|
|
errmsg("get_candidate_buf_full_ckpt, sent %u to flush", need_flush_num)));
|
|
return need_flush_num;
|
|
}
|
|
|
|
/**
|
|
* @Description: Push buffer bufId to thread threadId's candidate list.
|
|
* @in: buf_id, buffer id which need push to the list
|
|
* @in: thread_id, bgwriter thread id
|
|
*/
|
|
static void candidate_buf_push(int buf_id, int thread_id)
|
|
{
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
uint32 list_size = bgwriter->cand_list_size;
|
|
uint32 tail_loc;
|
|
|
|
pg_memory_barrier();
|
|
volatile uint64 head = pg_atomic_read_u64(&bgwriter->head);
|
|
pg_memory_barrier();
|
|
volatile uint64 tail = pg_atomic_read_u64(&bgwriter->tail);
|
|
|
|
if (unlikely(tail - head >= list_size)) {
|
|
Assert(0);
|
|
return;
|
|
}
|
|
tail_loc = tail % list_size;
|
|
bgwriter->cand_buf_list[tail_loc] = buf_id;
|
|
(void)pg_atomic_fetch_add_u64(&bgwriter->tail, 1);
|
|
pg_memory_barrier();
|
|
}
|
|
|
|
/**
|
|
* @Description: Pop a buffer from the head of thread threadId's candidate list and store the buffer in buf_id.
|
|
* @in: buf_id, store the buffer id from the list.
|
|
* @in: thread_id, bgwriter thread id
|
|
*/
|
|
bool candidate_buf_pop(int *buf_id, int thread_id)
|
|
{
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
uint32 list_size = bgwriter->cand_list_size;
|
|
uint32 head_loc;
|
|
|
|
while (true) {
|
|
pg_memory_barrier();
|
|
uint64 head = pg_atomic_read_u64(&bgwriter->head);
|
|
pg_memory_barrier();
|
|
volatile uint64 tail = pg_atomic_read_u64(&bgwriter->tail);
|
|
|
|
if (unlikely(head >= tail)) {
|
|
return false; /* candidate list is empty */
|
|
}
|
|
|
|
head_loc = head % list_size;
|
|
*buf_id = bgwriter->cand_buf_list[head_loc];
|
|
if (pg_atomic_compare_exchange_u64(&bgwriter->head, &head, head + 1)) {
|
|
pg_memory_barrier();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int64 get_thread_candidate_nums(int thread_id)
|
|
{
|
|
BgWriterProc *bgwriter = &g_instance.bgwriter_cxt.bgwriter_procs[thread_id];
|
|
volatile uint64 head = pg_atomic_read_u64(&bgwriter->head);
|
|
pg_memory_barrier();
|
|
volatile uint64 tail = pg_atomic_read_u64(&bgwriter->tail);
|
|
int64 curr_cand_num = tail - head;
|
|
Assert(curr_cand_num >= 0);
|
|
return curr_cand_num;
|
|
}
|
|
|
|
/**
|
|
* @Description: Return a rough estimate of the current number of buffers in the candidate list.
|
|
*/
|
|
uint32 get_curr_candidate_nums(void)
|
|
{
|
|
uint32 currCandidates = 0;
|
|
for (int i = 0; i < g_instance.bgwriter_cxt.bgwriter_num; i++) {
|
|
BgWriterProc *curr_writer = &g_instance.bgwriter_cxt.bgwriter_procs[i];
|
|
if (curr_writer->proc != NULL) {
|
|
currCandidates += get_thread_candidate_nums(i);
|
|
}
|
|
}
|
|
return currCandidates;
|
|
}
|
|
|
|
void ckpt_shutdown_bgwriter()
|
|
{
|
|
if (!g_instance.attr.attr_storage.enableIncrementalCheckpoint) {
|
|
return;
|
|
}
|
|
/* Wait for all buffer writer threads to exit. */
|
|
while (pg_atomic_read_u32(&g_instance.bgwriter_cxt.curr_bgwriter_num) != 0) {
|
|
pg_usleep(MILLISECOND_TO_MICROSECOND);
|
|
}
|
|
}
|