본문으로 바로가기
본문으로 바로가기

pg_clickhouse 레퍼런스 문서

설명

pg_clickhouse는 ClickHouse 데이터베이스에 대한 원격 쿼리 실행을 가능하게 하는 PostgreSQL 확장 기능으로, foreign data wrapper를 포함합니다. PostgreSQL 13 이상 및 ClickHouse 23 이상에서 동작합니다.

시작하기

pg_clickhouse를 사용해 보는 가장 간단한 방법은 Docker image를 사용하는 것입니다. 이 Docker 이미지는 pg_clickhouse 확장이 포함된 표준 PostgreSQL Docker 이미지를 기반으로 합니다.

docker run --name pg_clickhouse -e POSTGRES_PASSWORD=my_pass \
       -d ghcr.io/clickhouse/pg_clickhouse:18
docker exec -it pg_clickhouse psql -U postgres

ClickHouse 테이블을 가져오고 쿼리를 푸시다운하는 작업을 시작하려면 튜토리얼을 참고하십시오.

사용법

CREATE EXTENSION pg_clickhouse;
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
       OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (user 'default');
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA taxi FROM SERVER taxi_srv INTO taxi;

Versioning Policy

pg_clickhouse는 공개 릴리스에 대해 Semantic Versioning을 준수합니다.

  • 메이저 버전은 API 변경 시 증가합니다.
  • 마이너 버전은 하위 호환 SQL 변경 시 증가합니다.
  • 패치 버전은 바이너리 전용 변경 시 증가합니다.

설치가 완료되면 PostgreSQL은 두 가지 종류의 버전을 추적합니다.

  • 라이브러리 버전(PostgreSQL 18 이상에서 PG_MODULE_MAGIC으로 정의됨)은 전체 시맨틱 버전을 포함하며, pg_get_loaded_modules() 함수의 출력에서 확인할 수 있습니다.
  • 익스텐션 버전(컨트롤 파일에서 정의됨)은 메이저 및 마이너 버전만 포함하며, pg_catalog.pg_extension 테이블, pg_available_extension_versions() 함수의 출력, 그리고 \dx pg_clickhouse에서 확인할 수 있습니다.

실제로 이는 패치 버전이 증가하는 릴리스(예: v0.1.0에서 v0.1.1로)가 v0.1을 로드한 모든 데이터베이스에 적용되며, 업그레이드를 위해 ALTER EXTENSION을 실행할 필요가 없음을 의미합니다.

반면 마이너 또는 메이저 버전이 증가하는 릴리스에는 SQL 업그레이드 스크립트가 함께 제공되며, 해당 익스텐션을 포함하는 모든 기존 데이터베이스는 업그레이드를 적용하기 위해 ALTER EXTENSION pg_clickhouse UPDATE를 실행해야 합니다.

DDL SQL Reference

다음 SQL DDL 구문에서는 pg_clickhouse를 사용합니다.

CREATE EXTENSION

CREATE EXTENSION을 사용하여 데이터베이스에 pg_clickhouse 확장을 추가합니다.

CREATE EXTENSION pg_clickhouse;

WITH SCHEMA를 사용하여 특정 스키마에 설치합니다 (권장):

CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;

ALTER EXTENSION

ALTER EXTENSION을 사용하여 pg_clickhouse를 변경합니다. 예를 들어:

  • pg_clickhouse의 새 릴리스를 설치한 후에는 UPDATE 절을 사용합니다:

    ALTER EXTENSION pg_clickhouse UPDATE;
    
  • SET SCHEMA를 사용하여 확장을 새 스키마로 이동합니다:

    CREATE SCHEMA ch;
    ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
    

DROP EXTENSION

DROP EXTENSION을 사용하여 데이터베이스에서 pg_clickhouse 확장을 삭제합니다:

DROP EXTENSION pg_clickhouse;

이 명령은 pg_clickhouse에 의존하는 객체가 존재하면 실패합니다. 이러한 객체도 함께 삭제하려면 CASCADE 절을 사용하십시오:

DROP EXTENSION pg_clickhouse CASCADE;

CREATE SERVER

CREATE SERVER를 사용하여 ClickHouse 서버에 연결되는 외부 서버(foreign server)를 생성합니다. 예:

CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
       OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');

