콘텐츠로 이동

(KO) PostgreSQL GUC 파라미터 — config_generic, PGC 컨텍스트, SET/SHOW, Check/Assign 훅, SIGHUP 리로드

목차

데이터베이스 엔진은 수백 개의 동작 조정 파라미터를 제공한다. 버퍼 풀 크기, 플래너 비용 계수, WAL 플러시 정책, 잠금 타임아웃, 로깅 상세도가 그 예다. 이것들을 통틀어 시스템의 **설정 표면(configuration surface)**이라 부르며, 이 표면을 어떻게 설계하느냐는 부차적인 문제가 아닌 1순위 엔지니어링 과제다. Architecture of a Database System(Hellerstein, Stonebraker & Hamilton, 2007 — dbms-papers/fntdb07-architecture.md 수록)은 DBMS를 프로세스 매니저, 쿼리 프로세서, 스토리지 매니저, 공유 유틸리티로 구성된 협력 컴포넌트 집합으로 정의하며, 이 컴포넌트 거의 모두가 DBA, 자동 튜너, 또는 서버 시작 로직이 설정해야 할 파라미터를 가진다고 지적한다.

어떤 설정 서브시스템이든 네 가지 횡단 관심사(cross-cutting concern)를 해결해야 한다.

  1. 타이핑과 검증. 파라미터는 불리언, 범위가 있는 정수, 실수, 문자열, 또는 열거형 중 하나다. 값을 받을 때 반드시 타입 불일치나 범위 초과를 사전에 거부해야 한다. work_mem = 4MB처럼 단위가 붙은 값은 정규화된 기본 단위로 변환해야 한다.

  2. 권한과 타이밍 — 누가, 언제 변경할 수 있는가. 일부 파라미터는 프로세스 시작 후 변경할 수 없다(공유 메모리 세그먼트 크기는 백엔드가 매핑된 상태에서 바꿀 수 없다). 일부는 운영자 권한이 필요하다(fsync를 끄는 것은 일반 사용자의 권한 밖이다). 일부는 세션 단위이며 누구나 자유롭게 설정할 수 있다(work_mem). 설정 시스템은 파라미터마다 가장 이른 바인딩 시점과 최소 권한을 명시하는 **컨텍스트 래더(context ladder)**가 필요하다.

  3. 우선순위가 있는 다중 소스. 파라미터의 실효 값은 여러 소스를 순서대로 적용한 결과다. 컴파일 기본값, 환경 변수, 커맨드라인, 설정 파일, 데이터베이스/롤별 기본값, 마지막으로 대화형 SET이 그 소스다. 시스템은 현재 값이 어디서 왔는지 추적해야 우선순위가 낮은 소스(파일 재읽기)가 우선순위가 높은 소스(커맨드라인 스위치)를 덮어쓰지 않는다.

  4. 범위와 롤백. 나중에 중단되는 트랜잭션 안에서 변경한 값은 되돌아가야 한다. SET LOCAL은 트랜잭션 종료 시 되돌아가야 한다. SET 절을 가진 함수는 함수 반환 시 되돌아가야 한다. 이를 위해 트랜잭션 중첩 깊이에 따라 키를 붙인 저장 값 스택이 필요하다.

이 네 가지 규율은 프로그래밍 언어 런타임이 동적 스코프 변수에 적용하는 것과 동일하다. PostgreSQL이 이 서브시스템에 붙인 이름 — GUC, “Grand Unified Configuration” — 은 바로 그 계보에서 나왔다. 이 문서는 PostgreSQL이 네 가지 관심사 각각을 별도 메커니즘이 아닌 단일 테이블 구동 메커니즘으로 어떻게 충족하는지를 보여준다.

다섯 번째 관심사는 더 미묘하지만 같은 비중을 가진다. 읽기 경로가 저렴해야 한다. 설정 값은 극히 빈번하게 읽힌다. 플래너는 쿼리 하나를 비용 계산하는 동안 enable_seqscan, random_page_cost 등 십수 개 파라미터를 참조한다. 실행기는 정렬 노드마다 work_mem을 확인한다. 모든 ereportlog_min_messages를 참조한다. 파라미터를 읽을 때마다 해시 조회나 카탈로그 프로브가 필요하다면 설정 시스템이 시스템에서 가장 뜨거운 코드의 병목이 된다. 따라서 쓰기는 풍부한 검증 퍼널을 거치되 읽기는 일반 C 전역 변수를 직접 로드하는 수준이어야 한다. PostgreSQL은 실제 값을 일반 변수(int work_mem;)에 저장하고 GUC 레코드의 variable 필드가 그 변수를 가리키게 함으로써 이를 달성한다. GUC 레코드는 그 포인터를 타고 값을 쓰고, 코드 나머지는 GUC 레이어를 전혀 거치지 않고 전역 변수를 직접 읽는다. 이 분리 — 무거운 쓰기, 무비용 읽기 — 가 천 개의 파라미터를 감당할 수 있게 만드는 조용한 아키텍처 결정이다.

엔진을 막론하고 세 가지 반복 패턴이 등장한다.

  • 플랫 키-값 카탈로그 테이블. 가장 단순한 설계는 파라미터를 (name, value) 행으로 시스템 테이블에 저장하고 쓰기 시 애플리케이션 로직으로 검증한다. SQL 인트로스펙션이 간단하지만 타입 안전과 바인딩 타임 규칙이 분산된 호출 지점으로 밀려나고, 복잡한 파라미터에 필요한 변수별 훅 로직의 자연스러운 위치가 없다.

  • 정적으로 타이핑된 디스크립터 배열. 더 풍부한 설계 — PostgreSQL의 방식 — 는 각 파라미터를 컴파일된 배열의 구조체로 선언한다. 이름, 타입, 범위, 기본값, 실제 값을 보유하는 C 변수의 주소, 그리고 검증과 부작용을 위한 콜백 함수 포인터가 들어간다. 디스크립터가 단일 진실의 원천이고, SQL 인트로스펙션(pg_settings)은 배열에서 계산된 지 저장소가 아니다. 덕분에 work_mem을 읽을 때 카탈로그 조회 없이 C 변수 역참조만으로도 충분하면서, 동시에 SQL로 모든 정보를 노출할 수 있다.

  • 파일 리로드 대 재시작. 모든 서버는 설정 파일에서 실시간 재읽기 가능한 파라미터와 프로세스 시작 시 고정되는 파라미터를 구분한다. PostgreSQL의 SIGHUP / pg_reload_conf(), MySQL의 SET GLOBAL + 설정, Oracle의 ALTER SYSTEM ... SCOPE=BOTH가 각각의 “리로드” 작업이며, 별도로 재시작 전용 파라미터 클래스를 노출한다.

