diff --git a/Makefile b/Makefile index ba9ce1d..4083df2 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ OBJS = ptrack.o datapagemap.o engine.o $(WIN32RES) PGFILEDESC = "ptrack - block-level incremental backup engine" EXTENSION = ptrack -EXTVERSION = 2.2 -DATA = ptrack--2.1.sql ptrack--2.0--2.1.sql ptrack--2.1--2.2.sql +EXTVERSION = 2.3 +DATA = ptrack--2.1.sql ptrack--2.0--2.1.sql ptrack--2.1--2.2.sql ptrack--2.2--2.3.sql TAP_TESTS = 1 diff --git a/README.md b/README.md index 898df12..8042ce3 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ postgres=# CREATE EXTENSION ptrack; ## Configuration -The only one configurable option is `ptrack.map_size` (in MB). Default is `-1`, which means `ptrack` is turned off. In order to reduce number of false positives it is recommended to set `ptrack.map_size` to `1 / 1000` of expected `PGDATA` size (i.e. `1000` for a 1 TB database). +The only one configurable option is `ptrack.map_size` (in MB). Default is `0`, which means `ptrack` is turned off. In order to reduce number of false positives it is recommended to set `ptrack.map_size` to `1 / 1000` of expected `PGDATA` size (i.e. `1000` for a 1 TB database). To disable `ptrack` and clean up all remaining service files set `ptrack.map_size` to `0`. @@ -74,7 +74,7 @@ Usage example: postgres=# SELECT ptrack_version(); ptrack_version ---------------- - 2.2 + 2.3 (1 row) postgres=# SELECT ptrack_init_lsn(); @@ -115,15 +115,23 @@ Usually, you have to only install new version of `ptrack` and do `ALTER EXTENSIO Since version 2.2 we use a different algorithm for tracking changed pages. Thus, data recorded in the `ptrack.map` using pre 2.2 versions of `ptrack` is incompatible with newer versions. After extension upgrade and server restart old `ptrack.map` will be discarded with `WARNING` and initialized from the scratch. +#### Upgrading from 2.2.* to 2.3.*: + +* Stop your server +* Update ptrack binaries +* Remove global/ptrack.map.mmap if it exist in server data directory +* Start server +* Do `ALTER EXTENSION 'ptrack' UPDATE;`. + ## Limitations 1. You can only use `ptrack` safely with `wal_level >= 'replica'`. Otherwise, you can lose tracking of some changes if crash-recovery occurs, since [certain commands are designed not to write WAL at all if wal_level is minimal](https://www.postgresql.org/docs/12/populate.html#POPULATE-PITR), but we only durably flush `ptrack` map at checkpoint time. 2. The only one production-ready backup utility, that fully supports `ptrack` is [pg_probackup](https://github.com/postgrespro/pg_probackup). -3. Currently, you cannot resize `ptrack` map in runtime, only on postmaster start. Also, you will loose all tracked changes, so it is recommended to do so in the maintainance window and accompany this operation with full backup. See [TODO](#TODO) for details. +3. You cannot resize `ptrack` map in runtime, only on postmaster start. Also, you will loose all tracked changes, so it is recommended to do so in the maintainance window and accompany this operation with full backup. -4. You will need up to `ptrack.map_size * 3` of additional disk space, since `ptrack` uses two additional temporary files for durability purpose. See [Architecture section](#Architecture) for details. +4. You will need up to `ptrack.map_size * 2` of additional disk space, since `ptrack` uses additional temporary file for durability purpose. See [Architecture section](#Architecture) for details. ## Benchmarks @@ -131,11 +139,10 @@ Briefly, an overhead of using `ptrack` on TPS usually does not exceed a couple o ## Architecture -We use a single shared hash table in `ptrack`, which is mapped in memory from the file on disk using `mmap`. Due to the fixed size of the map there may be false positives (when some block is marked as changed without being actually modified), but not false negative results. However, these false postives may be completely eliminated by setting a high enough `ptrack.map_size`. +We use a single shared hash table in `ptrack`. Due to the fixed size of the map there may be false positives (when some block is marked as changed without being actually modified), but not false negative results. However, these false postives may be completely eliminated by setting a high enough `ptrack.map_size`. -All reads/writes are made using atomic operations on `uint64` entries, so the map is completely lockless during the normal PostgreSQL operation. Because we do not use locks for read/write access and cannot control `mmap` eviction back to disk, `ptrack` keeps a map (`ptrack.map`) since the last checkpoint intact and uses up to 2 additional temporary files: +All reads/writes are made using atomic operations on `uint64` entries, so the map is completely lockless during the normal PostgreSQL operation. Because we do not use locks for read/write access, `ptrack` keeps a map (`ptrack.map`) since the last checkpoint intact and uses up to 1 additional temporary file: -* working copy `ptrack.map.mmap` for doing `mmap` on it (there is a [TODO](#TODO) item); * temporary file `ptrack.map.tmp` to durably replace `ptrack.map` during checkpoint. Map is written on disk at the end of checkpoint atomically block by block involving the CRC32 checksum calculation that is checked on the next whole map re-read after crash-recovery or restart. @@ -165,8 +172,6 @@ Available test modes (`MODE`) are `basic` (default) and `paranoia` (per-block ch ### TODO -* Use POSIX `shm_open()` instead of `open()` to do not create an additional working copy of `ptrack` map file. * Should we introduce `ptrack.map_path` to allow `ptrack` service files storage outside of `PGDATA`? Doing that we will avoid patching PostgreSQL binary utilities to ignore `ptrack.map.*` files. * Can we resize `ptrack` map on restart but keep the previously tracked changes? -* Can we resize `ptrack` map dynamicaly? * Can we write a formal proof, that we never loose any modified page with `ptrack`? With TLA+? diff --git a/engine.c b/engine.c index 5685e9a..42fa65a 100644 --- a/engine.c +++ b/engine.c @@ -2,14 +2,14 @@ * engine.c * Block level incremental backup engine core * - * Copyright (c) 2019-2020, Postgres Professional + * Copyright (c) 2019-2022, Postgres Professional * * IDENTIFICATION * ptrack/engine.c * * INTERFACE ROUTINES (PostgreSQL side) * ptrackMapInit() --- allocate new shared ptrack_map - * ptrackMapAttach() --- attach to the existing ptrack_map + * ptrackCleanFiles() --- remove ptrack files * assign_ptrack_map_size() --- ptrack_map_size GUC assign callback * ptrack_walkdir() --- walk directory and mark all blocks of all * data files in ptrack_map @@ -88,160 +88,110 @@ ptrack_write_chunk(int fd, pg_crc32c *crc, char *chunk, size_t size) } /* - * Delete ptrack file and free the memory when ptrack is disabled. + * Delete ptrack files when ptrack is disabled. * - * This is performed by postmaster at start or by checkpointer, + * This is performed by postmaster at start, * so that there are no concurrent delete issues. */ -static void -ptrackCleanFilesAndMap(void) +void +ptrackCleanFiles(void) { char ptrack_path[MAXPGPATH]; - char ptrack_mmap_path[MAXPGPATH]; char ptrack_path_tmp[MAXPGPATH]; sprintf(ptrack_path, "%s/%s", DataDir, PTRACK_PATH); - sprintf(ptrack_mmap_path, "%s/%s", DataDir, PTRACK_MMAP_PATH); sprintf(ptrack_path_tmp, "%s/%s", DataDir, PTRACK_PATH_TMP); - elog(DEBUG1, "ptrack: clean files and map"); + elog(DEBUG1, "ptrack: clean map files"); if (ptrack_file_exists(ptrack_path_tmp)) durable_unlink(ptrack_path_tmp, LOG); if (ptrack_file_exists(ptrack_path)) durable_unlink(ptrack_path, LOG); - - if (ptrack_map != NULL) - { -#ifdef WIN32 - if (!UnmapViewOfFile(ptrack_map)) -#else - if (!munmap(ptrack_map, sizeof(ptrack_map))) -#endif - elog(LOG, "could not unmap ptrack_map"); - - ptrack_map = NULL; - } - - if (ptrack_file_exists(ptrack_mmap_path)) - durable_unlink(ptrack_mmap_path, LOG); } /* - * Copy PTRACK_PATH file to special temporary file PTRACK_MMAP_PATH used for mapping, - * or create new file, if there was no PTRACK_PATH file on disk. - * - * Map the content of PTRACK_MMAP_PATH file into memory structure 'ptrack_map' using mmap. + * Read ptrack map file into shared memory pointed by ptrack_map. + * This function is called only at startup, + * so data is read directly (without synchronization). */ -void -ptrackMapInit(void) +static bool +ptrackMapReadFromFile(const char *ptrack_path) { - int ptrack_fd; - pg_crc32c crc; - pg_crc32c *file_crc; - char ptrack_path[MAXPGPATH]; - char ptrack_mmap_path[MAXPGPATH]; - struct stat stat_buf; - bool is_new_map = true; - - elog(DEBUG1, "ptrack init"); - - /* We do it at server start, so the map must be not allocated yet. */ - Assert(ptrack_map == NULL); + elog(DEBUG1, "ptrack read map"); - if (ptrack_map_size == 0) - return; + /* Do actual file read */ + { + int ptrack_fd; + size_t readed; - sprintf(ptrack_path, "%s/%s", DataDir, PTRACK_PATH); - sprintf(ptrack_mmap_path, "%s/%s", DataDir, PTRACK_MMAP_PATH); + ptrack_fd = BasicOpenFile(ptrack_path, O_RDWR | PG_BINARY); -ptrack_map_reinit: + if (ptrack_fd < 0) + elog(ERROR, "ptrack read map: failed to open map file \"%s\": %m", ptrack_path); - /* Remove old PTRACK_MMAP_PATH file, if exists */ - if (ptrack_file_exists(ptrack_mmap_path)) - durable_unlink(ptrack_mmap_path, LOG); + readed = 0; + do + { + ssize_t last_readed; - if (stat(ptrack_path, &stat_buf) == 0 && - stat_buf.st_size != PtrackActualSize) - { - elog(WARNING, "ptrack init: unexpected \"%s\" file size %zu != " UINT64_FORMAT ", deleting", - ptrack_path, (Size) stat_buf.st_size, PtrackActualSize); - durable_unlink(ptrack_path, LOG); + /* + * Try to read as much as possible + * (linux guaranteed only 0x7ffff000 bytes in one read + * operation, see read(2)) + */ + last_readed = read(ptrack_fd, (char *) ptrack_map + readed, PtrackActualSize - readed); + + if (last_readed > 0) + { + readed += last_readed; + } + else if (last_readed == 0) + { + /* + * We don't try to read more that PtrackActualSize and + * file size was already checked in ptrackMapInit() + */ + elog(ERROR, "ptrack read map: unexpected end of file while reading map file \"%s\", expected to read %zu, but read only %zu bytes", + ptrack_path, PtrackActualSize, readed); + } + else if (last_readed < 0 && errno != EINTR) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("ptrack read map: could not read map file \"%s\": %m", ptrack_path))); + close(ptrack_fd); + return false; + } + } while (readed < PtrackActualSize); + + close(ptrack_fd); } - /* - * If on-disk PTRACK_PATH file is present and has expected size, copy it - * to read and restore state. - */ - if (stat(ptrack_path, &stat_buf) == 0) + /* Check PTRACK_MAGIC */ + if (strcmp(ptrack_map->magic, PTRACK_MAGIC) != 0) { - copy_file(ptrack_path, ptrack_mmap_path); - is_new_map = false; /* flag to check map file format and checksum */ - ptrack_fd = BasicOpenFile(ptrack_mmap_path, O_RDWR | PG_BINARY); + elog(WARNING, "ptrack read map: wrong map format of file \"%s\"", ptrack_path); + return false; } - else - /* Create new file for PTRACK_MMAP_PATH */ - ptrack_fd = BasicOpenFile(ptrack_mmap_path, O_RDWR | O_CREAT | PG_BINARY); - - if (ptrack_fd < 0) - elog(ERROR, "ptrack init: failed to open map file \"%s\": %m", ptrack_mmap_path); -#ifdef WIN32 + /* Check ptrack version inside old ptrack map */ + if (ptrack_map->version_num != PTRACK_MAP_FILE_VERSION_NUM) { - HANDLE mh = CreateFileMapping((HANDLE) _get_osfhandle(ptrack_fd), - NULL, - PAGE_READWRITE, - 0, - (DWORD) PtrackActualSize, - NULL); - - if (mh == NULL) - elog(ERROR, "ptrack init: failed to create file mapping: %m"); - - ptrack_map = (PtrackMap) MapViewOfFile(mh, FILE_MAP_ALL_ACCESS, 0, 0, 0); - if (ptrack_map == NULL) - { - CloseHandle(mh); - elog(ERROR, "ptrack init: failed to mmap ptrack file: %m"); - } + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("ptrack read map: map format version %d in the file \"%s\" is incompatible with file format of extension %d", + ptrack_map->version_num, ptrack_path, PTRACK_MAP_FILE_VERSION_NUM), + errdetail("Deleting file \"%s\" and reinitializing ptrack map.", ptrack_path))); + return false; } -#else - if (ftruncate(ptrack_fd, PtrackActualSize) < 0) - elog(ERROR, "ptrack init: failed to truncate file: %m"); - - ptrack_map = (PtrackMap) mmap(NULL, PtrackActualSize, - PROT_READ | PROT_WRITE, MAP_SHARED, - ptrack_fd, 0); - if (ptrack_map == MAP_FAILED) - elog(ERROR, "ptrack init: failed to mmap file: %m"); -#endif - if (!is_new_map) + /* Check CRC */ { - XLogRecPtr init_lsn; - - /* Check PTRACK_MAGIC */ - if (strcmp(ptrack_map->magic, PTRACK_MAGIC) != 0) - elog(ERROR, "ptrack init: wrong map format of file \"%s\"", ptrack_path); - - /* Check ptrack version inside old ptrack map */ - if (ptrack_map->version_num != PTRACK_VERSION_NUM) - { - ereport(WARNING, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("ptrack init: map format version %d in the file \"%s\" is incompatible with loaded version %d", - ptrack_map->version_num, ptrack_path, PTRACK_VERSION_NUM), - errdetail("Deleting file \"%s\" and reinitializing ptrack map.", ptrack_path))); - - /* Clean up everything and try again */ - ptrackCleanFilesAndMap(); - - is_new_map = true; - goto ptrack_map_reinit; - } + pg_crc32c crc; + pg_crc32c *file_crc; - /* Check CRC */ INIT_CRC32C(crc); COMP_CRC32C(crc, (char *) ptrack_map, PtrackCrcOffset); FIN_CRC32C(crc); @@ -252,88 +202,85 @@ ptrackMapInit(void) * Read ptrack map values without atomics during initialization, since * postmaster is the only user right now. */ - init_lsn = ptrack_map->init_lsn.value; - elog(DEBUG1, "ptrack init: crc %u, file_crc %u, init_lsn %X/%X", - crc, *file_crc, (uint32) (init_lsn >> 32), (uint32) init_lsn); + elog(DEBUG1, "ptrack read map: crc %u, file_crc %u, init_lsn %X/%X", + crc, *file_crc, (uint32) (ptrack_map->init_lsn.value >> 32), (uint32) ptrack_map->init_lsn.value); - /* TODO: Handle this error. Probably we can just recreate the file */ if (!EQ_CRC32C(*file_crc, crc)) { - ereport(ERROR, + ereport(WARNING, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("ptrack init: incorrect checksum of file \"%s\"", ptrack_path), - errhint("Delete \"%s\" and start the server again.", ptrack_path))); + errmsg("ptrack read map: incorrect checksum of file \"%s\"", ptrack_path), + errdetail("Deleting file \"%s\" and reinitializing ptrack map.", ptrack_path))); + return false; } } - else - { - memcpy(ptrack_map->magic, PTRACK_MAGIC, PTRACK_MAGIC_SIZE); - ptrack_map->version_num = PTRACK_VERSION_NUM; - } + return true; } /* - * Map must be already initialized by postmaster at start. - * mmap working copy of ptrack_map. + * Read PTRACK_PATH file into already allocated shared memory, check header and checksum + * or create new file, if there was no PTRACK_PATH file on disk. */ void -ptrackMapAttach(void) +ptrackMapInit(void) { - char ptrack_mmap_path[MAXPGPATH]; - int ptrack_fd; + char ptrack_path[MAXPGPATH]; struct stat stat_buf; + bool is_new_map = true; - elog(DEBUG1, "ptrack attach"); - - /* We do it at process start, so the map must be not allocated yet. */ - Assert(ptrack_map == NULL); + elog(DEBUG1, "ptrack init"); if (ptrack_map_size == 0) return; - sprintf(ptrack_mmap_path, "%s/%s", DataDir, PTRACK_MMAP_PATH); - if (!ptrack_file_exists(ptrack_mmap_path)) - { - elog(WARNING, "ptrack attach: '%s' file doesn't exist ", ptrack_mmap_path); - return; - } - - if (stat(ptrack_mmap_path, &stat_buf) == 0 && - stat_buf.st_size != PtrackActualSize) - elog(ERROR, "ptrack attach: ptrack_map_size doesn't match size of the file \"%s\"", ptrack_mmap_path); - - ptrack_fd = BasicOpenFile(ptrack_mmap_path, O_RDWR | PG_BINARY); - if (ptrack_fd < 0) - elog(ERROR, "ptrack attach: failed to open ptrack map file \"%s\": %m", ptrack_mmap_path); + sprintf(ptrack_path, "%s/%s", DataDir, PTRACK_PATH); - elog(DEBUG1, "ptrack attach: before mmap"); -#ifdef WIN32 + if (stat(ptrack_path, &stat_buf) == 0) { - HANDLE mh = CreateFileMapping((HANDLE) _get_osfhandle(ptrack_fd), - NULL, - PAGE_READWRITE, - 0, - (DWORD) PtrackActualSize, - NULL); - - if (mh == NULL) - elog(ERROR, "ptrack attach: failed to create file mapping: %m"); - - ptrack_map = (PtrackMap) MapViewOfFile(mh, FILE_MAP_ALL_ACCESS, 0, 0, 0); - if (ptrack_map == NULL) + elog(DEBUG3, "ptrack init: map \"%s\" detected, trying to load", ptrack_path); + if (stat_buf.st_size != PtrackActualSize) { - CloseHandle(mh); - elog(ERROR, "ptrack attach: failed to mmap ptrack file: %m"); + elog(WARNING, "ptrack init: unexpected \"%s\" file size %zu != " UINT64_FORMAT ", deleting", + ptrack_path, (Size) stat_buf.st_size, PtrackActualSize); + durable_unlink(ptrack_path, LOG); + } + else if (ptrackMapReadFromFile(ptrack_path)) + { + is_new_map = false; + } + else + { + /* + * ptrackMapReadFromFile failed + * this can be crc mismatch, version mismatch and other errors + * We treat it as non fatal and create new map in memory, + * that will be written on disk on checkpoint + */ + elog(WARNING, "ptrack init: broken map file \"%s\", deleting", + ptrack_path); + durable_unlink(ptrack_path, LOG); } } -#else - ptrack_map = (PtrackMap) mmap(NULL, PtrackActualSize, - PROT_READ | PROT_WRITE, MAP_SHARED, - ptrack_fd, 0); - if (ptrack_map == MAP_FAILED) - elog(ERROR, "ptrack attach: failed to mmap ptrack file: %m"); -#endif + + /* + * Initialyze memory for new map + */ + if (is_new_map) + { + memcpy(ptrack_map->magic, PTRACK_MAGIC, PTRACK_MAGIC_SIZE); + ptrack_map->version_num = PTRACK_MAP_FILE_VERSION_NUM; + ptrack_map->init_lsn.value = InvalidXLogRecPtr; + /* + * Fill entries with InvalidXLogRecPtr + * (InvalidXLogRecPtr is actually 0) + */ + memset(ptrack_map->entries, 0, PtrackContentNblocks * sizeof(pg_atomic_uint64)); + /* + * Last part of memory representation of ptrack_map (crc) is actually unused + * so leave it as it is + */ + } } /* @@ -365,7 +312,6 @@ ptrackCheckpoint(void) /* Delete ptrack_map and all related files, if ptrack was switched off */ if (ptrack_map_size == 0) { - ptrackCleanFilesAndMap(); return; } else if (ptrack_map == NULL) @@ -392,7 +338,7 @@ ptrackCheckpoint(void) * into the memory with mmap after a crash/restart. That way, we have to * write values taking into account all paddings/alignments. * - * Write both magic and varsion_num at once. + * Write both magic and version_num at once. */ /* @@ -519,20 +465,10 @@ assign_ptrack_map_size(int newval, void *extra) elog(DEBUG1, "assign_ptrack_map_size: MyProc %d newval %d ptrack_map_size " UINT64_FORMAT, MyProcPid, newval, ptrack_map_size); - /* - * XXX: for some reason assign_ptrack_map_size is called twice during the - * postmaster boot! First, it is always called with bootValue, so we use - * -1 as default value and no-op here. Next, it is called with the actual - * value from config. That way, we use 0 as an option for user to turn - * off ptrack and clean up all files. - */ - if (newval == -1) - return; - /* Delete ptrack_map and all related files, if ptrack was switched off. */ if (newval == 0) { - ptrackCleanFilesAndMap(); + ptrack_map_size = 0; return; } @@ -550,15 +486,6 @@ assign_ptrack_map_size(int newval, void *extra) elog(DEBUG1, "assign_ptrack_map_size: ptrack_map_size set to " UINT64_FORMAT, ptrack_map_size); - - /* Init map on postmaster start */ - if (!IsUnderPostmaster) - { - if (ptrack_map == NULL) - ptrackMapInit(); - } - else - ptrackMapAttach(); } } diff --git a/engine.h b/engine.h index 3386cc2..5daf69a 100644 --- a/engine.h +++ b/engine.h @@ -4,6 +4,7 @@ * header for ptrack map for tracking updates of relation's pages * * + * Copyright (c) 2019-2022, Postgres Professional * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -23,9 +24,6 @@ /* #include "utils/relcache.h" */ #include "access/hash.h" - -/* Working copy of ptrack.map */ -#define PTRACK_MMAP_PATH "global/ptrack.map.mmap" /* Persistent copy of ptrack.map to restore after crash */ #define PTRACK_PATH "global/ptrack.map" /* Used for atomical crash-safe update of ptrack.map */ @@ -36,6 +34,9 @@ * buffer size for disk writes. On fast NVMe SSD it gives * around 20% increase in ptrack checkpoint speed compared * to PTRACK_BUF_SIZE == 1000, i.e. 8 KB writes. + * (PTRACK_BUS_SIZE is a count of pg_atomic_uint64) + * + * NOTE: but POSIX defines _POSIX_SSIZE_MAX as 32767 (bytes) */ #define PTRACK_BUF_SIZE ((uint64) 8000) @@ -80,7 +81,7 @@ typedef PtrackMapHdr * PtrackMap; #define PtrackActualSize \ (offsetof(PtrackMapHdr, entries) + PtrackContentNblocks * sizeof(pg_atomic_uint64) + sizeof(pg_crc32c)) -/* CRC32 value offset in order to directly access it in the mmap'ed memory chunk */ +/* CRC32 value offset in order to directly access it in the shared memory chunk */ #define PtrackCrcOffset (PtrackActualSize - sizeof(pg_crc32c)) /* Block address 'bid' to hash. To get slot position in map should be divided @@ -102,7 +103,7 @@ extern int ptrack_map_size_tmp; extern void ptrackCheckpoint(void); extern void ptrackMapInit(void); -extern void ptrackMapAttach(void); +extern void ptrackCleanFiles(void); extern void assign_ptrack_map_size(int newval, void *extra); diff --git a/ptrack--2.2--2.3.sql b/ptrack--2.2--2.3.sql new file mode 100644 index 0000000..1a97e79 --- /dev/null +++ b/ptrack--2.2--2.3.sql @@ -0,0 +1,5 @@ +/* ptrack/ptrack--2.2--2.3.sql */ + +-- Complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION ptrack UPDATE;" to load this file.\ quit + diff --git a/ptrack.c b/ptrack.c index 66f5676..10b4d40 100644 --- a/ptrack.c +++ b/ptrack.c @@ -2,14 +2,13 @@ * ptrack.c * Block level incremental backup engine * - * Copyright (c) 2019-2020, Postgres Professional + * Copyright (c) 2019-2022, Postgres Professional * * IDENTIFICATION * ptrack/ptrack.c * * INTERFACE ROUTINES (PostgreSQL side) * ptrackMapInit() --- allocate new shared ptrack_map - * ptrackMapAttach() --- attach to the existing ptrack_map * assign_ptrack_map_size() --- ptrack_map_size GUC assign callback * ptrack_walkdir() --- walk directory and mark all blocks of all * data files in ptrack_map @@ -17,7 +16,7 @@ * * Currently ptrack has following public API methods: * - * # ptrack_version --- returns ptrack version string (2.0 currently). + * # ptrack_version --- returns ptrack version string (2.3 currently). * # ptrack_get_pagemapset('LSN') --- returns a set of changed data files with * bitmaps of changed blocks since specified LSN. * # ptrack_init_lsn --- returns LSN of the last ptrack map initialization. @@ -42,6 +41,7 @@ #include "replication/basebackup.h" #endif #include "storage/copydir.h" +#include "storage/ipc.h" #include "storage/lmgr.h" #if PG_VERSION_NUM >= 120000 #include "storage/md.h" @@ -59,9 +59,10 @@ PG_MODULE_MAGIC; PtrackMap ptrack_map = NULL; -uint64 ptrack_map_size; +uint64 ptrack_map_size = 0; int ptrack_map_size_tmp; +static shmem_startup_hook_type prev_shmem_startup_hook = NULL; static copydir_hook_type prev_copydir_hook = NULL; static mdwrite_hook_type prev_mdwrite_hook = NULL; static mdextend_hook_type prev_mdextend_hook = NULL; @@ -70,6 +71,7 @@ static ProcessSyncRequests_hook_type prev_ProcessSyncRequests_hook = NULL; void _PG_init(void); void _PG_fini(void); +static void ptrack_shmem_startup_hook(void); static void ptrack_copydir_hook(const char *path); static void ptrack_mdwrite_hook(RelFileNodeBackend smgr_rnode, ForkNumber forkno, BlockNumber blkno); @@ -103,15 +105,23 @@ _PG_init(void) "Sets the size of ptrack map in MB used for incremental backup (0 disabled).", NULL, &ptrack_map_size_tmp, - -1, - -1, 32 * 1024, /* limit to 32 GB */ - PGC_POSTMASTER, 0, + 0, 32 * 1024, /* limit to 32 GB */ + PGC_POSTMASTER, + GUC_UNIT_MB, NULL, assign_ptrack_map_size, NULL); + /* Request server shared memory */ + if (ptrack_map_size != 0) + RequestAddinShmemSpace(PtrackActualSize); + else + ptrackCleanFiles(); + /* Install hooks */ + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = ptrack_shmem_startup_hook; prev_copydir_hook = copydir_hook; copydir_hook = ptrack_copydir_hook; prev_mdwrite_hook = mdwrite_hook; @@ -129,12 +139,48 @@ void _PG_fini(void) { /* Uninstall hooks */ + shmem_startup_hook = prev_shmem_startup_hook; copydir_hook = prev_copydir_hook; mdwrite_hook = prev_mdwrite_hook; mdextend_hook = prev_mdextend_hook; ProcessSyncRequests_hook = prev_ProcessSyncRequests_hook; } +/* + * ptrack_shmem_startup hook: allocate or attach to shared memory. + */ +static void +ptrack_shmem_startup_hook(void) +{ + bool map_found; + + if (prev_shmem_startup_hook) + prev_shmem_startup_hook(); + + /* + * Create or attach to the shared memory state + */ + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + + if (ptrack_map_size != 0) + { + ptrack_map = ShmemInitStruct("ptrack map", + PtrackActualSize, + &map_found); + if (!map_found) + { + ptrackMapInit(); + elog(DEBUG1, "Shared memory for ptrack is ready"); + } + } + else + { + ptrack_map = NULL; + } + + LWLockRelease(AddinShmemInitLock); +} + /* * Ptrack follow up for copydir() routine. It parses database OID * and tablespace OID from path string. We do not need to recursively diff --git a/ptrack.control b/ptrack.control index ec0af9d..85ede4c 100644 --- a/ptrack.control +++ b/ptrack.control @@ -1,5 +1,5 @@ # ptrack extension comment = 'block-level incremental backup engine' -default_version = '2.2' +default_version = '2.3' module_pathname = '$libdir/ptrack' relocatable = true diff --git a/ptrack.h b/ptrack.h index d205115..a5c1f01 100644 --- a/ptrack.h +++ b/ptrack.h @@ -4,6 +4,7 @@ * header for ptrack map for tracking updates of relation's pages * * + * Copyright (c) 2019-2022, Postgres Professional * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -22,9 +23,11 @@ #include "utils/relcache.h" /* Ptrack version as a string */ -#define PTRACK_VERSION "2.2" +#define PTRACK_VERSION "2.3" /* Ptrack version as a number */ -#define PTRACK_VERSION_NUM 220 +#define PTRACK_VERSION_NUM 230 +/* Last ptrack version that changed map file format */ +#define PTRACK_MAP_FILE_VERSION_NUM 220 /* * Structure identifying block on the disk. diff --git a/run_tests.sh b/run_tests.sh index c52d9ed..1b4a693 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -56,6 +56,7 @@ pg_config # Build and install ptrack extension echo "############### Compiling and installing ptrack extension" cp --recursive ${PTRACK_SRC} ${PG_SRC}/contrib/ptrack +make USE_PGXS=1 --directory=${PG_SRC}/contrib/ptrack/ clean make USE_PGXS=1 PG_CPPFLAGS="-coverage" SHLIB_LINK="-coverage" --directory=${PG_SRC}/contrib/ptrack/ install if [ "${TEST_CASE}" = "tap" ]; then diff --git a/t/001_basic.pl b/t/001_basic.pl index 48e0f66..bdb1eca 100644 --- a/t/001_basic.pl +++ b/t/001_basic.pl @@ -28,7 +28,7 @@ BEGIN } } -plan tests => 24; +plan tests => 23; note('PostgreSQL 15 modules are used: ' . ($pg_15_modules ? 'yes' : 'no')); @@ -182,7 +182,6 @@ BEGIN # Check that we have lost everything ok(! -f $node->data_dir . "/global/ptrack.map", "ptrack.map should be cleaned up"); ok(! -f $node->data_dir . "/global/ptrack.map.tmp", "ptrack.map.tmp should be cleaned up"); -ok(! -f $node->data_dir . "/global/ptrack.map.mmap", "ptrack.map.mmap should be cleaned up"); ($res, $res_stdout, $res_stderr) = $node->psql("postgres", "SELECT ptrack_get_pagemapset('0/0')"); is($res, 3, 'errors out if ptrack is disabled');