지원되는 옵션은 다음과 같습니다.

  • driver: 사용할 ClickHouse 연결 드라이버로, "binary" 또는 "http" 중 하나입니다. 필수입니다.
  • dbname: 연결 시 사용할 ClickHouse 데이터베이스입니다. 기본값은 "default"입니다.
  • host: ClickHouse 서버의 호스트 이름입니다. 기본값은 "localhost"입니다.
  • port: ClickHouse 서버에 연결할 포트입니다. 기본값은 다음과 같습니다.
    • driver가 "binary"이고 host가 ClickHouse Cloud 호스트인 경우 9440
    • driver가 "binary"이고 host가 ClickHouse Cloud 호스트가 아닌 경우 9004
    • driver가 "http"이고 host가 ClickHouse Cloud 호스트인 경우 8443
    • driver가 "http"이고 host가 ClickHouse Cloud 호스트가 아닌 경우 8123

ALTER SERVER

ALTER SERVER를 사용하여 외부 서버를 수정합니다. 예를 들면 다음과 같습니다.

ALTER SERVER taxi_srv OPTIONS (SET driver 'http');

옵션은 CREATE SERVER와 동일합니다.

DROP SERVER

DROP SERVER를 사용하여 외부 서버를 제거합니다:

DROP SERVER taxi_srv;

다른 객체가 해당 서버에 의존하고 있으면 이 명령은 실패합니다. 이러한 의존성도 함께 삭제하려면 CASCADE를 사용하십시오:

DROP SERVER taxi_srv CASCADE;

CREATE USER MAPPING

CREATE USER MAPPING을(를) 사용하여 PostgreSQL 사용자를 ClickHouse USER에 매핑합니다. 예를 들어 taxi_srv 외부 서버에 연결할 때 현재 PostgreSQL 사용자를 원격 ClickHouse USER에 매핑하려면 다음과 같이 합니다:

CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (user 'demo');

지원되는 옵션은 다음과 같습니다:

  • user: ClickHouse 사용자 이름입니다. 기본값은 "default"입니다.
  • password: ClickHouse 사용자의 비밀번호입니다.

ALTER USER MAPPING

ALTER USER MAPPING을 사용하여 사용자 매핑 정의를 변경합니다.

ALTER USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (SET user 'default');

옵션은 CREATE USER MAPPING과 같습니다.

DROP USER MAPPING

DROP USER MAPPING을 사용하여 사용자 매핑을 삭제합니다.

DROP USER MAPPING FOR CURRENT_USER SERVER taxi_srv;

IMPORT FOREIGN SCHEMA

IMPORT FOREIGN SCHEMA를 사용하여 ClickHouse 데이터베이스에 정의된 모든 테이블을 PostgreSQL 스키마의 외부 테이블로 가져옵니다:

CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA demo FROM SERVER taxi_srv INTO taxi;

가져오기를 특정 테이블로 제한하려면 LIMIT TO를 사용하십시오:

IMPORT FOREIGN SCHEMA demo LIMIT TO (trips) FROM SERVER taxi_srv INTO taxi;

테이블을 제외할 때는 EXCEPT를 사용하십시오:

IMPORT FOREIGN SCHEMA demo EXCEPT (users) FROM SERVER taxi_srv INTO taxi;

pg_clickhouse는 지정된 ClickHouse 데이터베이스(위 예시의 「demo」)에 있는 모든 테이블 목록을 가져오고, 각 테이블의 컬럼 정의를 가져온 다음, 외부 테이블을 생성하기 위해 CREATE FOREIGN TABLE 명령을 실행합니다. 컬럼은 지원되는 데이터 타입과, 감지 가능한 경우 CREATE FOREIGN TABLE에서 지원하는 옵션을 사용하여 정의됩니다.

가져온 식별자 대소문자 보존

IMPORT FOREIGN SCHEMA는 가져오는 테이블 및 컬럼 이름에 대해 quote_identifier()를 실행하며, 이 함수는 대문자나 공백 문자가 있는 식별자를 이중 인용부호로 감쌉니다. 따라서 이와 같은 테이블 및 컬럼 이름은 PostgreSQL 쿼리에서 반드시 이중 인용부호로 감싸야 합니다. 모두 소문자이고 공백 문자가 없는 이름은 인용부호로 감쌀 필요가 없습니다.

