콘텐츠로 이동

(KO) PostgreSQL 익스텐션 — CREATE EXTENSION, 컨트롤 파일, 버전 스크립트

목차

관계형 데이터베이스의 핵심은 타입이 붙은 오브젝트들의 컬렉션이다. 테이블, 함수, 연산자, 타입, 캐스트, 인덱스가 CREATE 구문으로 만들어지고 시스템 카탈로그에 기록된다. 익스텐션 추상화는 이 CREATE 기계가 답하지 못하는 패키징 문제를 해결한다. 관련 오브젝트 묶음을 하나의 설치 가능하고 버전이 붙고 삭제 가능한 단위로 다루려면 어떻게 해야 하는가.

익스텐션 없이는 PostGIS나 hstore 같은 기능이 그저 어느 설치 스크립트가 만든 카탈로그 항목들의 더미에 불과하다. 카탈로그 어디에도 그 항목들이 서로 묶여 있다는 사실, 특정 패키지 버전 3.2에서 왔다는 사실, 그 중 하나를 삭제하려 할 때 나머지가 의존하므로 거부해야 한다는 사실이 기록되지 않는다. pg_dump는 각 오브젝트의 DDL을 개별로 덤프해야 하며, 이는 서버 버전 간에 취약하고 덤프를 비대하게 만든다.

PostgreSQL 9.1에서 도입된 익스텐션 개념은 확장 가능한 시스템 어디서나 반복되는 세 가지 하위 문제에 대한 답이다.

  1. 그룹화 / 멤버십. 오브젝트 X가 패키지 P의 구성원이라는 카탈로그 사실이 있어야 한다. PostgreSQL은 이를 각 구성원 오브젝트에서 소유 pg_extension 행으로 향하는 pg_depend 엣지(DEPENDENCY_EXTENSION 타입, 'e')로 기록한다. 멤버십이 있기 때문에 DROP EXTENSION이 캐스케이드되고 pg_dump가 수백 개의 CREATE 구문 대신 단 하나의 CREATE EXTENSION 행을 내보낼 수 있다.

  2. 버전 관리와 업그레이드 경로. 패키지는 진화한다. 오늘 1.0을 설치하고 내일 1.2로 업그레이드하면 처음부터 1.2를 설치한 것과 동일한 카탈로그 상태가 되어야 한다. 마이그레이션 프레임워크처럼 델타 스크립트(P--1.0--1.1.sql, P--1.1--1.2.sql) 디렉터리와 올바른 델타 순서를 결정하는 경로 탐색 단계로 해결한다.

  3. 출처와 재현성. 덤프-복원(pg_dump/pg_restore) 또는 pg_upgrade는 내부 DDL을 재실행하지 않고 이름과 버전으로 패키지를 재생성해야 한다. 덤프는 “이 서버에서 hstore 버전 1.2를 설치하라”고 명령하고, 로컬 스크립트 파일이 실제 작업을 수행한다.

Database System Concepts는 DBMS의 데이터 정의 계층을 데이터 사전(카탈로그)의 관리자로 규정한다. 항목 간 무결성 제약(외래 키, 의존성)도 사전 사실이다. POSTGRES 프로젝트 논문(Stonebraker & Rowe 1986, “The Design of POSTGRES”; Stonebraker & Kemnitz 1991, “The POSTGRES Next-Generation DBMS”)은 사용자 정의 타입·연산자·접근 방법·절차적 언어라는 확장성을 후속 과제가 아닌 일등 시민 설계 목표로 삼는다. 현대 익스텐션 메커니즘은 그 확장성을 설치 가능한 제품으로 출하하게 만든 패키징 계층이다. C 공유 라이브러리와 SQL 글루 스크립트를 카탈로그로 묶고 버전을 부여한다.

익스텐션 구현자가 선택해야 하는 설계 공간은 세 축으로 나뉜다.

  1. 멤버십은 어디에 저장하는가? 모든 오브젝트에서 외래 키가 달리는 전용 “패키지” 카탈로그인가, 아니면 기존 의존성 그래프를 재사용하는가? PostgreSQL은 pg_depend를 재사용하므로 익스텐션 코드 자체가 작다. DROP EXTENSION의 대부분은 기존 의존성 워커가 처리한다.

  2. 버전을 어떻게 조합하는가? 선형 마이그레이션 체인인가, 임의의 그래프와 최단 경로 선택인가? PostgreSQL은 그래프를 선택했다. 어떤 from--to 스크립트든 엣지이며, 엔진이 다익스트라를 실행해 가장 저렴한 설치·업그레이드 경로를 찾는다.

  3. 누가 설치할 수 있고, 어느 스키마에 설치되는가? 슈퍼유저 전용 모델이 가장 단순하지만 매니지드 클라우드 테넌트를 막는다. PostgreSQL은 두 가지 완화를 추가한다. 재배치 가능(relocatable) 익스텐션(DBA가 스키마를 선택)과 신뢰(trusted) 익스텐션(데이터베이스 CREATE 권한을 가진 비슈퍼유저가 선별된 서브셋을 설치 가능).

데이터베이스 오브젝트의 패키징·버전 관리·의존성 추적은 시스템 간에 인식 가능한 엔지니어링 관례로 수렴한다. 이를 먼저 명명해 두면 PostgreSQL의 구체적 심볼들이 공통 플레이북 안의 한 가지 선택으로 읽힌다.

오브젝트 생성 전에 파싱하는 패키지 디스크립터

섹션 제목: “오브젝트 생성 전에 파싱하는 패키지 디스크립터”

모든 패키지 시스템은 카탈로그를 건드리기 전에 소형 매니페스트를 읽는다. 패키지 이름, 기본 버전, 정책 플래그(누가 설치할 수 있는지, 어느 스키마에 설치되는지)를 사전에 확보해 저렴하게 거부할 수 있어야 한다. 매니페스트는 대개 키/값 텍스트 파일이다. 공통 패턴은 매니페스트 파싱 → 정책 검증 → 작업 계획 → 실행이며, 매니페스트 파싱은 부작용이 없다.

버전 델타 스크립트와 경로 플래너

섹션 제목: “버전 델타 스크립트와 경로 플래너”

