Skip to main content

실험적 기능

네이티브 TCP 클라이언트 API는 실험적 기능입니다. 공개 API는 향후 릴리스에서 변경될 수 있습니다.
기본 클라이언트, 데이터 소스, 세션, 작업 인터페이스와 의존성 주입 확장 기능을 사용하면 컴파일러 경고 CHTCP0001이 발생합니다. API를 평가하는 동안 이 경고를 인지하고 사용하려면 프로젝트 파일의 NoWarn에 추가하세요:
API를 사용하는 코드는 향후 릴리스에서 수정해야 할 수 있다는 점을 받아들인 후에만 경고를 표시하지 않도록 설정하세요.

지원되는 .NET 버전

네이티브 TCP 클라이언트는 다음 .NET 버전을 지원합니다.
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

지원되는 ClickHouse 버전

TCP 클라이언트는 ClickHouse 서버의 최근 LTS 버전 2개와 최근 릴리스 3개를 공식 지원합니다.

설치

네이티브 TCP 클라이언트는 ClickHouse.Driver 패키지에 포함되어 있습니다. NuGet에서 해당 패키지를 설치하세요:
또는 NuGet Package Manager를 사용할 수 있습니다:

빠른 시작

HTTP 클라이언트에서 마이그레이션

TCP API는 IClickHouseClient와 별개이며, ClickHouseClient나 ClickHouseConnection에서 선택할 수 있는 전송 옵션이 아닙니다. 대부분의 SQL은 재사용할 수 있지만, ClickHouseTcpClient를 생성하고 각 작업을 그에 대응하는 TCP 작업으로 마이그레이션해야 합니다.

메서드 대응 관계

예를 들어 HTTP 리더를 사용하는 반복문은 다음과 같습니다.
비동기 TCP 행 스트림으로 전환됩니다:

옵션과 매개변수

HTTP QueryOptions와 ClickHouseParameterCollection은 이에 대응하는 TCP 유형과 서로 바꿔 사용할 수 없습니다. 값을 ClickHouseTcpQueryOptions로 옮기세요. 이 유형에서는 설정이 문자열이며 매개변수가 옵션 객체에 포함됩니다:
TCP 클라이언트는 ClickHouse 네이티브 {name:Type} 플레이스홀더를 지원하지만 ADO.NET 방식의 @name 플레이스홀더는 재작성하지 않습니다. 쿼리별 데이터베이스와 역할, 베어러 토큰, 사용자 정의 헤더, 매개변수 리졸버 또는 포매터, 읽기 컨버터도 지원하지 않습니다. HTTP에서만 사용할 수 있는 기능은 제한 사항을 참조하십시오.

연결과 결과의 수명

두 상위 수준 클라이언트 모두 여러 스레드에서 안전하게 사용할 수 있으므로 재사용해야 합니다. TCP 클라이언트는 HTTP 연결이 아닌 지속적인 네이티브 연결을 관리하고 IAsyncDisposable을 구현하므로, 일반적으로 애플리케이션 종료 시 비동기적으로 해제해야 합니다. QueryAsync가 반환한 행은 호출자가 소유합니다. StreamAsync가 반환한 블록과 그 컬럼은 임시로 빌려 쓰는 데이터로, 현재 반복에서만 유효합니다. 반복이 끝난 뒤에도 필요한 값은 복사하십시오. 연결이 풀로 반환되도록 열거를 항상 완료하거나 해제하십시오. 여러 작업을 동일한 연결에서 실행하면서 세션 상태를 유지해야 한다면 OpenSessionAsync를 사용하십시오. 동일한 테이블에 두 클라이언트를 사용하는 방법은 전체 HTTP에서 TCP로 마이그레이션하는 예시를 참조하십시오.

구성

네이티브 TCP 클라이언트는 다음 2가지 방법으로 구성할 수 있습니다.
  • 연결 문자열: 서버 엔드포인트, 인증 자격 증명 및 기타 연결 옵션을 지정하는 키/값 쌍을 세미콜론으로 구분한 문자열입니다.
  • ClickHouseTcpClientOptions 객체: 코드에서 설정하는, 타입이 명확하게 지정된 변경 불가능한 구성 객체입니다.
두 방식 중 하나를 ClickHouseTcpClient 생성자에 전달하세요. ClickHouseTcpConnectionStringBuilder를 사용하면 연결 문자열을 파싱하거나 생성한 후 ClickHouseTcpClientOptions로 변환할 수 있습니다.
다음 섹션에서는 사용 가능한 설정과 각 설정의 기본값 및 적용 시 동작을 설명합니다.

연결 설정

연결 문자열에서 TimeSpan 속성 값은 초 단위로 지정합니다.

데이터 포맷과 직렬화

연결 풀링(Connection pooling)

하나의 연결은 한 번에 하나의 작업만 실행합니다. 풀을 사용하면 여러 작업을 동시에 실행할 수 있으며, 작업 간에 연결을 재사용합니다.

보안

TLS를 사용하지 않으면 비밀번호와 모든 쿼리 데이터가 암호화되지 않은 채 네트워크를 통해 전송됩니다. 로컬 개발 환경이 아니라면 TlsAllowInvalidCertificates를 false로 설정해 두십시오.
보안 native protocol은 기존 연결에서 보안 연결로 전환하는 방식이 아니라 별도의 서버 엔드포인트를 사용합니다. TLS 클라이언트는 보안 native protocol 포트에 연결해야 하며, 평문 연결로 폴백되지 않습니다. ConfigureTls는 마지막에 적용되며 인증서 및 호스트명 유효성 검사를 재정의할 수 있으므로 사용자 정의 콜백을 주의 깊게 검토하십시오.

로깅 및 디버깅

클라이언트 로거는 클라이언트 측의 수명 주기 및 작업 이벤트를 기록합니다. 서버 로그 패킷은 대신 ClickHouseTcpQueryCallbacks.OnLog로 전송됩니다. SQL 문 텍스트에는 민감한 데이터가 포함될 수 있으므로 기본적으로 활동 태그에서는 제외하고 디버그 로그에서는 길이를 대폭 줄입니다.