예를 들어, 다음과 같은 ClickHouse 테이블이 있다고 가정합니다:

 CREATE OR REPLACE TABLE test
 (
     id UInt64,
     Name TEXT,
     updatedAt DateTime DEFAULT now()
 )
 ENGINE = MergeTree
 ORDER BY id;

IMPORT FOREIGN SCHEMA로 다음 foreign table이 생성됩니다:

 CREATE TABLE test
 (
     id          BIGINT      NOT NULL,
     "Name"      TEXT        NOT NULL,
     "updatedAt" TIMESTAMPTZ NOT NULL
 );

따라서 쿼리에서는 예를 들어 다음과 같이 적절하게 따옴표를 사용해야 합니다.

 SELECT id, "Name", "updatedAt" FROM test;

서로 다른 이름을 사용하거나 이름을 모두 소문자로 하여(대소문자를 구분하지 않는 이름으로) 객체를 생성하려면 CREATE FOREIGN TABLE을 사용합니다.

CREATE FOREIGN TABLE

CREATE FOREIGN TABLE을 사용하여 ClickHouse 데이터베이스의 데이터를 쿼리하는 외부 테이블을 생성합니다.

CREATE FOREIGN TABLE uact (
    user_id    bigint NOT NULL,
    page_views int,
    duration   smallint,
    sign       smallint
) SERVER taxi_srv OPTIONS(
    table_name 'uact'
    engine 'CollapsingMergeTree'
);

지원되는 테이블 옵션은 다음과 같습니다.

  • database: 원격 데이터베이스의 이름입니다. 기본값은 외부 서버에 대해 정의된 데이터베이스입니다.
  • table_name: 원격 테이블의 이름입니다. 기본값은 외부 테이블에 대해 지정된 이름입니다.
  • engine: ClickHouse 테이블에서 사용하는 table engine입니다. CollapsingMergeTree()AggregatingMergeTree()의 경우 pg_clickhouse는 테이블에서 실행되는 함수 표현식에 매개변수를 자동으로 적용합니다.

각 컬럼의 원격 ClickHouse 데이터 타입에 알맞은 data type을 사용합니다. AggregateFunction TypeSimpleAggregateFunction Type 컬럼의 경우, 데이터 타입을 함수에 전달되는 ClickHouse 타입에 매핑하고, 적절한 컬럼 옵션을 통해 집계 함수의 이름을 지정합니다.

예:

(aggregatefunction 'sum')

CREATE FOREIGN TABLE test (
    column1 bigint  OPTIONS(AggregateFunction 'uniq'),
    column2 integer OPTIONS(AggregateFunction 'anyIf'),
    column3 bigint  OPTIONS(AggregateFunction 'quantiles(0.5, 0.9)')
) SERVER clickhouse_srv;

AggregateFunction FUNCTION이 사용된 컬럼의 경우, pg_clickhouse는 해당 컬럼에 적용되는 집계 함수에 Merge를 자동으로 덧붙입니다.

ALTER FOREIGN TABLE

ALTER FOREIGN TABLE을(를) 사용하여 외부 테이블의 정의를 변경합니다.

ALTER TABLE table ALTER COLUMN b OPTIONS (SET AggregateFunction 'count');

지원되는 테이블 및 컬럼 옵션은 CREATE FOREIGN TABLE의 옵션과 동일합니다.

DROP FOREIGN TABLE

DROP FOREIGN TABLE을(를) 사용하여 foreign table을 삭제합니다:

DROP FOREIGN TABLE uact;

외부 테이블에 의존하는 객체가 하나라도 있으면 이 명령은 실패합니다. 해당 객체들도 함께 삭제하려면 CASCADE 절을 사용하십시오:

DROP FOREIGN TABLE uact CASCADE;

DML SQL Reference

아래 SQL DML 구문에서는 pg_clickhouse를 사용합니다. 예제는 make-logs.sql로 생성되는 다음 ClickHouse 테이블을 기반으로 합니다.

