Архитектура PostgreSQL. Часть 3. Инициализация бэкэнда

4ca792f406ed03cc555d5ef1aabf17ae.png

Приветствую!

Продолжаем изучение работы PostgreSQL. Мы остановились на моменте общей инициализации процесса. Сегодня мы рассмотрим процесс инициализации Postgres — входную точку до основного цикла.

PostgresMain — точка входа для процесса бэкэнда. Расположена в src/backend/tcop/postgres.c

Бэкэнд может работать как автономно (Standalone), так и в обычном (многопользовательском) режиме (из‑под Postmaster)

Описание работы ведется с учетом работы в многопользовательском режиме (IsUnderPostmasterравен true)

Работа в автономном режиме

Если работа идет автономно, то многие опции еще не инициализированы. Например, настройки GUC. Поэтому, перед самим циклом нужно проинициализировать все значения, но только если мы запустились не из-под Postmaster. 

Для этого во время инициализации из разных мест вызываются многие функции инициализации встречавшиеся ранее. Например, InitializeGUCOptions, но с проверкой, что не запустились из Postmaster. Для проверки этого есть переменная IsUnderPostmaster.

В коде есть много мест с паттерном

if (!IsUnderPostmaster) {
  someOperation();
}

Его можно интерпретировать как «выполнение функции someOperation должно происходить при запуске в автономном режиме»

Например, в самом начале точки входа есть такая проверка

void
PostgresMain(int argc, char *argv[],
			 const char *dbname,
			 const char *username)
{
  int			firstchar;
  StringInfoData input_message;
  sigjmp_buf	local_sigjmp_buf;
  volatile bool send_ready_for_query = true;
  bool		idle_in_transaction_timeout_enabled = false;
  bool		idle_session_timeout_enabled = false;

  /* Initialize startup process environment if necessary. */
  if (!IsUnderPostmaster)
      InitStandaloneProcess(argv[0]);
  
  // ...
}

Установка состояния работы

Как только начинаем работу в качестве бэкэнда, то входим в состояние InitProcessing.

Состояние нормальной работы (NormalProcessing)выставляется после инициализации процесса бэкэнда (InitPostgres)

ProcessingMode

Не только Postmaster имеет машину состояний, но и бэкэнд. Его машина состояний описывает перечислением ProcessingMode

typedef enum ProcessingMode
{
	BootstrapProcessing,		/* bootstrap creation of template database */
	InitProcessing,				/* initializing system */
	NormalProcessing			/* normal processing */
} ProcessingMode;

Всего есть 3 состояния:

  • BootstrapProcessing — создание БД из шаблона

  • InitProcessing — старт и инициализация бэкэнда

  • NormalProcessing — обычная работа

Для них также определены свои макросы:

extern ProcessingMode Mode;

#define IsBootstrapProcessingMode() (Mode == BootstrapProcessing)
#define IsInitProcessingMode()		(Mode == InitProcessing)
#define IsNormalProcessingMode()	(Mode == NormalProcessing)

#define GetProcessingMode() Mode

#define SetProcessingMode(mode) \
	do { \
		AssertArg((mode) == BootstrapProcessing || \
				  (mode) == InitProcessing || \
				  (mode) == NormalProcessing); \
		Mode = (mode); \
	} while(0)

Перечисление и макросы определены в src/include/miscadmin.h

Сигналы

После настраиваем свои обработчики сигналов:

  • SIGHUP — перезагрузка конфигурации;

  • SIGINT — отмена текущего запроса (Fast shutdown);

  • SIGTERM — отмена текущего запроса и выход (Smart shutdown);

  • SIGQUIT — быстрый выход (Immediate shutdown);

  • SIGPIPE — игнорируется, т.к. это безопасней чем прерывать «who-knows-what operation», а заметим мы это на следующей итерации цикла;

  • SIGUSR1 — имеет множественные значения, например, восстановление БД;

  • SIGUSR2 — игнорируется;

  • SIGFPE — ошибка операций с плавающей точкой.

Множественные значения на 1 сигнал

SIGUSR1 — сигнал со множеством различных значений. Но как определяется значение?

В Postgres для этого используется общая память.

Структура ProcSignalSlot предоставляет возможность передачи дополнительной информации для сигналов

typedef struct
{
	volatile pid_t pss_pid;
	volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
	pg_atomic_uint64 pss_barrierGeneration;
	pg_atomic_uint32 pss_barrierCheckMask;
	ConditionVariable pss_barrierCV;
} ProcSignalSlot;

Поле pss_signalFlags — массив булевских значений, представляющий причины для сигналов.

Сами причины определяются перечислением ProcSignalReason

typedef enum
{
	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
	PROCSIG_BARRIER,			/* global barrier interrupt  */
	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */

	/* Recovery conflict reasons */
	PROCSIG_RECOVERY_CONFLICT_DATABASE,
	PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
	PROCSIG_RECOVERY_CONFLICT_LOCK,
	PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,

	NUM_PROCSIGNALS				/* Must be last! */
} ProcSignalReason;

Для обработки SIGUSR1 используется следующим образом

// Обработчик SIGUSR1 срабатывает
void
procsignal_sigusr1_handler(SIGNAL_ARGS)
{
	int			save_errno = errno;

	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
		HandleCatchupInterrupt();

	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
		HandleNotifyInterrupt();

	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
		HandleParallelMessageInterrupt();

	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
		HandleWalSndInitStopping();

	if (CheckProcSignal(PROCSIG_BARRIER))
		HandleProcSignalBarrierInterrupt();

	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
		HandleLogMemoryContextInterrupt();

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE);

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK);

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);

	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
		RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);

	SetLatch(MyLatch);

	errno = save_errno;
}

// В обработчике последовательно проходятся все SigProcReason
// и для проверки вызывается эта функция
static bool
CheckProcSignal(ProcSignalReason reason)
{
	volatile ProcSignalSlot *slot = MyProcSignalSlot;

	if (slot != NULL)
	{
		/* Careful here --- don't clear flag if we haven't seen it set */
		if (slot->pss_signalFlags[reason])
		{
			slot->pss_signalFlags[reason] = false;
			return true;
		}
	}

	return false;
}

Все функции и определения находятся в файле src/backend/storage/ipc/procsignal.c

Postgres также использует SIGALRM. За его обработку отвечает модуль timeout src/backend/utils/misc/timeout.c