사용자 지정 설정

ClickHouseTcpClientOptions.CustomSettings는 모든 쿼리와 삽입 작업에 ClickHouse 서버 설정을 적용합니다. 작업별 ClickHouseTcpQueryOptions.Settings 값은 이름이 같은 클라이언트 수준의 값을 재정의합니다.
연결 문자열에서 각 서버 설정 이름 앞에 set_ 접두사를 붙이세요.
CustomSettings 또는 작업별 Settings에 설정을 추가할 때는 set_ 접두사를 붙이지 마세요.

ClickHouseTcpQueryOptions

ClickHouseTcpQueryOptions는 작업별 설정을 제공합니다. 모든 속성은 선택 사항입니다.

ClickHouseTcpInsertOptions

ClickHouseTcpInsertOptions는 ClickHouseTcpQueryOptions를 확장하여 네이티브 블록 삽입에 필요한 설정을 추가합니다. 모든 쿼리 옵션 속성도 사용할 수 있습니다.

ClickHouseTcpClient

ClickHouseTcpClient는 네이티브 TCP 프로토콜용 고수준 API입니다. SQL 문과 스칼라 쿼리를 실행하고, 결과를 열 지향 블록, 행 또는 POCO 형태로 스트리밍하며, 열 지향 데이터나 행 데이터를 삽입하고, 특정 연결에 고정된 세션을 엽니다. 이 클라이언트는 여러 스레드에서 안전하게 사용할 수 있으며 연결 풀을 자체적으로 관리합니다. 엔드포인트마다 클라이언트를 하나씩 생성해 애플리케이션 전체에서 공유하고 종료할 때 리소스를 해제하세요. 작업은 MaxPoolSize에 지정된 한도까지 동시에 실행되며, 한도를 초과한 작업은 연결을 얻을 때까지 최대 PoolTimeout 동안 대기합니다.

클라이언트 생성

연결 문자열(connection string) 또는 ClickHouseTcpClientOptions를 사용해 클라이언트를 생성합니다. 사용 가능한 설정은 구성을 참조하세요. 연결 문자열을 사용하는 경우:
타입이 엄격하게 지정된 옵션 사용:
비동기 해제를 권장합니다. 클라이언트를 해제하면 유휴 연결을 닫고 진행 중인 작업이 완료되기를 최대 PoolTimeout까지 기다린 후, 완료되지 않은 작업을 중단합니다.

의존성 주입

AddClickHouseTcpDataSource는 싱글턴 데이터 소스 하나를 등록하고, 공유 클라이언트를 IClickHouseTcpClient와 IClickHouseTcpOperations로 사용할 수 있게 합니다.
서비스 제공자가 데이터 소스와 연결 풀을 관리합니다. 주입된 클라이언트는 해제하지 말고, 애플리케이션 종료 시 서비스 제공자를 해제하세요. 옵션에 LoggerFactory가 지정되지 않은 경우 등록 시 컨테이너의 ILoggerFactory를 사용합니다. serviceKey를 사용하면 서로 독립적인 여러 데이터 소스를 등록할 수 있습니다.

쿼리 실행

DDL이나 뮤테이션과 같이 행을 반환하지 않는 SQL 문에는 ExecuteAsync를 사용하세요.
이 메서드는 서버가 SQL 문을 수신했음을 확인하면 완료됩니다. SQL 문이 행을 반환하는 경우 ExecuteAsync는 반환된 행을 읽고 버립니다. INSERT INTO ... VALUES를 사용할 때는 InsertAsync 또는 InsertRowsAsync를 사용하고, 값을 직접 추가하지 말고 SQL 문을 VALUES에서 끝내십시오.

데이터 읽기

애플리케이션에 필요한 결과 형태에 맞춰 읽기 API를 선택하세요.

스칼라 읽기

ExecuteScalarAsync는 첫 번째 행의 첫 번째 컬럼 값을 object로 반환합니다.
이 메서드는 응답 전체를 읽고 첫 번째 값을 제외한 나머지 값은 버립니다. 결과가 많은 쿼리를 조기에 중단하는 용도로 사용하지 말고 한 행만 반환하는 쿼리를 작성하세요. 첫 번째 값이 NULL이거나 쿼리가 행을 반환하지 않으면 모두 null이 반환됩니다.

행 읽기

QueryAsync는 각 행을 해당 행 전용 object[]로 스트리밍하며, 배열의 값은 결과 컬럼 순서대로 들어 있습니다.
열거가 진행된 후에도 행 배열은 계속 유효합니다. 이 방식은 결과 모델이 없을 때 편리하지만, 행마다 배열을 하나씩 할당하고 값 타입을 박싱합니다.

POCO 읽기

QueryAsync<T>는 컬럼을 값을 설정할 수 있는 공개 속성에 매핑합니다. 이름을 비교할 때는 대소문자와 밑줄을 구분하지 않습니다. 속성 이름을 명시적으로 지정하려면 ClickHouseTcpColumn을, 매핑에서 속성을 제외하려면 ClickHouseTcpNotMapped를 사용하세요.
POCO 매핑은 데이터 변환을 수행하고 각 행에 객체 하나를 할당합니다.

열 지향 블록 읽기

StreamAsync는 네이티브 열 지향 쿼리 결과를 Block 값의 시퀀스로 반환합니다. 데이터를 읽는 가장 빠른 방법입니다.
블록은 빌려 쓰는 객체입니다. 블록과 그 컬럼 및 값 스팬은 현재 반복에서만 유효합니다. 이를 직접 해제하거나 반복 이후까지 보관하지 마세요. 반복문 본문이 끝난 뒤에도 필요한 데이터는 소유한 메모리에 명시적으로 복사해야 합니다.
연결이 풀로 반환되도록 열거를 끝까지 완료하거나 열거자를 해제하세요.
Block.Column<T>은 디코더가 생성한 CLR 타입에 접근합니다. 변환이 필요하면 Block.ReadAs<T> 또는 IDateTimeColumn과 같은 전용 인터페이스를 사용하세요.