CREATE TABLE logs (
    req_id    Int64 NOT NULL,
    start_at   DateTime64(6, 'UTC') NOT NULL,
    duration  Int32 NOT NULL,
    resource  Text  NOT NULL,
    method    Enum8('GET' = 1, 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', 'QUERY') NOT NULL,
    node_id   Int64 NOT NULL,
    response  Int32 NOT NULL
) ENGINE = MergeTree
  ORDER BY start_at;

CREATE TABLE nodes (
    node_id Int64 NOT NULL,
    name    Text  NOT NULL,
    region  Text  NOT NULL,
    arch    Text  NOT NULL,
    os      Text  NOT NULL
) ENGINE = MergeTree
  PRIMARY KEY node_id;

EXPLAIN

EXPLAIN 명령은 정상적으로 동작하지만, VERBOSE 옵션을 사용하면 ClickHouse의 「Remote SQL」 쿼리가 출력됩니다:

try=# EXPLAIN (VERBOSE)
       SELECT resource, avg(duration) AS average_duration
         FROM logs
        GROUP BY resource;
                                     QUERY PLAN
------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=64)
   Output: resource, (avg(duration))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT resource, avg(duration) FROM "default".logs GROUP BY resource
(4 rows)

이 쿼리는 「Foreign Scan」 플랜 노드를 통해 원격 SQL을 ClickHouse로 푸시다운합니다.

SELECT

SELECT 구문을 사용하여 다른 테이블과 마찬가지로 pg_clickhouse 테이블에 쿼리를 실행합니다:

try=# SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
          start_at          | duration |    resource
----------------------------+----------+----------------
 2025-12-05 15:07:32.944188 |      175 | /widgets/totam
(1 row)

pg_clickhouse는 집계 함수를 포함한 쿼리 실행을 가능한 한 많이 ClickHouse로 푸시다운하도록 동작합니다. 푸시다운 정도를 확인하려면 EXPLAIN을 사용하십시오. 예를 들어 위의 쿼리의 경우 모든 실행이 ClickHouse로 푸시다운됩니다.

try=# EXPLAIN (VERBOSE, COSTS OFF)
       SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
                                             QUERY PLAN
-----------------------------------------------------------------------------------------------------
 Foreign Scan on public.logs
   Output: start_at, duration, resource
   Remote SQL: SELECT start_at, duration, resource FROM "default".logs WHERE ((req_id = 4117909262))
(3 rows)

pg_clickhouse는 동일한 원격 서버에 있는 테이블 간 JOIN 연산도 푸시다운합니다:

try=# EXPLAIN (ANALYZE, VERBOSE)
       SELECT name, count(*), round(avg(duration))
         FROM logs
         LEFT JOIN nodes on logs.node_id = nodes.node_id
        GROUP BY name;
                                                                                  QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=72) (actual time=3.201..3.221 rows=8.00 loops=1)
   Output: nodes.name, (count(*)), (round(avg(logs.duration), 0))
   Relations: Aggregate on ((logs) LEFT JOIN (nodes))
   Remote SQL: SELECT r2.name, count(*), round(avg(r1.duration), 0) FROM  "default".logs r1 ALL LEFT JOIN "default".nodes r2 ON (((r1.node_id = r2.node_id))) GROUP BY r2.name
   FDW Time: 0.086 ms
 Planning Time: 0.335 ms
 Execution Time: 3.261 ms
(7 rows)

세심하게 튜닝하지 않으면 로컬 테이블과 조인할 때 쿼리 효율이 떨어집니다. 이 예시에서는 원격 테이블 대신 nodes 테이블의 로컬 복제본을 생성하고, 해당 로컬 테이블과 조인합니다:

try=# CREATE TABLE local_nodes AS SELECT * FROM nodes;
SELECT 8