스키마 마이그레이션 도구(Flyway, Rails migrations, Alembic)와 데이터베이스 패키지 관리자는 모두 같은 형태로 수렴한다. 버전 전환을 수행하는 스크립트들의 디렉터리, 그리고 “from”과 “to” 버전이 주어지면 실행할 스크립트를 선택·정렬하는 플래너다. 선형 명명 규칙(V1__, V2__)은 체인을 의미하고, from--to 규칙은 그래프와 최단 경로 탐색을 의미한다. 그래프 형태가 엄밀히 더 일반적이다. 패키지 작성자가 증분 단계 옆에 직접 1.0--1.3 “빠른 전환” 스크립트를 제공할 수 있다.

의존성 그래프에 기록되는 멤버십

섹션 제목: “의존성 그래프에 기록되는 멤버십”

별도의 “이 패키지에 뭐가 들어있는가” 테이블을 발명하는 대신 성숙한 시스템들은 DROP ... RESTRICT/CASCADE를 위해 이미 존재하는 오브젝트 의존성 그래프를 재사용한다. 패키지는 또 하나의 노드가 되고, 구성원 오브젝트가 그 노드로 엣지를 갖는다. 삭제 로직, 덤프 순서 로직, “다른 것이 의존하므로 삭제 불가” 오류가 모두 기존 그래프 워커로부터 무료로 따라온다.

제어된 에스컬레이션 해치를 가진 권한 게이팅

섹션 제목: “제어된 에스컬레이션 해치를 가진 권한 게이팅”

패키지 설치는 임의의 DDL을 실행하므로 기본 규칙은 “슈퍼유저만”이다. 하지만 멀티 테넌트 배포에서는 비슈퍼유저가 검증된 패키지를 설치해야 한다. 일반적인 개선 방법은 패키지별 “권한 있는 역할로서 비권한 호출자 대신 실행해도 안전함” 플래그와, 스크립트 실행 기간 동안 자동으로 되돌아오는 일시적 권한 전환을 조합하는 것이다.

flowchart TD
  A["CREATE EXTENSION foo"] --> B["컨트롤 파일 읽기<br/>foo.control (매니페스트)"]
  B --> C["버전 선택<br/>(명시적 또는 default_version)"]
  C --> D{"직접 설치<br/>스크립트 존재?"}
  D -->|yes| E["foo--ver.sql 실행"]
  D -->|no| F["버전 그래프 구성<br/>+ 다익스트라 최단 경로"]
  F --> G["설치 스크립트 실행 후<br/>업그레이드 델타를 순서대로 실행"]
  E --> H["pg_extension 행 삽입"]
  G --> H
  H --> I["스크립트가 creating_extension = true<br/>상태에서 오브젝트 생성"]
  I --> J["각 오브젝트가 자동으로<br/>pg_depend에 DEPENDENCY_EXTENSION 엣지 기록"]

그림 1 — 공통 패키지 설치 파이프라인, PostgreSQL 심볼 이름으로 인스턴스화. 매니페스트는 .control 파일; 경로 플래너는 from--to 스크립트 그래프 위의 다익스트라; 멤버십은 DEPENDENCY_EXTENSION 엣지.

PostgreSQL은 전체 메커니즘을 파일 하나, src/backend/commands/extension.c와 카탈로그 하나, pg_extension에 구현한다. 파일 맨 위 주석이 미니멀리스트 설계 의도를 직접 서술한다.

// extension.c header comment — src/backend/commands/extension.c
// All we need internally to manage an extension is an OID so that the
// dependent objects can be associated with it. An extension is created by
// populating the pg_extension catalog from a "control" file.
// The extension control file is parsed with the same parser we use for
// postgresql.conf. An extension also has an installation script file,
// containing SQL commands to create the extension's objects.

익스텐션은 OID 하나와 카탈로그 행 하나다. 나머지(멤버십, 드롭 캐스케이드, 덤프)는 postgres-dependency-tracking.md에서 다루는 범용 의존성 기계와 postgres-ddl-execution.md의 DDL 실행기에 위임된다.

카탈로그는 의도적으로 얇다. 고정 길이 열 여섯 개와 가변 길이 배열 두 개다.

// FormData_pg_extension — src/include/catalog/pg_extension.h
CATALOG(pg_extension,3079,ExtensionRelationId)
{
Oid oid;
NameData extname; /* extension name */
Oid extowner BKI_LOOKUP(pg_authid);
Oid extnamespace BKI_LOOKUP(pg_namespace); /* namespace of
* contained objects */
bool extrelocatable; /* if true, allow ALTER EXTENSION SET SCHEMA */
#ifdef CATALOG_VARLEN
text extversion BKI_FORCE_NOT_NULL; /* extension version name */
Oid extconfig[1] BKI_LOOKUP(pg_class); /* dumpable config tables */
text extcondition[1]; /* WHERE clauses for them */
#endif
} FormData_pg_extension;

구성원 오브젝트 목록이 없다는 점에 주목해야 한다. 멤버십은 전적으로 pg_depend에 산다. extversion 열이 “어느 버전이 설치되어 있는가”의 단일 진실 원천이다. extconfig/extconditionpg_extension_config_dump() 기능(WHERE 절로 필터링된 데이터를 덤프해야 하는 설정 테이블)을 지원한다. OID와 이름 syscache(EXTENSIONOID, EXTENSIONNAME)를 뒷받침하는 고유 인덱스가 두 개 있다.

컨트롤 파일 extname.controlParseConfigFp로, 즉 GUC/postgresql.conf 파서와 동일한 파서로 ExtensionControlFile 구조체에 파싱된다. 인식되는 키는 구조체 필드와 일대일 대응한다.

// ExtensionControlFile — src/backend/commands/extension.c
typedef struct ExtensionControlFile
{
char *name;
char *directory; /* directory for script files */
char *default_version; /* default install target version */
char *module_pathname; /* substituted for MODULE_PATHNAME */
char *comment;
char *schema; /* target schema (allowed if !relocatable) */
bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
bool superuser; /* must be superuser to install? */
bool trusted; /* allow becoming superuser on the fly? */
int encoding; /* encoding of the script file, or -1 */
List *requires; /* names of prerequisite extensions */
List *no_relocate; /* prerequisites that must not be relocated */
/* ... control_dir, basedir elided ... */
} ExtensionControlFile;

new_ExtensionControlFile은 키가 없을 때 적용되는 기본값을 설정한다. 이 기본값들은 익스텐션 작성자가 의존하는 계약의 일부다.

