Skip to main content
clickhouse-c는 ClickHouse 네이티브 프로토콜을 위한 헤더 전용 C 클라이언트입니다. 소스 코드와 각 헤더별 참고 문서는 GitHub 리포지토리에서 확인할 수 있습니다. 고수준 클라이언트와 달리, 이 라이브러리는 의도적으로 많은 기능을 대신 처리하지 않습니다. 핵심 헤더는 사용자가 제공하는 I/O 콜백을 통해 Native 형식 블록을 디코딩하고 인코딩합니다. 소켓, TLS 컨텍스트, 할당자, 재시도, 연결 풀링은 사용자가 직접 관리합니다. 따라서 내장하기에 충분히 작습니다. clickhouse.h만 포함하면 링크 시점 의존성은 libc 외에 없습니다.
이 라이브러리는 현재 활발히 개발 중입니다. v1은 핵심 ClickHouse 타입을 디코딩합니다. 제약 사항이나 누락된 기능은 issue tracker를 통해 보고하십시오. 다만 이 라이브러리에는 설계상 일부 기능이 포함되어 있지 않다는 점을 이해하십시오.

라이브러리가 하지 않는 일

다음은 의도적으로 범위에 포함하지 않은 항목입니다. 이러한 항목은 애플리케이션 또는 관련 라이브러리에서 처리하십시오:
  • HTTP protocol. HTTP 인터페이스를 사용할 때는 libcurl을 직접 래핑하십시오.
  • DNS 해석, endpoint failover, 연결 풀링, retry, 백오프.
  • TLS 컨텍스트 수명 주기. OpenSSL 백엔드는 이미 연결된 SSL을 사용합니다.
  • 스레딩. 각 chc_client는 설계상 단일 스레드 방식입니다.
  • 라이브러리 내부의 async I/O. blocking client는 chc_io.read를 동기적으로 호출합니다. 자체적으로 I/O를 수행하지 않는 이벤트 루프 클라이언트가 필요하면 ioless client를 사용하십시오.

라이브러리 구성 방식

clickhouse-c는 평면적인 헤더 집합 형태로 제공됩니다. 각 헤더에는 선언과 구현이 모두 포함되어 있으며, 센티널 매크로로 보호됩니다. 빌드에 필요한 헤더를 선택하십시오.

필수 서버 설정

decoder는 wire에서 출력 가능한 타입 이름을 읽으므로, 반드시 텍스트로 인코딩되어야 합니다. ClickHouse는 기본적으로 이를 텍스트로 기록하지만, 서버 또는 session profile에서 이를 binary로 설정해도 디코딩이 깨지지 않도록 쿼리에서 이 설정을 명시적으로 고정하십시오:

프로젝트에 추가하기

설치할 package는 없으므로, Git submodule이나 복사본을 통해 헤더 파일을 소스 트리에 포함해야 합니다. 정확히 하나의 translation unit에서 CHC_IMPLEMENTATION을 정의해 구현을 포함하고; 나머지 모든 unit에서는 선언만을 위해 동일한 헤더 파일을 포함합니다.
chc_alloc_stdlib를 사용하려면 clickhouse.h를 포함하기 전에 CHC_PROVIDE_STDLIB_ALLOC를 정의하십시오. lz4/zstd 의존성을 제거하려면 clickhouse-compression.h를 포함하기 전에 CHC_NO_LZ4 또는 CHC_NO_ZSTD를 정의하십시오.

TCP를 통해 연결하기

ClickHouse 서버와 통신하려면 소켓을 직접 구성하고, 이를 chc_io로 감싼 다음 chc_client_init에 전달해야 합니다. chc_client_init는 Hello 핸드셰이크를 동기적으로 수행합니다. 이 library는 DNS, 페일오버, 재연결, 풀링을 처리하지 않으며, 이러한 부분은 호출자 측에서 관리해야 합니다.
chc_client는 단일 스레드 방식으로 동작하며 하나의 연결만 래핑합니다. 라이브러리는 chc_io 콜백을 동기적으로 호출하며, 해당 콜백이 내부적으로 무엇을 수행할지(epoll, io_uring, WaitLatchOrSocket 사용 등)는 구현에 따라 달라집니다.

쿼리 실행

쿼리를 전송한 다음 CHC_PKT_END_OF_STREAM이 나올 때까지 패킷을 계속 읽으십시오. chc_client_send_query_ex를 사용해 필수 서버 설정(server setting)을 적용하십시오. 기본 chc_client_send_query는 비어 있는 설정 목록을 전송하며, 서버의 기본 설정을 그대로 상속합니다.
서버 예외는 CHC_PKT_EXCEPTION 패킷으로 전달되며, chc_client_recv_packet에서 non-OK를 반환하는 방식으로 전달되지 않습니다. non-OK가 반환되는 경우는 전송 계층의 실패뿐입니다. 결과의 첫 번째 CHC_PKT_DATA 패킷은 0개의 행을 포함한 스키마(schema)를 설명하는 헤더 블록이며, 그 뒤에 데이터 블록이 이어집니다. chc_packet_clear는 패킷의 블록 또는 예외를 해제하므로, 대신 소유권을 넘겨받으려면 먼저 패킷의 해당 필드를 null로 설정하십시오.