다중 소스 설계가 피해야 할 미묘한 함정은 우선순위가 낮은 값이 높은 값을 덮어쓰는 것이다. 리로드 시 설정 파일을 다시 읽을 때 파일 값이 그보다 우선순위가 높은 커맨드라인 스위치나 대화형 SET을 덮어써서는 안 된다. 엔진들은 현재 값에 출처(provenance)를 태그하고 강등을 거부하는 방식으로 이를 해결한다. PostgreSQL의 GucSource 순서가 정확히 이 역할을 한다. 새 값은 그 소스의 순위가 현재 값을 설정한 소스 이상일 때만 적용된다.

열거형 파라미터는 별도 언급이 필요하다. client_min_messagesbytea_output처럼 고정된 명명 대안 집합을 가지는 파라미터는 입력 이름을 그 집합과 대조 검증하고 확인된 정수를 저장하는 것이 좋다. 이렇게 하면 코드가 문자열을 재파싱하는 대신 int로 스위치를 구동할 수 있다. PostgreSQL은 config_enumconfig_enum_entry[] 옵션 배열로 이를 모델링한다. 항목이 hidden일 수 있어 입력으로는 받아들이지만 사용자에게 표시되는 합법적인 값 목록에는 포함하지 않는다.

디스크립터 배열 방식은 교과서가 잘 강조하지 않는 두 가지 이점을 제공한다. 첫째, 검증과 부작용 로직이 선언 옆에 존재한다 — 디스크립터 내의 함수 포인터로서 파라미터와 그 의미가 함께 이동한다. 둘째, 바인딩 타임이 암묵적 관례가 아닌 1등급 필드가 된다. 따라서 엔진은 “지금 그것을 바꿀 수 없다”를 각 서브시스템이 규칙을 재구현하는 대신 기계적으로 거부할 수 있다.

하나의 헤더, 다섯 개의 타입 레코드

섹션 제목: “하나의 헤더, 다섯 개의 타입 레코드”

모든 GUC는 첫 번째 멤버가 공유 config_generic 헤더인 정적으로 초기화된 구조체다. C의 보장 — 구조체의 첫 번째 멤버가 구조체의 주소를 공유한다 — 덕에 제네릭 코드가 어떤 타입 레코드도 config_generic *로 캐스트하거나 그 반대로 캐스트할 수 있다. PostgreSQL의 Node 시스템과 정확히 같은 방식이다.

// struct config_generic — src/include/utils/guc_tables.h
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 */
Oid srole; /* role that set the current value */
Oid reset_srole; /* role that set the reset value */
GucStack *stack; /* stacked prior values */
void *extra; /* "extra" pointer for current actual value */
/* ... dlist/slist links for nondef / stack / report lists ... */
char *sourcefile; /* file current setting is from */
int sourceline; /* line in source file */
};

다섯 개의 구체 레코드 타입은 그 헤더를 임베드하고 타입별 필드를 추가한다. 불리언 레코드가 대표적이다.

// struct config_bool — src/include/utils/guc_tables.h
struct config_bool
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
bool *variable; /* address of the live C variable */
bool boot_val; /* compiled-in default */
GucBoolCheckHook check_hook;
GucBoolAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
bool reset_val; /* value RESET returns to */
void *reset_extra;
};

config_intconfig_real은 추가로 min/max를 가진다. config_enum은 이름→값 쌍의 config_enum_entry 배열(options)을 가지며 variable은 확인된 열거형 값을 받는 int *다. config_stringchar **variableconst char *boot_val을 가진다. 열거형 테이블 항목의 예를 보면:

// ConfigureNamesEnum[] — src/backend/utils/misc/guc_tables.c
{
{"bytea_output", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the output format for bytea."),
NULL
},
&bytea_output, /* int *variable */
BYTEA_OUTPUT_HEX, /* boot_val (an enum member) */
bytea_output_options, /* config_enum_entry[] */
NULL, NULL, NULL
},

variable 필드가 핵심이다. 백엔드의 나머지 코드가 GUC 레이어를 전혀 거치지 않고 직접 읽는 일반 C 전역 변수의 주소다(&work_mem, &enable_seqscan 등). GUC 레이어의 역할은 그 변수에 올바르게 쓰는 것이고, 읽기는 단순한 로드다.

boot_val은 어떤 소스도 참조하기 전에 모든 변수가 보유하는 컴파일 기본값이다. reset_valRESET이 되돌아가는 값으로, 시작 시 가장 높은 우선순위 비대화형 소스(파일, 커맨드라인, 환경 변수)로부터 설정된다. 이렇게 하면 세션 안에서 RESET work_mem이 하드코딩된 기본값이 아닌 설정된 기준값으로 돌아간다.

레코드들은 guc_tables.c의 다섯 개 손으로 작성된 배열에 들어있으며 각각 NULL 이름으로 끝난다. 전형적인 항목은 중첩된 중괄호 초기화자다. 안쪽 중괄호가 config_generic 헤더를 채우고, 바깥쪽이 타입별 꼬리를 채운다.

// ConfigureNamesBool[] — src/backend/utils/misc/guc_tables.c
{
{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of sequential-scan plans."),
NULL,
GUC_EXPLAIN
},
&enable_seqscan, /* variable */
true, /* boot_val */
NULL, NULL, NULL /* check_hook, assign_hook, show_hook */
},
// ConfigureNamesInt[] — src/backend/utils/misc/guc_tables.c
{
{"min_dynamic_shared_memory", PGC_POSTMASTER, RESOURCES_MEM,
gettext_noop("Amount of dynamic shared memory reserved at startup."),
NULL,
GUC_UNIT_MB
},
&min_dynamic_shared_memory,
0, 0, (int) Min((size_t) INT_MAX, SIZE_MAX / (1024 * 1024)),
NULL, NULL, NULL
},

PGC_USERSETPGC_POSTMASTER 컨텍스트와 GUC_EXPLAIN / GUC_UNIT_MB 플래그는 선언적임에 주목하라. 이 값들은 테이블에 위치하며 제네릭 메커니즘이 해석한다. config_group 필드(QUERY_TUNING_METHOD, RESOURCES_MEM 등)는 pg_settings 출력과 샘플 설정 파일 정리에만 쓰이는 거칠은 분류다.