데이터 삽입

네이티브 삽입은 데이터를 열 지향 wire 블록 형태로 전송합니다. SQL 문은 값 목록을 직접 포함하지 않는 INSERT INTO ... VALUES 형식이어야 합니다. 클라이언트 측 작업을 최소화하려면 타입이 지정된 컬럼을 사용하세요. 애플리케이션에 이미 행 지향 데이터가 있다면 행 또는 POCO 오버로드를 사용하세요.

열 지향 삽입

InsertAsync는 타입이 지정된 컬럼별로 미리 묶어 놓은 데이터를 받습니다. 이 방식이 데이터를 삽입하는 가장 빠른 방법입니다.
컬럼은 인수 순서가 아니라 이름을 기준으로 SQL 컬럼 목록과 일치해야 하며, 각 컬럼의 행 수도 같아야 합니다. SQL 문에서 생략된 테이블 컬럼의 기본값은 서버가 채웁니다. 삽입이 완료될 때까지 호출자가 소유한 배열을 수정하지 마세요. 이 API를 사용하면 행 삽입에 필요한 행에서 컬럼으로의 변환과 값 유형의 박싱을 피할 수 있습니다. 빌려온 결과 블록에서 읽은 컬럼도 해당 블록이 해제되기 전에 삽입이 완료된다면 InsertAsync에 직접 전달할 수 있습니다.

행 삽입

InsertRowsAsync는 object[] 형식의 행을 받습니다. 각 값은 순서대로 SQL 컬럼 목록의 컬럼에 대응합니다.
행은 블록 단위로 컬럼(column)으로 변환됩니다. 각 행에는 SQL 문에 명시된 모든 컬럼의 값이 하나씩 있어야 하며, 대상 컬럼이 Variant 또는 Dynamic인 경우를 제외하면 같은 컬럼의 값은 모두 동일한 CLR 타입을 사용해야 합니다. 작업이 완료될 때까지 행을 수정하지 마십시오.

POCO 삽입

제네릭 InsertRowsAsync<T> 오버로드는 POCO 읽기에 적용되는 것과 동일한 명명 및 속성 규칙에 따라 대상 컬럼을 공개 읽기 가능 프로퍼티에 매핑합니다.
SQL 문에 지정된 모든 컬럼은 호환되는 public getter에 매핑되어야 합니다. POCO 행은 wire 블록 단위로 변환되므로, 앞선 블록이 이미 전송된 뒤에야 이후 블록의 유효하지 않은 값이 발견될 수 있습니다. ClickHouseTcpNotMapped를 사용하면 ImportBatch처럼 애플리케이션에서만 사용하는 속성을 매핑에서 제외할 수 있습니다.

SQL 매개변수

네이티브 클라이언트는 값을 ClickHouse 고유의 {name:Type} 플레이스홀더에 바인딩합니다. ADO.NET 스타일의 @name 플레이스홀더를 재작성하지는 않습니다.
각 매개변수는 SQL 플레이스홀더 또는 ClickHouseTcpParameter.ClickHouseType 속성으로 유형을 지정해야 합니다. 테이블과 컬럼 이름에는 Identifier 유형을 사용하세요. DateTime 또는 DateTimeOffset이 특정 시점을 나타내는 경우 {timestamp:DateTime('UTC')}처럼 시간대를 명시하세요. 그렇지 않으면 서버 세션의 시간대에 따라 값이 가리키는 시점이 달라질 수 있습니다.

쿼리 ID

모든 작업에는 ClickHouse 시스템 테이블, 클라이언트 로그, 추적 스팬에서 사용하는 쿼리 ID가 있습니다. QueryId가 null이거나 비어 있으면 클라이언트가 새 GUID를 생성합니다. 작업을 애플리케이션 식별자와 연관시켜야 할 때는 쿼리 ID를 설정하세요:

쿼리 진행 상황 및 메타데이터

응답 중간에 전달되는 메타데이터를 수신하려면 ClickHouseTcpQueryOptions.Callbacks를 설정하세요.
진행 상황 값은 누적 합계가 아닌 증가분입니다. 콜백은 응답을 읽는 스레드에서 패킷 순서대로 동기적으로 실행됩니다. 콜백은 신속하게 처리하고 예외를 던지지 않도록 하십시오. OnLog, OnProfileEvents, OnTotals, OnExtremes에 전달되는 블록은 일시적으로 참조하는 것이므로 콜백 밖으로 전달해서는 안 됩니다. ClickHouse는 삽입 작업으로 업로드된 행에 대한 쿼리 진행 상황 패킷을 보내지 않습니다. 클라이언트가 전송한 블록의 진행 상황을 확인하려면 OnBlockWritten을 사용하십시오. 이 콜백은 전송 진행 상황을 알려 줄 뿐, 서버가 삽입을 커밋했음을 뜻하지는 않습니다.

취소

모든 작업에 CancellationToken을 전달할 수 있습니다.
스트리밍된 결과를 취소하거나 끝까지 처리하지 않으면 해당 연결을 안전하게 재사용할 수 없으므로 클라이언트는 연결을 풀에 반환하지 않고 닫습니다. 클라이언트는 계속 사용할 수 있으며, 이후 작업에는 다른 연결을 열거나 기존 연결을 재사용합니다. 따라서 고정된 세션에서 작업을 취소하면 세션이 종료되고 임시 테이블과 설정이 손실될 수 있습니다.

서버 정보 및 상태 검사

GetServerInfoAsync는 네이티브 핸드셰이크에서 확인한 서버 아이덴티티, 버전, 시간대 및 협상된 프로토콜 정보를 반환합니다.
wire 수준의 기능은 클라이언트와 서버가 협상한 revision인 ProtocolRevision에 따라 사용 가능 여부를 판단합니다. 데이터 타입과 함수 같은 SQL 기능은 Version에 따라 사용 가능 여부를 판단합니다. 이 메서드는 기존 연결을 사용할 수 있으면 재사용하고, 풀이 비어 있으면 새 연결을 엽니다. native protocol을 통한 준비 상태 확인에는 PingAsync를 사용하세요.
Ping에 성공하면 네이티브 연결과 인증이 정상적으로 작동하는지 확인할 수 있습니다. 다만 애플리케이션 테이블에 대한 액세스 가능 여부는 확인할 수 없습니다.