컬럼 데이터 읽기

블록은 컬럼 지향입니다. 각 컬럼에는 chc_column_layout이 반환하는 물리적 레이아웃이 있으며, 이에 따라 분기 처리합니다. 선언된 유형은 chc_block_column_type에서 가져옵니다. 복합 레이아웃은 중첩될 수 있으므로 Nullable(Array(String))를 읽을 때는 널 허용 래퍼를 벗기고, 배열 오프셋을 따라간 다음, 문자열 데이터를 슬라이스해야 합니다. 일반 숫자형, 문자열, 널 허용 컬럼을 위한 reader는 다음과 같습니다:
CHC_COL_FIXED 데이터는 wire 상에서는 리틀 엔디언이며, 빅 엔디언 호스트에서는 다중 바이트 정수를 직접 바이트 스왑해야 합니다. 오프셋과 LowCardinality 키는 디코드 시점에 이미 호스트 바이트 순서로 스왑됩니다. UUIDs는 리틀 엔디언 UInt64 절반 2개로 구성되며, IPv4는 4바이트 리틀 엔디언 정수이고, IPv6는 네트워크 바이트 순서입니다. DateTime64 틱은 UTC이며, 유형의 시간대는 메타데이터일 뿐입니다. 신뢰할 수 없는 피어로부터 수집할 때는 순회하기 전에 각 컬럼에 대해 chc_column_validate를 호출하십시오. chc_block_read는 배열 오프셋과 LowCardinality 키 같은 필드 간 불변식을 검증하지 않으므로, 위조된 블록으로 인해 내부 컬럼 경계를 넘어 읽을 수 있습니다.

데이터 삽입

chc_build_* helpers로 컬럼을 생성하고, 이를 chc_block_builder에 추가한 다음 chc_client_send_data에 전달합니다. builder는 호출자가 제공한 스토리지를 사용하며 데이터를 복사하지 않고 포인터만 기록하므로, 스토리지, 컬럼 트리, 타입, 이름, 슬랩은 전송이 완료될 때까지 유지되어야 합니다. INSERT는 쿼리를 전송하고 서버의 헤더 블록을 기다린 다음, 하나 이상의 데이터 블록을 전송한 후 마지막으로 스트림을 종료하기 위해 빈 블록을 전송합니다.
chc_build_fixedn_rows * elem_size개의 리틀 엔디언 바이트를 사용합니다. chc_build_string은 패킹된 슬랩에 대해 host 바이트 순서의 누적 exclusive 종료 오프셋을 사용합니다. helpers는 컬럼 노드를 값으로 반환합니다. 유형에 맞게 이를 중첩하세요. 예를 들어 고정 노드 또는 문자열 노드를 chc_build_nullable에 전달하고, 그 결과를 chc_build_array에 전달한 다음 배열 루트를 추가합니다. Tuple, LowCardinality, Map, geo 컬럼은 모두 동일한 트리를 사용합니다. Map은 Array(Tuple(K, V))입니다. 블록의 모든 컬럼은 동일한 최상위 행 수를 가져야 합니다. writer는 트리를 파싱된 ClickHouse 유형과 대조하여 검사하지만, 호출자는 각 추가 작업마다 chc_block_col 저장소 크기를 맞춰야 합니다. 또한 chc_block_column에서 디코딩한 컬럼을 직접 추가해 다시 인코딩할 수도 있고, builder를 건너뛰고 chc_block_col 배열과 함께 chc_block_write_cols를 호출할 수도 있습니다. builder를 더 낮은 수준의 chc_block_write가 아니라 chc_client_send_data를 통해 전달하면 클라이언트가 협상된 revision을 기준으로 블록 옵션을 설정하고 Compression을 적용할 수 있습니다.

압축

chc_client_opts에 압축 모드와 초기화된 코덱을 전달합니다. 클라이언트는 들어오는 데이터 패킷의 압축을 해제하고, 나가는 패킷은 압축합니다. 압축 헤더는 LZ4ZSTD 어댑터를 제공하며, 각 초기화 함수는 자체 슬롯만 채우므로 둘 중 어느 쪽이든 지원하려면 둘 다 호출하십시오.
프로젝트에서 바인딩을 제공하지 않는 압축 라이브러리를 사용하려면 chc_codec를 직접 구현하십시오; vtable은 clickhouse-compression.h에 선언되어 있습니다.

TLS