GucContext는 권한과 타이밍의 래티스(lattice)다. 위에서 아래로 읽으면, 각 레벨은 바인딩 타임 면에서 엄격하게 더 허용적이다.

// GucContext — src/include/utils/guc.h
typedef enum
{
PGC_INTERNAL, /* cannot be set by the user at all */
PGC_POSTMASTER, /* only at server start (postgresql.conf / command line) */
PGC_SIGHUP, /* server-wide, changeable on config reload */
PGC_SU_BACKEND, /* at connection start; superuser/granted at startup pkt */
PGC_BACKEND, /* at connection start; anyone via startup packet */
PGC_SUSET, /* superuser (or granted) any time, incl. SET */
PGC_USERSET, /* anyone, any time, via SET */
} GucContext;

이 단일 열거형은 바인딩 타임(값이 처음 고정될 수 있는 시점)과 권한(설정자에게 필요한 권한)을 함께 인코딩한다. shared_buffersPGC_POSTMASTER다. 공유 메모리 레이아웃이 시작 시 한 번 계산되기 때문이다. fsyncPGC_SIGHUP이어서 리로드 시 전환할 수 있지만 DBA가 제어하는 파일을 편집해야만 가능하다. work_memPGC_USERSET이어서 어떤 클라이언트든 자기 세션에서 올릴 수 있다.

직교하는 GucSource 열거형은 현재 값이 어디서 왔는지를 기록해 우선순위가 낮은 소스의 재처리가 우선순위 높은 것을 절대 덮어쓰지 않도록 한다.

// GucSource — src/include/utils/guc.h (abridged)
typedef enum
{
PGC_S_DEFAULT, /* hard-wired default ("boot_val") */
PGC_S_DYNAMIC_DEFAULT, /* default computed during initialization */
PGC_S_ENV_VAR, /* postmaster environment variable */
PGC_S_FILE, /* postgresql.conf */
PGC_S_ARGV, /* postmaster command line */
PGC_S_GLOBAL, PGC_S_DATABASE, PGC_S_USER, PGC_S_DATABASE_USER,
PGC_S_CLIENT, /* from client connection request */
PGC_S_OVERRIDE, /* special case to forcibly set default */
PGC_S_INTERACTIVE, /* dividing line for error reporting */
PGC_S_TEST, /* test per-database or per-user setting */
PGC_S_SESSION, /* SET command */
} GucSource;

두 열거형은 독립적인 축이다. context변수의 속성으로 테이블에 고정되어 있다. source현재 값의 속성으로 변수의 생애 동안 변한다. 쓰기 경로는 요청 컨텍스트를 변수의 선언된 컨텍스트와 비교 확인하고, 커밋하는 값에 소스를 찍는다.

래더는 퍼널 상단 근처의 단일 switch (record->context)로 강제된다. 가장 흥미로운 두 단계는 단순한 수락/거절이 아니다. PGC_POSTMASTERPGC_SIGHUP 타임에 설정 파일 재읽기를 오직 비교 목적으로만 수락한다. 정규화된 값이 실제로 달라지면 변경을 거부하는 플래그를 세운다. 서버가 DBA에게 “재시작이 필요한 파라미터를 편집했다”고 알리는 방법이다. PGC_SU_BACKENDPGC_SUSETpg_parameter_aclcheck로 권한을 확인한다. GRANT SET ON PARAMETER로 비슈퍼유저에게 파라미터를 위임할 수 있다.

// set_config_with_handle (context switch, abridged) — src/backend/utils/misc/guc.c
switch (record->context)
{
case PGC_INTERNAL:
if (context != PGC_INTERNAL)
ereport(elevel, (errmsg("parameter \"%s\" cannot be changed", ...)));
break;
case PGC_POSTMASTER:
if (context == PGC_SIGHUP)
prohibitValueChange = true; /* re-read to compare, not apply */
else if (context != PGC_POSTMASTER)
ereport(elevel, (errmsg("... cannot be changed without restarting ...")));
break;
case PGC_SUSET:
if (context == PGC_USERSET || context == PGC_BACKEND)
{
AclResult aclresult = pg_parameter_aclcheck(record->name, srole, ACL_SET);
if (aclresult != ACLCHECK_OK)
ereport(elevel, (errmsg("permission denied to set parameter \"%s\"", ...)));
}
break;
case PGC_USERSET:
/* always okay */
break;
}

prohibitValueChange 플래그는 값을 파싱하고 정규화한 이후에 확인한다. 입력 형식의 변형(예: 1GB1024MB)을 정규화한 뒤에야 “실제로 달라졌는가” 비교가 의미 있기 때문이다.

시작 시 build_guc_variables()가 다섯 개 배열을 순회하며 각 레코드의 vartype을 찍고 모든 레코드를 이름 포인터로 키를 잡은 하나의 대소문자 무관 dynahash에 삽입한다. 이후 모든 조회는 find_option()을 거친다. find_option()은 커스텀 점 이름(예: 확장의 mymodule.setting)에 대한 플레이스홀더 레코드를 지연 생성하기도 한다. 이렇게 하면 소유 모듈이 로드되기 전에도 파라미터를 참조할 수 있다.

모든 쓰기 — SQL SET, 설정 파일 적용, ALTER SYSTEM, 내부 오버라이드 — 는 set_config_with_handle()을 거친다(set_config_option의 워커다). 이 퍼널이 컨텍스트 래더 강제, 값 검증과 정규화, 트랜잭션 스택 관리, 새 값 커밋, assign 훅 실행, 클라이언트 리포팅을 모두 한 곳에서 처리한다.

조회는 절대 배열을 순회하지 않는다. 시작 시 다섯 개 테이블이 하나의 dynahash로 접혀 들어간다. find_option()이 확인을 수행하고, 확장이 아직 등록하지 않은 커스텀 점 이름은 즉석에서 새 레코드로 생성한다.