// new_ExtensionControlFile — src/backend/commands/extension.c
control->name = pstrdup(extname);
control->relocatable = false;
control->superuser = true; /* default: superuser-only */
control->trusted = false; /* default: not trusted */
control->encoding = -1; /* default: database encoding */

기본(primary) 컨트롤 파일(foo.control)은 어떤 키든 설정할 수 있다. 보조(secondary/auxiliary) 컨트롤 파일(foo--1.2.control)은 버전별 오버라이드를 담지만 directorydefault_version을 설정할 수 없다. 이 두 키는 버전이 아닌 익스텐션 전체에 속하기 때문이다. parse_extension_control_file이 이를 강제하고, relocatable/schema 상호 배제도 강제한다.

// parse_extension_control_file — src/backend/commands/extension.c
if (control->relocatable && control->schema != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true")));

스크립트 파일들은 컨트롤 파일 옆(또는 directory= 아래)에 놓이며, 수행하는 버전 전환으로 이름 붙는다. PostgreSQL은 디렉터리를 나열해 파일 이름을 파싱해서 버전 그래프를 탐색한다. 인덱스 파일 따위는 없다. get_extension_script_filename이 두 가지 명명 형태를 보여 준다.

// get_extension_script_filename — src/backend/commands/extension.c
if (from_version)
snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql",
scriptdir, control->name, from_version, version); /* upgrade delta */
else
snprintf(result, MAXPGPATH, "%s/%s--%s.sql",
scriptdir, control->name, version); /* install script */

hstore--1.4.sql설치 스크립트(그래프 정점, installable로 표시)이고 hstore--1.3--1.4.sql은 1.3에서 1.4로의 업그레이드 엣지다. 이 이중 대시 규칙 때문에 익스텐션·버전 이름에 --이 금지된다(check_valid_extension_name / check_valid_version_name 참조). --가 허용되면 파일명 문법이 모호해진다.

flowchart LR
  subgraph installs["설치 가능 버전"]
    v10["1.0"]
    v12["1.2"]
  end
  v10 -->|"foo--1.0--1.1.sql"| v11["1.1"]
  v11 -->|"foo--1.1--1.2.sql"| v12
  v12 -->|"foo--1.2--1.3.sql"| v13["1.3"]
  v10 -->|"foo--1.0--1.3.sql<br/>(빠른 전환 엣지)"| v13
  classDef tgt fill:#d5f5d5,stroke:#2a2;
  class v13 tgt;

그림 2 — 버전 그래프. get_ext_ver_list가 디렉터리 목록에서 이를 구성하고, find_install_path가 모든 installable 정점에서 대상(1.3)까지 다익스트라를 실행해 가장 저렴한 경로를 선택한다. 1.0--1.3 빠른 전환 엣지가 존재하면 두 홉 직선 경로가 세 홉 증분 체인을 이긴다.

CreateExtension은 얇은 래퍼다. 이름을 확인하고, 중복을 거부(또는 IF NOT EXISTS를 처리)하며, 중첩을 금지하고(전역 creating_extension 플래그로 추적, 한 번에 하나의 익스텐션만 생성 중일 수 있음), SCHEMA/VERSION/CASCADE 옵션을 해체한 뒤 CreateExtensionInternal에 넘긴다. 내부 워커에서 계획이 이루어진다. 먼저 기본 컨트롤 파일을 읽고 대상 버전을 결정한다.

// CreateExtensionInternal — src/backend/commands/extension.c
pcontrol = read_extension_control_file(extensionName);
if (versionName == NULL)
{
if (pcontrol->default_version)
versionName = pcontrol->default_version;
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("version to install must be specified")));
}
check_valid_version_name(versionName);

그런 다음 대상 버전에 대한 직접 설치 스크립트가 있는지 묻는다. stat()foo--<target>.sql을 찾으면 경로 탐색이 필요 없다. 없으면 버전 그래프를 구성하고 find_install_path를 실행해 시작 설치 버전과 업그레이드 델타 시퀀스를 계산한다.

// CreateExtensionInternal — src/backend/commands/extension.c
filename = get_extension_script_filename(pcontrol, NULL, versionName);
if (stat(filename, &fst) == 0)
{
updateVersions = NIL; /* easy: one install script */
}
else
{
evi_list = get_ext_ver_list(pcontrol);
evi_target = get_ext_ver_info(versionName, &evi_list);
evi_start = find_install_path(evi_list, evi_target, &updateVersions);
if (evi_start == NULL)
ereport(ERROR, ... "has no installation script nor update path" ...);
versionName = evi_start->name; /* install this first, then upgrade */
}

계획이 확정된 후에야 카탈로그를 건드린다. 대상 스키마를 결정하고, requires 전제 조건을 재귀적으로 설치하며(get_required_extension으로, CASCADE를 처리하고 순환 의존성을 감지함), InsertExtensionTuplepg_extension 행을 삽입하고, 기본 설치 스크립트를 실행한 뒤 ApplyExtensionUpdates로 업그레이드 델타를 재실행한다. 순서가 중요하다. 스크립트가 실행되기 전에 카탈로그 행이 있어야 한다. 스크립트의 CREATE 구문들이 의존성 엣지를 달 유효한 CurrentExtensionObject OID가 필요하기 때문이다.

스크립트 실행: 의존성 기록 스위치

섹션 제목: “스크립트 실행: 의존성 기록 스위치”

execute_extension_script가 멤버십 마법이 일어나는 곳이다. SQL 실행 전에 두 전역 변수를 설정한다.

// execute_extension_script — src/backend/commands/extension.c
creating_extension = true;
CurrentExtensionObject = extensionOid;
PG_TRY();
{
char *c_sql = read_extension_script_file(control, filename);
/* ... @extschema@ / @extowner@ / MODULE_PATHNAME substitution ... */
execute_sql_string(c_sql, filename);
}
PG_FINALLY();
{
creating_extension = false;
CurrentExtensionObject = InvalidOid;
}
PG_END_TRY();

creating_extension이 true인 동안 스크립트가 생성하는 모든 오브젝트는 DDL 계층에서 recordDependencyOnCurrentExtension을 호출해 CurrentExtensionObject로 향하는 DEPENDENCY_EXTENSION('e') 엣지를 추가한다. 이것이 오브젝트를 익스텐션의 구성원으로 만드는 유일한 메커니즘이다. 익스텐션 코드는 구성원을 열거하지 않는다. 구성원들이 생성 시점에 스스로를 등록한다. 자세한 내용은 postgres-dependency-tracking.md에서 다룬다.