Модуль (ре)инициализируется: все таймауты сбрасываются и заново регистрируется функция обработки сигнала.

BaseInit

Функция BaseInit (src/backend/utils/init/postinit.c) вызывается каждым процессом в начале своей работы. Ее главная задача — инициализация общих модулей:

Модуль fd

Модуль для работы с файлами именуется fd

Типы и функции для работы с файлами объявляются в src/include/storage/fd.h

Для работы с файлами вместо «сырых» дескрипторов используется свой тип File, который на самом деле и есть int

typedef int File;

Также объявляются функции для работы с файлами. Например, объявляются функции для создания/открытия фалов

File PathNameOpenFile(const char *fileName, int fileFlags);
File PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
File OpenTemporaryFile(bool interXact);

Причина добавления отдельного модуля проста — максимальное количество открытых файлов. Во время работы открываются не только файлы таблиц, но и для сортировки, например.

Реализация содержится в src/backend/storage/file/fd.c

Для хранения используется LRU список внутренней структуры vfd

typedef struct vfd
{
	int			fd;				/* current FD, or VFD_CLOSED if none */
	unsigned short fdstate;		/* bitflags for VFD's state */
	ResourceOwner resowner;		/* owner, for automatic cleanup */
	File		nextFree;		/* link to next free VFD, if in freelist */
	File		lruMoreRecently;	/* doubly linked recency-of-use list */
	File		lruLessRecently;
	off_t		fileSize;		/* current size of file (0 if not temporary) */
	char	   *fileName;		/* name of file, or NULL for unused VFD */
	/* NB: fileName is malloc'd, and must be free'd when closing the VFD */
	int			fileFlags;		/* open(2) flags for (re)opening the file */
	mode_t		fileMode;		/* mode to pass to open(2) */
} Vfd;

/*
 * Virtual File Descriptor array pointer and size.  This grows as
 * needed.  'File' values are indexes into this array.
 * Note that VfdCache[0] is not a usable VFD, just a list header.
 */
static Vfd *VfdCache;
static Size SizeVfdCache = 0;

Сам массив представляет собой двусвязный список. Причем элемент с 0 индексом — особый: это всегда начало/конец списка и его дескриптор равен VFD_CLOSED.

При инициализации модуля вызывается функция InitFileAccess

/*
 * InitFileAccess --- initialize this module during backend startup
 *
 * This is called during either normal or standalone backend start.
 * It is *not* called in the postmaster.
 */
void
InitFileAccess(void)
{
	Assert(SizeVfdCache == 0);	/* call me only once */

	/* initialize cache header entry */
	VfdCache = (Vfd *) malloc(sizeof(Vfd));
	if (VfdCache == NULL)
		ereport(FATAL,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd));
	VfdCache->fd = VFD_CLOSED;

	SizeVfdCache = 1;

	/* register proc-exit hook to ensure temp files are dropped at exit */
	on_proc_exit(AtProcExit_Files, 0);
}

Функция для открытия файла и записи его в LRU

File
PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
{
	char	   *fnamecopy;
	File		file;
	Vfd		   *vfdP;
  
	/*
	 * We need a malloc'd copy of the file name; fail cleanly if no room.
	 */
	fnamecopy = strdup(fileName);
	if (fnamecopy == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));

	file = AllocateVfd();
	vfdP = &VfdCache[file];

	/* Close excess kernel FDs. */
	ReleaseLruFiles();

	vfdP->fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);

	if (vfdP->fd < 0)
	{
		int			save_errno = errno;

		FreeVfd(file);
		free(fnamecopy);
		errno = save_errno;
		return -1;
	}
	++nfile;

	vfdP->fileName = fnamecopy;
	/* Saved flags are adjusted to be OK for re-opening file */
	vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL);
	vfdP->fileMode = fileMode;
	vfdP->fileSize = 0;
	vfdP->fdstate = 0x0;
	vfdP->resowner = NULL;

	Insert(file);

	return file;
}

В случае, если мы не можем создать виртуальный файл, то присутствует возможность открытия файлов напрямую, но нужно уведомить модуль об этом

/* If you've really really gotta have a plain kernel FD, use this */
int	BasicOpenFile(const char *fileName, int fileFlags);
int	BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);

/* Use these for other cases, and also for long-lived BasicOpenFile FDs */
extern bool AcquireExternalFD(void);
extern void ReserveExternalFD(void);
extern void ReleaseExternalFD(void);

Модуль sync

Некоторые файлы общие для нескольких процессов. Если кто-то модифицирует файл, то надо уведомить других. Этим занимается модуль sync (src/backend/storage/sync/sync.c)

Опопвещения работают следующим образом:

  1. Регистрируются изменения в файлах

bool RegisterSyncRequest(const FileTag *ftag, 
                         SyncRequestType type,
                         bool retryOnError);

В нее передаются структуры FileTag и SyncRequestType

// Тип запроса синхронизации
typedef enum SyncRequestType
{
	SYNC_REQUEST,				/* schedule a call of sync function */
	SYNC_UNLINK_REQUEST,		/* schedule a call of unlink function */
	SYNC_FORGET_REQUEST,		/* forget all calls for a tag */
	SYNC_FILTER_REQUEST			/* forget all calls satisfying match fn */
} SyncRequestType;

// Идентификация файла и места для синхронизации
typedef struct FileTag
{
	int16		handler;		/* SyncRequestHandler value, saving space */
	int16		forknum;		/* ForkNumber, saving space */
	RelFileNode rnode;
	uint32		segno;
} FileTag;

// src/include/storage/relfilenode.h
typedef struct RelFileNode
{
	Oid			spcNode;		/* tablespace */
	Oid			dbNode;			/* database */
	Oid			relNode;		/* relation */
} RelFileNode;

Изменения регистрируются для Checkpointer процесса

  1. Во время чекпоинта вызывается функция синхронизации

void ProcessSyncRequests(void);

Модуль smgr

Модуль smgr (src/backend/storage/smgr/smgr.c) отвечает за все операции с файловой системой.

Работа с файловой системой ведется посредством структуры SMgrRelationData (src/include/storage/smgr.h)