// add_guc_variable — src/backend/utils/misc/guc.c
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&var->name, HASH_ENTER_NULL, &found);
if (unlikely(hentry == NULL))
ereport(elevel, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
Assert(!found);
hentry->gucvar = var;
return true;

플레이스홀더는 이름이 valid_custom_variable_name()을 통과할 때만 생성된다 — “점으로 구분된 두 개 이상의 식별자”라는 scan.l과 동일한 어휘 규칙이다. mymodule.threshold는 지연 커스텀 GUC로 수락되고, 알 수 없는 단순 이름은 인식되지 않는 파라미터로 거부된다. 소유 모듈이 나중에 DefineCustomIntVariable을 호출하면, 플레이스홀더가 이미 할당된 값을 보존한 채로 완전한 타입 레코드로 승격된다.

flowchart TD
    A["SET work_mem='64MB'<br/>(ExecSetVariableStmt)"] --> B["set_config_option<br/>context=PGC_USERSET/SUSET<br/>source=PGC_S_SESSION"]
    C["postgresql.conf 행<br/>(ProcessConfigFile)"] --> D["set_config_option<br/>context=PGC_SIGHUP<br/>source=PGC_S_FILE"]
    E["ALTER SYSTEM SET<br/>(auto.conf에 쓰기)"] --> C
    B --> F["set_config_with_handle"]
    D --> F
    F --> G["find_option<br/>name -> config_generic"]
    G --> H{"컨텍스트 래더 확인:<br/>이 컨텍스트가<br/>이 변수를 설정할 수 있는가?"}
    H -- "불가" --> I["ereport: 변경 불가 /<br/>재시작 필요 / 권한 없음"]
    H -- "가능" --> J["parse_and_validate_value<br/>타입 + 범위 + check_hook"]
    J -- "유효하지 않음" --> I
    J -- "ok, newval+extra" --> K["push_old_value<br/>(SET LOCAL / 롤백용 스택)"]
    K --> L["*variable = newval<br/>source/scontext/srole 설정"]
    L --> M["assign_hook(newval, extra)<br/>부작용 실행"]
    M --> N{"GUC_REPORT?"}
    N -- "예" --> O["status |= GUC_NEEDS_REPORT<br/>-> ParameterStatus 메시지"]
    N -- "아니오" --> P["완료"]

parse_and_validate_value()는 타입 인식 게이트다. vartype별로 텍스트 값을 파싱하고, 선언된 min/max를 단위 인식 오류 텍스트와 함께 강제하고, 변수별 check 훅을 실행한다. check 훅은 값을 거부하거나 정규 형식으로 재작성하고, assign 훅에 값과 함께 전달되는 불투명한 extra 블롭(사전 계산된 파생 형식)을 할당할 수 있다.

// parse_and_validate_value (PGC_INT arm) — src/backend/utils/misc/guc.c
case PGC_INT:
{
struct config_int *conf = (struct config_int *) record;
const char *hintmsg;
if (!parse_int(value, &newval->intval, conf->gen.flags, &hintmsg))
{
ereport(elevel, ( /* invalid value, with unit hint */ ));
return false;
}
if (newval->intval < conf->min || newval->intval > conf->max)
{
/* ... "%d is outside the valid range ... (min .. max)" ... */
return false;
}
if (!call_int_check_hook(conf, &newval->intval, newextra, source, elevel))
return false;
}
break;

call_*_check_hook 래퍼는 GUC 오류 리포팅 채널(GUC_check_errmsg_string 등 — 훅이 GUC_check_errdetail로 설정)을 확립하고 훅의 false 반환을 적절한 ereport로 변환한다.

// call_bool_check_hook — src/backend/utils/misc/guc.c
if (!conf->check_hook)
return true; /* no hook: trivially valid */
GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
GUC_check_errmsg_string = NULL;
/* ... reset the other GUC_check_* strings ... */
if (!conf->check_hook(newval, extra, source))
{
ereport(elevel,
(errcode(GUC_check_errcode_value),
GUC_check_errmsg_string ?
errmsg_internal("%s", GUC_check_errmsg_string) :
errmsg("invalid value for parameter \"%s\": %d",
conf->gen.name, (int) *newval),
/* ... errdetail / errhint from the hook ... */ ));
FlushErrorState();
return false;
}
return true;

역할 분담은 의도적이다. check 훅은 커밋 전에 실행되며 부작용이 없어야 한다. 투기적으로 호출될 수 있기 때문이다(예: ALTER ROLE ... SETsource == PGC_S_TEST로 제안된 값을 검증할 때). assign 훅은 커밋 시 실행되며 실제 부작용을 수행한다 — 파생 전역 재계산, 타이머 재설정, 캐시 무효화. extra 블롭은 check 훅에서 수행한 비용이 드는 파싱 결과를 재계산 없이 assign 훅에 전달하는 수단이다.

extra 메커니즘은 롤백 미묘함도 해결한다. 중단된 트랜잭션 안의 SET은 깔끔하게 되돌아가야 하므로, assign 훅은 역방향으로 재실행 가능해야 한다. AtEOXact_GUC()가 이전 값을 복원할 때, 이전 값에 저장된 extra로 assign 훅을 다시 실행한다. 부작용이 원래 적용된 것과 정확히 반대로 취소된다. 이것이 assign 훅이 절대 실패하면 안 되는 이유다. 실패할 수 있는 모든 것 — 파싱, 범위 확인, 의미론적 검증 — 은 실패가 아직 저렴한 check 훅으로 밀어 넣는다. *variable이 덮어쓰이고 assign 훅이 실행될 때쯤이면 값은 유효함이 확인됐고 연산은 완료가 보장된다. 이 불변 — check 훅은 거부할 수 있고, assign 훅은 거부하면 안 된다 — 이 모든 GUC 작성자가 지켜야 할 계약이며, 트랜잭션 스택을 건전하게 만드는 것이다. 중단 시 스택 항목을 팝하면 항상 저장된 상태를 재적용할 수 있다.

트랜잭션 스태킹: SET LOCAL과 롤백

섹션 제목: “트랜잭션 스태킹: SET LOCAL과 롤백”

값이 덮어쓰이기 전에 push_old_value()가 현재 트랜잭션 중첩 레벨(GUCNestLevel)로 태그된 이전 값을 변수별 스택에 저장한다. 스택 항목의 상태(GUC_SET, GUC_SET_LOCAL, GUC_SAVE)는 왜 푸시됐는지를 기록하며, 트랜잭션이나 서브트랜잭션 종료 시 어떤 일이 일어날지 결정한다.

// push_old_value — src/backend/utils/misc/guc.c (entry-merge arm)
stack = gconf->stack;
if (stack && stack->nest_level >= GUCNestLevel)
{
Assert(stack->nest_level == GUCNestLevel);
switch (action)
{
case GUC_ACTION_SET:
/* SET overrides any prior action at same nest level */
if (stack->state == GUC_SET_LOCAL)
discard_stack_value(gconf, &stack->masked);
stack->state = GUC_SET;
break;
case GUC_ACTION_LOCAL:
if (stack->state == GUC_SET)
{
/* SET then SET LOCAL: remember SET's value as "masked" */
stack->masked_scontext = gconf->scontext;
set_stack_value(gconf, &stack->masked);
stack->state = GUC_SET_LOCAL;
}
break;
case GUC_ACTION_SAVE:
Assert(stack->state == GUC_SAVE);
break;
}
return;
}

트랜잭션 종료 시 AtEOXact_GUC(isCommit, nestLevel)가 스택이 비어있지 않은 모든 변수를 순회하며 nestLevel 이상의 항목을 팝한다. 중단 시(또는 SET LOCAL / GUC_SAVE 항목의 경우 커밋 시)에는 이전 값을 복원하고, 일반 GUC_SET의 경우 커밋 시에는 값을 유지한다. 이것이 SET LOCAL(COMMIT 시 되돌아감), 세이브포인트 롤백(서브트랜잭션의 SET을 되돌림), 함수 SET 절(GUC_ACTION_SAVE, 함수 종료 시 되돌아감)의 메커니즘이다. 변수 하나의 값 생애주기:

flowchart TD
    A["boot_val<br/>source=PGC_S_DEFAULT"] --> B["시작: file/argv/env<br/>reset_val도 설정"]
    B --> C["세션 실행 중<br/>현재 값"]
    C -->|"SET x = v<br/>GUC_ACTION_SET"| D["push_old_value(SET)<br/>*variable=v"]
    C -->|"SET LOCAL x = v<br/>GUC_ACTION_LOCAL"| E["push_old_value(LOCAL)<br/>현재 값 마스킹"]
    D -->|"COMMIT"| F["AtEOXact_GUC: v 유지"]
    D -->|"ROLLBACK"| G["AtEOXact_GUC: 이전 값 복원"]
    E -->|"COMMIT 또는 ROLLBACK"| H["AtEOXact_GUC: 이전 값 복원<br/>(LOCAL은 절대 살아남지 않음)"]
    C -->|"RESET x"| I["*variable = reset_val<br/>source=reset_source"]

SQL 표면은 guc_funcs.c에 있다. ExecSetVariableStmt()가 파싱된 VariableSetStmt를 디스패치한다. 일반 SET x = v는 인수 목록을 평탄화하고 호출자가 슈퍼유저면 PGC_SUSET, 아니면 PGC_USERSETset_config_option()을 호출한다. RESETSET ... TO DEFAULT는 NULL 값으로 호출하고, SET TRANSACTION / SET SESSION CHARACTERISTICS는 여러 기반 GUC로 팬아웃한다. SHOWGetPGVariableShowGUCOption()을 거쳐 show_hook과 단위 포맷팅을 적용한 값을 돌려준다.

// ExecSetVariableStmt (VAR_SET_VALUE arm) — src/backend/utils/misc/guc_funcs.c
(void) set_config_option(stmt->name,
ExtractSetVariableArgs(stmt),
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
action, true, 0, false);

문법이 SET에 인수 목록을 넘길 수 있으므로(SET search_path = a, b, c), 값이 퍼널에 도달하기 전에 flatten_set_variable_args()가 파싱된 인수 목록을 단일 정규 문자열로 평탄화한다. 변수의 플래그를 참조해 목록 입력이 허용되는지와 각 원소를 인용해야 하는지 결정한다.

// flatten_set_variable_args — src/backend/utils/misc/guc_funcs.c
record = find_option(name, false, true, WARNING);
flags = record ? record->flags : 0;
/* Complain if list input and non-list variable */
if ((flags & GUC_LIST_INPUT) == 0 && list_length(args) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("SET %s takes only one argument", name)));