함수는 또한 안전한 실행 환경을 설정한다. client_min_messages/log_min_messages를 최소 WARNING으로 강제하고, check_function_bodies를 비활성화하며, search_path를 대상 스키마를 먼저, 필요 익스텐션들의 스키마를 다음, pg_temp를 마지막으로 구성한다. pg_temp를 마지막에 두어 악의적인 임시 오브젝트가 실제 오브젝트를 가릴 수 없게 한다.

매크로 치환: @extschema@, @extowner@, MODULE_PATHNAME

섹션 제목: “매크로 치환: @extschema@, @extowner@, MODULE_PATHNAME”

스크립트 텍스트가 실행되기 전에 일련의 replace_text 치환이 수행된다. 재배치 불가능한 익스텐션은 @extschema@로 스키마를 하드코딩할 수 있다. 필요 익스텐션의 스키마는 @extschema:other_ext@로, 소유자는 @extowner@로, C 라이브러리 경로는 MODULE_PATHNAME으로 접근한다. 치환된 각 값은 quote_identifier를 거치며, 값에 따옴표 관련 문자가 포함되면 치환을 거부해 SQL 인젝션 취약점을 막는다.

// execute_extension_script — src/backend/commands/extension.c
const char *quoting_relevant_chars = "\"$'\\";
/* ... */
if (!control->relocatable)
{
const char *qSchemaName = quote_identifier(schemaName);
t_sql = DirectFunctionCall3Coll(replace_text, C_COLLATION_OID, t_sql,
CStringGetTextDatum("@extschema@"),
CStringGetTextDatum(qSchemaName));
if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
ereport(ERROR, ... "invalid character in extension \"%s\" schema" ...);
}

재배치 가능 익스텐션과 ALTER EXTENSION SET SCHEMA

섹션 제목: “재배치 가능 익스텐션과 ALTER EXTENSION SET SCHEMA”

재배치 가능 익스텐션은 오브젝트들이 스키마를 하드코딩하지 않으므로 설치 후 다른 스키마로 이동할 수 있다고 약속한다. AlterExtensionNamespaceALTER EXTENSION ... SET SCHEMA를 구현한다. 소유권을 확인하고, extrelocatable을 검증한 뒤, 직접 익스텐션에 의존하는 모든 오브젝트를 pg_depend에서 스캔해 각각의 네임스페이스를 이동하고, 마지막으로 pg_extension 행의 extnamespace를 업데이트한다.

// AlterExtensionNamespace — src/backend/commands/extension.c
/* Check extension is supposed to be relocatable */
if (!extForm->extrelocatable)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("extension \"%s\" does not support SET SCHEMA",
NameStr(extForm->extname))));

no_relocate 컨트롤 키는 익스텐션이 자신의 전제 조건 중 하나의 재배치를 금지하게 한다. 네임스페이스 스캔 중 의존하는 익스텐션이 이 익스텐션을 no_relocate 목록에 나열하면 SET SCHEMA가 거부된다. 익스텐션 A가 설치 시점에 B의 스키마 이름을 캡처해(하드코딩된 참조 등으로) B가 나중에 이동하면 깨지는 경우에 중요하다.

신뢰 익스텐션: 제어된 권한 에스컬레이션

섹션 제목: “신뢰 익스텐션: 제어된 권한 에스컬레이션”

기본적으로 superuser = true이므로 익스텐션 설치에 슈퍼유저가 필요하다. 신뢰(trusted) 익스텐션(trusted = true)은 이를 완화한다. 현재 데이터베이스에 CREATE 권한을 가진 비슈퍼유저가 설치할 수 있으며, 스크립트는 부트스트랩 슈퍼유저로 실행된다. 정책 게이트는 작은 함수 하나다.

// extension_is_trusted — src/backend/commands/extension.c
static bool
extension_is_trusted(ExtensionControlFile *control)
{
AclResult aclresult;
/* Never trust unless extension's control file says it's okay */
if (!control->trusted)
return false;
/* Allow if user has CREATE privilege on current database */
aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId,
GetUserId(), ACL_CREATE);
return (aclresult == ACLCHECK_OK);
}

비슈퍼유저로 게이트를 통과하면 execute_extension_scriptSetUserIdAndSecContextSECURITY_LOCAL_USERID_CHANGE와 함께 호출해 BOOTSTRAP_SUPERUSERID로 일시 전환한다. 트랜잭션이 중단되면 전환이 자동으로 되돌아가고, GUC 변경도 guc.c가 롤백한다. “DBMS 공통 설계” 절의 제어된 에스컬레이션 해치가 구체화된 것이다. 인코어(in-core) 신뢰 익스텐션 집합(컨트롤 파일에 trusted = true가 있는 것들)은 프로젝트가 직접 관리한다. 테넌트가 권한을 탈출할 수 있게 만드는 것은 의도적으로 신뢰 표시를 받지 않는다.

flowchart TD
  A["execute_extension_script"] --> B{"control->superuser<br/>&& !superuser()?"}
  B -->|no| F["호출자 권한으로 스크립트 실행"]
  B -->|yes| C{"extension_is_trusted?<br/>(trusted 플래그 + DB CREATE 권한)"}
  C -->|no| D["ERROR: 권한 없음"]
  C -->|yes| E["SetUserIdAndSecContext<br/>BOOTSTRAP_SUPERUSERID"]
  E --> G["부트스트랩 슈퍼유저로 스크립트 실행"]
  G --> H["신원 + GUC는 트랜잭션 종료 / 중단 시 자동 복원"]
  F --> H

그림 3 — execute_extension_script의 슈퍼유저 / 신뢰 결정 흐름. 에스컬레이션은 스크립트 범위로 한정되며 자동 복원된다.

ExecAlterExtensionStmtALTER EXTENSION ... UPDATE를 처리한다. extversion에서 설치된 버전을 읽고, 옵션 목록 또는 default_version에서 대상을 읽은 뒤, identify_update_path로 델타 시퀀스를 구하고 ApplyExtensionUpdates로 재실행한다. 델타를 하나씩 적용한다. 이렇게 해야 오래된 업그레이드 스크립트가 새 버전의 컨트롤 파라미터를 보지 않는다.