추가 예시

읽기, 삽입, 타입, 세션, TLS, 관측성 및 오류 처리를 다루는 완전한 실행 가능 예시는 GitHub 리포지토리의 네이티브 TCP 예시를 참조하세요.

세션

일반적인 클라이언트 작업은 풀에서 사용 가능한 연결을 빌려 사용하므로, 연결에 종속된 상태는 격리되지 않고 호출 간에 유지된다는 보장도 없습니다. 풀의 연결마다 서로 다른 서버에 접속할 수도 있습니다. 임시 테이블이나 SET SQL 문을 사용하거나 일련의 작업을 동일한 연결에서 실행해야 한다면 세션을 여세요.
IClickHouseTcpSession은 클라이언트와 동일한 쿼리, 실행, 삽입, 핑, 서버 정보 API를 제공합니다. 세션은 스레드로부터 안전하지 않습니다. 세션에서는 작업이 순차적으로 처리되므로 현재 작업이 끝나기 전에 다른 작업을 시작하면 요청이 거부됩니다. 스트리밍 결과를 끝까지 순회하거나 열거자를 해제할 때까지 세션은 사용 중인 상태로 유지됩니다. 각 세션은 유지되는 동안 MaxPoolSize 슬롯 1개를 차지합니다. 클라이언트는 풀의 다른 연결을 계속 사용할 수 있지만, 풀에 연결이 1개뿐이면 다른 클라이언트 작업은 세션이 해제되거나 PoolTimeout이 만료될 때까지 대기합니다. 상위 클라이언트를 해제하기 전에 세션을 해제하세요. 세션을 해제하면 전용으로 할당된 연결을 풀에 반환하지 않고 닫으므로, 이후 호출자가 해당 세션의 임시 테이블이나 설정을 이어받지 않습니다. 연결 또는 프로토콜 오류, 취소, 결과 스트림의 처리 중단으로 세션을 더 이상 사용할 수 없게 될 수 있습니다. IsOpen이 false가 되면 새 세션을 여세요.

모범 사례

클라이언트 수명과 풀링

엔드포인트와 구성의 조합마다 ClickHouseTcpClient 또는 ClickHouseTcpDataSource를 하나씩 생성하고 애플리케이션이 실행되는 동안 재사용하세요. 클라이언트는 스레드 안전성을 보장하며 자체 연결 풀을 관리합니다. 작업마다 클라이언트를 생성하면 매번 새 풀이 만들어져 연결을 재사용할 수 없습니다. 의존성 주입을 사용하는 애플리케이션에서는 AddClickHouseTcpDataSource를 한 번 등록하고 IClickHouseTcpClient를 주입하세요. 서비스 공급자가 클라이언트를 관리하므로 주입받아 사용하는 쪽에서 인스턴스를 해제해서는 안 됩니다. 의존성 주입을 사용하지 않는다면 애플리케이션 종료 시 await using으로 클라이언트를 해제하세요. 각 네이티브 연결은 한 번에 하나의 작업만 처리합니다. 따라서 MaxPoolSize는 열린 연결 수와 동시 작업 수를 모두 제한하며, 추가 호출자는 슬롯이 확보될 때까지 최대 PoolTimeout만큼 기다립니다. 서버 측 연결 및 쿼리 제한을 초과하지 않으면서 애플리케이션의 실제 동시 작업량을 감당할 수 있는 값을 선택하세요. 이 값을 높여도 개별 쿼리가 빨라지지는 않습니다. 다음과 같이 풀의 연결을 사용하는 작업에 유의하세요.
  • 스트리밍 결과는 열거자가 해제될 때까지 연결을 점유합니다.
  • 세션은 유지되는 동안 연결 하나를 점유하며, 한 번에 하나의 작업만 처리합니다.
  • 결과 처리를 중단하거나, 진행 중인 작업을 취소하거나, 프로토콜 또는 연결에 장애가 발생하면 해당 연결은 재사용되지 않고 닫힙니다.
세션은 사용 후 즉시 해제하고, 비동기 결과는 항상 끝까지 열거하거나 열거자를 해제하세요. 풀 고갈, 예기치 않은 연결 종료, 빈번한 재연결을 진단하려면 ClickHouse.Driver.Tcp.Pool 로깅 범주를 사용하세요.

타임아웃과 취소

TCP 클라이언트는 대기 상황에 따라 서로 다른 시간 제한을 적용합니다. 풀 슬롯을 기다렸다가 새 연결을 여는 데는 최대 PoolTimeout + DialTimeout이 걸릴 수 있습니다. ReadTimeout은 전송 계층에서 읽을 때마다 다시 시작되므로, 진행 상황이나 데이터를 계속 보내는 장기 실행 쿼리는 이 시간보다 오래 실행될 수 있습니다. 정상적인 작업에서도 서버가 응답하지 않을 수 있는 시간을 고려해 충분히 길게 설정하세요. 다른 타임아웃으로 작업 시간이 확실히 제한되는 경우에만 TimeSpan.Zero로 설정하세요. 작업 시작부터 끝까지 타임아웃을 적용하려면 취소 토큰을 사용하세요.
클라이언트는 안전하게 전송할 수 있는 경우 네이티브 Cancel 패킷을 전송한 다음, 응답을 끝까지 읽지 못한 연결을 닫습니다. 이후에도 풀에 있는 클라이언트는 사용할 수 있지만, 취소로 인해 고정된 세션은 사용할 수 없게 될 수 있습니다. 취소되거나 시간 초과된 삽입은 처리 결과를 알 수 없으므로, 무작정 다시 실행하지 말고 삽입 재시도 지침을 따르세요.

날짜 및 시간 처리