try=# EXPLAIN (ANALYZE, VERBOSE)
       SELECT name, count(*), round(avg(duration))
         FROM logs
         LEFT JOIN local_nodes on logs.node_id = local_nodes.node_id
        GROUP BY name;
                                                             QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=147.65..150.65 rows=200 width=72) (actual time=6.215..6.235 rows=8.00 loops=1)
   Output: local_nodes.name, count(*), round(avg(logs.duration), 0)
   Group Key: local_nodes.name
   Batches: 1  Memory Usage: 32kB
   Buffers: shared hit=1
   ->  Hash Left Join  (cost=31.02..129.28 rows=2450 width=36) (actual time=2.202..5.125 rows=1000.00 loops=1)
         Output: local_nodes.name, logs.duration
         Hash Cond: (logs.node_id = local_nodes.node_id)
         Buffers: shared hit=1
         ->  Foreign Scan on public.logs  (cost=10.00..20.00 rows=1000 width=12) (actual time=2.089..3.779 rows=1000.00 loops=1)
               Output: logs.req_id, logs.start_at, logs.duration, logs.resource, logs.method, logs.node_id, logs.response
               Remote SQL: SELECT duration, node_id FROM "default".logs
               FDW Time: 1.447 ms
         ->  Hash  (cost=14.90..14.90 rows=490 width=40) (actual time=0.090..0.091 rows=8.00 loops=1)
               Output: local_nodes.name, local_nodes.node_id
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               Buffers: shared hit=1
               ->  Seq Scan on public.local_nodes  (cost=0.00..14.90 rows=490 width=40) (actual time=0.069..0.073 rows=8.00 loops=1)
                     Output: local_nodes.name, local_nodes.node_id
                     Buffers: shared hit=1
 Planning:
   Buffers: shared hit=14
 Planning Time: 0.551 ms
 Execution Time: 6.589 ms

이 경우 로컬 컬럼 대신 node_id로 그룹화하여 집계 작업의 더 많은 부분을 ClickHouse로 위임한 다음, 나중에 조회 테이블과 조인할 수 있습니다:

try=# EXPLAIN (ANALYZE, VERBOSE)
       WITH remote AS (
           SELECT node_id, count(*), round(avg(duration))
             FROM logs
            GROUP BY node_id
       )
       SELECT name, remote.count, remote.round
         FROM remote
         JOIN local_nodes
           ON remote.node_id = local_nodes.node_id
        ORDER BY name;
                                                          QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=65.68..66.91 rows=490 width=72) (actual time=4.480..4.484 rows=8.00 loops=1)
   Output: local_nodes.name, remote.count, remote.round
   Sort Key: local_nodes.name
   Sort Method: quicksort  Memory: 25kB
   Buffers: shared hit=4
   ->  Hash Join  (cost=27.60..43.79 rows=490 width=72) (actual time=4.406..4.422 rows=8.00 loops=1)
         Output: local_nodes.name, remote.count, remote.round
         Inner Unique: true
         Hash Cond: (local_nodes.node_id = remote.node_id)
         Buffers: shared hit=1
         ->  Seq Scan on public.local_nodes  (cost=0.00..14.90 rows=490 width=40) (actual time=0.010..0.016 rows=8.00 loops=1)
               Output: local_nodes.node_id, local_nodes.name, local_nodes.region, local_nodes.arch, local_nodes.os
               Buffers: shared hit=1
         ->  Hash  (cost=15.10..15.10 rows=1000 width=48) (actual time=4.379..4.381 rows=8.00 loops=1)
               Output: remote.count, remote.round, remote.node_id
               Buckets: 1024  Batches: 1  Memory Usage: 9kB
               ->  Subquery Scan on remote  (cost=1.00..15.10 rows=1000 width=48) (actual time=4.337..4.360 rows=8.00 loops=1)
                     Output: remote.count, remote.round, remote.node_id
                     ->  Foreign Scan  (cost=1.00..5.10 rows=1000 width=48) (actual time=4.330..4.349 rows=8.00 loops=1)
                           Output: logs.node_id, (count(*)), (round(avg(logs.duration), 0))
                           Relations: Aggregate on (logs)
                           Remote SQL: SELECT node_id, count(*), round(avg(duration), 0) FROM "default".logs GROUP BY node_id
                           FDW Time: 0.055 ms
 Planning:
   Buffers: shared hit=5
 Planning Time: 0.319 ms
 Execution Time: 4.562 ms

이제 「Foreign Scan」 노드는 node_id별 집계를 푸시다운하여 Postgres로 다시 가져와야 하는 행(row)의 수를 1000개(전체)에서 각 노드당 1개씩 총 8개로 줄입니다.

PREPARE, EXECUTE, DEALLOCATE

v0.1.2부터 pg_clickhouse에서는 주로 PREPARE 명령으로 생성되는 매개변수화된 쿼리를 지원합니다.

try=# PREPARE avg_durations_between_dates(date, date) AS
       SELECT date(start_at), round(avg(duration)) AS average_duration
         FROM logs
        WHERE date(start_at) BETWEEN $1 AND $2
        GROUP BY date(start_at)
        ORDER BY date(start_at);
PREPARE