DROP EXTENSION은 익스텐션 전용 코드가 거의 필요 없다. RemoveExtensionById에 도달할 때 범용 의존성 워커가 이미 캐스케이드 삭제를 위한 모든 DEPENDENCY_EXTENSION 구성원을 수집했다. 이 함수는 현재 생성 중인 익스텐션을 삭제하는 것을 막은 뒤 pg_extension 행만 삭제한다.

// RemoveExtensionById — src/backend/commands/extension.c
if (extId == CurrentExtensionObject)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot drop extension \"%s\" because it is being modified",
get_extension_name(extId))));

바로 위의 주석은 설계를 직접 서술한다. “All we need do here is remove the pg_extension tuple itself. Everything else is taken care of by the dependency infrastructure.” 이 문장이 익스텐션 서브시스템의 전체 아키텍처적 베팅이다.

컨트롤 파일 탐색: extension_control_path

섹션 제목: “컨트롤 파일 탐색: extension_control_path”

PostgreSQL 18에서 컨트롤 파일은 고정된 SHAREDIR/extension 디렉터리만이 아니라 경로를 탐색해 찾는다. extension_control_path GUC(PGC_SUSET 설정)가 디렉터리 목록을 담으며, find_in_pathsfoo.control을 찾아 목록을 순회한다. GUC가 비어 있으면 시스템 SHAREDIR/extension으로 폴백한다. 패키저는 코어 설치 트리 바깥에 익스텐션의 컨트롤/스크립트 파일을 배치할 수 있다. 불변 시스템 이미지와 테넌트별 오버레이에 유용하다. 경로 구성 요소는 반드시 절대 경로여야 한다.

// find_in_paths — src/backend/commands/extension.c
if (!is_absolute_path(path))
ereport(ERROR,
errcode(ERRCODE_INVALID_NAME),
errmsg("component in parameter \"%s\" is not an absolute path",
"extension_control_path"));

심볼을 관심사별로 묶었다. 파일은 /data/hgryoo/references/postgres/ 아래에 있다. 정식 앵커는 심볼 이름이고, 줄 번호는 이 절 말미의 위치 힌트 표에 updated: 날짜에 고정되어 있다.

카탈로그와 조회 (pg_extension.h, extension.c)

섹션 제목: “카탈로그와 조회 (pg_extension.h, extension.c)”
  • FormData_pg_extension / Form_pg_extension — 카탈로그 행: extname, extowner, extnamespace, extrelocatable, extversion, extconfig[], extcondition[]. 구성원 목록 없음 — 멤버십은 pg_depend에 있다.
  • ExtensionRelationId (3079) — 카탈로그 OID; ExtensionOidIndexId / ExtensionNameIndexId가 syscache를 뒷받침한다.
  • EXTENSIONOID / EXTENSIONNAME — syscache 선언에 쓰이는 MAKE_SYSCACHE 값.
  • get_extension_oid / get_extension_name — syscache를 이용한 이름↔OID 변환.
  • get_extension_schema — 익스텐션 OID에서 extnamespace 읽기.
  • get_function_sibling_type — C 함수의 OID가 주어지면 같은 익스텐션 내에서 이름으로 타입을 찾는다. 고정 스키마를 가정할 수 없는 재배치 가능 익스텐션이 사용한다. ExtensionSiblingCache에 캐시되며 ext_sibling_callback으로 무효화된다.
  • ExtensionControlFile (구조체) — 파싱된 매니페스트; 인식되는 키별 필드 + control_dir / basedir 작업 상태.
  • new_ExtensionControlFile — 할당 및 기본값 설정(superuser = true, relocatable = false, trusted = false, encoding = -1).
  • parse_extension_control_fileParseConfigFp로 파일을 파싱하고, 키를 필드에 매핑하며, 기본 파일 전용 키와 relocatable/schema 상호 배제를 강제한다.
  • read_extension_control_file — 기본 foo.control 파싱.
  • read_extension_aux_control_file — 플랫 복사 후 버전별 foo--ver.control로 오버레이.
  • get_extension_control_directories / find_extension_control_filename / find_in_pathsextension_control_path 전체에서 foo.control 결정.
  • Extension_control_path — GUC 변수(문자열).

파일명과 버전명 문법 (extension.c)

섹션 제목: “파일명과 버전명 문법 (extension.c)”
  • is_extension_control_filename / is_extension_script_filename.control / .sql 접미사 검사.
  • check_valid_extension_name / check_valid_version_name — 빈 문자열, --, 선행/후행 -, 디렉터리 구분자 거부.
  • get_extension_script_directory — 스크립트 위치 결정(컨트롤 디렉터리, 절대 directory=, 또는 basedir/directory).
  • get_extension_script_filenamename--ver.sql(설치) 또는 name--from--to.sql(업그레이드) 구성.
  • get_extension_aux_control_filenamename--ver.control 구성.

버전 그래프와 경로 탐색 (extension.c)

섹션 제목: “버전 그래프와 경로 탐색 (extension.c)”
  • ExtensionVersionInfo (구조체) — 그래프 정점: name, reachable 엣지, installable 플래그, 다익스트라 작업 상태(distance, distance_known, previous).
  • get_ext_ver_list — 스크립트 디렉터리를 ReadDir로 순회해 각 name--...sql 파일명을 정점과 엣지로 변환.
  • get_ext_ver_info — 버전 이름으로 정점 찾기 또는 생성.
  • get_nearest_unprocessed_vertex — 다익스트라를 위한 O(N^2) 최소 거리 선택.
  • find_update_path — 두 버전 간 다익스트라 최단 경로; strcmp 동점 처리로 결정론적.
  • identify_update_path — 경로가 없으면 오류를 내는 래퍼.
  • find_install_path — 모든 installable 정점을 시작점으로 간주해 비설치 대상까지의 가장 저렴한 경로를 반환.

CREATE / ALTER / DROP 드라이버 (extension.c)