타임스탬프에는 DateTime('UTC') 또는 DateTime64(S, 'UTC')를 사용하고, 애플리케이션 코드에서는 DateTimeOffset 또는 Kind가 Utc인 DateTime을 사용하세요. 이렇게 하면 나타내는 시점이 명확해지고 서버나 세션 시간대에 의존하지 않습니다. TCP 클라이언트는 DateTime 값을 기록할 때 다음 규칙을 적용합니다. 일광 절약 시간 전환으로 건너뛰어 실제로 존재하지 않는 시각은 해당하는 시점이 없으므로 거부됩니다. 시계를 되돌릴 때 중복되는 시각은 먼저 발생한 시점을 선택합니다. 어느 시점인지 명시해야 한다면 DateTimeOffset을 사용하세요. 특정 시점을 나타내는 SQL 매개변수는 플레이스홀더에 시간대를 포함하세요.
열 지향 읽기에서는 DateTime과 DateTime64가 기본적으로 원시 uint 또는 long 값을 반환합니다. 날짜와 시간으로 표현된 값이 더 편리하다면 Block.ReadAs<DateTimeOffset>, IDateTimeColumn.GetDateTimeOffset 또는 해당하는 POCO 속성을 사용하세요. 모든 자릿수를 보존해야 한다면 원시 값을 그대로 사용하세요. DateTimeOffset의 해상도는 100나노초이므로 소수점 자릿수가 8 또는 9인 DateTime64를 이를 통해 읽으면 틱보다 작은 단위의 정밀도가 손실됩니다. Date와 Date32에는 DateOnly를 사용하세요. 음수이거나 24시간을 초과할 수 있는 Time과 Time64 값에는 TimeSpan을 사용하세요. TimeOnly는 컬럼 값이 하루 중 시각으로 제한된 경우에만 적합합니다.

스트리밍과 결과의 수명

고처리량 작업에는 StreamAsync와 타입이 지정된 컬럼을 사용하세요. 이는 ClickHouse의 열 지향 저장 구조에 맞으며 행마다 배열을 생성하거나 박싱 및 POCO 할당을 수행하지 않아도 됩니다. 메모리 할당을 최소화하는 것보다 애플리케이션이 소유하는 모델이 중요하다면 QueryAsync<T>를 사용하고, 컴파일 시점에 스키마를 알 수 없을 때만 타입이 지정되지 않은 QueryAsync를 사용하세요. StreamAsync가 반환하는 블록은 임시로 빌려 쓰는 데이터입니다. 블록과 그 컬럼, 복합 하위 컬럼, 모든 스팬은 현재 루프 반복에서만 유효합니다. 해당 반복 중에 바로 처리하거나 반복이 끝난 뒤에도 필요한 값은 복사하세요:
반환된 블록을 직접 해제하지 마십시오. 열거자 해제는 await foreach에 맡기십시오. 결과의 일부만 필요하다면 SQL에서 LIMIT로 결과를 제한하고 필요한 컬럼만 선택하십시오. 반복을 도중에 중단하면 남은 응답이 취소되어 해당 연결을 재사용할 수 없게 됩니다.

삽입 형태와 배칭

데이터가 이미 열 지향 형태라면 타입이 지정된 컬럼과 함께 InsertAsync를 사용하는 것이 좋습니다. 이 방식은 클라이언트 측 변환이 가장 적습니다. POCO 데이터에는 InsertRowsAsync<T>가 가장 적합합니다. 타입이 지정되지 않은 object[] 행은 가장 유연하지만 배열을 할당하고 값 타입을 박싱합니다. 작은 삽입을 여러 번 실행하기보다 삽입 호출 한 번에 적절한 수의 행을 전송하세요. 호출 한 번에서 MaxRowsPerBlock은 데이터를 네이티브 wire 블록으로 나눕니다. 기본값인 50,000행은 행과 POCO 입력을 변환할 때 사용하는 임시 컬럼 버퍼의 크기도 제한합니다. 이 값을 늘리면 폭이 좁은 행의 처리량이 개선될 수 있지만 최대 메모리 사용량도 증가합니다. null로 설정하면 전체 삽입을 하나의 블록으로 기록하며, 해당 블록의 행 지향 입력 전체를 한꺼번에 변환합니다. MaxSendBufferBytes는 인코딩된 데이터가 소켓으로 플러시되기 전까지 버퍼에 저장되는 양을 별도로 제한합니다. 컬럼 간에 확인하는 유연한 상한이므로 폭이 넓은 컬럼 하나만으로도 이 값을 초과할 수 있습니다. 행과 POCO 입력은 블록 단위로 변환됩니다. 따라서 앞선 블록이 이미 전송된 뒤에야 뒤쪽 블록에서 잘못된 값이 발견될 수 있습니다. 데이터 품질이 불확실하다면 삽입 전에 NULL 허용 여부와 CLR 타입을 검사하고, 동일한 논리적 배치를 재시도할 가능성이 있다면 데이터에서 파생한 DeduplicationToken을 설정하세요.

지원되는 데이터 타입

아래 표는 디코딩된 IColumn<T>가 반환하는 타입과 Block.ReadAs<T>를 통한 변환, QueryAsync<T>의 POCO 매핑 또는 삽입을 통해 사용할 수 있는 추가 타입을 구분합니다. 특정 매핑의 지원 여부는 이러한 작업에 사용되는 리졸버를 통해 확인할 수 있습니다.
Nullable, Array, Tuple, Map, LowCardinality의 매핑은 각 타입의 내부 타입에도 재귀적으로 적용됩니다.

타입 매핑: ClickHouse에서 읽기

Default .NET type은 Block.Column<T>와 IColumn<T>에서 제공하는 T입니다. Also readable as 컬럼에 나열된 타입은 Block.ReadAs<T>와 POCO 속성을 통해서도 읽을 수 있습니다.

정수 타입

부동 소수점 타입

Decimal 타입

ClickHouseTcpDecimal은 크기 제한이 없는 정수 가수와 소수 자릿수를 저장하므로 decimal이 표현할 수 있는 정밀도를 초과하는 값도 보존합니다.

불리언 타입

문자열 타입