typedef struct SMgrRelationData
{
	/* rnode is the hashtable lookup key, so it must be first! */
	RelFileNodeBackend smgr_rnode;	/* relation physical identifier */

	/* pointer to owning pointer, or NULL if none */
	struct SMgrRelationData **smgr_owner;

	/*
	 * The following fields are reset to InvalidBlockNumber upon a cache flush
	 * event, and hold the last known size for each fork.  This information is
	 * currently only reliable during recovery, since there is no cache
	 * invalidation for fork extension.
	 */
	BlockNumber smgr_targblock; /* current insertion target block */
	BlockNumber smgr_cached_nblocks[MAX_FORKNUM + 1];	/* last known size */

	/* additional public fields may someday exist here */

	/*
	 * Fields below here are intended to be private to smgr.c and its
	 * submodules.  Do not touch them from elsewhere.
	 */
	int			smgr_which;		/* storage manager selector */

	/*
	 * for md.c; per-fork arrays of the number of open segments
	 * (md_num_open_segs) and the segments themselves (md_seg_fds).
	 */
	int			md_num_open_segs[MAX_FORKNUM + 1];
	struct _MdfdVec *md_seg_fds[MAX_FORKNUM + 1];

	/* if unowned, list link in list of all unowned SMgrRelations */
	dlist_node	node;
} SMgrRelationData;

typedef SMgrRelationData *SMgrRelation;

Работа осуществляется посредством функций, использующих эту структуру. Например, для чтения из файла используется функция smgrread

/*
 *	smgrread() -- read a particular block from a relation into the supplied
 *				  buffer.
 *
 *		This routine is called from the buffer manager in order to
 *		instantiate pages in the shared buffer cache.  All storage managers
 *		return pages in the format that POSTGRES expects.
 */
void
smgrread(SMgrRelation reln, 
         ForkNumber forknum, 
         BlockNumber blocknum,
		 char *buffer);

Под капотом, используется абстракция хранилища и для каждого хранилища есть свой «провайдер». Провайдер описывается структурой f_smgr, являющийся таблицей функций.

typedef struct f_smgr
{
	void		(*smgr_init) (void);	/* may be NULL */
	void		(*smgr_shutdown) (void);	/* may be NULL */
	void		(*smgr_open) (SMgrRelation reln);
	void		(*smgr_close) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_create) (SMgrRelation reln, ForkNumber forknum,
								bool isRedo);
	bool		(*smgr_exists) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_unlink) (RelFileNodeBackend rnode, ForkNumber forknum,
								bool isRedo);
	void		(*smgr_extend) (SMgrRelation reln, ForkNumber forknum,
								BlockNumber blocknum, char *buffer, bool skipFsync);
	bool		(*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
								  BlockNumber blocknum);
	void		(*smgr_read) (SMgrRelation reln, ForkNumber forknum,
							  BlockNumber blocknum, char *buffer);
	void		(*smgr_write) (SMgrRelation reln, ForkNumber forknum,
							   BlockNumber blocknum, char *buffer, bool skipFsync);
	void		(*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
								   BlockNumber blocknum, BlockNumber nblocks);
	BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
	void		(*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
								  BlockNumber nblocks);
	void		(*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum);
} f_smgr;

Все известные провайдеры хранятся в массиве smgrsw

static const f_smgr smgrsw[] = {
	/* magnetic disk */
    // Реализация по умолчанию
    // Использует модуль fd
    // src/backend/storage/smgr/md.c
	{
		.smgr_init = mdinit,
		.smgr_shutdown = NULL,
		.smgr_open = mdopen,
		.smgr_close = mdclose,
		.smgr_create = mdcreate,
		.smgr_exists = mdexists,
		.smgr_unlink = mdunlink,
		.smgr_extend = mdextend,
		.smgr_prefetch = mdprefetch,
		.smgr_read = mdread,
		.smgr_write = mdwrite,
		.smgr_writeback = mdwriteback,
		.smgr_nblocks = mdnblocks,
		.smgr_truncate = mdtruncate,
		.smgr_immedsync = mdimmedsync,
	}
};

Все операции строятся следующим образом:

  1. По ID провайдера находится его таблица функций.

  2. Вызывается функция для совершения необходимой операции.

Чтение из файла реализуется таким образом

void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
		 char *buffer)
{
	smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);
}

Модуль bufmgr

Постоянное чтение с файловой системы будет довольно дорогим занятием. Для оптимизации используется буферизация — модуль bufmgr управляет страницами, считанными из файлов, и при необходимости сбрасывает их на диск.

Тип буфера определяется в src/include/storage/buf.h

/*
 * Buffer identifiers.
 *
 * Zero is invalid, positive is the index of a shared buffer (1..NBuffers),
 * negative is the index of a local buffer (-1 .. -NLocBuffer).
 */
typedef int Buffer;

#define InvalidBuffer	0

Сами операции с буфером объявляются в src/include/storage/bufmgr.h. Например, для чтения буфера объявляются функции

Buffer ReadBuffer(Relation reln, BlockNumber blockNum);
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum,
                          BlockNumber blockNum, ReadBufferMode mode,
                          BufferAccessStrategy strategy);
Buffer ReadBufferWithoutRelcache(RelFileNode rnode,
                                 ForkNumber forkNum, BlockNumber blockNum,
                                 ReadBufferMode mode, BufferAccessStrategy strategy);

Реализация располагается в src/backend/storage/buffer/bufmgr.c

Так как одна и та же страница может быть использована в нескольких процессах, то для отслеживания этого используется структура PrivateRefCountEntry

typedef struct PrivateRefCountEntry
{
	Buffer		buffer;
	int32		refcount;
} PrivateRefCountEntry;

Для хранения страниц используется массив PrivateRefCountArray

/* 64 bytes, about the size of a cache line on common systems */
#define REFCOUNT_ARRAY_ENTRIES 8

static struct PrivateRefCountEntry 
  PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES];

InitProcess

Теперь вызывается функция InitProcess. (src/backend/storage/lmgr/proc.c)

Она инициализирует специфичные для типа процесса (AutoVacuum, BgWorker, WalSender и т.д.) переменные и структуры.

Для отслеживания состояния приложения существует структура PROC_HDR

typedef struct PROC_HDR
{
    // ...
  
	/* Length of allProcs array */
	uint32		allProcCount;
	/* Head of list of free PGPROC structures */
	PGPROC	   *freeProcs;
	/* Head of list of autovacuum's free PGPROC structures */
	PGPROC	   *autovacFreeProcs;
	/* Head of list of bgworker free PGPROC structures */
	PGPROC	   *bgworkerFreeProcs;
	/* Head of list of walsender free PGPROC structures */
	PGPROC	   *walsenderFreeProcs;

    // ...
} PROC_HDR;