준비된 문을 실행할 때는 평소와 같이 EXECUTE를 사용합니다:

try=# EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
    date    | average_duration
------------+------------------
 2025-12-09 |              190
 2025-12-10 |              194
 2025-12-11 |              197
 2025-12-12 |              190
 2025-12-13 |              195
(5 rows)

pg_clickhouse는 집계 연산을 평소와 같이 push down 하며, 이는 EXPLAIN verbose 출력에서 확인할 수 있습니다:

try=# EXPLAIN (VERBOSE) EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
                                                                                                            QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=36)
   Output: (date(start_at)), (round(avg(duration), 0))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= '2025-12-09')) AND ((date(start_at) <= '2025-12-13')) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)

전체 날짜 값이 전송되고, 파라미터 플레이스홀더는 전송되지 않았다는 점에 유의하십시오. 이는 PostgreSQL [PREPARE notes]에서 설명하는 것처럼 처음 다섯 번의 요청에 적용됩니다. 여섯 번째 실행에서는 ClickHouse의 {param:type} 스타일 [쿼리 매개변수]를 전송합니다: 매개변수:

                                                                                                         QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=36)
   Output: (date(start_at)), (round(avg(duration), 0))
   Relations: Aggregate on (logs)
   Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= {p1:Date})) AND ((date(start_at) <= {p2:Date})) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)

준비된 구문(prepared statement)을 해제하려면 DEALLOCATE를 사용하십시오:

try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATE

INSERT

INSERT 명령어를 사용하여 원격 ClickHouse 테이블에 값을 삽입합니다.

try=# INSERT INTO nodes(node_id, name, region, arch, os)
VALUES (9,  'Augustin Gamarra', 'us-west-2', 'amd64', 'Linux')
     , (10, 'Cerisier', 'us-east-2', 'amd64', 'Linux')
     , (11, 'Dewalt', 'use-central-1', 'arm64', 'macOS')
;
INSERT 0 3

COPY

원격 ClickHouse 테이블에 여러 행을 일괄 삽입하려면 COPY 명령을 사용합니다.

try=# COPY logs FROM stdin CSV;
4285871863,2025-12-05 11:13:58.360760,206,/widgets,POST,8,401
4020882978,2025-12-05 11:33:48.248450,199,/users/1321945,HEAD,3,200
3231273177,2025-12-05 12:20:42.158575,220,/search,GET,2,201
\.
>> COPY 3

⚠️ Batch API 제한 사항

pg_clickhouse에서는 PostgreSQL FDW batch insert API를 아직 지원하지 않습니다. 따라서 COPY는 현재 레코드를 삽입할 때 INSERT SQL 문을 사용합니다. 이는 향후 릴리스에서 개선될 예정입니다.

LOAD

LOAD를 사용하여 공유 라이브러리인 pg_clickhouse를 로드합니다:

try=# LOAD 'pg_clickhouse';
LOAD

일반적으로 LOAD를 사용할 필요는 없습니다. Postgres는 pg_clickhouse의 기능(함수, 외부 테이블 등)이 처음 사용될 때 pg_clickhouse를 자동으로 로드합니다.

LOAD를 사용해 pg_clickhouse를 로드하는 것이 유용한 유일한 경우는, 해당 매개변수에 의존하는 쿼리를 실행하기 전에 SET을 통해 pg_clickhouse 매개변수를 설정하려는 경우입니다.

SET

SET을 사용하여 pg_clickhouse.session_settings 런타임 매개변수를 설정합니다. 이 매개변수는 이후 쿼리에 적용될 ClickHouse settings를 구성합니다. 예시:

SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';

기본값은 join_use_nulls 1입니다. 빈 문자열로 설정하면 ClickHouse 서버의 설정을 사용합니다.

SET pg_clickhouse.session_settings = '';

이 구문은 각 항목이 쉼표로 구분되고, 각 키와 값이 한 칸 이상의 공백으로 구분되는 키/값 쌍 목록입니다. 키는 ClickHouse settings와 일치해야 합니다. 값에 포함된 공백, 쉼표, 백슬래시는 역슬래시로 이스케이프합니다:

SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';

또는 공백과 쉼표를 이스케이프하지 않아도 되도록 값을 작은따옴표로 감싸서 사용하거나, 큰따옴표를 두 번 사용할 필요가 없도록 dollar quoting을 사용하는 것을 고려하십시오:

SET pg_clickhouse.session_settings = $$join_algorithm 'grace_hash,hash'$$;

가독성을 중시하고 여러 설정을 지정해야 한다면 다음과 같이 여러 줄로 작성하십시오:

SET pg_clickhouse.session_settings TO $$
    connect_timeout 2,
    count_distinct_implementation uniq,
    final 1,
    group_by_use_nulls 1,
    join_algorithm 'prefer_partial_merge',
    join_use_nulls 1,
    log_queries_min_type QUERY_FINISH,
    max_block_size 32768,
    max_execution_time 45,
    max_result_rows 1024,
    metrics_perf_events_list 'this,that',
    network_compression_method ZSTD,
    poll_interval 5,
    totals_mode after_having_auto
$$;

pg_clickhouse는 설정을 검증하지 않고, 모든 쿼리에 대해 설정을 그대로 ClickHouse에 전달합니다. 따라서 각 ClickHouse 버전에 존재하는 모든 설정을 지원합니다.

pg_clickhouse는 pg_clickhouse.session_settings를 설정하기 전에 로드되어야 합니다. 이를 위해 [shared library preloading]을 사용하거나, 확장에 포함된 객체 중 하나를 사용하여 로드되도록 하면 됩니다.

ALTER ROLE

ALTER ROLESET 명령을 사용하여 특정 역할에 대해 pg_clickhouse를 미리 로드(preload)하거나(및/또는) 해당 파라미터를 SET 하십시오.

try=# ALTER ROLE CURRENT_USER SET session_preload_libraries = pg_clickhouse;
ALTER ROLE

try=# ALTER ROLE CURRENT_USER SET pg_clickhouse.session_settings = 'final 1';
ALTER ROLE

ALTER ROLERESET 명령을 사용하여 pg_clickhouse 프리로딩 및/또는 매개변수를 초기화합니다:

try=# ALTER ROLE CURRENT_USER RESET session_preload_libraries;
ALTER ROLE

try=# ALTER ROLE CURRENT_USER RESET pg_clickhouse.session_settings;
ALTER ROLE

Preloading

모든 또는 거의 모든 Postgres 연결에서 pg_clickhouse를 사용해야 하는 경우, [shared library preloading]을 사용하여 자동으로 로드되도록 설정하는 것을 고려하십시오:

session_preload_libraries

PostgreSQL의 모든 새 연결마다 공유 라이브러리를 로드합니다:

session_preload_libraries = pg_clickhouse

서버를 재시작하지 않고 업데이트를 적용하는 데 유용합니다. 연결만 다시 수립하면 됩니다. ALTER ROLE을 통해 특정 사용자나 역할에 대해서도 설정할 수 있습니다.

shared_preload_libraries

PostgreSQL 부모 프로세스가 시작될 때 공유 라이브러리를 로드합니다:

shared_preload_libraries = pg_clickhouse

메모리를 절약하고 세션마다 발생하는 로드 오버헤드를 줄이는 데 유용하지만, 라이브러리를 업데이트할 때는 클러스터를 재시작해야 합니다.

FUNCTION 및 연산자 참조

데이터 타입

pg_clickhouse는 다음 ClickHouse 데이터 타입을 PostgreSQL 데이터 타입으로 매핑합니다.

ClickHousePostgreSQL비고
Boolboolean
Datedate
Date32date
DateTimetimestamp
Decimalnumeric
Float32real
Float64double precision
IPv4inet
IPv6inet
Int16smallint
Int32integer
Int64bigint
Int8smallint
JSONjsonbHTTP 엔진에서만 사용
Stringtext
UInt16integer
UInt32bigint
UInt64bigintBIGINT 최대값을 초과하는 값에서 오류 발생
UInt8smallint
UUIDuuid

함수

이 함수들은 ClickHouse 데이터베이스에 대한 쿼리 인터페이스를 제공합니다.

clickhouse_raw_query

SELECT clickhouse_raw_query(
    'CREATE TABLE t1 (x String) ENGINE = Memory',
    'host=localhost port=8123'
);