데이터가 유효한 UTF-8이 아닐 수 있다면 IStringColumn.GetBytes(row)를 사용하거나 byte[]로 읽으세요. FixedString(N)은 끝에 붙은 0바이트를 포함해 N바이트를 모두 보존합니다.

날짜 및 시간 타입

열 지향으로 읽을 때 정확한 wire 값을 유지하기 위해 원시 정수 표현을 기본값으로 사용합니다. 날짜와 시간으로 변환할 때는 타입에 선언된 시간대를 사용하며, 선언된 시간대가 없으면 세션 시간대를 사용합니다. Time 또는 Time64를 TimeOnly로 읽을 때 값이 음수이거나 24시간 이상이면 예외가 발생합니다. ClickHouse의 전체 값 범위를 처리하려면 TimeSpan을 사용하세요.

Enum 타입

IEnumColumn에서는 선언된 레이블과 서수 값의 대응 관계도 확인할 수 있습니다.

기타 스칼라 타입

복합 타입

튜플에는 최대 7개의 원소를 담을 수 있습니다. Nested 컬럼은 필드 수에 관계없이 INestedColumn을 통해 읽는 것이 가장 좋습니다. INestedColumn은 이름이 지정된 개별 필드 컬럼과 공유 행 오프셋을 제공하므로 객체로 래핑된 레코드를 할당할 필요가 없습니다. ClickHouse의 기본 설정인 flatten_nested = 1에서는 중첩 필드가 점으로 구분된 별도의 Array(T) 컬럼으로 전달됩니다.

Variant, Dynamic 및 JSON 타입

Variant와 Dynamic은 각 행에서 선택된 타입에 해당하는 CLR 값이나 null을 반환합니다. 두 타입의 열 지향 뷰에서는 판별자 스트림과 각 런타임 타입에 대응하는 타입이 지정된 하위 컬럼에 접근할 수 있어, 컬럼 단위로 데이터를 처리할 때 박싱을 피할 수 있습니다. TCP 클라이언트는 ClickHouse의 String 직렬화 방식으로 JSON을 읽습니다. 클라이언트는 기본적으로 output_format_native_write_json_as_string = 1을 활성화하며, 이 설정을 비활성화하면 JSON 읽기가 지원되지 않습니다. ClickHouse가 JSON을 파싱하고 정규화하기 때문에 반환된 텍스트는 삽입된 텍스트와 키 순서, 공백 및 숫자 표기 방식이 다를 수 있습니다.

Geometry 타입

Geometry는 6가지 Geometry 타입을 담는 IVariantColumn로 제공됩니다.

QBit 타입

TCP 클라이언트는 스트라이드가 없는 인수 2개짜리 QBit(T, N) 형식을 지원합니다. 스트라이드가 있는 인수 3개짜리 형식은 지원하지 않습니다.

AggregateFunction 타입

AggregateFunction 컬럼에는 함수별 중간 상태가 저장됩니다. sumMerge(column) 등을 사용해 서버에서 최종 값을 계산한 후, 중간 상태 대신 해당 값을 읽으세요.

타입 매핑: ClickHouse에 쓰기

각 엔트리는 InsertAsync에 전달된 IColumn의 한 행에 해당하는 CLR 유형입니다. 매핑은 엄격하게 적용되며, TCP 클라이언트는 Convert와 같은 방식으로 형식을 변환하지 않습니다.

정수 타입

부동 소수점 타입

BFloat16에 값을 쓸 때는 각 float 값이 16비트 브레인 부동 소수점 형식으로 변환되면서 하위 비트가 잘립니다.

Decimal 타입

스케일이 적용된 가수가 선언된 정밀도를 초과하면 쓰기 작업에서 OverflowException이 발생합니다.

불리언 타입

문자열 타입

FixedString(N)은 의도적으로 string을 허용하지 않습니다. 정확한 이진 표현이 명확해지도록 값을 명시적으로 인코딩하고 필요한 길이만큼 채우세요.

날짜 및 시간 타입

정수 값을 그대로 사용할 때는 읽기 테이블에 표시된 단위가 적용됩니다. DateTimeOffset은 절대 시점을 유지합니다. DateTime은 Kind 값에 따라 처리됩니다. Unspecified 값은 컬럼에 지정된 시간대의 현지 시각으로 해석되며, 컬럼에 시간대가 지정되지 않았다면 세션 시간대의 현지 시각으로 해석됩니다.

Enum 타입

정의되지 않은 레이블은 데이터 전송 전에 거부됩니다.

기타 스칼라 타입

복합 타입

허용되는 데이터 구조는 재귀적이므로 Array(Nullable(DateTime))은 각 행에서 DateTime?[]을 허용하고, Map(String, UInt32)는 KeyValuePair<string, uint>[]을 허용합니다. Nested에 직접 데이터를 쓰려면 이전 읽기 작업에서 반환된 밀집 컬럼 구조를 사용합니다. 애플리케이션에서 생성하는 삽입 데이터에는 기본 flatten_nested = 1 표현을 유지하고, 점으로 구분된 각 Array(T) 필드를 별도의 컬럼으로 기록하십시오.

Variant, Dynamic 및 JSON 타입

Variant에서는 런타임 타입이 대체 타입 중 정확히 하나의 기본 CLR 타입과 일치해야 합니다. DateTime을 DateTime 대체 타입에 쓰는 것과 같은 편의 매핑은 Variant 내부에 적용되지 않습니다. 대신 해당 대체 타입의 기본 원시 타입을 사용하세요. Dynamic은 스칼라 값과 재귀적으로 지원되는 배열, 맵, 튜플 값을 지원합니다.

Geometry 타입

애플리케이션에서 생성한 Geometry 컬럼에서는 Point와 MultiPolygon만 서로 구별되는 CLR 형태를 가집니다. Ring과 LineString은 CLR 형태가 같고 Polygon과 MultiLineString도 마찬가지이므로, 클라이언트는 object 값이 어느 타입을 나타내는지 추론할 수 없습니다. 디코딩된 Geometry 컬럼에는 판별자가 그대로 남아 있어 이러한 모호성 없이 다시 삽입할 수 있습니다.