Указанные поля представляют списки свободных структур для каждого типа процесса. Структура PGPROCпредставляет состояние процесса.

При старте каждый процесс получает свою структуру из списка свободных и иницилизирует ее значениями по умолчанию (для ID — невалидные, для bool — false и т. д.)

Так как мы бэкэнд, то получаем свою структуру из списка freeProcs. Операция получения структуры — конкурентная, поэтому она исполняется с помощью блокировки.

SpinLockAcquire(ProcStructLock);

MyProc = *procgloballist;

if (MyProc != NULL)
{
    *procgloballist = (PGPROC *) MyProc->links.next;
    SpinLockRelease(ProcStructLock);
}
else
{
    /*
     * If we reach here, all the PGPROCs are in use.  This is one of the
     * possible places to detect "too many backends", so give the standard
     * error message.  XXX do we need to give a different failure message
     * in the autovacuum case?
     */
    SpinLockRelease(ProcStructLock);
    // ...
}

InitPostgres

После общей инициализации процесса, нужно провести инициализацию конкретную. Для этого вызывается функция InitPostgres (src/backend/utils/init/postinit.c)

Получение Id

На предыдущем шаге мы получили структуру нашего бэкэнда. Теперь необходимо присвоить ему собственный ID. Идентификаторы бэкэндов растут линейно и определяются индексом в массиве shmInvalBuffer

ProcState  *stateP = NULL;
SISeg	   *segP = shmInvalBuffer;

// ...

/* Look for a free entry in the procState array */
for (int index = 0; index < segP->lastBackend; index++)
{
    if (segP->procState[index].procPid == 0)	/* inactive slot? */
    {
        stateP = &segP->procState[index];
        break;
    }
}

// ...

MyBackendId = (stateP - &segP->procState[0]) + 1;

Сам этот массив используется, чтобы бэкэнды обменивались между собой информацией для инвалидации общего кеша

Дополнительно все бэкэнды регистрируются в списке для получения сигналов при закрытии приложения.

Регистрация таймаутов

Дальше происходит регистрация обработчиков таймаутов:

  • DEADLOCK_TIMEOUT— таймаут при получении блокировки, после которого проверяется дедлок;

  • STATEMENT_TIMEOUT— таймаут на каждое выражение;

  • LOCK_TIMEOUT— таймаут ожидания при получении блокировки (таблицы, строки или любого другого объекта БД);

  • IDLE_SESSION_TIMEOUT— таймаут неактивного сеанса;

  • IDLE_IN_TRANSACTION_SESSION_TIMEOUT— таймаут неактивного сеанса для транзакции;

  • CLIENT_CONNECTION_TIMEOUT— таймаут проверки подключения клиента.

Таймауты определяются структурой TimeoutId

/*
 * Identifiers for timeout reasons.  Note that in case multiple timeouts
 * trigger at the same time, they are serviced in the order of this enum.
 */
typedef enum TimeoutId
{
	/* Predefined timeout reasons */
	STARTUP_PACKET_TIMEOUT,
	DEADLOCK_TIMEOUT,
	LOCK_TIMEOUT,
	STATEMENT_TIMEOUT,
	STANDBY_DEADLOCK_TIMEOUT,
	STANDBY_TIMEOUT,
	STANDBY_LOCK_TIMEOUT,
	IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
	IDLE_SESSION_TIMEOUT,
	CLIENT_CONNECTION_CHECK_TIMEOUT,
	/* First user-definable timeout reason */
	USER_TIMEOUT,
	/* Maximum number of timeout reasons */
	MAX_TIMEOUTS = USER_TIMEOUT + 10
} TimeoutId;

Регистрируются таймауты функцией RegisterTimeout из модуля timeout

/* callback function signature */
typedef void (*timeout_handler_proc) (void);

TimeoutId RegisterTimeout(TimeoutId id, timeout_handler_proc handler);

Инициализация кеша

Для производительности Postgres использует кеш. Всего есть 3 типа кешей:

Реализация хэш-таблицы

В Postgres существует свой тип данных для быстрого доступа к данным по ключу — хэш-таблица.

Структура хэш-таблицы определяется в src/backend/utils/hash/dynahash.c

struct HTAB
{
	HASHHDR    *hctl;			/* => shared control information */
	HASHSEGMENT *dir;			/* directory of segment starts */
	HashValueFunc hash;			/* hash function */
	HashCompareFunc match;		/* key comparison function */
	HashCopyFunc keycopy;		/* key copying function */
	HashAllocFunc alloc;		/* memory allocator */
	MemoryContext hcxt;			/* memory context if default allocator used */
	char	   *tabname;		/* table name (for error messages) */
	bool		isshared;		/* true if table is in shared memory */
	bool		isfixed;		/* if true, don't enlarge */

	/* freezing a shared table isn't allowed, so we can keep state here */
	bool		frozen;			/* true = no more inserts allowed */

	/* We keep local copies of these fixed values to reduce contention */
	Size		keysize;		/* hash key length in bytes */
	long		ssize;			/* segment size --- must be power of 2 */
	int			sshift;			/* segment shift = log2(ssize) */
};

Функции для работы с хэш-таблицами объявляются в src/include/utils/hsearch.h. Например, функция для создания хэш-таблицы:

HTAB *hash_create(const char *tabname, long nelem,
                  const HASHCTL *info, int flags);

Также существует и другая реализация — simplehash

Она определена в src/include/lib/simplehash.h

Это не просто структура данных. Это набор макросов, который генерирует шаблонную хэш-таблицу и все вспомогательные функции. Например, функция для создния таблицы

#define SH_MAKE_PREFIX(a) CppConcat(a,_)
#define SH_MAKE_NAME(name) SH_MAKE_NAME_(SH_MAKE_PREFIX(SH_PREFIX),name)
#define SH_MAKE_NAME_(a,b) CppConcat(a,b)

#define SH_CREATE SH_MAKE_NAME(create)

#define SH_SCOPE extern