섹션 제목: “CREATE / ALTER / DROP 드라이버 (extension.c)”
  • CreateExtension — 구문 옵션 파싱, 중복 제거, 중첩 금지, 내부 워커 디스패치.
  • CreateExtensionInternal — 플래너+실행기: 버전 결정, 스크립트 계획, 스키마 결정, requires 설치, 행 삽입, 스크립트 실행. CASCADE를 위해 재귀.
  • get_required_extension — 전제 조건 결정/자동 설치; parents 목록으로 순환 의존성 감지.
  • InsertExtensionTuplepg_extension 행 구성 및 삽입, 소유자/스키마/전제 조건 의존성 기록, 생성 후 훅 실행. pg_upgrade를 위해 익스포트됨.
  • ExecAlterExtensionStmtALTER EXTENSION UPDATE: 설치된 버전 읽기, 경로 계산, ApplyExtensionUpdates 호출.
  • ApplyExtensionUpdates — 델타 하나씩 재실행: 행 재작성, 전제 조건 의존성 갱신, from--to 스크립트 실행.
  • AlterExtensionNamespaceSET SCHEMA: 재배치 가능 확인, 각 구성원 오브젝트 이동, no_relocate 처리, extnamespace 업데이트.
  • ExecAlterExtensionContentsStmt / ExecAlterExtensionContentsRecurseALTER EXTENSION ADD/DROP member.
  • RemoveExtensionByIdpg_extension 행 삭제(나머지는 의존성 기계가 처리).
  • extension_config_removeextconfig 배열에서 OID 제거(pg_extension_config_dump 관리).

스크립트 실행과 정책 (extension.c)

섹션 제목: “스크립트 실행과 정책 (extension.c)”
  • execute_extension_scriptcreating_extension / CurrentExtensionObject 설정, 슈퍼유저/신뢰 정책 적용, search_path와 GUC 설정, 매크로 치환, SQL 실행, 전역 변수 초기화.
  • execute_sql_string — 치환 후 스크립트 텍스트를 구문 파싱하여 구문 단위로 실행.
  • extension_is_trusted — 정책 게이트: trusted 플래그 + 데이터베이스 CREATE 권한.
  • read_extension_script_file / read_whole_file.sql 파일 읽기, 인코딩 검증 및 변환.
  • creating_extension / CurrentExtensionObject — 의존성 계층이 새 오브젝트를 익스텐션에 귀속시키기 위해 읽는 두 전역 변수.

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