QBit 타입

AggregateFunction 타입

압축

클라이언트는 기본적으로 압축을 사용하며, 기록하는 블록에는 LZ4를 적용합니다. ClickHouse도 응답 블록에 기본적으로 LZ4를 사용합니다. 압축은 결과 블록과 삽입 시 업로드되는 블록에 적용되지만, 쿼리 텍스트, 진행 상태, 예외 및 기타 프로토콜 패킷은 압축되지 않고 일반적인 wire 인코딩을 유지합니다. 클라이언트가 사용할 코덱은 연결 문자열의 Compression 키로 선택하세요:
동등한 기능을 하는 강타입 구성에서는 IClickHouseCompressor를 사용합니다:
압축을 비활성화하려면 Compressor = null로 설정하세요. 사용자 정의 압축기는 네이티브 블록 프레이밍을 지원해야 합니다. HTTP 본문만 지원하는 압축기를 사용하면 클라이언트를 생성할 때 오류가 발생합니다.

응답 코덱 선택

Compressor는 클라이언트가 기록하는 블록의 압축 방식을 결정합니다. 네이티브 쿼리 패킷은 서버에 압축을 요청하지만 코덱은 지정하지 않습니다. 따라서 ClickHouse는 network_compression_method 설정에 따라 응답 코덱을 선택합니다. 서버의 기본 코덱은 LZ4입니다. 필요에 따라 작업별로 응답 코덱을 설정하세요:
LZ4는 일반적으로 CPU 사용량이 적어 고속 네트워크에서 기본 옵션으로 가장 적합합니다. ZSTD는 일반적으로 CPU를 약간 더 사용하지만 압축 결과의 크기가 더 작습니다. 서버가 같은 호스트에 있거나 연결 속도가 매우 빠르다면 압축을 비활성화하는 것이 유리할 수 있습니다. 기본값을 변경하기 전에 실제 사용 환경을 반영한 데이터로 성능을 측정하세요.

오류 처리

ClickHouse 또는 네이티브 연결에서 발생한 오류는 ClickHouseTcpException을 상속하며, ClickHouseTcpException은 다시 DbException을 상속합니다. ClickHouseTcpServerException에서는 서버가 보낸 정확한 숫자 코드인 RawCode와 드라이버에 해당 코드의 이름이 정의되어 있다면 그에 대응하는 ClickHouseErrorCode인 Code를 확인할 수 있습니다. 또한 서버 예외의 Name과 ServerStackTrace, 중첩된 서버 예외도 InnerException을 통해 확인할 수 있습니다.
ClickHouseErrorCode에 정의되지 않은 코드는 Code == ClickHouseErrorCode.Unknown으로 표시됩니다. 열거형에 모든 ClickHouse 오류가 정의되어 있다고 가정하지 말고 RawCode를 확인하세요. 인수 유효성 검사 및 객체 수명 주기 관련 오류에는 일반적인 .NET 예외 타입이 그대로 사용됩니다. 취소하면 OperationCanceledException이 발생합니다. DialTimeout과 ReadTimeout이 발생하면 내부 예외로 TimeoutException을 포함하는 ClickHouseTcpConnectionException이 발생하고, 해당 연결은 폐기됩니다. PoolTimeout이 발생하면 ClickHouseTcpException이 아닌 일반 TimeoutException이 발생합니다. 연결 자체에 문제가 생긴 것은 아니므로 동시 작업 수를 줄이거나 풀 크기를 늘리거나 결과 열거를 완료해야 합니다.

작업 재시도

삽입 중 연결이 끊기거나 타임아웃이 발생하면 삽입 결과를 알 수 없습니다. 클라이언트가 응답을 받지 못하기 전에 서버가 이미 행을 커밋했을 수 있습니다. 재시도하기 전에 해당 논리적 배치에 사용할 DeduplicationToken을 재시도 시에도 동일하게 유지되도록 설정하세요:
해당 배치를 재시도할 때는 같은 토큰을 사용하고, 새 데이터에는 다른 토큰을 사용하십시오. 다른 작업은 SQL 문을 다시 실행해도 안전하고 해당 오류가 일시적일 때만 재시도하십시오. 대부분의 서버 오류는 재시도하기보다 쿼리나 구성을 변경해야 해결할 수 있습니다.

로깅 및 진단

TCP 클라이언트는 Microsoft.Extensions.Logging과 연동됩니다. 로깅은 선택 사항이며, LoggerFactory가 없으면 클라이언트는 로거를 생성하지 않고 로그 메시지의 포맷도 지정하지 않습니다.
클라이언트를 의존성 주입 확장 기능을 통해 등록하면, 전달된 옵션에 로거 팩토리가 이미 지정되어 있지 않은 한 컨테이너의 ILoggerFactory를 사용합니다.

로깅 범주

표준 .NET 로깅 구성에서 범주별 로그 수준을 설정하세요.
작업 시작 메시지는 Debug 수준으로 기록되며, SQL 문을 최대 StatementMaxLength자까지 포함합니다. 기본값은 5자입니다. 로그에서 SQL 문을 제외하려면 이 값을 0 이하로 설정하십시오. 값을 늘릴 때는 먼저 매개변수와 리터럴에 민감한 데이터가 포함될 가능성을 고려하십시오.

서버 로그 메시지

로깅 통합은 클라이언트 측 동작을 보고합니다. ClickHouse 자체의 쿼리 로그 패킷은 이와 별개로 ClickHouseTcpQueryCallbacks.OnLog에 전달됩니다. 이 패킷을 활성화하려면 send_logs_level 서버 설정을 사용하세요:
로그 블록은 일시적으로 빌려 쓰는 것으로, 콜백이 반환되면 해제됩니다. 동기적으로 처리하거나 보관해야 할 값은 별도로 복사해 두세요. 콜백은 신속하게 실행하고 예외가 발생하지 않도록 하세요. 콜백에서 예외가 발생하면 해당 작업과 연결이 종료됩니다.

OpenTelemetry