GUC_LIST_INPUT / GUC_LIST_QUOTE 플래그는 이 로직의 선언적 짝이다. 테이블의 단일 비트가 평탄화기에게 정규 문자열을 어떻게 조립할지 알려줘, search_path 같은 목록 값 파라미터는 플래그 외에 특수 케이스 코드가 필요 없다.

postgresql.confProcessConfigFile()ProcessConfigFileInternal()로 적용된다. 주 파일과 PG_AUTOCONF_FILENAME(postgresql.auto.conf, ALTER SYSTEM이 쓰는 파일)을 ConfigVariable 목록으로 파싱하고, 현재 상태와 비교해 같은 set_config_with_handle() 퍼널에 source = PGC_S_FILE로 재적용한다. 파일에서 변경된 PGC_POSTMASTER 변수는 적용 대신 재시작이 필요하다고 보고된다. 리로드는 SIGHUP으로 구동된다. 시그널 핸들러가 ConfigReloadPending 플래그를 세우고, 각 프로세스가 메인 루프에서 이를 확인한다.

// SIGHUP 리로드 관용구 — e.g. src/backend/tcop/postgres.c
if (ConfigReloadPending)
{
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
}

각 백엔드가 독립적으로 ProcessConfigFile(PGC_SIGHUP)을 호출하므로, 리로드는 탈중앙화된다. postmaster, checkpointer, archiver, autovacuum, WAL 프로세스, 모든 일반 백엔드가 같은 시그널에 각자 파일을 재읽고 재적용한다. (프로세스 트리 전체에 SIGHUP이 전달되는 정확한 프로세스별 배관은 postgres-postmaster.md에서 다룬다.) 탈중앙화 설계는 단일 “설정 코디네이터”가 없다는 뜻이다. 일관성은 모든 프로세스가 동일한 결정론적 ProcessConfigFile을 동일한 파일에 실행함으로써 달성된다. 프로세스 간 핸드셰이크 없이 모두 같은 실효값으로 수렴한다. 주목할 결과로, 리로드는 프로세스 트리 전체에 걸쳐 원자적이지 않다. SIGHUP 이후 잠깐의 창(window) 동안 서로 다른 백엔드가 방금 변경된 PGC_SIGHUP 파라미터를 놓고 서로 다른 값을 볼 수 있다. 각 백엔드가 루프에서 ConfigReloadPending을 확인하는 지점에 도달할 때까지다. 이 경로를 타는 파라미터 — 타임아웃, 로깅 레벨, 플래너 토글 — 에서 그 일시적인 불일치는 무해하다.