typedef struct SH_TYPE
{
	/*
	 * Size of data / bucket array, 64 bits to handle UINT32_MAX sized hash
	 * tables.  Note that the maximum number of elements is lower
	 * (SH_MAX_FILLFACTOR)
	 */
	uint64		size;

	/* how many elements have valid contents */
	uint32		members;

	/* mask for bucket and size calculations, based on size */
	uint32		sizemask;

	/* boundary after which to grow hashtable */
	uint32		grow_threshold;

	/* hash buckets */
	SH_ELEMENT_TYPE *data;

#ifndef SH_RAW_ALLOCATOR
	/* memory context to use for allocations */
	MemoryContext ctx;
#endif

	/* user defined data, useful for callbacks */
	void	   *private_data;
}			SH_TYPE;

#ifdef SH_RAW_ALLOCATOR
/* _hash _create(uint32 nelements, void *private_data) */
SH_SCOPE	SH_TYPE *SH_CREATE(uint32 nelements, void *private_data);
#else
/*
 * _hash _create(MemoryContext ctx, uint32 nelements,
 *								 void *private_data)
 */
SH_SCOPE	SH_TYPE *SH_CREATE(MemoryContext ctx, uint32 nelements,
							   void *private_data);
#endif

* В большинстве случаев используется dynahash

  • Системный кеш (CatalogCache) — кеш файловой системы. В ней хранится информация о системных таблицах: конвертеры, классы операторов, метод доступа и другие

  • Кеш планов (PlanCache) — кеш планировщика

Стоит упомянуть, что инициализация кеша отношений включает 3 этапа:

  1. Аллокация памяти (контекст кеша, хеш‑таблицы)

  2. Инициализация кеша под важные общие таблицы (pg_database, pg_authid, pg_auth_members, pg_shseclabel, pg_subscription

  3. Загрузка остальных таблиц в кеш.

Фазы инициализации кеша отношений «разбросаны» по всей функции InitPostres, т.к. для каждой фазы есть свои ограничения. Например, для аутентификации пользователя нам требуются только некоторые таблицы. Поэтому, 2 фаза прогревает кеш для аутенификации, а 3 фаза выполняется после нее и загружает все остальные таблицы в кеш.

Инициализация порталов

Теперь, инициализируется менеджер порталов

  • Выделяется собственный контекст памяти — TopPortalContext;

  • Создается хэш-таблица для поиска курсора по его имени. Хэш-таблица.

Что такое портал

Портал — объект, представляющий исполняющийся запрос. Этот тип используется в расширенном протоколе, на этапе привязки, после парсинга.

Порталы могут быть именованными (имя курсора) или безымянными (одиночный SELECT). 

Структура портала определяется в src/include/utils/portal.h

typedef struct PortalData *Portal;

typedef struct PortalData
{
	/* Bookkeeping data */
	const char *name;			/* portal's name */
	const char *prepStmtName;	/* source prepared statement (NULL if none) */
	MemoryContext portalContext;	/* subsidiary memory for portal */
	ResourceOwner resowner;		/* resources owned by portal */
	void		(*cleanup) (Portal portal); /* cleanup hook */

	/*
	 * State data for remembering which subtransaction(s) the portal was
	 * created or used in.  If the portal is held over from a previous
	 * transaction, both subxids are InvalidSubTransactionId.  Otherwise,
	 * createSubid is the creating subxact and activeSubid is the last subxact
	 * in which we ran the portal.
	 */
	SubTransactionId createSubid;	/* the creating subxact */
	SubTransactionId activeSubid;	/* the last subxact with activity */

	/* The query or queries the portal will execute */
	const char *sourceText;		/* text of query (as of 8.4, never NULL) */
	CommandTag	commandTag;		/* command tag for original query */
	QueryCompletion qc;			/* command completion data for executed query */
	List	   *stmts;			/* list of PlannedStmts */
	CachedPlan *cplan;			/* CachedPlan, if stmts are from one */

	ParamListInfo portalParams; /* params to pass to query */
	QueryEnvironment *queryEnv; /* environment for query */

	/* Features/options */
	PortalStrategy strategy;	/* see above */
	int			cursorOptions;	/* DECLARE CURSOR option bits */
	bool		run_once;		/* portal will only be run once */

	/* Status data */
	PortalStatus status;		/* see above */
	bool		portalPinned;	/* a pinned portal can't be dropped */
	bool		autoHeld;		/* was automatically converted from pinned to
								 * held (see HoldPinnedPortals()) */

	/* If not NULL, Executor is active; call ExecutorEnd eventually: */
	QueryDesc  *queryDesc;		/* info needed for executor invocation */

	/* If portal returns tuples, this is their tupdesc: */
	TupleDesc	tupDesc;		/* descriptor for result tuples */
	/* and these are the format codes to use for the columns: */
	int16	   *formats;		/* a format code for each column */

	/*
	 * Outermost ActiveSnapshot for execution of the portal's queries.  For
	 * all but a few utility commands, we require such a snapshot to exist.
	 * This ensures that TOAST references in query results can be detoasted,
	 * and helps to reduce thrashing of the process's exposed xmin.
	 */
	Snapshot	portalSnapshot; /* active snapshot, or NULL if none */

	/*
	 * Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
	 * PORTAL_UTIL_SELECT query.  (A cursor held past the end of its
	 * transaction no longer has any active executor state.)
	 */
	Tuplestorestate *holdStore; /* store for holdable cursors */
	MemoryContext holdContext;	/* memory containing holdStore */

	/*
	 * Snapshot under which tuples in the holdStore were read.  We must keep a
	 * reference to this snapshot if there is any possibility that the tuples
	 * contain TOAST references, because releasing the snapshot could allow
	 * recently-dead rows to be vacuumed away, along with any toast data
	 * belonging to them.  In the case of a held cursor, we avoid needing to
	 * keep such a snapshot by forcibly detoasting the data.
	 */
	Snapshot	holdSnapshot;	/* registered snapshot, or NULL if none */

	/*
	 * atStart, atEnd and portalPos indicate the current cursor position.
	 * portalPos is zero before the first row, N after fetching N'th row of
	 * query.  After we run off the end, portalPos = # of rows in query, and
	 * atEnd is true.  Note that atStart implies portalPos == 0, but not the
	 * reverse: we might have backed up only as far as the first row, not to
	 * the start.  Also note that various code inspects atStart and atEnd, but
	 * only the portal movement routines should touch portalPos.
	 */
	bool		atStart;
	bool		atEnd;
	uint64		portalPos;

	/* Presentation data, primarily used by the pg_cursors system view */
	TimestampTz creation_time;	/* time at which this portal was defined */
	bool		visible;		/* include this portal in pg_cursors? */

	/* Stuff added at the end to avoid ABI break in stable branches: */
	int			createLevel;	/* creating subxact's nesting level */
}			PortalData;

Порталы могут быть 2 видов: SQL (SELECT, CURSOR) и портал уровня протокола. Основная разница — возможность прокрутки:

  • SCROLL — есть возможность прокрутки запроса вперед и назад

  • NO SCROLL — запрос исполняется только «вперед»

Для SQL возможны оба варианта, а порталы уровня протокола допускают только NO SCROLL.

Дополнительно в этом же файле объявляются функции для работы с порталами. Определяются они в src/backend/utils/mmgr/portalmem.c

Инициализация для сборщика статистики

Сборщик статистики был инициализирован в Postmaster. Но взаимодействовать с ним мы пока не можем. 

Для отслеживания статуса бэкэнда есть структура PgBackendStatus

/* ----------
 * PgBackendStatus
 *
 * Each live backend maintains a PgBackendStatus struct in shared memory
 * showing its current activity.  (The structs are allocated according to
 * BackendId, but that is not critical.)  Note that the collector process
 * has no involvement in, or even access to, these structs.
 *
 * Each auxiliary process also maintains a PgBackendStatus struct in shared
 * memory.
 * ----------
 */
typedef struct PgBackendStatus
{
	/*
	 * To avoid locking overhead, we use the following protocol: a backend
	 * increments st_changecount before modifying its entry, and again after
	 * finishing a modification.  A would-be reader should note the value of
	 * st_changecount, copy the entry into private memory, then check
	 * st_changecount again.  If the value hasn't changed, and if it's even,
	 * the copy is valid; otherwise start over.  This makes updates cheap
	 * while reads are potentially expensive, but that's the tradeoff we want.
	 *
	 * The above protocol needs memory barriers to ensure that the apparent
	 * order of execution is as it desires.  Otherwise, for example, the CPU
	 * might rearrange the code so that st_changecount is incremented twice
	 * before the modification on a machine with weak memory ordering.  Hence,
	 * use the macros defined below for manipulating st_changecount, rather
	 * than touching it directly.
	 */
	int			st_changecount;

	/* The entry is valid iff st_procpid > 0, unused if st_procpid == 0 */
	int			st_procpid;

	/* Type of backends */
	BackendType st_backendType;

	/* Times when current backend, transaction, and activity started */
	TimestampTz st_proc_start_timestamp;
	TimestampTz st_xact_start_timestamp;
	TimestampTz st_activity_start_timestamp;
	TimestampTz st_state_start_timestamp;

	/* Database OID, owning user's OID, connection client address */
	Oid			st_databaseid;
	Oid			st_userid;
	SockAddr	st_clientaddr;
	char	   *st_clienthostname;	/* MUST be null-terminated */

	/* Information about SSL connection */
	bool		st_ssl;
	PgBackendSSLStatus *st_sslstatus;

	/* Information about GSSAPI connection */
	bool		st_gss;
	PgBackendGSSStatus *st_gssstatus;

	/* current state */
	BackendState st_state;

	/* application name; MUST be null-terminated */
	char	   *st_appname;

	/*
	 * Current command string; MUST be null-terminated. Note that this string
	 * possibly is truncated in the middle of a multi-byte character. As
	 * activity strings are stored more frequently than read, that allows to
	 * move the cost of correct truncation to the display side. Use
	 * pgstat_clip_activity() to truncate correctly.
	 */
	char	   *st_activity_raw;

	/*
	 * Command progress reporting.  Any command which wishes can advertise
	 * that it is running by setting st_progress_command,
	 * st_progress_command_target, and st_progress_param[].
	 * st_progress_command_target should be the OID of the relation which the
	 * command targets (we assume there's just one, as this is meant for
	 * utility commands), but the meaning of each element in the
	 * st_progress_param array is command-specific.
	 */
	ProgressCommandType st_progress_command;
	Oid			st_progress_command_target;
	int64		st_progress_param[PGSTAT_NUM_PROGRESS_PARAM];

	/* query identifier, optionally computed using post_parse_analyze_hook */
	uint64		st_query_id;
} PgBackendStatus;

На данном этапе мы получаем свою из массива всех структур, для отправки статистики. Так как Id бэкэндов возрастают линейно (от 1 до MAX_BACKENDS), то наша структура имеет индекс MyBackendId - 1 в этом массиве.

MyBEEntry = &BackendStatusArray[MyBackendId - 1];

Аутентификация

Дальше происходит аутентификация пользователя. Вызывается функция ClientAuthentication (src/backend/libpq/auth.c)

/*
 * Client authentication starts here.  If there is an error, this
 * function does not return and the backend process is terminated.
 */
void
ClientAuthentication(Port *port);

Эта функция сначала проверяет валидность сертификатов пользователя (если есть) и затем вызывает функцию аутентификации для выбранного метода аутентификации.

Представление pg_hba.conf в коде

Структуры для работы с pg_hba.conf определяются в src/include/libpq/hba.h

Файл представляется в виде множества строк, которые в свою очередь представляются структурой HbaLine

typedef struct HbaLine
{
	int			linenumber;
	char	   *rawline;
	ConnType	conntype;
	List	   *databases;
	List	   *roles;
	struct sockaddr_storage addr;
	int			addrlen;		/* zero if we don't have a valid addr */
	struct sockaddr_storage mask;
	int			masklen;		/* zero if we don't have a valid mask */
	IPCompareMethod ip_cmp_method;
	char	   *hostname;
	UserAuth	auth_method;
	char	   *usermap;
	char	   *pamservice;
	bool		pam_use_hostname;
	bool		ldaptls;
	char	   *ldapscheme;
	char	   *ldapserver;
	int			ldapport;
	char	   *ldapbinddn;
	char	   *ldapbindpasswd;
	char	   *ldapsearchattribute;
	char	   *ldapsearchfilter;
	char	   *ldapbasedn;
	int			ldapscope;
	char	   *ldapprefix;
	char	   *ldapsuffix;
	ClientCertMode clientcert;
	ClientCertName clientcertname;
	char	   *krb_realm;
	bool		include_realm;
	bool		compat_realm;
	bool		upn_username;
	List	   *radiusservers;
	char	   *radiusservers_s;
	List	   *radiussecrets;
	char	   *radiussecrets_s;
	List	   *radiusidentifiers;
	char	   *radiusidentifiers_s;
	List	   *radiusports;
	char	   *radiusports_s;
} HbaLine;

Также имеются методы доступа. Они представляются структурой UserAuth

/*
 * The following enum represents the authentication methods that
 * are supported by PostgreSQL.
 *
 * Note: keep this in sync with the UserAuthName array in hba.c.
 */
typedef enum UserAuth
{
	uaReject,
	uaImplicitReject,			/* Not a user-visible option */
	uaTrust,
	uaIdent,
	uaPassword,
	uaMD5,
	uaSCRAM,
	uaGSS,
	uaSSPI,
	uaPAM,
	uaBSD,
	uaLDAP,
	uaCert,
	uaRADIUS,
	uaPeer
#define USER_AUTH_LAST uaPeer	/* Must be last value of this enum */
} UserAuth;

Все методы аутентификации представлены в документации, кроме ImplicitReject. Он представляет собой невалидный/неизвестный метод доступа (SpecialCase). Ведет себя так же как и uaReject.

// src/backend/libpq/hba.c
/*
 *	Scan the pre-parsed hba file, looking for a match to the port's connection
 *	request.
 */
static void
check_hba(hbaPort *port)
{
  // ...
  
  /* If no matching entry was found, then implicitly reject. */
  hba = palloc0(sizeof(HbaLine));
  hba->auth_method = uaImplicitReject;
  port->hba = hba;
}

Успешная аутентификация не означает полный доступ к системе. Осталось пройти несколько фильтров:

  • Пользователю может быть запрещен вход;

  • Количество подключений достигло максимума;

  • При завершении работы БД или обновлении (pg_upgrade), доступ имеет только суперпользователь.

Аутентификация могла занять некоторое время. Особенно при использовании логина/пароля. За это время БД могла исчезнуть/переименоваться. Не лишним было бы проверить это.

Первым делом получается блокировка объекта БД. Если в момент нашей аутентификации, кто-то начал DROP DATABASE, то после получения блокировки мы об этом узнаем.

После происходит проверка прав доступа к БД. Для работой с различными правами доступа используется ACL

ACL

Для поддержки прав доступа Postgres определяет свой фреймворк ACL — Access Control List.

Каждый объект БД может иметь свой список доступа. Элемент списка представляется структурой AclItem (src/include/utils/acl.h)

typedef struct AclItem
{
	Oid			ai_grantee;		/* ID that this item grants privs to */
	Oid			ai_grantor;		/* grantor of privs */
	AclMode		ai_privs;		/* privilege bits */
} AclItem;

Тип AclMode, используемый в структуре, определяется в src/include/nodes/parsenodes.h

typedef uint32 AclMode;			/* a bitmask of privilege bits */

Права хранятся в виде битовой маски. Для хранения используется 32 битный int. Верхние 16 бит — для определения GRANT опций (выполнения SQL запросов), а нижние — для реального доступа.

Биты прав для SQL запросов определяются там же

#define ACL_INSERT		(1<<0)	/* for relations */
#define ACL_SELECT		(1<<1)
#define ACL_UPDATE		(1<<2)
#define ACL_DELETE		(1<<3)
#define ACL_TRUNCATE	(1<<4)
#define ACL_REFERENCES	(1<<5)
#define ACL_TRIGGER		(1<<6)
#define ACL_EXECUTE		(1<<7)	/* for functions */
#define ACL_USAGE		(1<<8)	/* for languages, namespaces, FDWs, and
								 * servers */
#define ACL_CREATE		(1<<9)	/* for namespaces and databases */
#define ACL_CREATE_TEMP (1<<10) /* for databases */
#define ACL_CONNECT		(1<<11) /* for databases */
#define N_ACL_RIGHTS	12		/* 1 plus the last 1<

Например, сейчас происходит проверка возможности подключения пользователя к БД. Проверка доступа осуществляется вызовом функции

// src/include/utils/acl.h
/* result codes for pg_*_aclcheck */
typedef enum
{
	ACLCHECK_OK = 0,
	ACLCHECK_NO_PRIV,
	ACLCHECK_NOT_OWNER
} AclResult;

// src/backend/catalog/aclchk.c
AclResult
pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode)
{
	if (pg_database_aclmask(db_oid, roleid, mode, ACLMASK_ANY) != 0)
		return ACLCHECK_OK;
	else
		return ACLCHECK_NO_PRIV;
}