심볼파일
FormData_pg_extensionsrc/include/catalog/pg_extension.h28
Extension_control_path (GUC 변수)src/backend/commands/extension.c76
creating_extension / CurrentExtensionObjectsrc/backend/commands/extension.c79
ExtensionControlFile (구조체)src/backend/commands/extension.c85
ExtensionVersionInfo (구조체)src/backend/commands/extension.c109
get_extension_oidsrc/backend/commands/extension.c187
get_extension_schemasrc/backend/commands/extension.c232
get_function_sibling_typesrc/backend/commands/extension.c272
check_valid_extension_namesrc/backend/commands/extension.c360
check_valid_version_namesrc/backend/commands/extension.c407
is_extension_control_filenamesrc/backend/commands/extension.c454
is_extension_script_filenamesrc/backend/commands/extension.c462
get_extension_control_directoriessrc/backend/commands/extension.c473
find_extension_control_filenamesrc/backend/commands/extension.c541
get_extension_script_directorysrc/backend/commands/extension.c567
get_extension_aux_control_filenamesrc/backend/commands/extension.c586
get_extension_script_filenamesrc/backend/commands/extension.c604
parse_extension_control_filesrc/backend/commands/extension.c641
read_extension_control_filesrc/backend/commands/extension.c829
read_extension_aux_control_filesrc/backend/commands/extension.c848
read_extension_script_filesrc/backend/commands/extension.c871
execute_sql_stringsrc/backend/commands/extension.c1046
extension_is_trustedsrc/backend/commands/extension.c1174
execute_extension_scriptsrc/backend/commands/extension.c1196
get_ext_ver_infosrc/backend/commands/extension.c1469
get_nearest_unprocessed_vertexsrc/backend/commands/extension.c1502
get_ext_ver_listsrc/backend/commands/extension.c1530
identify_update_pathsrc/backend/commands/extension.c1593
find_update_pathsrc/backend/commands/extension.c1636
find_install_pathsrc/backend/commands/extension.c1729
CreateExtensionInternalsrc/backend/commands/extension.c1784
get_required_extensionsrc/backend/commands/extension.c2022
CreateExtensionsrc/backend/commands/extension.c2094
InsertExtensionTuplesrc/backend/commands/extension.c2192
RemoveExtensionByIdsrc/backend/commands/extension.c2280
extension_config_removesrc/backend/commands/extension.c3028
AlterExtensionNamespacesrc/backend/commands/extension.c3193
ExecAlterExtensionStmtsrc/backend/commands/extension.c3408
ApplyExtensionUpdatessrc/backend/commands/extension.c3555
ExecAlterExtensionContentsStmtsrc/backend/commands/extension.c3713
new_ExtensionControlFilesrc/backend/commands/extension.c4003
find_in_pathssrc/backend/commands/extension.c4028
extension_control_path (GUC 정의)src/backend/utils/misc/guc_tables.c4402
  • 익스텐션은 내부적으로 OID 하나와 pg_extension 행 하나일 뿐이며, 멤버십은 익스텐션에 저장되지 않는다. 2026-06-05에 src/include/catalog/pg_extension.h에서 확인했다. FormData_pg_extensionextname, extowner, extnamespace, extrelocatable, extversion, extconfig[], extcondition[]을 담으며 구성원 목록이 없다. extension.c 상단 주석이 설계를 직접 서술한다. “All we need internally to manage an extension is an OID so that the dependent objects can be associated with it.” 멤버십은 pg_dependDEPENDENCY_EXTENSION('e') 엣지다.

  • 컨트롤 파일은 postgresql.conf 파서로 파싱되며, directory / default_version은 기본 컨트롤 파일만 설정할 수 있다. parse_extension_control_file에서 확인했다. ParseConfigFp(GUC 파서)를 호출하며, version != NULL(보조 name--ver.control)이면 directorydefault_version을 “parameter “%s” cannot be set in a secondary extension control file”로 거부한다. 같은 함수가 relocatable/schema 상호 배제도 강제한다.

  • 컨트롤 파일 기본값은 슈퍼유저 전용, 재배치 불가능, 비신뢰다. new_ExtensionControlFile에서 확인했다. relocatable = false, superuser = true, trusted = false, encoding = -1.

  • 버전 그래프는 스크립트 디렉터리를 나열해 파일명을 파싱함으로써 탐색되며, 설치/업그레이드 계획은 다익스트라 최단 경로다. get_ext_ver_list(스크립트 디렉터리를 ReadDir로 순회해 name--...sql을 정점/엣지로 변환), find_update_path(O(N^2) 최소 거리 선택과 strcmp 동점 처리를 가진 다익스트라 코어), find_install_path(모든 installable 정점을 씨앗으로 삼는)에서 확인했다.

  • CreateExtensionInternal은 카탈로그를 건드리기 전에 계획한다. 직접 설치 스크립트를 먼저 시도하고, 파일이 없을 때만 경로를 탐색한다. CreateExtensionInternal에서 확인했다. get_extension_script_filename(pcontrol, NULL, versionName)stat()을 실행한다. 성공하면 updateVersions = NIL, 아니면 그래프를 구성하고 find_install_path를 호출해 선택된 설치 시작 버전으로 versionName을 재설정한다. pg_extension 행은 스크립트 실행 전에 InsertExtensionTuple로 삽입된다. 스크립트의 CREATE 구문들이 의존성 엣지를 달 유효한 CurrentExtensionObject OID가 필요하기 때문이다.

  • execute_extension_script는 의존성 기록 전역 변수를 활성화하고 모든 종료 경로에서 초기화하는 단일 지점이다. execute_extension_script에서 확인했다. PG_TRY 안에서 creating_extension = trueCurrentExtensionObject = extensionOid를 설정하고(2026-06-05 기준 1319-1320행), 매칭되는 PG_FINALLY에서 둘 다 false / InvalidOid로 초기화한다(1443-1444행). 플래그가 설정된 동안 스크립트가 생성하는 각 오브젝트는 DDL 계층에서 recordDependencyOnCurrentExtension을 호출한다.

  • 신뢰 익스텐션 에스컬레이션은 부트스트랩 슈퍼유저로의 로컬 자동 복원 신원 전환이다. execute_extension_script에서 확인했다. 비슈퍼유저가 호출자일 때 extension_is_trusted(control)이 true를 반환하면 SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, save_sec_context | SECURITY_LOCAL_USERID_CHANGE)를 호출한다. extension_is_trusted 자체는 control->trusted가 설정되지 않으면 false를 반환하며, 그 다음 object_aclcheckMyDatabaseIdACL_CREATE를 요구한다. SECURITY_LOCAL_USERID_CHANGE 컨텍스트가 트랜잭션 종료/중단 시 신원 복원을 보장한다.

  • ALTER EXTENSION ... SET SCHEMAextrelocatable에 게이팅되며 전제 조건의 no_relocate 거부권을 처리한다. AlterExtensionNamespace에서 확인했다. extForm->extrelocatable이 아니면 “extension “%s” does not support SET SCHEMA”로 오류를 낸다. 그 후 pg_depend 스캔 중 의존하는 각 익스텐션의 no_relocate 목록(dcontrol->no_relocate foreach, 3324행 근방)을 참조해 이 익스텐션이 이름에 있으면 이동을 거부한다.

  • DROP EXTENSION은 익스텐션 전용 로직이 거의 없다. RemoveExtensionById에서 확인했다. 현재 생성 중인 익스텐션 삭제를 거부(extId == CurrentExtensionObject)한 뒤 pg_extension 튜플만 삭제한다. 헤더 주석은 “Everything else is taken care of by the dependency infrastructure”라고 서술한다.

  • PG 18은 extension_control_path GUC로 컨트롤 파일을 찾는다. SHAREDIR/extension만이 아니다. extension_control_pathguc_tables.c에 정의된 PGC_SUSET 문자열 GUC임을 확인했다. find_in_paths가 구성 요소를 순회하며 절대 경로가 아닌 것은 “component in parameter “%s” is not an absolute path”로 거부한다. 값이 비어 있으면 시스템 SHAREDIR/extension으로 폴백한다. (PG 18 기능이므로 REL_18에서 이를 단언하는 것이 맞다.)

  1. 조밀한 버전 그래프에서 find_install_path의 최악 비용. 다익스트라 구현은 get_nearest_unprocessed_vertex를 이용해 O(V^2)이며, find_install_path는 개념적으로 모든 installable 정점에서 탐색을 재실행한다. 빠른 전환 엣지가 많은 익스텐션에서 상수 인자는 여기서 측정되지 않았다. 조사 방향: O(100)개 버전과 조밀한 from--to 엣지를 가진 합성 컨트롤 디렉터리로 get_ext_ver_info / find_update_path 호출 횟수를 측정.

  2. requires 전제 조건이 CASCADE 아래 자체적으로 중간 설치 상태일 때의 정확한 실패 모드. get_required_extensionparents 목록으로 순환을 감지하지만, 재귀 CASCADE 설치와 단일 익스텐션 동시 생성 creating_extension 플래그의 상호작용은 추적된 예시가 필요하다. 조사 방향: 순환이 있는 세 익스텐션 requires 체인을 구성하고 어느 가드가 먼저 발동하는지 관찰.

  3. extconfig/extcondition 설정 테이블 덤프가 재배치된 익스텐션과 상호작용하는 방식. pg_extension_config_dump()는 OID로 덤프 가능한 설정 테이블을 기록한다. ALTER EXTENSION SET SCHEMA 후 테이블은 이동하지만 OID는 안정적이다. extcondition의 WHERE 절 텍스트가 새 스키마와 관련해 오래될 수 있는지는 여기서 다루지 않았다. 조사 방향: 스키마 한정 조건을 가진 설정 테이블을 등록한 익스텐션을 재배치하고 pg_dump 출력을 검사.

PostgreSQL 너머 — 비교 설계와 연구 최전선