clickhouse-openssl.hSSL_read/SSL_write를 사용하는 chc_io 백엔드를 제공합니다. OpenSSL은 직접 구동해야 합니다: 라이브러리는 SSL_CTX를 생성하거나, 인증서를 검증하거나, SNI를 설정하거나, SSL_connect / SSL_shutdown을 호출하지 않습니다. chc_io.read가 호출될 때는 핸드셰이크가 이미 완료되어 있어야 합니다.
ClickHouse Cloud 및 TLS가 활성화된 다른 배포는 포트 9440에서 네이티브 프로토콜을 사용합니다. 두 백엔드 모두 선택적 check_cancel 콜백을 받을 수 있으며, 이 콜백은 읽기 사이에 폴링되고 chc_openssl_io_set_deadline / chc_posix_io_set_deadline을 통해 읽기 제한 시간도 설정할 수 있습니다.

Ioless (async) 클라이언트

clickhouse-async.h는 이벤트 루프용 TCP 클라이언트의 ioless 변형입니다. 소켓은 전혀 직접 다루지 않습니다. 수신한 바이트를 전달하고 전송할 바이트를 꺼내 사용하며, epoll, io_uring, 또는 WaitLatchOrSocket은 직접 구동해야 합니다. 옵션, 패킷 유형, 블록 빌더는 블로킹 클라이언트와 동일합니다. chc_async_client_init는 I/O를 수행하지 않으므로 블로킹되지 않습니다. 핸드셰이크는 이후 재개 가능한 상태 머신으로 실행되며, 모든 송신과 수신도 마찬가지입니다. parse가 전달된 바이트 범위를 넘어가면, 호출은 블로킹되는 대신 CHC_WOULD_BLOCK을 반환합니다 — inbound 바이트를 더 전달한 뒤 다시 호출하면, parser는 블록 중간부터 재개됩니다.
pump는 바이트를 양방향으로 전송합니다. Outbound에서는 chc_async_pending_out이 큐에 대기 중인 바이트에 대한 포인터와 길이를 반환합니다. socket이 그중 일부를 수락하면 해당 개수를 사용해 chc_async_consume_out을 호출하십시오. 부분 쓰기여도 괜찮습니다. Inbound에서는 socket에서 읽은 데이터를 chc_async_submit에 전달하십시오. 전송은 절대 block되지 않으며 backpressure도 적용되지 않으므로, pending-out 길이를 주시하고 너무 커지면 더 이상 전송을 요청하지 마십시오. 작동하는 liburing driver는 test/test_async_uring.c에 있습니다.

메모리와 할당자

모든 진입점은 chc_alloc vtable을 받으므로, 메모리 할당은 호스트에서 사용하는 방식에 따라 이루어집니다.
clickhouse.h를 포함하기 전에 CHC_PROVIDE_STDLIB_ALLOC를 정의하고, 표준 malloc 기반 할당자를 사용하려면 chc_alloc_stdlib()를 호출하십시오.

오류 및 서버 예외

함수는 CHC_OK (0) 또는 0이 아닌 CHC_ERR_* 코드를 반환합니다. 이 코드는 반환값이며, 호출자 스택에 할당된 chc_err에는 사람이 이해할 수 있는 메시지가 담깁니다. 이 라이브러리는 오류를 위해 힙을 할당하지 않습니다.
서버 측 쿼리 오류는 chc_err 실패가 아닙니다. 이는 패킷 스트림에서 CHC_PKT_EXCEPTION으로 전달되며, 서버의 code, display_text, stack_trace를 포함합니다. chc_err 확인은 전송, protocol, 디코드 실패에만 사용하십시오.

지원되는 데이터 타입

블록 리더는 다음을 디코딩합니다.
  • Int8Int256, UInt8UInt256
  • Float32, Float64, BFloat16
  • Bool
  • Decimal32, Decimal64, Decimal128, Decimal256
  • Date, Date32, DateTime, DateTime64, Time, Time64
  • String, FixedString(N)
  • UUID, IPv4, IPv6
  • Enum8, Enum16
  • Nullable(T), Array(T), Tuple(...), Map(K, V), Nested(...)
  • LowCardinality(T)
  • Interval
  • QBit(...)
  • Point, Ring, Polygon, MultiPolygon
  • SimpleAggregateFunction(f, T), 이는 내부 T로 디코딩됩니다
  • JSONObject('json'), 문자열 직렬화에서는 String 컬럼으로 디코딩됩니다(아래 참조)
JSONObject('json')는 문자열 직렬화에서 디코딩됩니다. 쿼리에서 output_format_native_write_json_as_string=1을 설정하십시오. 지원되는 각 행은 CHC_COL_STRING 컬럼의 하나의 JSON 문서로 전달됩니다. chc_build_string으로 동일한 형태를 생성하십시오. writer는 파싱된 타입에 필요한 접두사를 출력합니다. Variant, Dynamic, AggregateFunction은 아직 디코딩되지 않으며 CHC_ERR_TYPE을 반환합니다. 폴백으로 서버 측에서 이를 String으로 캐스팅하십시오.
마지막 수정일 2026년 7월 23일