Arrow Flight 인터페이스
Arrow Flight 인터페이스 (Arrow Flight interface)
ClickHouse는 Apache Arrow Flight 프로토콜을 지원해요. 이는 gRPC 위에서 Arrow IPC 포맷을 사용해 효율적인 컬럼 지향 데이터 전송을 하는 고성능 RPC 프레임워크예요. 여기서 서버 설정, 인증, 세션 관리, RPC 메서드를 설명해 드릴게요.
출처: 문서
본문
개요 (Overview)
ClickHouse는 Apache Arrow Flight 프로토콜을 지원해요. 이는 gRPC 위에서 Arrow IPC 포맷을 사용해 효율적인 컬럼 지향 데이터 전송을 하는 고성능 RPC 프레임워크예요.
구현은 Arrow Flight SQL 지원을 포함해, Flight SQL 프로토콜을 말하는 BI 도구와 애플리케이션이 ClickHouse를 직접 쿼리할 수 있게 해 줘요.
주요 기능:
- SQL 쿼리를 실행하고 Apache Arrow 포맷으로 결과를 가져오기.
- Arrow 포맷을 사용해 테이블에 데이터 삽입.
- Flight SQL 명령으로 메타데이터(카탈로그, 스키마, 테이블, 기본 키) 조회.
- Flight SQL을 통한 서버 측 준비된 문(prepared statements) 생성, 바인드, 실행, 종료.
- Flight SQL 액션을 통한 세션 및 설정 관리.
- TLS 암호화 및 사용자 이름/비밀번호 인증.
PollFlightInfo를 통한 증분 결과 검색.CancelFlightInfo를 통한 쿼리 취소.
Arrow Flight 서버 활성화 (Enabling the Arrow Flight Server)
Arrow Flight 서버를 활성화하려면 ClickHouse 서버 설정에 arrowflight_port 설정을 추가해요:
<clickhouse>
<arrowflight_port>9090</arrowflight_port>
</clickhouse>
시작 시 로그 메시지가 인터페이스 활성화를 확인해 줘요:
{} <Information> Application: Arrow Flight compatibility protocol: 0.0.0.0:9090
TLS 설정 (TLS configuration)
Arrow Flight 인터페이스에 TLS를 활성화하려면 다음 설정을 구성해요:
<clickhouse>
<arrowflight_port>9090</arrowflight_port>
<arrowflight>
<enable_ssl>true</enable_ssl>
<ssl_cert_file>/path/to/server-cert.pem</ssl_cert_file>
<ssl_key_file>/path/to/server-key.pem</ssl_key_file>
</arrowflight>
</clickhouse>
TLS가 활성화되면 클라이언트는 grpc:// 대신 grpc+tls:// 스킴으로 연결해야 해요.
인증 (Authentication)
Arrow Flight 인터페이스는 두 가지 인증 방법을 지원해요:
기본 인증 (Basic Authentication)
클라이언트는 표준 HTTP Authorization: *** 헤더를 통해 사용자 이름과 비밀번호로 인증해요. 인증에 성공하면 서버는 응답 헤더에 Bearer 토큰을 반환해요.
Bearer 토큰 인증 (Bearer Token Authentication)
후속 요청은 기본 인증에서 반환된 Bearer 토큰을 Authorization: Bearer *** 헤더를 통해 사용할 수 있어요. 토큰은 사용할 때마다 자동으로 갱신되며 default_session_timeout 서버 설정(기본값: 60초)에 따라 만료돼요.
Python 예시 (Python Example)
import pyarrow.flight as flight
client = flight.FlightClient("grpc://localhost:9090")
# Basic auth returns a bearer token for subsequent calls
token_pair = client.authenticate_basic_token("default", "")
options = flight.FlightCallOptions(headers=[token_pair])
TLS 사용 시:
import pyarrow.flight as flight
with open("ca-cert.pem", "rb") as f:
tls_root_certs = f.read()
client = flight.FlightClient(
"grpc+tls://localhost:9090",
tls_root_certs=tls_root_certs,
)
token_pair = client.authenticate_basic_token("default", "password")
options = flight.FlightCallOptions(headers=[token_pair])
세션 관리 (Session Management)
Arrow Flight 인터페이스는 사용자 정의 gRPC 메타데이터 헤더를 통해 ClickHouse 세션을 지원해요:
| 헤더 | 설명 |
|---|---|
x-clickhouse-session-id |
세션 식별자. 제공되면 여러 요청이 같은 세션 상태(임시 테이블, 설정)를 공유해요. |
x-clickhouse-session-timeout |
세션 타임아웃(초). max_session_timeout을 초과하면 안 돼요. |
x-clickhouse-session-check |
세션을 만들지 않고 존재 여부만 확인하려면 1로 설정. |
x-clickhouse-session-close |
요청 완료 후 세션을 닫으려면 1로 설정. 서버 설정에서 enable_arrow_close_session가 true여야 해요. |
참고: Arrow Flight는 HTTP/2 위의 gRPC를 사용하므로 메타데이터 헤더 이름은 대소문자를 구분하며 정확히 보여준 대로 소문자로 지정해야 해요(예: X-ClickHouse-Session-Id가 아니라 x-clickhouse-session-id). 이는 HTTP/2 필드 이름이 소문자만 포함해야 한다고 정하는 RFC 9113, Section 8.2에 따른 요구사항이에요. 이는 헤더 이름이 대소문자를 구분하지 않는 HTTP/1.1과 다르죠.
세션을 사용하면 SetSessionOptions 액션(아래 DoAction 참조)을 통해 영구적인 ClickHouse 설정을 지정할 수 있어요.
서버 설정 레퍼런스 (Server Configuration Reference)
| 설정 | 기본값 | 설명 |
|---|---|---|
arrowflight_port |
— | Arrow Flight 서버 포트. 이 설정이 지정된 경우에만 서버가 시작해요. |
arrowflight.enable_ssl |
false |
TLS 암호화 활성화. |
arrowflight.ssl_cert_file |
— | TLS 인증서 파일 경로. TLS 활성화 시 필수. |
arrowflight.ssl_key_file |
— | TLS 개인 키 파일 경로. TLS 활성화 시 필수. |
arrowflight.tickets_lifetime_seconds |
600 |
flight 티켓이 만료되고 정리되기까지의 시간(초). 0으로 설정하면 자동 티켓 만료를 비활성화. |
arrowflight.cancel_ticket_after_do_get |
false |
true면 티켓이 DoGet에 소비된 직후 취소되어 메모리를 확보. |
arrowflight.poll_descriptors_lifetime_seconds |
600 |
poll 디스크립터가 만료되기까지의 시간(초). 0으로 설정하면 자동 만료를 비활성화. |
arrowflight.cancel_flight_descriptor_after_poll_flight_info |
false |
true면 poll 디스크립터가 PollFlightInfo에 소비된 후 취소. |
arrowflight.max_prepared_statements_per_user |
100 |
사용자당 열 수 있는 준비된 문의 최대 수. 0으로 설정하면 제한 비활성화. |
arrowflight.prepared_statements_lifetime_seconds |
-1 |
준비된 문 수명 모드. > 0: 이 값을 수명으로 사용하고 세션 바인딩 및 세션 없는 문 모두에 대해 각 요청마다 만료를 갱신. 0: 자동 만료 비활성화. -1: 세션 바인딩 문의 경우 세션 타임아웃을 수명으로 사용하고 각 요청마다 갱신하며, 세션 없는 문은 자동으로 만료되지 않음. |
enable_arrow_close_session |
true |
클라이언트가 x-clickhouse-session-close 헤더로 세션을 닫을 수 있게 허용. |
default_session_timeout |
60 |
기본 세션 타임아웃(초). Bearer 토큰 만료도 제어. |
max_session_timeout |
3600 |
허용되는 최대 세션 타임아웃(초). |
지원되는 RPC 메서드 (Supported RPC Methods)
GetFlightInfo
쿼리를 실행하고 결과 스키마, 데이터 검색용 티켓이 있는 엔드포인트, 행 수, 바이트 수를 포함한 FlightInfo를 반환해요.
다음 중 하나일 수 있는 FlightDescriptor를 받아요:
- PATH 디스크립터: 테이블 이름으로 해석되는 단일 구성 요소 경로.
SELECT * FROM <table>을 생성해요. - CMD 디스크립터: 원시 SQL 쿼리 문자열 또는 직렬화된 Flight SQL protobuf 명령(아래 Flight SQL 명령 참조).
쿼리는 완전히 실행되고 결과는 서버 측 티켓에 저장돼요. 각 데이터 블록은 별도의 엔드포인트/티켓을 생성하므로 클라이언트가 병렬로 데이터를 가져올 수 있어요.
# Query by table name
descriptor = flight.FlightDescriptor.for_path("my_table")
info = client.get_flight_info(descriptor, options)
# Query by SQL
descriptor = flight.FlightDescriptor.for_command(
"SELECT * FROM my_table WHERE id > 100"
)
info = client.get_flight_info(descriptor, options)
# Retrieve results
for endpoint in info.endpoints:
reader = client.do_get(endpoint.ticket, options)
table = reader.read_all()
print(table.to_pandas())
PollFlightInfo
장기 실행 쿼리에 대한 증분 결과 검색을 가능하게 해요. 전체 쿼리가 완료될 때까지 기다리는 것(GetFlightInfo가 그러함) 대신, PollFlightInfo는 결과를 블록 단위로 반환해요.
첫 호출에서 쿼리가 실행되기 시작해요. 응답은 다음을 포함해요:
- 지금까지 사용 가능한 데이터 블록에 대한 엔드포인트가 있는
FlightInfo. - 다음 폴을 위한
FlightDescriptor(더 많은 결과가 예상되는 경우).
반환된 디스크립터로 하는 후속 호출은 추가 블록을 검색해요. 더 이상 데이터가 없으면 응답은 다음 디스크립터를 포함하지 않아요.
참고: 현재 구현은 데이터가 없을 때 즉시 반환하는 대신 데이터 블록이 사용 가능해질 때까지 차단해요.
GetSchema
전체 쿼리를 실행하지 않고 쿼리 결과에 대한 Arrow 스키마를 반환해요. GetFlightInfo와 같은 디스크립터 유형을 받아들여요.
descriptor = flight.FlightDescriptor.for_command(
"SELECT 1 AS x, 'hello' AS y"
)
schema_result = client.get_schema(descriptor, options)
schema = schema_result.schema
print(schema) # x: int32, y: string
DoGet
주어진 티켓에 대한 데이터를 검색해요. 다음 중 하나를 받아들여요:
GetFlightInfo또는PollFlightInfo가 반환한 티켓.- 티켓 값으로서의 원시 SQL 쿼리 문자열.
# Using a ticket from GetFlightInfo
reader = client.do_get(endpoint.ticket, options)
table = reader.read_all()
# Using a raw SQL query as ticket
ticket = flight.Ticket("SELECT number FROM system.numbers LIMIT 10")
reader = client.do_get(ticket, options)
table = reader.read_all()
DoPut
ClickHouse로 데이터를 보내요. FlightDescriptor와 Arrow record batch 스트림을 받아들여요.
테이블 이름으로 삽입 (PATH 디스크립터):
schema = pa.schema([("id", pa.int64()), ("name", pa.string())])
batch = pa.record_batch(
[pa.array([1, 2, 3]), pa.array(["Alice", "Bob", "Charlie"])],
schema=schema,
)
descriptor = flight.FlightDescriptor.for_path("my_table")
writer, _ = client.do_put(descriptor, schema, options)
writer.write_batch(batch)
writer.close()
SQL로 삽입 (CMD 디스크립터):
descriptor = flight.FlightDescriptor.for_command(
"INSERT INTO my_table FORMAT Arrow"
)
writer, _ = client.do_put(descriptor, schema, options)
writer.write_batch(batch)
writer.close()
Flight SQL CommandStatementUpdate로 DDL/DML 실행:
Flight SQL 클라이언트는 DDL/DML 문(CREATE, INSERT, ALTER 등)을 실행하기 위해 CommandStatementUpdate를 사용해요. 응답은 영향받은 행 수를 포함해요.
Flight SQL CommandStatementIngest로 대량 인제스트:
기존 테이블에 대한 추가(append)만 지원돼요(TABLE_NOT_EXIST_OPTION_FAIL + TABLE_EXISTS_OPTION_APPEND). 카탈로그와 임시 테이블은 이 명령에 지원되지 않아요.
transaction_id는 CommandStatementUpdate 또는 CommandStatementIngest에 지원되지 않아요. 제공되면 ClickHouse는 NotImplemented 오류를 반환해요.
참고: 데이터 전송에는 Arrow 포맷만 허용돼요. SQL에서 다른 포맷(예: FORMAT JSON)을 지정하면 오류가 발생해요.
DoAction
명명된 액션을 실행해요. 다음 액션이 지원돼요:
CancelFlightInfo
FlightInfo와 연관된 실행 중인 쿼리를 취소해요. 쿼리 ID는 FlightInfo의 app_metadata 필드에서 추출돼요. 쿼리와 연관된 poll 디스크립터도 취소해요.
# Start a long-running query via PollFlightInfo, then cancel it
cancel_request = flight.CancelFlightInfoRequest(info)
result = client.cancel_flight_info(cancel_request, options)
# result.status is CancelStatus.CANCELLED if successful
SetSessionOptions
현재 세션에 대한 ClickHouse 서버 설정을 설정해요. x-clickhouse-session-id 헤더를 통해 세션 ID가 설정되어 있어야 해요.
지원되는 값 유형: string, boolean, integer, double, string 목록.
설정 이름을 알 수 없으면 INVALID_NAME 오류가 반환돼요. 값을 파싱할 수 없으면 INVALID_VALUE 오류가 반환돼요.
GetSessionOptions
세션의 현재 모든 ClickHouse 설정과 그 값을 반환해요. 설정 이름을 문자열 값에 매핑한 맵을 반환해요(내부적으로 system.settings를 조회).
CreatePreparedStatement
서버 측 준비된 문을 만들고 문 핸들을 반환해요. 요청은 ? 자리 표시자가 있는 SQL 쿼리 텍스트를 담고 있어요.
transaction_id는 이 액션에 지원되지 않아요. 제공되면 ClickHouse는 NotImplemented 오류를 반환해요.
쿼리 문의 경우 응답은 다음을 포함할 수 있어요:
dataset_schema: 결과 세트의 스키마.parameter_schema: 문 파라미터의 스키마.
유효한 쿼리에서 스키마 추론이 실패하면(예: 자리 표시자를 NULL로 바꾸는 것이 그 쿼리에 유효하지 않을 때), ClickHouse는 여전히 준비된 문을 만들고 dataset_schema 없이 핸들을 반환해요.
dataset_schema는 Flight SQL 명세가 의도한 대로 최선의 추측이에요. 명세는 결과 스키마가 파라미터에 따라 달라질 수 있고, 서버가 최선의 추측을 해야 하며, 클라이언트가 스키마가 정확하다고 가정해서는 안 된다고 명시해요. 그것에 의존하지 말고, 문을 실행해 데이터를 설명하는 스키마를 얻으세요. ClickHouse에서 두 가지 이유로 제공되는 것과 다를 수 있어요:
- 추론이 각
?를NULL로 바꾸므로, 결과 열을 결정하는 자리 표시자는 나중에 바인드하는 값이 아니라 그NULL로부터 타입이 정해져요.SELECT ? AS x는Nothing타입의 열을 추론하지만5를 바인드하면UInt8이 제공돼요.SELECT id, name FROM t WHERE id = ?처럼 조건(predicate)에서만 사용되는 자리 표시자는 결과 타입이 테이블에서 오므로 이 문제가 없어요. - Arrow 등가물이 없는 열은
output_format_arrow_unsupported_types에서 Arrow 타입을 가져오며, 모든 호출이 그 호출을 하는 세션에서 그 값을 해석해요. 핸들은 단일 세션이 아니라 사용자에게 속하므로, 나중 호출이 그것을 다르게 해석해utf8이 광고됐는데binary를 제공하거나 그 반대로 할 수 있어요. 준비된 쿼리 자체 안에서 모드를 설정하면 둘 다에 고정돼요.
준비된 문은 단일 세션이 아니라 인증된 사용자가 소유해요. 같은 사용자로 여러 세션을 열면, 그 세션 중 어느 것에서든 같은 문 핸들을 실행, 재바인드, 종료할 수 있어요.
다른 사용자는 자신이 만들지 않은 문 핸들을 실행, 바인드, 종료할 수 없어요.
arrowflight.prepared_statements_lifetime_seconds가 만료 동작을 제어해요:
> 0: 구성된 값을 문 수명으로 사용. 세션 바인딩 및 세션 없는 문 모두에 대해 각 요청마다 만료가 갱신돼요.0: 준비된 문이 자동으로 만료되지 않아요.-1(기본값): 문이 세션에서 만들어지면 그 수명은 세션 타임아웃을 따르고 그 세션의 각 요청마다 갱신돼요. 세션 없이 만들어지면 자동으로 만료되지 않아요.
만료된 문은 제거되고 더 이상 arrowflight.max_prepared_statements_per_user에 포함되지 않아요.
ClosePreparedStatement
요청이 비어 있지 않은 문 핸들을 담고 있을 때 준비된 문을 닫고 연관된 서버 측 리소스를 해제해요.
ClickHouse는 핸들이 비어 있을 때 ClosePreparedStatement로 대량 닫기도 지원해요:
x-clickhouse-session-id가 있으면 그 세션에서 인증된 사용자의 모든 준비된 문을 닫아요.- 세션 ID가 없으면 인증된 사용자의 세션 없는 준비된 문만 닫아요.
준비된 문이 세션에서 만들어지면(x-clickhouse-session-id로) 그 세션이 닫힐 때 자동으로 닫히기도 해요.
Flight SQL 명령 (Flight SQL Commands)
CMD 디스크립터가 직렬화된 Flight SQL protobuf 메시지를 담고 있으면 ClickHouse는 다음 명령을 처리해요:
GetFlightInfo / GetSchema를 통해 지원
| 명령 | 설명 |
|---|---|
CommandStatementQuery |
임의의 SQL 쿼리 실행. transaction_id는 지원되지 않음. |
CommandGetSqlInfo |
서버 메타데이터(이름, 버전, Arrow 버전, 기능) 검색. |
CommandGetCatalogs |
카탈로그 나열. 빈 결과를 반환(ClickHouse는 카탈로그를 사용하지 않음). |
CommandGetDbSchemas |
데이터베이스 나열. 선택적 db_schema_filter_pattern(SQL LIKE 패턴) 지원. |
CommandGetTables |
테이블 나열. 스키마, 테이블 이름, 테이블 유형, 선택적 스키마 포함에 대한 필터 지원. |
CommandGetTableTypes |
테이블 엔진 유형 나열(system.table_engines에서). |
CommandGetPrimaryKeys |
지정된 테이블의 기본 키 열 검색. |
CommandPreparedStatementQuery |
핸들로 준비된 SELECT-스타일 문 실행. |
DoPut를 통해 지원
| 명령 | 설명 |
|---|---|
CommandStatementUpdate |
DDL/DML 문(CREATE, INSERT, ALTER 등) 실행. 영향받은 행 수를 반환. transaction_id는 지원되지 않음. |
CommandStatementIngest |
기존 테이블에 Arrow 데이터 대량 삽입. 추가 모드만 지원. transaction_id는 지원되지 않음. |
CommandPreparedStatementQuery |
DoPut로 보내질 때 준비된 문에 대한 파라미터 값을 바인드한 다음, 문 핸들과 함께 DoPutPreparedStatementResult를 반환. 하나의 파라미터 집합(한 행)만 허용되며, 바인드된 값의 수는 ? 자리 표시자 수와 정확히 일치해야 함. |
CommandPreparedStatementUpdate |
핸들로 준비된 DDL/DML 문을 실행하고 영향받은 행 수를 반환. |
ClickHouse에서 지원되지 않음 (Unsupported in ClickHouse)
이 명령들은 ClickHouse가 제공하지 않는 기능에 매핑되므로 Arrow Flight SQL 인터페이스에서 지원되지 않아요.
| 명령 | 이유 |
|---|---|
CommandGetCrossReference |
ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약을 구현하지 않아 크로스 레퍼런스 메타데이터를 사용할 수 없음. |
CommandGetExportedKeys |
ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약을 구현하지 않아 내보낸 키 메타데이터를 사용할 수 없음. |
CommandGetImportedKeys |
ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약을 구현하지 않아 가져온 키 메타데이터를 사용할 수 없음. |
CommandStatementSubstraitPlan |
ClickHouse는 Substrait 플랜을 지원하지 않음. |
완전한 예시 (Complete Example)
import pyarrow as pa
import pyarrow.flight as flight
# Connect and authenticate
client = flight.FlightClient("grpc://localhost:9090")
token = client.authenticate_basic_token("default", "")
options = flight.FlightCallOptions(headers=[token])
# Insert data using DoPut with a PATH descriptor
schema = pa.schema([("id", pa.uint32()), ("value", pa.string())])
batch = pa.record_batch(
[pa.array([1, 2, 3], type=pa.uint32()), pa.array(["a", "b", "c"])],
schema=schema,
)
descriptor = flight.FlightDescriptor.for_path("test")
writer, _ = client.do_put(descriptor, schema, options)
writer.write_batch(batch)
writer.close()
# Query data using GetFlightInfo + DoGet
descriptor = flight.FlightDescriptor.for_command(
"SELECT * FROM test ORDER BY id"
)
info = client.get_flight_info(descriptor, options)
for endpoint in info.endpoints:
reader = client.do_get(endpoint.ticket, options)
table = reader.read_all()
print(table.to_pandas())
id value
0 1 a
1 2 b
2 3 c
데이터 포맷 (Data Format)
모든 데이터는 Apache Arrow IPC 포맷으로 전송돼요. Arrow 포맷만 지원되며, 다른 ClickHouse 포맷(예: FORMAT JSON, FORMAT CSV)을 지정하면 오류가 발생해요.
ClickHouse 데이터 타입은 직렬화 중에 Arrow 타입으로 매핑돼요. Arrow Flight는 항상 표준(canonical) Arrow 매핑을 사용하며, Arrow와 ArrowStream 출력 포맷과 달리 타입이 어떻게 표현되는지 바꾸는 output_format_arrow_* 설정(output_format_arrow_string_as_string, output_format_arrow_low_cardinality_as_dictionary, output_format_arrow_date_as_uint16, output_format_arrow_fixed_string_as_fixed_byte_array, 딕셔너리 인덱스 설정)을 따르지 않아요. 따라서 같은 쿼리가 Arrow Flight에서는 FORMAT Arrow에서보다 다른 스키마를 생성할 수 있는데, 두 가지 이유 때문이에요(설계상):
- Flight SQL은 메타데이터 응답의 스키마를 고정해요. 예를 들어
CommandGetTables는catalog_name: utf8, db_schema_name: utf8, table_name: utf8 not null, table_type: utf8 not null, table_schema: bytes not null을 반환해야 해요. 세션 설정이 그utf8열을binary로 바꾸게 허용하면 ClickHouse가 모든 Flight SQL 드라이버에 대해 비준수가 되고,table_schema안에 광고하는 테이블별 스키마도 바뀔 거예요. - Flight 클라이언트는 스키마와 데이터를 별도의 호출로 가져와요(
GetFlightInfo또는GetSchema, 그 다음DoGet). 스키마를 바꿀 수 있는 어떤 설정이든, 그 사이에 세션이 바뀌면 광고된 스키마와 전달된 스트림이 어긋나게 하는 경로가 돼요.
유일한 예외는 Arrow 등가물이 전혀 없는 타입이에요. JSON, Dynamic, QBit, AggregateFunction 같은 것들. 고정할 표준 매핑이 없으므로 ClickHouse가 표현을 선택해야 하고, output_format_arrow_unsupported_types로 어느 것을 쓸지 정할 수 있어요:
| 값 | 동작 |
|---|---|
throw |
쿼리가 거부됨. |
text |
행당 하나의 값을 텍스트 형태로, Arrow Utf8 열로 — CAST(col AS String)이 반환하는 것. |
binary (기본값) |
행당 하나의 값을 바이너리 형태로, Arrow Binary 열로 — RowBinary가 사용하는 인코딩. |
AggregateFunction 열은 text 모드에서도 Arrow Binary 열로 유지되는 유일한 타입이에요. 그 텍스트 형태가 유효한 UTF-8이 아닌 원시 집계 상태이기 때문이며, Arrow Utf8 열은 유효한 UTF-8을 담아야 해요. 읽을 수 있는 값을 원한다면 finalizeAggregation을 사용하세요.
같은 이유로 ClickHouse는 Utf8 열에 쓰기 전에 text 값의 유효하지 않은 모든 UTF-8 시퀀스를 U+FFFD(�)로 대체해요. String을 담은 Dynamic은 그 바이트를 그대로 직렬화하고, 그것들은 임의적일 수 있으므로, 이렇게 하지 않으면 그 열이 Arrow 명세를 위반하고 엄격한 클라이언트에 거부될 수 있어요. 이미 유효한 텍스트가 아닌 값만 바뀌어요. 바이트를 정확히 보존해야 하는 곳에는 binary 모드를 사용하세요.
output_format_arrow_string_as_string은 FORMAT Arrow에서도 이 열들에는 절대 적용되지 않아요. 실제 String과 FixedString 열만 다룹니다. 그래서 clickhouse.opaque 열의 Arrow 타입은 항상 어떤 인코딩을 담고 있는지 말해 줘요. 텍스트 형태는 Utf8, 바이너리 형태는 Binary.
이것이 Dynamic에 담긴 집계 상태가 text 모드에서 손실되는 이유예요. AggregateFunction 열은 그렇지 않은데도 말이죠. 그 열은 Dynamic에서 타입이 정해지는데, 이는 그 행들이 무엇을 담고 있는지에 대해 아무것도 말하지 않으며, 어떤 값을 보기 전에 스키마가 고정되므로 상태에 고유한 Binary 열을 줄 수 없어요. 유지하려면 binary 모드를 사용하세요. Variant는 대안들을 나열하므로 그중 하나인 AggregateFunction은 고유한 Binary 자식을 얻고 영향을 받지 않아요.
그런 열은 그 외에는 진짜 Utf8/Binary 열과 구분할 수 없으므로 Arrow 확장 타입으로 선언돼요. 필드 메타데이터에 ARROW:extension:name = clickhouse.opaque와 원래 ClickHouse 타입 이름이 ARROW:extension:metadata에 담겨요. 확장 이름을 인식하지 못하는 클라이언트는 Arrow 명세가 규정하는 대로 일반 저장 타입을 보게 돼요. 중첩 열은 자신의 필드에 태그가 붙어서, Array(JSON)의 자식이 태그를 담고 Map(JSON, ...)의 키도 컨테이너 자체가 아니라 태그를 담아요.
이전의 부울 output_format_arrow_unsupported_types_as_binary는 여전히 동작하며, 0이면 throw, 1이면 binary와 동일해요. output_format_arrow_unsupported_types가 기본값으로 남아 있을 때만 참조돼요.
호환성 (Compatibility)
Arrow Flight 인터페이스는 Arrow Flight 또는 Arrow Flight SQL 프로토콜을 지원하는 어떤 클라이언트나 도구와도 호환돼요. 다음을 포함해요:
- Python (
pyarrow) - Java (
org.apache.arrow.flight) - C++ (
arrow::flight) - Go (
apache/arrow/go) - ADBC (Arrow Database Connectivity) 드라이버
- DBeaver 및 Flight SQL 지원이 있는 다른 도구
도구에 네이티브 ClickHouse 커넥터가 있다면(예: JDBC, ODBC, 네이티브 프로토콜), 성능이나 포맷 호환성을 위해 Arrow Flight가 특별히 필요하지 않는 한 그것을 사용하는 것이 좋아요.
클라이언트 측 ArrowFlight 기능 (Client-side ArrowFlight features)
ClickHouse는 또한 외부 Arrow Flight 서버에서 데이터를 읽기 위한 Flight 클라이언트로도 동작할 수 있어요. 다음을 참고해요: