Innovating today, leading tomorrow

OpenSQL_Internals
[OpenSQL] PostgreSQL의 Extension Framework

[OpenSQL] PostgreSQL의 Extension Framework

PostgreSQL의 Extension 소개

PostgreSQL은 확장성이 좋은 오픈소스 데이터베이스로 잘 알려져 있습니다. 그런 명성을 이루는 데에는 PostgreSQL의 기능을 유연하게 확장할 수 있도록 Extension Framework가 제공되기 때문입니다. Extension을 이용한 기능 확장은 PostgreSQL의 다양한 데이터 타입 및 함수 지원을 가능하게 하고 더 나아가 내부 기능에 대한 개선도 가능하게 합니다. PostgreSQL의 Extension Framework로 확장할 수 있는 모듈은 아래와 같습니다.

  • 데이터 타입과 연산자
  • 사용자 정의 함수와 집계 함수
  • 데이터 저장 형식과 인덱스
  • WAL 로깅과 인스턴스 간 복제
  • 트랜잭션 엔진
  • 백그라운드 프로세스
  • Optimizer와 Executor
  • DB 메타데이터와 설정

Extension의 또 다른 중요한 특징 중 하나는 Extension이 PostgreSQL 인스턴스에 동적으로 로딩 된다는 점입니다. 즉, PostgreSQL에서는 Extension을 사용하면 DB 인스턴스를 다시 기동할 필요 없이 새로운 기능을 추가할 수 있다는 뜻입니다. 이 글에서는 어떻게 PostgreSQL의 Extension Framework가 Extension의 동적 로딩을 가능하게 하는지 알아보도록 하겠습니다.

Extension Framework 구성 요소

PostgreSQL의 Extension Framework는 크게 세 개의 레이어로 나뉘어져 있습니다.

  • SQL Level Layer: SQL로 작성된 사용자 정의 객체
  • C Level Layer: C 언어로 작성된 PostgreSQL 내부 동작 제어 구현체
  • Extension Building Infrastructure: SQL 또는 C Level Layer에서 작성한 객체와 구현체를 번들링(Bundling)하는 역할

SQL Level Layer

SQL Level Layer는 SQL 또는 Procedural Language를 사용하여 사용자 정의 객체를 생성하는 방식의 기능 확장을 제공합니다. 사용자 정의 객체의 예는 아래와 같습니다.

  • 사용자 정의 함수(User-defined Function)
  • 사용자 정의 연산자(User-defined Operator)
  • 사용자 정의 데이터 타입(User-defined Data Type)
  • 사용자 정의 집계함수(User-defined Aggregates)

추가하려는 Extension의 유형에 따라 사용자 정의 객체를 생성함으로써 기능 확장이 가능하기도 합니다. 예를 들어 PostgreSQL에 YAML 형식의 새로운 데이터 타입과 어떤 키워드가 YAML 데이터에 존재하는지 확인하는 CONTAINS라는 연산자를 추가하고자 한다면 데이터 타입과 연산자를 정의하여 Extension을 구현하면 됩니다.

하지만 SQL Level Layer를 사용하여 기능 확장을 하는 데에는 한계가 있습니다. 위의 예제로 사용된 YAML 데이터 타입에 대해 조회 성능을 높이기 위해 인덱스를 사용할 수 있게 기능 확장을 하려면 PostgreSQL 내부 엔진에 대한 제어가 필요하게 되는데 이런 부분은 SQL Level Layer에서는 다루기 힘듭니다. 이런 상황을 위해 PostgreSQL Extension Framework는 C Level Layer를 제공하여 Extension이 내부 엔진에 대한 제어를 할 수 있게 해줍니다.

C Level Layer

Extension은 앞서 설명 드린 대로 DB 인스턴스에 동적으로 로딩됩니다. SQL Level Layer에서는 DDL과 PL/pgSQL과 같은 Procedural Language를 사용하여 구현하기 때문에 문제가 없습니다. 하지만 C 언어로 작성한 코드를 빌드하여 기동되어 있는 DB 엔진에 붙이기 위해서는 좀 더 섬세한 작업 필요할 수 밖에 없습니다.

PostgreSQL은 C Level Layer 기능 확장을 보다 쉽게 할 수 있도록 몇 가지 장치를 제공합니다. 이 글에서는 대표적인 예를 소개하도록 하겠습니다.

PG_MODULE_MAGIC

PG_MODULE_MAGIC은 PostgreSQL 8.2 버전부터 필요한 매크로로서, 동적으로 로드된 오브젝트 파일이 PostgreSQL 서버와 호환되는지 확인하기 위해 사용됩니다. 이 매크로는 fmgr.h 헤더 파일을 포함한 후에 하나의 소스 파일에만 작성해야 하고, PG_MODULE_MAGIC을 작성하지 않으면 PostgreSQL 서버가 오브젝트 파일을 로드할 때 오류를 발생시킵니다.

PostgreSQL이 PG_MODULE_MAGIC을 사용해서 호환성을 확인하는 로직은 다음과 같습니다. PostgreSQL 서버가 오브젝트 파일을 로드할 때, 오브젝트 파일에 PG_MODULE_MAGIC이라는 심볼이 있는지 확인합니다. 이 심볼은 PG_MODULE_MAGIC 매크로에 의해 생성되며, PostgreSQL의 버전과 ABI (Application Binary Interface) 정보를 담고 있습니다. 만약 오브젝트 파일에 PG_MODULE_MAGIC 심볼이 없거나, 값이 서버와 일치하지 않으면, 서버는 오브젝트 파일을 로드하지 않고 오류를 발생시킵니다. 이렇게 하면, PostgreSQL의 메이저 버전이 다르거나 다른 ABI를 가진 오브젝트 파일을 잘못 로드하는 것을 방지할 수 있습니다.

Hook

PostgreSQL의 Hook은 데이터베이스의 기능을 확장할 수 있는 강력한 도구입니다. Hook을 사용하면 쿼리 실행, 인증, 보안 등의 작업에 개입하거나 추가적인 작업을 수행할 수 있습니다. 예를 들어, 쿼리 실행 시간을 측정 또는 로깅하거나, 비밀번호 생성이나 변경 시에 복잡도를 검사하거나, 행 보안 정책을 설정하거나 변경하는 등의 기능을 할 수 있습니다.

프로그래밍 측면에서 PostgreSQL의 Hook은 함수 포인터로 정의되어 있으며, Backend 프로세스가 처음으로 기동될 때는 NULL로 초기화되어 있습니다. Hook을 사용하기 위해서는 특정 타입의 함수를 정의하고, 해당 함수를 Hook에 할당해야 합니다. 개발하려는 Extension에서 Hook의 시그니처에 맞춰 함수를 개발하여 포함하게 되면, Extension을 로딩할 때 Hook의 함수포인터에 해당 함수를 할당하게 됩니다.

데이터베이스의 특정 이벤트가 발생했을 때 Hook에 할당된 함수를 호출하게 되어 있기 때문에 Extension 개발자는 Hook을 사용하여 데이터베이스의 실행 주기에 관여를 할 수 있게 됩니다. 아래 Hook의 예제를 유형 별로 정리했습니다.

카테고리이름
일반emit_log_hook
shmem_startup_hook
shmem_request_hook
보안check_password_hook
ClientAuthentication_hook
ExecutorCheckPerms_hook
object_access_hook
openssl_tls_init_hook
row_security_policy_hook_permissive
row_security_policy_hook_restrictive
함수 관리자needs_fmgr_hook
fmgr_hook
Optimizerexplain_get_index_name_hook
ExplainOneQuery_hook
get_attavgwidth_hook
get_index_stats_hook
get_relation_info_hook
get_relation_stats_hook
planner_hook
join_search_hook
set_rel_pathlist_hook
set_join_pathlist_hook
create_upper_paths_hook
post_parse_analyze_hook
ExecutorExecutorStart_hook
ExecutorRun_hook
ExecutorFinish_hook
ExecutorEnd_hook
ProcessUtility_hook
PL/pgSQLfunc_setup
func_beg
func_end
stmt_beg
stmt_end
Reference.
https://pgpedia.info/h/hooks.html

Lifecycle Callback

PostgreSQL의 Lifecycle Callback은 PostgreSQL의 객체나 함수 호출에 연결되어 특정 작업을 수행할 수 있는 함수 포인터입니다. 이 함수 포인터는 PostgreSQL 안에서 생기는 주기(scope)의 시작 또는 끝에 호출되게 됩니다. 예를 들어 객체의 생성 주기 시작과 끝에 필요로 하는 동작이 있는 경우 Callback을 저장해두어 해당 시점에 호출되도록 사용할 수 있고, 또는 트랜잭션의 시작이나 끝에 필요로 하는 동작이 있는 경우에도 사용될 수 있습니다. Callback은 Hook과 달리 전역 변수에 연결되지 않고, 위 예제처럼 특정 주기에 맞춰 호출됩니다.

아래 Lifecycle Callback의 예제를 유형 별로 정리했습니다.

카테고리이름
Process Lifecyclebefore_shmem_exit
on_proc_exit
on_shmem_exit
Transaction LifecycleRegisterXactCallback
RegisterSubXactCallback
Cache Invalidation LifecycleCacheRegisterRelcacheCallback
CacheRegisterSyscacheCallback
Etc.registerExprContextCallback
registerResourceReleaseCallback
Reference.
https://wiki.postgresql.org/wiki/PostgresServerExtensionPoints#Callbacks

Abstract Interface

PostgreSQL Extension 중에는 비슷한 유형의 Extension이 존재합니다. 예를 들어 원격에 있는 데이터베이스에 접근하는 방식을 제공하는 Foreign Data Wrapper가 있습니다. 이기종 데이터베이스에 대한 FDW Extension은 접근 시 사용하는 Connector Library나 표현식과 데이터 타입 변환 등에서 차이가 발생하지만 동작 방식의 큰 흐름은 같습니다. 또 다른 예로는 상기 예제로 나온 새로운 데이터 타입과 연산자에 대한 인덱스 탐색 방식을 추가하는 Extension이 있습니다. 인덱스를 만들고 탐색하는 과정에서 수행해야 하는 동작 방식이 조금씩 다를 수 있지만 인덱스의 자료구조나 인덱스를 저장하고 로깅하는 방식 등 큰 흐름에서는 마찬가지로 같습니다.

이렇게 비슷한 유형의 Extension을 매번 처음부터 개발해야 한다면 비효율적일 수 밖에 없습니다. 그래서 PostgreSQL은 일종의 템플릿 개념의 Abstract Interface를 제공합니다. FDW나 인덱스와 같이 자주 개발되는 Extension의 경우 Framework를 통일하고 상이한 부분에 대해서만 Interface로 열어놔 구현체를 개발자가 제공하도록 합니다. FDW에서는 Handler Function이라고 불리는 Interface를 리스트로 제공하고 해당 함수들을 개발하도록 가이드 합니다. 인덱스의 경우에는 GiST, GIN 등 템플릿 인덱스를 제공하여 새로운 데이터 타입과 연산자에 대해 인덱스 동작의 일부 부분을 책임지는 함수 개발을 통해 Extension을 제공할 수 있도록 도와줍니다.

Reference.
https://www.postgresql.org/docs/current/fdwhandler.html

Extension Building Infrastructure

SQL 또는 C Level Layer를 통해 기능 확장에 대한 코드를 작성했다면 이 코드를 번들링하고 빌드하는 역할을 하는 것이 Building Infrastructure입니다. PGXS라고 불리는 이 시스템은 PostgreSQL 설치에 포함되어 있으며, C 코드를 포함하는 확장 모듈에 주로 사용됩니다. PGXS는 PostgreSQL과 인터페이스하는 모든 소프트웨어를 빌드할 수 있는 범용적인 프레임워크는 아니고, Extension 개발에 쓰이는 공통 빌드 규칙을 자동화시켜주는 역할을 합니다. 따라서 복잡한 Extension 개발에는 자체 개발한 빌드 시스템을 구축해야 할 수도 있습니다.

Reference.
https://postgresql.kr/docs/13/extend-pgxs.html

Extension의 동적 로딩

PostgreSQL에서 Extension을 추가할 때는 세 개의 구성 요소가 필요합니다.

  1. Control 파일
  2. SQL Script 파일
  3. Shared Library (Optional)

Control 파일은 Extension의 메타 정보를 담고 있는 파일이며 SQL Script 파일은 SQL Level Layer의 기능 확장을 정의한 파일입니다. C Level Layer의 기능 확장을 구현한 경우 구현체를 PGXS를 사용하여 Shared Library로 생성해주어야 합니다.

이어질 내용에서 에서는 PostgreSQL에서 널리 사용되는 pg_stat_statements를 예시로 각각의 요소들의 예제를 보여드리도록 하겠습니다.

Control 파일

Control 파일은 Extension의 메타정보를 담는 역할을 하며 파일 이름은 [extension 이름].control 형식으로 지정해야 합니다. 예를 들어,

CREATE EXTENSION pg_stat_statements;

위 구문이 수행되면 $sharedir/extension 에서 pg_stat_statements.control이라는 파일을 조회하여 알맞은 Extension을 로딩할 수 있게 됩니다. 아래는 pg_stat_statements.control 파일의 모습입니다.

# pg_stat_statements extension
comment = ‘track planning and execution statistics of all SQL statements executed’
default_version = ‘1.9’
module_pathname = ‘$libdir/pg_stat_statements’
relocatable = true

“comment”는 해당 Extension에 대한 간략한 설명을 쓸 수 있게 한 필드입니다. “default_version”은 Extension의 기본 버전을 명시하는 필드이고, “module_pathname”은 Extension이 공유 라이브러리를 포함하는 경우에 사용되는 파라미터입니다. 이 파라미터는 공유 라이브러리의 위치를 지정하며, 기본값은 $libdir입니다. “relocatable”은 Extension이 설치된 후에도 스키마를 변경할 수 있는지 여부를 지정하는 파라미터입니다. 파라미터 값을 relocatable = true로 설정하면 Extension이 이동 가능하다는 의미이고, relocatable = false로 설정하면 Extension이 고정되어 있다는 의미입니다.

SQL script 파일

SQL Script 파일은 Extension이 로딩될 때 최초 한번 수행되는 SQL Script를 모아 놓은 파일입니다. extension – – version.sql 형식으로 명명되며 Control 파일과 동일하게 $sharedir/extension 에 위치하고 있습니다. 아래 파일은 pg_stat_statements–1.0–1.1.sql 파일의 예시 입니다.

/* contrib/pg_stat_statements/pg_stat_statements–1.0–1.1.sql */

— complain if script is sourced in psql, rather than via ALTER EXTENSION
echo Use “ALTER EXTENSION pg_stat_statements UPDATE TO ‘1.1’” to load this file. quit

/* First we have to remove them from the extension */
ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements;
ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements();

/* Then we can drop them */
DROP VIEW pg_stat_statements;
DROP FUNCTION pg_stat_statements();

/* Now redefine */
CREATE FUNCTION pg_stat_statements(
OUT userid oid,
OUT dbid oid,
OUT query text,
OUT calls int8,
OUT total_time float8,
OUT rows int8,
OUT shared_blks_hit int8,
OUT shared_blks_read int8,
OUT shared_blks_dirtied int8,
OUT shared_blks_written int8,
OUT local_blks_hit int8,
OUT local_blks_read int8,
OUT local_blks_dirtied int8,
OUT local_blks_written int8,
OUT temp_blks_read int8,
OUT temp_blks_written int8,
OUT blk_read_time float8,
OUT blk_write_time float8
)
RETURNS SETOF record
AS ‘MODULE_PATHNAME’
LANGUAGE C;

CREATE VIEW pg_stat_statements AS
SELECT * FROM pg_stat_statements();

GRANT SELECT ON pg_stat_statements TO PUBLIC;

CREATE EXTENSION 구문을 통해 pg_stat_statements Extension이 로딩되면 pg_stat_statements 함수를 생성하는 CREATE FUNCTION 구문이 실행됩니다. 해당 구문은 PostgreSQL 서버에서 실행된 쿼리의 통계정보를 반환하는 함수인데 C 언어로 작성된 외부 모듈의 경로를 지정하고 있습니다. MODULE_PATHNAME 이라는 경로에 존재하는 C 라이브러리를 활용하겠다는 것인데 ‘MODULE_PATHNAME’ 은 Control 파일에서 지정한 module_pathname 입니다. 즉, Control 파일에서 명시한대로 $libdir/pg_stat_statements를 이 Extension에서 사용할 것이라는 뜻입니다. 이제 PostgreSQL은 $libdir/pg_stat_statements.so를 찾아서 인스턴스에 동적 로딩하게 되고 개발자가 작성한 C 언어 구현체가 바인딩되게 됩니다.

즉, Extension을 로딩하려면 $sharedir/extension에 Control 파일, SQL Script 파일이 존재하는지 확인해야 하며 해당 Extension이 공유 라이브러리를 사용하고 있다면 MODULE_PATH에 지정한 경로에 .so 파일이 존재해야 합니다.

지금까지 PostgreSQL의 Extension Framework에 관해 알아보았습니다

PostgreSQL의 Foreign Data Wrapper’를 바로 이어서 확인해보세요!

광고성 정보 수신

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다.
내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보
  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
 

개인정보 수집 및 이용

개인정보 수집, 활용 목적 및 기간

(주)티맥스티베로의 개인정보 수집 및 이용 목적은 다음과 같습니다. 내용을 자세히 읽어보신 후 동의 여부를 결정해 주시기 바랍니다.

  • 수집 목적: 티맥스티베로 뉴스레터 발송 및 고객 관리
  • 수집 항목: 성함, 회사명, 회사 이메일, 연락처, 부서명, 직급, 산업, 담당업무, 관계사 여부, 방문 경로
  • 보유 및 이용 기간: 동의 철회 시까지

※ 위 개인정보 수집 및 이용에 대한 동의를 거부할 권리가 있습니다.
※ 필수 수집 항목에 대한 동의를 거부하는 경우 뉴스레터 구독이 제한될 수 있습니다.

개인정보의 처리 위탁 정보

  • 업체명: 스티비 주식회사
  • 위탁 업무 목적 및 범위: 광고가 포함된 뉴스레터 발송 및 수신자 관리
  •