PostgreSQL под капотом. Часть 3. Инициализация бэкэнда
Приветствую!
Продолжаем изучение работы 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)
Опопвещения работают следующим образом:
Регистрируются изменения в файлах
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 процесса
Во время чекпоинта вызывается функция синхронизации
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,
}
};
Все операции строятся следующим образом:
По ID провайдера находится его таблица функций.
Вызывается функция для совершения необходимой операции.
Чтение из файла реализуется таким образом
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 типа кешей:
Кеш отношений (RelationCache) — кеш для быстрого доступа к таблицам. Использует хеш‑таблицу.
Реализация хэш-таблицы
В 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
/* <prefix>_hash <prefix>_create(uint32 nelements, void *private_data) */
SH_SCOPE SH_TYPE *SH_CREATE(uint32 nelements, void *private_data);
#else
/*
* <prefix>_hash <prefix>_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 этапа:
Аллокация памяти (контекст кеша, хеш‑таблицы)
Инициализация кеша под важные общие таблицы (pg_database, pg_authid, pg_auth_members, pg_shseclabel, pg_subscription
Загрузка остальных таблиц в кеш.
Фазы инициализации кеша отношений «разбросаны» по всей функции 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<<x */
#define ACL_NO_RIGHTS 0
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE ACL_UPDATE
Например, сейчас происходит проверка возможности подключения пользователя к БД. Проверка доступа осуществляется вызовом функции
// 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 конфигурации. Настройки получаются из различных источников:
Кеш (локаль и кодировка)
Аргументы точки входа
Startup пакет
Задержка
Раньше мы уже выполняли задержку перед аутентификацей (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 the reset value */
GucStack *stack; /* stacked prior values */
void *extra; /* "extra" pointer for current actual value */
char *last_reported; /* if variable is GUC_REPORT, value last sent
* to client (NULL if not yet sent) */
char *sourcefile; /* file current setting is from (NULL if not
* set in config file) */
int sourceline; /* line in source file */
};
За работу с GUC отвечает соответствующий модуль. Расположен в src/backend/utils/misc/guc.c
Для каждого типа значений, определен свой массив.
// src/backend/utils/misc/guc.c
static struct config_bool ConfigureNamesBool[];
static struct config_int ConfigureNamesInt[];
static struct config_real ConfigureNamesReal[];
static struct config_enum ConfigureNamesEnum[];
static struct config_string ConfigureNamesString[];
Для поиска среди всех настроек, существует массив guc_variables типа config_generic. Это сортированный массив всех переменных
// src/backend/utils/misc/guc.c
/*
* Actual lookup of variables is done through this single, sorted array.
*/
static struct config_generic **guc_variables;
Этот список заполняется во время инициализации вызовом build_guc_variables
void
build_guc_variables(void)
{
int size_vars;
int num_vars = 0;
struct config_generic **guc_vars;
int i;
for (i = 0; ConfigureNamesBool[i].gen.name; i++)
{
struct config_bool *conf = &ConfigureNamesBool[i];
/* Rather than requiring vartype to be filled in by hand, do this: */
conf->gen.vartype = PGC_BOOL;
num_vars++;
}
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
{
struct config_int *conf = &ConfigureNamesInt[i];
conf->gen.vartype = PGC_INT;
num_vars++;
}
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
{
struct config_real *conf = &ConfigureNamesReal[i];
conf->gen.vartype = PGC_REAL;
num_vars++;
}
for (i = 0; ConfigureNamesString[i].gen.name; i++)
{
struct config_string *conf = &ConfigureNamesString[i];
conf->gen.vartype = PGC_STRING;
num_vars++;
}
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
{
struct config_enum *conf = &ConfigureNamesEnum[i];
conf->gen.vartype = PGC_ENUM;
num_vars++;
}
/*
* Create table with 20% slack
*/
size_vars = num_vars + num_vars / 4;
guc_vars = (struct config_generic **)
guc_malloc(FATAL, size_vars * sizeof(struct config_generic *));
num_vars = 0;
for (i = 0; ConfigureNamesBool[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesBool[i].gen;
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
for (i = 0; ConfigureNamesString[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesString[i].gen;
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen;
if (guc_variables)
free(guc_variables);
guc_variables = guc_vars;
num_guc_variables = num_vars;
size_guc_variables = size_vars;
qsort((void *) guc_variables, num_guc_variables,
sizeof(struct config_generic *), guc_var_compare);
}
Выше было сказано, что отправляются только допускающие отправку опции. В config_generic определяется поле flags - битовая маска метаданных опции. Определяются флаги в src/include/utils/guc.h
/*
* bit values in "flags" of a GUC variable
*/
#define GUC_LIST_INPUT 0x0001 /* input can be list format */
#define GUC_LIST_QUOTE 0x0002 /* double-quote list elements */
#define GUC_NO_SHOW_ALL 0x0004 /* exclude from SHOW ALL */
#define GUC_NO_RESET_ALL 0x0008 /* exclude from RESET ALL */
#define GUC_REPORT 0x0010 /* auto-report changes to client */
#define GUC_NOT_IN_SAMPLE 0x0020 /* not in postgresql.conf.sample */
#define GUC_DISALLOW_IN_FILE 0x0040 /* can't set in postgresql.conf */
#define GUC_CUSTOM_PLACEHOLDER 0x0080 /* placeholder for custom variable */
#define GUC_SUPERUSER_ONLY 0x0100 /* show only to superusers */
#define GUC_IS_NAME 0x0200 /* limit string to NAMEDATALEN-1 */
#define GUC_NOT_WHILE_SEC_REST 0x0400 /* can't set if security restricted */
#define GUC_DISALLOW_IN_AUTO_FILE 0x0800 /* can't set in
* PG_AUTOCONF_FILENAME */
За возможность отправки клиенту отвечает флаг GUC_REPORT. Для синхронизации GUC с клиентом вызывается функция BeginReportingGUCOptions, которая его проверяет.
// src/backend/utils/misc/guc.c
/*
* Start up automatic reporting of changes to variables marked GUC_REPORT.
* This is executed at completion of backend startup.
*/
void
BeginReportingGUCOptions(void)
{
int i;
// ...
/* Transmit initial values of interesting variables */
for (i = 0; i < num_guc_variables; i++)
{
struct config_generic *conf = guc_variables[i];
if (conf->flags & GUC_REPORT)
ReportGUCOption(conf);
}
// ...
}
Дополнительно клиенту передается секретный ключ (пара): ID процесса и токен отмены. По этой паре можно будет запрашивать отмену выполнения запроса.
С этого момента клиент ждет ReadyForQuery пакета, который будет отослан уже в главном цикле.
Загрузка библиотек
Теперь загружаются библиотеки.
Загружаемые библиотеки указываются в переменных local_preload_libraries, shared_preload_libraries и session_preload_libraries в postgresql.conf
Сейчас загружаются session_preload_libraries и local_preload_libraries.
Динамическая загрузка и модуль dfmgr
Для загрузки библиотек Postgres использует динамическую загрузку библиотек.
Для работы с динамическими функциями/библиотеками используется модуль dfmgr
Он определяет интерфейс для взаимодействия с функциями, определяемыми во внешних библиотеках
Функция, которую необходимо вызвать идентфицируется структурой FunctionCallInfoBaseData (src/include/fmgr.h)
typedef struct FunctionCallInfoBaseData
{
FmgrInfo *flinfo; /* ptr to lookup info used for this call */
fmNodePtr context; /* pass info about context of call */
fmNodePtr resultinfo; /* pass or return extra info about result */
Oid fncollation; /* collation for function to use */
#define FIELDNO_FUNCTIONCALLINFODATA_ISNULL 4
bool isnull; /* function must set true if result is NULL */
short nargs; /* # arguments actually passed */
#define FIELDNO_FUNCTIONCALLINFODATA_ARGS 6
NullableDatum args[FLEXIBLE_ARRAY_MEMBER];
} FunctionCallInfoBaseData;
typedef struct FunctionCallInfoBaseData *FunctionCallInfo;
Каждая вызываемая функция имеет следующую сигнатуру
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
Для работы с внешними библиотеками используются функции
void *load_external_function(const char *filename, const char *funcname,
bool signalNotFound, void **filehandle);
void *lookup_external_function(void *filehandle, const char *funcname);
void load_file(const char *filename, bool restricted);
void **find_rendezvous_variable(const char *varName);
Size EstimateLibraryStateSpace(void);
void SerializeLibraryState(Size maxsize, char *start_address);
void RestoreLibraryState(char *start_address);
Для загрузки библиотек используется функция load_file. Она сначала выгружает (если уже была загружена), а затем загружает файл обратно. Для самой загрузки используется функция internal_load_library (src/backend/utils/fmgr/dfmgr.c)
/*
* Load the specified dynamic-link library file, unless it already is
* loaded. Return the pg_dl* handle for the file.
*
* Note: libname is expected to be an exact name for the library file.
*/
void *internal_load_library(const char *libname);
Работа этой функции заключается в следующем:
Проверить, что файл не был загружен ранее:
Если был загружен, то вернуть ссылку на хранящуюся библиотеку.
Аллоцировать память под структуру загруженного файла.
Открыть файл библиотеки системным вызовом dlopen.
Получить "магическую функцию". Она возвращает структуру
Pg_magic_struct
и используется для проверки совместимости по версиям.Вызывать функцию _PG_init, если присутствует.
Сохранить загруженную библиотеку в памяти.
Вернуть указатель на эту библиотеку.
Инициализация контекста памяти
Последнее, что осталось - аллокация памяти для входящего запроса.
Память для него хранится в 2 переменных:
MessageContext - контекст текущего запроса (Глобальный контекст).
row_description_context - контекст памяти, хранящий в себе описание строк при ответе клиенту. Например, название столбца, тип данных столбца (object ID) или модификаторы типа. (Только в файле с точкой входа src/backend/tcop/postgres.c).
Конец
На этом инициализация Postgres заканчивается и дальше идет главный цикл.