HTTP 인터페이스를 통해 ClickHouse 서비스에 연결하여 단일 쿼리를 실행한 뒤 연결을 종료합니다. 선택적인 두 번째 인수로 연결 문자열을 지정할 수 있으며, 기본값은 host=localhost port=8123입니다. 지원되는 연결 매개변수는 다음과 같습니다:

  • host: 연결할 호스트입니다. 필수입니다.
  • port: 연결할 HTTP 포트입니다. 기본값은 8123이며, host가 ClickHouse Cloud 호스트인 경우 기본값은 8443입니다.
  • dbname: 연결할 데이터베이스 이름입니다.
  • username: 연결할 사용자 이름입니다. 기본값은 default입니다.
  • password: 인증에 사용할 비밀번호입니다. 기본값은 비밀번호 없음입니다.

레코드를 반환하지 않는 쿼리에 유용하며, 값을 반환하는 쿼리의 결과는 단일 텍스트 값으로 반환됩니다:

SELECT clickhouse_raw_query(
    'SELECT schema_name, schema_owner from information_schema.schemata',
    'host=localhost port=8123'
);
      clickhouse_raw_query
---------------------------------
 INFORMATION_SCHEMA      default+
 default default                +
 git     default                +
 information_schema      default+
 system  default                +

(1 row)

푸시다운 함수

ClickHouse 외부 테이블을 쿼리하기 위해 조건절(HAVING, WHERE)에서 사용되는 모든 PostgreSQL 내장 함수는 동일한 이름과 함수 시그니처로 자동으로 ClickHouse로 푸시다운됩니다. 그러나 일부 함수는 이름이나 시그니처가 달라서 해당하는 함수로 매핑해야 합니다. pg_clickhouse는 다음 함수를 매핑합니다:

사용자 정의 함수

pg_clickhouse에서 생성한 이 사용자 정의 함수들은 PostgreSQL에 대응하는 함수가 없는 일부 ClickHouse 함수에 대해 foreign query pushdown(쿼리 푸시다운)을 제공합니다. 이러한 함수들 중 어떤 것이든 푸시다운할 수 없으면 예외를 발생시킵니다.

푸시다운 캐스트

pg_clickhouse는 호환되는 데이터 타입에 대해서 CAST(x AS bigint)와 같은 캐스트를 푸시다운합니다. 호환되지 않는 타입에는 푸시다운이 실패합니다. 이 예제에서 x가 ClickHouse UInt64라면, ClickHouse는 해당 값의 캐스트를 거부합니다.

호환되지 않는 데이터 타입으로의 캐스트를 푸시다운하기 위해, pg_clickhouse는 다음 FUNCTION을 제공합니다. 이 FUNCTION들이 푸시다운되지 않으면 PostgreSQL에서 예외를 발생시킵니다.

푸시다운 집계

다음 PostgreSQL 집계 함수는 ClickHouse로 푸시다운됩니다.

사용자 정의 집계 함수

pg_clickhouse에서 생성한 이러한 사용자 정의 집계 함수는 PostgreSQL에 동등한 기능이 없는 일부 ClickHouse 집계 함수에 대해 foreign query pushdown(원격 쿼리 푸시다운)을 제공합니다. 이 함수들 중 어느 하나라도 pushdown 대상이 될 수 없으면 예외를 발생시킵니다.

푸시다운 Ordered Set 집계

이러한 ordered-set aggregate functionsdirect argument를 매개변수로, ORDER BY 표현식을 인수로 전달하여 ClickHouse의 [Parametric aggregate functions]에 매핑됩니다. 예를 들어, 다음 PostgreSQL 쿼리는 다음과 같습니다:

SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY a) FROM t1;

다음 ClickHouse 쿼리에 해당합니다:

SELECT quantile(0.25)(a) FROM t1;

기본값이 아닌 ORDER BY 접미사인 DESCNULLS FIRST는 지원되지 않으며 오류가 발생합니다.

저자

David E. Wheeler

Copyright (c) 2025-2026, ClickHouse

"PostgreSQL 문서: 공유 라이브러리 사전 로드 [PREPARE notes]: https://www.postgresql.org/docs/current/sql-prepare.html#SQL-PREPARE-NOTES "PostgreSQL 문서: PREPARE 관련 참고 사항" [query parameters]: https://clickhouse.com/docs/guides/developer/stored-procedures-and-prepared-statements#alternatives-to-prepared-statements-in-clickhouse "ClickHouse 문서: ClickHouse에서 prepared statement의 대안"