TCP 클라이언트는 .NET의 System.Diagnostics.Activity API를 통해 분산 추적 스팬을 내보냅니다. OpenTelemetry 트레이서 프로바이더에서 TCP 활동 소스를 구독하세요:
콘솔 애플리케이션을 사용하거나 수동으로 설정하는 경우:
활동 소스 이름은 ClickHouse.Driver.Tcp로, HTTP 클라이언트의 소스와 별개이므로 각 전송 방식의 활동을 독립적으로 수집할 수 있습니다. 구독하는 리스너가 없으면 작업 시 활동이 생성되지 않습니다.

스팬과 속성

클라이언트는 SQL 작업, 핑, 새 연결에 대한 스팬을 생성합니다. SQL 스팬의 이름에는 SELECT나 INSERT처럼 SQL 문의 첫 키워드가 사용됩니다. 첫 키워드를 인식할 수 없는 SQL 문에는 query가 사용됩니다. connect 스팬은 소켓 연결, TLS 협상, 네이티브 핸드셰이크를 포함합니다. SQL 문 스팬에는 연결 풀에서 연결을 기다린 시간도 포함됩니다. 서버나 클라이언트에 보고할 만한 값이 있을 때만 카운터가 포함됩니다. 예를 들어, 데이터 블록을 업로드하는 삽입 작업에서는 클라이언트가 전송한 행 수는 보고되지만, 서버의 읽기 카운터는 보고되지 않습니다. 실패한 스팬에는 예외 유형, 메시지, 스택 추적이 담긴 exception 이벤트도 포함됩니다.

SQL 텍스트와 민감한 데이터

기본적으로 스팬에는 SQL 텍스트가 포함되지 않습니다. SQL 텍스트를 명시적으로 포함하도록 설정하고 적절한 길이 제한을 지정하세요.
StatementMaxLength는 db.query.text 속성과 디버그 로그에 기록되는 SQL의 길이를 모두 제한합니다. 값이 0 이하면 두 곳 모두에서 해당 내용을 생략합니다. 서버 오류에 쿼리 세부 정보가 포함될 수 있으므로 내보낸 예외 메시지와 스택 트레이스도 검토하세요.

추적 컨텍스트 전파

Activity.Current가 W3C ID 포맷을 사용하면 클라이언트는 트레이스 ID, 스팬 ID, 트레이스 플래그, 트레이스 상태를 네이티브 쿼리 패킷에 기록합니다. 그러면 ClickHouse는 동일한 분산 트레이스에 서버 스팬을 생성할 수 있습니다. 서버 측 스팬 수집과 샘플링은 ClickHouse 구성에 따라 결정됩니다. 예를 들어 샘플링 확률을 쿼리별로 설정할 수 있습니다:
전파된 쿼리 ID를 트레이스 ID와 함께 사용하면 클라이언트 스팬을 system.query_log와 연관시킬 수 있으며, ClickHouse의 system.opentelemetry_span_log가 구성되어 있다면 이 로그와도 연관시킬 수 있습니다.

제한 사항

TCP 클라이언트는 아직 실험 단계이므로 HTTP 클라이언트의 모든 기능을 지원하지는 않습니다.

HTTP에서만 지원되는 기능

TCP API는 ADO.NET 공급자가 아니므로 ORM에서 사용할 수 없습니다. 또한 CSV, JSONEachRow, Parquet 또는 원시 스트림의 입력과 출력, bearer 인증 또는 사용자 정의 HTTP 헤더, 사용자 정의 매개변수 유형 결정·서식 지정·읽기 변환, 쿼리별 데이터베이스 또는 역할을 지원하지 않습니다. 애플리케이션에 이러한 기능이 필요하다면 HTTP 클라이언트를 사용하세요.

SQL 매개변수

ClickHouse 네이티브 {name:Type} 플레이스홀더만 지원합니다. TCP 클라이언트는 ADO.NET 방식의 @name 플레이스홀더를 재작성하지 않습니다. ClickHouse 25.8~26.6 버전에서는 limit나 offset처럼 서버 설정과 이름이 같은 매개변수가 설정으로 해석되어 거부될 수 있습니다. 해당 버전을 지원해야 한다면 매개변수 이름을 row_limit 등으로 변경하세요.

데이터 타입 지원 범위

지원되는 데이터 타입 섹션에서 전체 매핑을 설명합니다. 현재 네이티브 코덱에는 다음과 같은 주요 제약이 있습니다.
  • 튜플에는 최대 7개의 원소를 담을 수 있습니다.
  • AggregateFunction(function, ...)의 중간 상태는 지원되지 않습니다. 서버에서 sumMerge(column) 등을 사용해 최종 값을 계산하고 그 결과를 읽으세요.
  • 인수가 2개이고 스트라이드를 사용하지 않는 QBit(T, N) 레이아웃만 지원됩니다.
  • JSON은 ClickHouse의 String 직렬화를 사용하므로 output_format_native_write_json_as_string = 1이 필요합니다. 클라이언트는 기본적으로 이 설정을 요청합니다.
  • 애플리케이션에서 구성한 삽입 데이터는 flatten_nested = 0일 때 Nested(...)에 대응하는 행 지향 형태를 지원하지 않습니다. 대신 기본값인 평탄화된 점 표기법의 Array(T) 컬럼을 사용하세요.
  • 애플리케이션에서 구성한 Geometry 컬럼은 Ring과 LineString처럼 CLR 형태가 같은 기하 도형 타입을 구별해 추론할 수 없습니다. 구체적인 기하 도형 타입으로 삽입하거나 판별자가 유지된 디코딩된 Geometry 컬럼을 다시 삽입하세요.

자동 재시도

클라이언트는 실패한 작업을 자동으로 재시도하지 않습니다. 다시 실행해도 안전한 작업만 재시도하고, 삽입 성공 여부를 알 수 없다면 재시도하기 전에 재시도 간 동일한 DeduplicationToken을 설정하세요. 자세한 내용은 작업 재시도를 참조하세요.
마지막 수정일 2026년 10월 14일