GUC_REPORT로 플래그된 파라미터(예: client_encoding, DateStyle, application_name, in_hot_standby)는 프로토콜 레벨 ParameterStatus 메시지로 클라이언트에 전달된다. 성공적인 변경은 GUC_NEEDS_REPORT 상태 비트를 세우고 변수를 guc_report_list에 연결한다. ReportChangedGUCOptions()(다음 쿼리 대기 직전 실행)가 목록을 소진하며 변경된 변수마다 메시지를 하나씩 보내고, BeginReportingGUCOptions()가 백엔드 시작 시 초기값을 전송한다.

서브시스템은 세 파일과 두 헤더에 걸쳐 깔끔하게 분리된다. 호출 흐름 기준 읽기 순서:

레코드 레이아웃과 열거형 (src/include/utils/guc_tables.h, src/include/utils/guc.h). config_generic부터 시작하라. name반드시 첫 번째여야 하므로 제네릭 코드가 캐스트할 수 있다. 그 다음 다섯 개 타입 레코드 config_bool / config_int / config_real / config_string / config_enum. 각각이 gen을 임베드하고 variable, boot_val, 세 개의 훅 포인터를, 숫자 타입은 min/max를 추가한다. enum config_grouppg_settings 분류 체계다. guc.h에서: GucContext(일곱 단계 권한/타이밍 래더), GucSource(값 출처), GucAction(GUC_ACTION_SET / LOCAL / SAVE), 훅 타입정의(GucBoolCheckHook 등), GUC_* 플래그 비트(GUC_LIST_INPUT, GUC_NO_SHOW_ALL, GUC_EXPLAIN, GUC_REPORT, GUC_UNIT_* 등).

변수 테이블 (src/backend/utils/misc/guc_tables.c). NULL 종단 배열 다섯 개: ConfigureNamesBool[], ConfigureNamesInt[], ConfigureNamesReal[], ConfigureNamesString[], ConfigureNamesEnum[]. 각 항목은 중첩된 중괄호 초기화자다. 열거형 변수는 추가로 config_enum_entry[] 옵션 배열(backslash_quote_options, bytea_output_options 등)을 참조한다. 이 파일이 모든 코어 GUC의 문자 그대로의 카탈로그다.

부트스트랩과 조회 (src/backend/utils/misc/guc.c). build_guc_variables()가 배열을 세고 각 vartype을 설정하고 guc_hashtab(ASCII 전용 대소문자 무관 커스텀 해시 guc_name_hash / guc_name_compare 기반 dynahash, setlocale 전반에서 안정적)을 구축한다. find_option()이 이 해시로 이름을 확인하고 map_old_guc_names로 낡은 별칭을 매핑하고 필요시 add_placeholder_variable / assignable_custom_variable_name으로 플레이스홀더를 생성한다. InitializeGUCOptions() / InitializeGUCOptionsFromEnvironment()가 프로세스 시작 시 boot_val과 환경 변수로부터 모든 변수를 시드한다.

쓰기 퍼널 (guc.c). set_config_option / set_config_option_ext / set_config_with_handle이 단일 변이 경로다. record->context 스위치(래더), parse_and_validate_value()(parse_int/parse_real/parse_boolcall_*_check_hook 래퍼 호출), push_old_value()(이전 값 스태킹), *variable 할당, assign 훅 실행, GUC_REPORT에 대한 GUC_NEEDS_REPORT 설정을 수행한다.

읽기와 리포팅 (guc.c). ShowGUCOption()SHOWpg_settings를 위해 변수의 현재 값을 렌더링한다(show_hook과 단위 변환 적용). GetConfigOption()이 C 측 읽기 함수다. BeginReportingGUCOptions() / ReportChangedGUCOptions() / ReportGUCOptionGUC_REPORT 변수에 대한 ParameterStatus 프로토콜 트래픽을 구동한다.

설정 파일 처리 (guc.c). ProcessConfigFileInternal()postgresql.confpostgresql.auto.confConfigVariable 목록으로 파싱하고 퍼널에 차이를 재적용한다. SIGHUP에 ProcessConfigFile()로 호출된다. 순서가 의도적이다. 주 파일을 먼저 파싱하고, 같은 파라미터라면 ALTER SYSTEM이 쓴 값이 손으로 편집한 postgresql.conf 값을 이기도록 PG_AUTOCONF_FILENAME이후에 파싱한다. 적용 후, DataDir이 알려지기 전 부트스트랩 창을 특별 처리한다. 데이터 디렉터리를 찾기 전까지는 주 파일의 data_directory 설정만 적용하는데, 아직 읽지 않은 auto.conf가 다른 설정을 오버라이드할 수 있기 때문이다. 적용 후 이전에 파일에서 설정됐지만 지금은 없는 변수는 리셋한다. postgresql.conf에서 행을 삭제하고 리로드하면 그 파라미터가 마지막 파일 값이 아닌 비파일 값으로 되돌아간다.

파싱 기계 자체(ParseConfigFile / ParseConfigFp / ParseConfigDirectory, guc.h에 선언, guc-file.l에 구현)는 include, include_dir, include_if_exists 지시어를 처리하고 name, value, filename, sourceline을 가진 연결 목록 ConfigVariable 노드를 생성한다. sourcefile/sourceline 필드는 각 config_generic으로 전파되어 pg_settings.sourcefile이 DBA에게 파라미터를 어떤 포함 파일이 설정했는지 알려줄 수 있다. 값이 깊이 중첩된 include_dir에서 왔을 때 매우 유용하다.

SQL 표면 (src/backend/utils/misc/guc_funcs.c). ExecSetVariableStmt()SET / RESET / SET TRANSACTION을 구현한다. flatten_set_variable_args() / ExtractSetVariableArgs()가 파싱된 인수 목록을 정규 문자열로 변환한다. SetPGVariable()이 헬퍼 진입점이다. GetPGVariable() / ShowAllGUCConfig()SHOWSHOW ALL을 구현한다. ProcessGUCArray()(guc.c에 있음)가 함수, 롤, 데이터베이스에 부착된 proconfig / setconfig 배열을 적용한다.

위치 힌트 (2026-06-05 기준, REL_18 273fe94)