섹션 제목: “PostgreSQL 너머 — 비교 설계와 연구 최전선”
  • POSTGRES의 원래 확장성 논문. Stonebraker & Rowe 1986(“The Design of POSTGRES”)과 Stonebraker & Kemnitz 1991(“The POSTGRES Next-Generation DBMS”)은 사용자 정의 타입·연산자·접근 방법·절차적 언어를 일등 시민으로 삼았다. 현대 익스텐션 메커니즘은 그 확장성을 설치 가능하고 버전이 붙은 제품으로 출하하게 만든 패키징 계층이다. 1986년의 확장성 축 각각이 현재 CREATE EXTENSION으로 출하되는 오브젝트와 어떻게 대응하는지 추적하면 그 연결이 완성된다. (참고문헌: dbms-papers/goes-around.md.omc/plans/postgres-paper-bibliography.md의 POSTGRES 설계 항목.)

  • 패키징 단위로서의 데이터 사전. Database System Concepts는 카탈로그를 데이터 사전으로, 오브젝트 간 의존성을 사전 사실로 규정한다. PostgreSQL의 베팅 — 멤버십이 단지 또 하나의 의존성 엣지 — 은 그 프레이밍의 최대 표현이다. 전용 “패키지 콘텐츠” 테이블이 없고 오직 pg_depend만 있다. 전용 패키지/매니페스트 카탈로그를 가진 시스템(Oracle PL/SQL 패키지, SQL Server 어셈블리)과 비교하면 트레이드오프가 선명해진다. 범용 의존성 그래프는 익스텐션 코드를 작게 유지하지만 “이 패키지에 뭐가 들어있는가”를 테이블 스캔이 아닌 그래프 쿼리로 만든다.

  • 스키마 마이그레이션 프레임워크 대 버전 그래프. Flyway, Rails migrations, Alembic은 거의 모두 마이그레이션을 선형 체인(V1 -> V2 -> V3)으로 모델링한다. PostgreSQL의 from--to 스크립트 규칙은 엄밀히 더 일반적이다. 방향성 그래프와 최단 경로 선택을 쓰므로 작성자가 증분 단계와 함께 1.0--1.3 빠른 전환을 제공해 find_install_path가 더 저렴한 경로를 선택하게 할 수 있다. 롤백 스토리, 브랜치 릴리스, 중간 스크립트 누락 비용 등 운영상의 결과를 선형 체인 도구와 비교하면 실무자 수준의 보완 자료가 된다.

  • 신뢰 익스텐션 대 클라우드 관리 허용 목록. trusted 플래그와 일시적 BOOTSTRAP_SUPERUSERID 전환은 “비슈퍼유저가 검증된 패키지를 설치하게 하라”에 대한 PostgreSQL 인코어 답변이다. 매니지드 클라우드(RDS, Cloud SQL, Aurora)는 위에 자체 허용 목록을 추가한다. 인코어 신뢰 집합은 정확성을 위해 관리되지 어떤 특정 공급자의 위협 모델을 위한 것이 아니기 때문이다. 인코어 게이트(extension_is_trusted)가 끝나고 공급자 허용 목록이 시작되는 지점을 명확히 하면 운영자의 혼란이 자주 해소된다.

  • extension_control_path와 불변 시스템 이미지. PG 18의 검색 가능한 컨트롤 경로(find_in_paths)는 SHAREDIR/extension이 읽기 전용인 불변 OS 이미지와 테넌트별 오버레이를 직접 겨냥한다. .so를 찾는 dynamic_library_path 메커니즘과 인접하지만 다르다. 단일 익스텐션의 .control, .sql 스크립트, 공유 라이브러리가 각기 다른 탐색 경로에 있을 수 있는 방식을 추적하면 “바이트들이 어디서 오는가”의 전체 그림이 완성된다.

인트리 소스 파일 (REL_18_STABLE, 커밋 273fe94)

섹션 제목: “인트리 소스 파일 (REL_18_STABLE, 커밋 273fe94)”
  • src/backend/commands/extension.c — 전체 메커니즘: 컨트롤 파일 파싱(parse_extension_control_file, read_extension_control_file, new_ExtensionControlFile), 파일명/버전명 문법(check_valid_extension_name, get_extension_script_filename), 버전 그래프와 경로 탐색(get_ext_ver_list, find_update_path, find_install_path), CREATE/ALTER/DROP 드라이버(CreateExtension, CreateExtensionInternal, ExecAlterExtensionStmt, ApplyExtensionUpdates, AlterExtensionNamespace, RemoveExtensionById), 스크립트 실행과 정책(execute_extension_script, execute_sql_string, extension_is_trusted), 컨트롤 경로 결정자(find_in_paths).
  • src/include/catalog/pg_extension.hFormData_pg_extension / Form_pg_extension, ExtensionRelationId (3079), OID/이름 인덱스 ID.
  • src/include/commands/extension.hcreating_extension / CurrentExtensionObject extern 선언과 CreateExtension / InsertExtensionTuple / RemoveExtensionById 프로토타입.
  • src/backend/utils/misc/guc_tables.cextension_control_path PGC_SUSET GUC 정의.
  • Stonebraker, M. & Rowe, L. (1986). “The Design of POSTGRES.” SIGMOD. 익스텐션 패키징 계층이 봉사하는 확장성-우선 설계 논문. (.omc/plans/postgres-paper-bibliography.md.)
  • Stonebraker, M. & Kemnitz, G. (1991). “The POSTGRES Next-Generation DBMS.” CACM 34(10). 접근 방법과 절차적 언어 확장성.
  • Stonebraker, M. (2005). “What Goes Around Comes Around.” 확장성이 왜 중요했는지를 프레이밍하는 타입 시스템·데이터 모델 역사(knowledge/research/dbms-papers/goes-around.md).
  • Database System Concepts (Silberschatz, Korth, Sudarshan, 7e) — 카탈로그의 데이터 사전 프레이밍과 오브젝트 간 의존성(knowledge/research/dbms-general/).

형제 문서 (교차 참조 — 메커니즘은 해당 문서 소유, 여기서 중복하지 않음)

섹션 제목: “형제 문서 (교차 참조 — 메커니즘은 해당 문서 소유, 여기서 중복하지 않음)”
  • postgres-dependency-tracking.mdpg_depend 그래프, DEPENDENCY_EXTENSION 엣지, recordDependencyOnCurrentExtension, DROP EXTENSIONpg_dump 순서를 처리하는 캐스케이드 워커. 이 문서가 의존하는 멤버십 메커니즘이 거기에 있다.
  • postgres-ddl-execution.mdexecute_sql_string이 구동하는 DDL 실행기와 ProcessUtility 경로; 스크립트 내 개별 CREATE가 카탈로그 오브젝트가 되는 방법.
  • postgres-guc-parameters.md.control 파일 읽기에 재사용되는 ParseConfigFp GUC 파서, 그리고 extension_control_path 설정.
  • postgres-system-catalogs.md — 더 넓은 카탈로그 집합 내의 하나의 행으로서의 pg_extension; syscache(EXTENSIONOID / EXTENSIONNAME) 기반 구조.
  • postgres-architecture-overview.md — FDW, 커스텀 접근 방법, 훅과 나란히 익스텐션 서브시스템이 위치하는 확장성 축.