Большая часть бизнес-логики содержится в функции aclmask (src/backend/utils/adt/acl.c): определение прав доступа для переданной маски операций. 

Общая логика работы выглядит следующим образом

AclMode
aclmask(const Acl *acl, Oid roleid, Oid ownerId,
		AclMode mask, AclMaskHow how)
{
	AclMode		result;
	AclMode		remaining;
	AclItem    *aidat;
	int			i,
				num;

	// Ранний выход при отсутствии требований к правам доступа
	if (mask == 0)
		return 0;

	result = 0;

	// Владелец всегда имеет доступ
	if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
		has_privs_of_role(roleid, ownerId))
	{
		result = mask & ACLITEM_ALL_GOPTION_BITS;
		if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
			return result;
	}

	num = ACL_NUM(acl);
	aidat = ACL_DAT(acl);

	// Проверка доступа для текущей роли
	for (i = 0; i < num; i++)
	{
		AclItem    *aidata = &aidat[i];

		if (aidata->ai_grantee == ACL_ID_PUBLIC ||
			aidata->ai_grantee == roleid)
		{
			result |= aidata->ai_privs & mask;
			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
				return result;
		}
	}

	// Проверка доступа для связных роли
	remaining = mask & ~result;
	for (i = 0; i < num; i++)
	{
		AclItem    *aidata = &aidat[i];

		if (aidata->ai_grantee == ACL_ID_PUBLIC ||
			aidata->ai_grantee == roleid)
			continue;			/* already checked it */

		if ((aidata->ai_privs & remaining) &&
			has_privs_of_role(roleid, aidata->ai_grantee))
		{
			result |= aidata->ai_privs & mask;
			if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
				return result;
			remaining = mask & ~result;
		}
	}

	return result;
}

Обновление GUC

Дальше происходит обновление GUC конфигурации. Настройки получаются из различных источников:

Задержка