섹션 제목: “위치 힌트 (2026-06-05 기준, REL_18 273fe94)”
심볼파일라인
struct config_genericsrc/include/utils/guc_tables.h171
struct config_boolsrc/include/utils/guc_tables.h216
struct config_intsrc/include/utils/guc_tables.h230
enum config_groupsrc/include/utils/guc_tables.h55
GucContext (enum end)src/include/utils/guc.h80
GucSource (enum end)src/include/utils/guc.h127
GucAction (enum end)src/include/utils/guc.h206
GUC_REPORT flagsrc/include/utils/guc.h220
ConfigureNamesBool[]src/backend/utils/misc/guc_tables.c799
ConfigureNamesInt[]src/backend/utils/misc/guc_tables.c2162
ConfigureNamesReal[]src/backend/utils/misc/guc_tables.c3879
ConfigureNamesString[]src/backend/utils/misc/guc_tables.c4170
ConfigureNamesEnum[]src/backend/utils/misc/guc_tables.c5004
ProcessConfigFileInternalsrc/backend/utils/misc/guc.c282
build_guc_variablessrc/backend/utils/misc/guc.c903
add_guc_variablesrc/backend/utils/misc/guc.c1047
find_optionsrc/backend/utils/misc/guc.c1235
guc_name_comparesrc/backend/utils/misc/guc.c1300
guc_name_hashsrc/backend/utils/misc/guc.c1330
InitializeGUCOptionssrc/backend/utils/misc/guc.c1530
push_old_valuesrc/backend/utils/misc/guc.c2134
AtEOXact_GUCsrc/backend/utils/misc/guc.c2262
BeginReportingGUCOptionssrc/backend/utils/misc/guc.c2546
ReportChangedGUCOptionssrc/backend/utils/misc/guc.c2596
parse_and_validate_valuesrc/backend/utils/misc/guc.c3129
set_config_with_handlesrc/backend/utils/misc/guc.c3405
GetConfigOptionsrc/backend/utils/misc/guc.c4355
ShowGUCOptionsrc/backend/utils/misc/guc.c5471
call_bool_check_hooksrc/backend/utils/misc/guc.c6810
ExecSetVariableStmtsrc/backend/utils/misc/guc_funcs.c43
ExtractSetVariableArgssrc/backend/utils/misc/guc_funcs.c167
flatten_set_variable_argssrc/backend/utils/misc/guc_funcs.c192
SetPGVariablesrc/backend/utils/misc/guc_funcs.c315
GetPGVariablesrc/backend/utils/misc/guc_funcs.c382

REL_18_STABLE, 커밋 273fe94(PG 18.x)의 /data/hgryoo/references/postgres를 기준으로 검증했다. 수행한 확인:

  • 레코드 레이아웃. struct config_genericconst char *name으로 시작하며 /* name of variable - MUST BE FIRST */ 주석이 달려 있다. 헤더로 캐스트하는 관용구를 확인한다. 다섯 개 타입 구조체(config_bool/int/real/string/enum) 각각이 struct config_generic gen을 첫 번째 멤버로 임베드하고 위에서 인용한 variable / boot_val / check_hook / assign_hook / show_hook 필드를 가지며, 숫자 레코드는 min/max를 추가한다. src/include/utils/guc_tables.h에서 확인했다.

  • 컨텍스트 래더. GucContextsrc/include/utils/guc.h에서 정확히 일곱 단계 PGC_INTERNAL, PGC_POSTMASTER, PGC_SIGHUP, PGC_SU_BACKEND, PGC_BACKEND, PGC_SUSET, PGC_USERSET를 열거한다. set_config_with_handle()switch (record->context)가 각 단계를 강제한다. PGC_SU_BACKENDPGC_BACKEND 폴스루(pg_parameter_aclcheck 포함), PGC_POSTMASTER-on-SIGHUP prohibitValueChange 경로, PGC_SUSET ACL 확인이 설명대로 존재한다.

  • 테이블. 다섯 개 ConfigureNames*[] 배열이 guc_tables.c의 표에 기재된 라인에 존재한다. enable_seqscan(bool, PGC_USERSET, GUC_EXPLAIN)과 min_dynamic_shared_memory(int, PGC_POSTMASTER, GUC_UNIT_MB) 항목을 그대로 인용했다.

  • 퍼널과 훅. build_guc_variables()guc_name_hash / guc_name_matchguc_hashtab를 구축한다. find_option()이 이 해시로 이름을 확인하고 플레이스홀더를 생성한다. parse_and_validate_value()가 범위 확인을 수행하고 call_int_check_hook 등을 호출한다. call_bool_check_hook()이 위에 인용한 대로 GUC_check_* 리포팅 채널을 확립한다. push_old_value() / AtEOXact_GUC()가 트랜잭션 스택을 구현한다.

  • SQL + 리로드 표면. ExecSetVariableStmt()VAR_SET_VALUE 경로에서 superuser() ? PGC_SUSET : PGC_USERSETPGC_S_SESSION으로 set_config_option을 호출한다. 위에 인용한 대로다. ProcessConfigFileInternal()이 주 파일과 PG_AUTOCONF_FILENAME을 파싱한다. if (ConfigReloadPending) { ... ProcessConfigFile(PGC_SIGHUP); } 관용구가 tcop/postgres.cpostmaster/interrupt.c, checkpointer.c, pgarch.c, syslogger.c, startup.c 등에 나타난다. 탈중앙화 리로드를 확인했다.

  • 범위 규율. 이 문서는 PG19 전용 사실을 의도적으로 주장하지 않는다. 인용한 모든 심볼, 열거형 멤버, 플래그 비트는 REL_18 트리에서 확인된다. contrib/는 범위 밖이다. 커스텀 변수 플레이스홀더는 코어 find_option / add_placeholder_variable 경로로만 기술한다.

PostgreSQL 너머 — 비교 설계와 연구 프런티어

섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 프런티어”

MySQL / InnoDB. MySQL의 시스템 변수는 PostgreSQL의 디스크립터 배열 정신을 공유한다(각 변수가 타입, 범위 — GLOBAL / SESSION / 둘 다 — , 확인/갱신 함수를 가진 sys_var 객체). 그러나 인터페이스는 SQL 우선이다. SET GLOBAL이 실행 중인 서버의 전역 값을 직접 변경하고, SET PERSIST(8.0+)가 PostgreSQL의 ALTER SYSTEMpostgresql.auto.conf에 가까운 mysqld-auto.cnf에 쓴다. MySQL은 더 많은 파라미터를 재시작 없이 변경 가능하게 만드는 데 더 기울어져 있다. PostgreSQL의 PGC_* 래더가 바인딩 타임을 각 변수의 정적이고 선언적인 속성으로 만드는 것과 대조된다.

Oracle. Oracle의 초기화 파라미터는 ALTER SYSTEM에 명시적 SCOPE(MEMORY / SPFILE / BOTH)를 가진다. “실행 중인 인스턴스를 변경”과 “다음 시작을 위해 영속”을 분리한다. PostgreSQL은 이 구분을 PGC_POSTMASTERPGC_SIGHUP 컨텍스트와 auto.conf 파일로 암묵적으로 인코딩한다. Oracle도 파라미터를 조회 가능한 V$PARAMETER 뷰로 노출하는데, PostgreSQL의 pg_settings와 같다.

SQLite. 반대 극단에서 SQLite의 PRAGMA 메커니즘은 연결별이고 대부분 일시적이다. 공유 메모리나 멀티프로세스 조정이 없다. PostgreSQL의 메커니즘이 존재하는 이유를 상기시켜 준다. 그 값들이 프로세스 트리 전체에서 일관성을 가져야 하고 리로드에서도 살아남아야 하기 때문이다.

자가 튜닝과 연구 프런티어. Architecture of a Database System(Hellerstein et al. 2007)은 튜닝 부담을 AutoAdmin 계열 연구의 동기로 이미 언급했다. 현대 프런티어는 자동 설정이다. OtterTune 같은 시스템은 GUC 표면을 고차원 최적화 문제로 다루고 머신러닝으로 탐색한다. “자가 주행” 데이터베이스 제안(Pavlo et al.)은 파라미터 튜닝을 폐쇄 제어 루프에 통합한다. PostgreSQL이 이런 시스템의 친화적인 타겟인 이유가 바로 여기서 설명한 설계다 — 선언된 타입, 범위, 단위, 바인딩 타임을 가진 균일하고 인트로스펙션 가능한 카탈로그(pg_settings) — 덕에 외부 튜너가 탐색 공간을 열거하고, 어느 파라미터가 재시작을 필요로 하는지 존중하고, ALTER SYSTEM으로 파라미터별 특수 글루 없이 변경을 적용할 수 있다. check/assign 훅 분리는 파라미터가 제네릭 튜너가 의존할 수 있는 의미론적 검증을 가지도록 만든다.

두 번째 프런티어는 멀티테넌트 배포에서의 테넌트별 / 워크로드별 설정이다. PostgreSQL은 이미 데이터베이스별 및 롤별 기본값(ALTER DATABASE/ROLE ... SET, PGC_S_DATABASE / PGC_S_USER 소스로 ProcessGUCArray가 적용)을 지원한다. 이것은 클라우드 데이터베이스 서비스가 관리 파라미터 그룹으로 발전시키는 워크로드 인식 설정의 거친 형태다. 앞서 설명한 출처 순서가 이 레이어링을 잘 정의되게 만든다. 데이터베이스별 기본값은 파일보다 우선하지만 대화형 SET보다는 낮다. 세션이 테넌트 기준선 위에서 스스로 튜닝할 수 있다. 학습된 정책이 선택한 쿼리별 또는 연산자별 설정으로 더 나아가는 것은 활발한 연구 방향이며, GUC_ACTION_SAVE / 함수 SET 절 메커니즘(CREATE FUNCTION ... SET work_mem을 뒷받침하는 같은 경로)이 이미 범위가 지정되고 자동으로 되돌아가는 오버라이드의 자연스러운 삽입 지점이다.

세 번째 실용적 주제는 설정 자체의 가시성이다. 모든 값이 source, scontext, srole, sourcefile, sourceline을 가지므로, pg_settingspg_file_settings로 운영자가 “이 파라미터가 왜 이 값으로 설정됐고, 누가 설정했는가?”를 추측 없이 답할 수 있다. 디스크립터 배열 설계가 거의 무료로 제공하는 기능이다. 출처 필드가 같은 레코드의 추가 열일 뿐이기 때문이다. 플랫 키-값 저장소 위에 구축된 엔진들은 대개 이를 나중에 덧붙인다. GUC 서브시스템이 가르치는 교훈은 바인딩 타임, 권한, 출처를 — 호출 지점 곳곳의 관례가 아닌 단일 균일 레코드의 1등급 필드로 만드는 것 — 이 하나의 메커니즘이 천 개의 파라미터로 확장하면서도 인트로스펙션 가능하고 튜닝 가능하고 안전하게 유지되게 만드는 것이라는 점이다.

  • 코드 (REL_18_STABLE, 커밋 273fe94, PG 18.x):
    • src/backend/utils/misc/guc.c — 부트스트랩(build_guc_variables), 조회(find_option), 쓰기 퍼널(set_config_with_handle), 검증(parse_and_validate_value, call_*_check_hook), 트랜잭션 스태킹(push_old_value, AtEOXact_GUC), 읽기(ShowGUCOption, GetConfigOption), 리포팅(BeginReportingGUCOptions, ReportChangedGUCOptions), 설정 파일 처리(ProcessConfigFileInternal).
    • src/backend/utils/misc/guc_tables.c — 다섯 개 ConfigureNames*[] 변수 배열과 열거형 옵션 테이블.
    • src/backend/utils/misc/guc_funcs.c — SQL 표면(ExecSetVariableStmt, SetPGVariable, GetPGVariable, flatten_set_variable_args).
    • src/include/utils/guc.h, src/include/utils/guc_tables.hconfig_generic 헤더, 다섯 개 타입 레코드, GucContext / GucSource / GucAction / config_group 열거형과 GUC_* 플래그 비트.
  • 이론:
    • Hellerstein, Stonebraker & Hamilton, Architecture of a Database System (2007) — knowledge/research/dbms-papers/fntdb07-architecture.md (DBMS 컴포넌트 분해와 튜닝 부담).
    • 교차 참조: knowledge/research/dbms-papers/README.md.omc/plans/postgres-paper-bibliography.md의 논문 서지 맵.
  • 형제 문서 (교차 참조, 중복 없음):
    • postgres-backend-lifecycle.md — 백엔드 GUC 상속과 병렬 워커 GUC 상태 복사(RestoreGUCState).
    • postgres-postmaster.md — 프로세스 트리 전체에 걸친 SIGHUP 전달.
    • postgres-overview-base-infra.md — 베이스 인프라 모듈 중 GUC 서브시스템의 위치.