Раньше мы уже выполняли задержку перед аутентификацей (pre_auth_delay). Теперь, по логике, надо задержаться уже после. Для этого используется параметр post_auth_delay из postgresql.conf

/* Apply PostAuthDelay as soon as we've read all options */
if (PostAuthDelay > 0)
    pg_usleep(PostAuthDelay * 1000000L);

Инициализация своей структуры бэкэнда

Теперь, когда мы «точно» сможем коммуницировать с клиентом, инициализируем себя для видимости другим бэкэндам и отправки своего статуса.

Ранее мы получили свою структуру PgBackendStatus, но на данный момент она не полностью инициализирована. Конечная инициализация происходит сейчас.

Одновременно с нами эту стуктуру (и массив) могут обновлять и другие бэкэнды (создание новых, завершение старых), поэтому для работы с ним нужно получить блокировку.

Для минимизации времени удержания лока сначала копируется текущая структура и работа идет уже со скопированной версией, а для записи мы ненадолго получаем лок и подменяем реальную переменную нашей.

void
pgstat_bestart(void)
{
	volatile PgBackendStatus *vbeentry = MyBEEntry;
	PgBackendStatus lbeentry;
  
    // Копирование во временную переменную 
    memcpy(&lbeentry,
		   unvolatize(PgBackendStatus *, vbeentry),
		   sizeof(PgBackendStatus));

    // Обновление временной переменной
	lbeentry.st_procpid = MyProcPid;
	lbeentry.st_backendType = MyBackendType;
	lbeentry.st_proc_start_timestamp = MyStartTimestamp;
	lbeentry.st_activity_start_timestamp = 0;
	lbeentry.st_state_start_timestamp = 0;
	lbeentry.st_xact_start_timestamp = 0;
	lbeentry.st_databaseid = MyDatabaseId;

	if (lbeentry.st_backendType == B_BACKEND
		|| lbeentry.st_backendType == B_WAL_SENDER
		|| lbeentry.st_backendType == B_BG_WORKER)
		lbeentry.st_userid = GetSessionUserId();
	else
		lbeentry.st_userid = InvalidOid;

	if (MyProcPort)
		memcpy(&lbeentry.st_clientaddr, &MyProcPort->raddr,
			   sizeof(lbeentry.st_clientaddr));
	else
		MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));

	lbeentry.st_state = STATE_UNDEFINED;
	lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
	lbeentry.st_progress_command_target = InvalidOid;
	lbeentry.st_query_id = UINT64CONST(0);

	// Входим в критическую зону
	PGSTAT_BEGIN_WRITE_ACTIVITY(vbeentry);

	lbeentry.st_changecount = vbeentry->st_changecount;

    // Подменяем значения
	memcpy(unvolatize(PgBackendStatus *, vbeentry),
		   &lbeentry,
		   sizeof(PgBackendStatus));

    // ...
  
    // Выходим из критической зоны
	PGSTAT_END_WRITE_ACTIVITY(vbeentry);

    // ...
}

Синхронизация настроек с клиентом

Сейчас можно начать синхронизацию с клиентом:

  • Настройки кастомизации: локаль, представление даты, кодировка, тайм-зона и т.д.

  • Общие метаданные: название приложения, является ли пользователь рутом, hot standby, версия сервера и т.д.

Все настройки, допускающие отправку клиенту, отправляются.

Представление GUC в коде

Из выше написанного видно, что настройки — не простые key-value значения. Они также имеют метаданные.

Настройки могут иметь значения с типами int, double, bool, string. Для каждого типа данных — своя структура. (src/include/utils/guc_tables.h)

/* GUC records for specific variable types */

struct config_bool
{
	struct config_generic gen;
	/* constant fields, must be set correctly in initial value: */
	bool	   *variable;
	bool		boot_val;
	GucBoolCheckHook check_hook;
	GucBoolAssignHook assign_hook;
	GucShowHook show_hook;
	/* variable fields, initialized at runtime: */
	bool		reset_val;
	void	   *reset_extra;
};

struct config_int
{
	struct config_generic gen;
	/* constant fields, must be set correctly in initial value: */
	int		   *variable;
	int			boot_val;
	int			min;
	int			max;
	GucIntCheckHook check_hook;
	GucIntAssignHook assign_hook;
	GucShowHook show_hook;
	/* variable fields, initialized at runtime: */
	int			reset_val;
	void	   *reset_extra;
};

struct config_real
{
	struct config_generic gen;
	/* constant fields, must be set correctly in initial value: */
	double	   *variable;
	double		boot_val;
	double		min;
	double		max;
	GucRealCheckHook check_hook;
	GucRealAssignHook assign_hook;
	GucShowHook show_hook;
	/* variable fields, initialized at runtime: */
	double		reset_val;
	void	   *reset_extra;
};

struct config_string
{
	struct config_generic gen;
	/* constant fields, must be set correctly in initial value: */
	char	  **variable;
	const char *boot_val;
	GucStringCheckHook check_hook;
	GucStringAssignHook assign_hook;
	GucShowHook show_hook;
	/* variable fields, initialized at runtime: */
	char	   *reset_val;
	void	   *reset_extra;
};

struct config_enum
{
	struct config_generic gen;
	/* constant fields, must be set correctly in initial value: */
	int		   *variable;
	int			boot_val;
	const struct config_enum_entry *options;
	GucEnumCheckHook check_hook;
	GucEnumAssignHook assign_hook;
	GucShowHook show_hook;
	/* variable fields, initialized at runtime: */
	int			reset_val;
	void	   *reset_extra;
};

Первым полем в каждой структуре является config_generic — структура, содержащая данные, характерные для каждого типа.

struct config_generic
{
	/* constant fields, must be set correctly in initial value: */
	const char *name;			/* name of variable - MUST BE FIRST */
	GucContext	context;		/* context required to set the variable */
	enum config_group group;	/* to help organize variables by function */
	const char *short_desc;		/* short desc. of this variable's purpose */
	const char *long_desc;		/* long desc. of this variable's purpose */
	int			flags;			/* flag bits, see guc.h */
	/* variable fields, initialized at runtime: */
	enum config_type vartype;	/* type of variable (set only at startup) */
	int			status;			/* status bits, see below */
	GucSource	source;			/* source of the current actual value */
	GucSource	reset_source;	/* source of the reset_value */
	GucContext	scontext;		/* context that set the current value */
	GucContext	reset_scontext; /* context that set 
    
            

© Habrahabr.ru