> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Flight SQL 클라이언트가 ClickHouse에 연결할 수 있도록 지원하는 ClickHouse의 Apache Arrow Flight 인터페이스 문서

# Arrow Flight 인터페이스

<h2 id="overview">
  개요
</h2>

ClickHouse는 [Apache Arrow Flight](https://arrow.apache.org/docs/format/Flight.html) 프로토콜을 지원합니다. 이 프로토콜은 [gRPC](https://grpc.io/)를 통해 [Arrow IPC](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc) 포맷을 사용해 열 지향 데이터를 효율적으로 전송할 수 있는 고성능 RPC 프레임워크입니다.

이 구현에는 [Arrow Flight SQL](https://arrow.apache.org/docs/format/FlightSql.html) 지원도 포함되어 있어, Flight SQL 프로토콜을 사용하는 BI 도구와 애플리케이션이 ClickHouse를 직접 쿼리할 수 있습니다.

주요 기능:

* SQL 쿼리를 실행하고 결과를 Apache Arrow 형식으로 가져옵니다.
* Arrow 형식을 사용해 테이블에 데이터를 삽입합니다.
* Flight SQL 명령을 통해 메타데이터(카탈로그, 스키마, 테이블, 프라이머리 키)를 쿼리합니다.
* Flight SQL을 통해 서버 측 prepared statement를 생성, 바인드, 실행, 종료합니다.
* Flight SQL 작업을 통해 세션 및 설정을 관리합니다.
* TLS 암호화와 사용자 이름/비밀번호 인증을 지원합니다.
* `PollFlightInfo`를 통한 점진적 결과 조회.
* `CancelFlightInfo`를 통한 쿼리 취소.

<h2 id="enabling-server">
  Arrow Flight 서버 활성화
</h2>

Arrow Flight 서버를 활성화하려면 ClickHouse 서버 구성에 `arrowflight_port` 설정을 추가합니다:

```xml theme={null}
<clickhouse>
    <arrowflight_port>9090</arrowflight_port>
</clickhouse>
```

시작되면 로그 메시지로 인터페이스가 활성화되었는지 확인할 수 있습니다:

```text theme={null}
{} <Information> Application: Arrow Flight compatibility protocol: 0.0.0.0:9090
```

<h2 id="tls-configuration">
  TLS 구성
</h2>

Arrow Flight 인터페이스에서 TLS를 활성화하려면 다음 설정을 지정하십시오:

```xml theme={null}
<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://` 스킴으로 연결해야 합니다.

<h2 id="authentication">
  인증
</h2>

Arrow Flight 인터페이스는 두 가지 인증 메서드를 지원합니다:

<h3 id="basic-auth">
  기본 인증
</h3>

클라이언트는 표준 HTTP `Authorization: Basic` 헤더를 사용해 사용자 이름과 비밀번호로 인증합니다. 인증에 성공하면 server는 응답 헤더에 Bearer 토큰을 반환합니다.

<h3 id="bearer-auth">
  Bearer 토큰 인증
</h3>

이후 요청에서는 기본 인증으로 반환된 Bearer 토큰을 `Authorization: Bearer <token>` 헤더를 통해 사용할 수 있습니다. 이 토큰은 사용할 때마다 자동으로 갱신되며, `default_session_timeout` 서버 설정에 따라 만료됩니다(기본값: 60초).

<h3 id="auth-python-example">
  Python 예시
</h3>

```python theme={null}
import pyarrow.flight as flight

client = flight.FlightClient("grpc://localhost:9090")

# 기본 인증은 이후 호출에 사용할 bearer 토큰을 반환합니다
token_pair = client.authenticate_basic_token("default", "")
options = flight.FlightCallOptions(headers=[token_pair])
```

TLS 사용 시:

```python theme={null}
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])
```

<h2 id="session-management">
  세션 관리
</h2>

Arrow Flight 인터페이스는 사용자 지정 gRPC 메타데이터 헤더를 통해 ClickHouse 세션을 지원합니다.

| Header | Description |
| - | - |
| `x-clickhouse-session-id` | 세션 식별자입니다. 지정하면 여러 요청이 동일한 세션 상태(임시 테이블, 설정)를 공유합니다. |
| `x-clickhouse-session-timeout` | 초 단위 세션 timeout입니다. `max_session_timeout`을 초과하면 안 됩니다. |
| `x-clickhouse-session-check` | 세션을 생성하지 않고 존재 여부만 확인하려면 `1`로 설정합니다. |
| `x-clickhouse-session-close` | 요청이 완료된 후 세션을 닫으려면 `1`로 설정합니다. server config에서 `enable_arrow_close_session`이 `true`로 설정되어 있어야 합니다. |

<Note>
  Arrow Flight는 HTTP/2 기반 gRPC를 사용하므로 메타데이터 헤더 이름은 대소문자를 구분하며, 아래에 표시된 그대로 반드시 소문자로 지정해야 합니다(예: `x-clickhouse-session-id`, `X-ClickHouse-Session-Id` 아님). 이는 HTTP/2 필드 이름에 소문자만 포함되어야 한다고 규정한 [RFC 9113, Section 8.2](https://www.rfc-editor.org/rfc/rfc9113#section-8.2)의 요구 사항입니다. 이는 헤더 이름이 대소문자를 구분하지 않는 HTTP/1.1과 다릅니다.
</Note>

세션을 사용하면 `SetSessionOptions` action을 통해 ClickHouse 설정을 지속적으로 적용할 수 있습니다([DoAction](#doaction) 참조).

<h2 id="configuration-reference">
  서버 구성 참고
</h2>

| 설정 | 기본값 | 설명 |
| - | - | - |
| `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` | 폴링 디스크립터가 만료되기까지의 시간(초)입니다. 자동 만료를 비활성화하려면 `0`으로 설정합니다. |
| `arrowflight.cancel_flight_descriptor_after_poll_flight_info` | `false` | `true`이면 폴링 디스크립터가 `PollFlightInfo`에서 사용된 후 취소됩니다. |
| `arrowflight.max_prepared_statements_per_user` | `100` | 사용자당 열어 둘 수 있는 prepared statement의 최대 개수입니다. 제한을 비활성화하려면 `0`으로 설정합니다. |
| `arrowflight.prepared_statements_lifetime_seconds` | `-1` | prepared statement 수명 모드입니다. `> 0`: 이 값을 수명으로 사용하고, 세션에 바인딩된 SQL 문과 세션에 바인딩되지 않은 SQL 문 모두에 대해 각 요청마다 만료 시점을 갱신합니다. `0`: 자동 만료를 비활성화합니다. `-1`: 세션에 바인딩된 SQL 문의 경우 세션 타임아웃을 수명으로 사용하고 각 요청마다 이를 갱신합니다. 세션에 바인딩되지 않은 SQL 문은 자동으로 만료되지 않습니다. |
| `enable_arrow_close_session` | `true` | 클라이언트가 `x-clickhouse-session-close` 헤더를 통해 세션을 종료할 수 있도록 허용합니다. |
| `default_session_timeout` | `60` | 기본 세션 타임아웃(초)입니다. Bearer token 만료에도 적용됩니다. |
| `max_session_timeout` | `3600` | 허용되는 최대 세션 타임아웃(초)입니다. |

<h2 id="rpc-methods">
  지원되는 RPC 메서드
</h2>

<h3 id="getflightinfo">
  GetFlightInfo
</h3>

쿼리를 실행하고 결과 스키마, 데이터 검색에 사용할 티켓이 포함된 엔드포인트, 행 수, 바이트 수를 담은 `FlightInfo`를 반환합니다.

다음 중 하나일 수 있는 `FlightDescriptor`를 받습니다:

* **PATH 디스크립터**: table 이름으로 해석되는 단일 구성 요소 경로입니다. `SELECT * FROM <table>`를 생성합니다.
* **CMD 디스크립터**: 원시 SQL 쿼리 문자열 또는 직렬화된 Flight SQL protobuf 명령입니다([Flight SQL Commands](#flight-sql-commands) 참조).

쿼리는 전체가 실행되며, 결과는 서버 측 티켓에 저장됩니다. 각 데이터 block은 별도의 엔드포인트/티켓을 생성하므로, 클라이언트는 데이터를 병렬로 검색할 수 있습니다.

```python theme={null}
# 테이블 이름으로 쿼리
descriptor = flight.FlightDescriptor.for_path("my_table")
info = client.get_flight_info(descriptor, options)

# SQL로 쿼리
descriptor = flight.FlightDescriptor.for_command(
    "SELECT * FROM my_table WHERE id > 100"
)
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())
```

<h3 id="pollflightinfo">
  PollFlightInfo
</h3>

장시간 실행되는 쿼리의 결과를 점진적으로 가져올 수 있도록 합니다. 전체 쿼리가 완료될 때까지 기다리는 `GetFlightInfo`와 달리, `PollFlightInfo`는 결과를 블록 단위로 반환합니다.

첫 번째 호출 시 쿼리 실행이 시작됩니다. 응답에는 다음이 포함됩니다:

* 현재까지 사용 가능한 데이터 블록의 endpoint가 포함된 `FlightInfo`
* 다음 폴링에 사용할 `FlightDescriptor`(추가 결과가 예상되는 경우)

이후에는 반환된 디스크립터로 추가 호출을 수행해 다음 블록들을 가져옵니다. 더 이상 사용할 수 있는 데이터가 없으면 응답에는 다음 디스크립터가 포함되지 않습니다.

<Note>
  현재 구현은 데이터 블록을 사용할 수 있을 때까지 대기하며, 데이터 없이 즉시 반환하지는 않습니다.
</Note>

<h3 id="getschema">
  GetSchema
</h3>

전체 쿼리를 실행하지 않고 쿼리 결과의 Arrow 스키마를 반환합니다. `GetFlightInfo`와 동일한 디스크립터 유형을 지원합니다.

```python theme={null}
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
```

<h3 id="doget">
  DoGet
</h3>

지정된 티켓에 대한 데이터를 가져옵니다. 다음 중 하나를 받습니다:

* `GetFlightInfo` 또는 `PollFlightInfo`에서 반환된 티켓
* 티켓 값으로 전달되는 원시 SQL 쿼리 문자열

```python theme={null}
# GetFlightInfo에서 반환된 티켓 사용
reader = client.do_get(endpoint.ticket, options)
table = reader.read_all()

# 티켓으로 SQL 쿼리 직접 사용
ticket = flight.Ticket("SELECT number FROM system.numbers LIMIT 10")
reader = client.do_get(ticket, options)
table = reader.read_all()
```

<h3 id="doput">
  DoPut
</h3>

데이터를 ClickHouse로 전송합니다. `FlightDescriptor`와 Arrow 레코드 배치 스트림을 인수로 받습니다.

**테이블 이름으로 삽입** (PATH 디스크립터):

```python theme={null}
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 디스크립터):

```python theme={null}
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 SQL 문(CREATE, INSERT, ALTER 등)을 실행할 때 `CommandStatementUpdate`를 사용합니다. 응답에는 영향을 받은 행 수가 포함됩니다.

**Flight SQL `CommandStatementIngest`를 통한 대량 수집:**

기존 테이블에 데이터를 추가하는 작업만 지원됩니다(`TABLE_NOT_EXIST_OPTION_FAIL` + `TABLE_EXISTS_OPTION_APPEND`). 이 명령에서는 카탈로그와 임시 테이블을 지원하지 않습니다.

`transaction_id`는 `CommandStatementUpdate`와 `CommandStatementIngest` 모두에서 지원되지 않습니다. 이를 제공하면 ClickHouse는 `NotImplemented` 오류를 반환합니다.

<Note>
  데이터 전송에는 `Arrow` 형식만 허용됩니다. SQL에서 다른 포맷을 지정하면(예: `FORMAT JSON`) 오류가 발생합니다.
</Note>

<h3 id="doaction">
  DoAction
</h3>

명명된 actions를 실행합니다. 지원되는 actions는 다음과 같습니다:

<h4 id="cancelflightinfo">
  CancelFlightInfo
</h4>

`FlightInfo`와 연결된 실행 중인 쿼리를 취소합니다. 쿼리 ID는 `FlightInfo`의 `app_metadata` 필드에서 추출됩니다. 또한 해당 쿼리와 연결된 모든 폴링 디스크립터도 취소합니다.

```python theme={null}
# PollFlightInfo를 통해 장기 실행 쿼리를 시작한 후 취소합니다
cancel_request = flight.CancelFlightInfoRequest(info)
result = client.cancel_flight_info(cancel_request, options)
# 성공 시 result.status는 CancelStatus.CANCELLED입니다
```

<h4 id="setsessionoptions">
  SetSessionOptions
</h4>

현재 세션의 ClickHouse 서버 설정을 지정합니다. `x-clickhouse-session-id` 헤더를 통해 세션 ID가 설정되어 있어야 합니다.

지원되는 값 타입: string, boolean, integer, double, string list입니다.

설정 이름을 인식할 수 없으면 오류 `INVALID_NAME`이 반환됩니다. 값을 파싱할 수 없으면 오류 `INVALID_VALUE`가 반환됩니다.

<h4 id="getsessionoptions">
  GetSessionOptions
</h4>

현재 세션의 모든 ClickHouse 설정과 해당 값을 반환합니다. 설정 이름을 문자열 값에 매핑한 맵을 반환합니다(내부적으로 `system.settings`를 쿼리합니다).

<h4 id="createpreparedstatement">
  CreatePreparedStatement
</h4>

서버 측 prepared statement를 생성하고 statement handle을 반환합니다. 요청에는 `?` 플레이스홀더가 포함된 SQL 쿼리 텍스트가 들어 있습니다.

이 작업에서는 `transaction_id`를 지원하지 않습니다. 이를 제공하면 ClickHouse는 `NotImplemented` 오류를 반환합니다.

쿼리 SQL 문의 경우 응답에 다음이 포함될 수 있습니다:

* `dataset_schema`: result set의 스키마
* `parameter_schema`: SQL 문 매개변수의 스키마

유효한 쿼리에서 스키마 추론이 실패하더라도(예를 들어 플레이스홀더를 `NULL`로 대체하는 것이 해당 쿼리에서 유효하지 않은 경우) ClickHouse는 여전히 prepared statement를 생성하고 `dataset_schema` 없이 handle을 반환합니다.

`dataset_schema`는 Flight SQL 명세가 의도한 대로 최선의 추측값일 뿐입니다. 명세는 결과 스키마가 매개변수에 따라 달라질 수 있으므로 서버는 최선의 추측을 제공해야 하며, clients는 해당 스키마가 정확하다고 가정해서는 안 된다고 명시합니다. 이 값에 의존하지 마시고, 데이터를 실제로 설명하는 스키마를 얻으려면 SQL 문을 실행하십시오. ClickHouse에서는 다음 두 가지 이유로 실제 제공되는 스키마와 다를 수 있습니다:

* 추론 시 각 `?`를 `NULL`로 대체하므로, 결과 컬럼을 결정하는 플레이스홀더는 이후에 바인딩하는 값이 아니라 그 `NULL`을 기준으로 타입이 결정됩니다. `SELECT ? AS x`는 `Nothing` 타입의 컬럼으로 추론되지만, `5`를 바인딩하면 `UInt8`이 제공됩니다. `SELECT id, name FROM t WHERE id = ?`처럼 프레디케이트에만 사용되는 플레이스홀더는 결과 타입이 테이블에서 결정되므로 이런 문제가 없습니다.
* Arrow에 대응하는 타입이 없는 컬럼은 [`output_format_arrow_unsupported_types`](/ko/reference/settings/formats/output-format)에 따라 Arrow 타입이 정해지며, 이 값은 호출을 수행하는 세션마다 각각 해석됩니다. handle은 단일 세션이 아니라 사용자에게 속하므로, 이후 호출에서 다르게 해석되어 `utf8`로 알려졌던 곳에 `binary`가 제공되거나 그 반대의 경우가 발생할 수 있습니다. prepared 쿼리 자체 안에서 모드를 설정하면 두 경우 모두 고정됩니다.

Prepared statements는 단일 세션이 아니라 인증된 사용자가 소유합니다. 동일한 사용자로 여러 세션을 열면 그 세션들 중 어느 세션에서든 동일한 statement handle을 실행, 다시 바인드, 종료할 수 있습니다.

다른 사용자는 자신이 생성하지 않은 statement handle을 실행, 바인드 또는 종료할 수 없습니다.

`arrowflight.prepared_statements_lifetime_seconds`는 만료 동작을 제어합니다:

* `> 0`: 구성된 값을 statement의 수명으로 사용합니다. 세션에 바인딩된 statement와 세션에 바인딩되지 않은 statement 모두에서 각 요청 시 만료 시간이 갱신됩니다.
* `0`: prepared statements는 자동으로 만료되지 않습니다.
* `-1` (기본값): statement가 세션에서 생성되면 수명은 해당 세션 타임아웃을 따르며, 그 세션의 각 요청 시 갱신됩니다. statement가 세션 없이 생성되면 자동으로 만료되지 않습니다.

만료된 statements는 제거되며 더 이상 `arrowflight.max_prepared_statements_per_user`에 포함되지 않습니다.

<h4 id="closepreparedstatement">
  ClosePreparedStatement
</h4>

요청에 비어 있지 않은 statement handle이 포함된 경우 prepared statement를 닫고, 관련 서버 측 리소스를 해제합니다.

ClickHouse는 handle이 비어 있을 때 `ClosePreparedStatement`를 사용한 일괄 종료도 지원합니다.

* `x-clickhouse-session-id`가 있으면 해당 세션에서 인증된 사용자의 모든 prepared statement를 닫습니다.
* 세션 ID가 없으면 인증된 사용자의 세션 없는 prepared statement만 닫습니다.

prepared statement가 세션(`x-clickhouse-session-id`를 통해)에서 생성된 경우, 해당 세션이 종료될 때 자동으로 함께 닫힙니다.

<h2 id="flight-sql-commands">
  Flight SQL 명령
</h2>

`CMD` 디스크립터에 직렬화된 [Flight SQL protobuf](https://arrow.apache.org/docs/format/FlightSql.html) 메시지가 포함된 경우, ClickHouse는 다음 명령을 처리합니다:

<h3 id="flightsql-getflightinfo">
  GetFlightInfo / GetSchema에서 지원
</h3>

| Command | Description |
| - | - |
| `CommandStatementQuery` | 임의의 SQL 쿼리를 실행합니다. `transaction_id`는 지원되지 않습니다. |
| `CommandGetSqlInfo` | 서버 메타데이터(이름, 버전, Arrow 버전, capability)를 가져옵니다. |
| `CommandGetCatalogs` | 카탈로그를 나열합니다. 빈 결과를 반환합니다(ClickHouse는 카탈로그를 사용하지 않습니다). |
| `CommandGetDbSchemas` | 데이터베이스를 나열합니다. 선택적 `db_schema_filter_pattern`(SQL `LIKE` 패턴)을 지원합니다. |
| `CommandGetTables` | 테이블을 나열합니다. 스키마, 테이블 이름, 테이블 타입, 선택적 스키마 포함 여부에 대한 필터를 지원합니다. |
| `CommandGetTableTypes` | 테이블 엔진 타입을 나열합니다(`system.table_engines` 기준). |
| `CommandGetPrimaryKeys` | 지정된 테이블의 프라이머리 키 컬럼을 가져옵니다. |
| `CommandPreparedStatementQuery` | 핸들로 준비된 `SELECT` 스타일 구문을 실행합니다. |

<h3 id="flightsql-doput">
  DoPut을 통해 지원됩니다
</h3>

| Command | Description |
| - | - |
| `CommandStatementUpdate` | DDL/DML 문(CREATE, INSERT, ALTER 등)을 실행합니다. 영향을 받은 행 수를 반환합니다. `transaction_id`는 지원되지 않습니다. |
| `CommandStatementIngest` | 기존 테이블에 Arrow 데이터를 대량 삽입합니다. append 모드만 지원됩니다. `transaction_id`는 지원되지 않습니다. |
| `CommandPreparedStatementQuery` | `DoPut`으로 전송할 때 prepared statement의 매개변수 값을 바인딩한 뒤, statement handle이 포함된 `DoPutPreparedStatementResult`를 반환합니다. 매개변수 집합은 1개(행 1개)만 허용되며, 바인딩된 값의 개수는 `?` placeholder의 개수와 정확히 일치해야 합니다. |
| `CommandPreparedStatementUpdate` | handle로 prepared DDL/DML 문을 실행하고 영향을 받은 행 수를 반환합니다. |

<h3 id="flightsql-not-implemented">
  ClickHouse에서 지원되지 않음
</h3>

다음 명령은 ClickHouse에서 제공하지 않는 기능에 해당하므로 Arrow Flight SQL 인터페이스에서 지원되지 않습니다.

| Command | Reason |
| - | - |
| `CommandGetCrossReference` | ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약 조건을 구현하지 않으므로 교차 참조 메타데이터를 제공하지 않습니다. |
| `CommandGetExportedKeys` | ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약 조건을 구현하지 않으므로 내보낸 키 메타데이터를 제공하지 않습니다. |
| `CommandGetImportedKeys` | ClickHouse는 관계형 데이터베이스가 아니며 외래 키 제약 조건을 구현하지 않으므로 가져온 키 메타데이터를 제공하지 않습니다. |
| `CommandStatementSubstraitPlan` | ClickHouse는 Substrait plan을 지원하지 않습니다. |

<h2 id="complete-example">
  전체 예시
</h2>

```python title="Query" theme={null}
import pyarrow as pa
import pyarrow.flight as flight

# 연결 및 인증
client = flight.FlightClient("grpc://localhost:9090")
token = client.authenticate_basic_token("default", "")
options = flight.FlightCallOptions(headers=[token])

# PATH 디스크립터를 사용하여 DoPut으로 데이터 삽입
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()

# 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())
```

```text title="Response" theme={null}
   id value
0   1     a
1   2     b
2   3     c
```

<h2 id="data-format">
  데이터 포맷
</h2>

모든 데이터는 Apache Arrow IPC 포맷으로 전송됩니다. `Arrow` 형식만 지원되며, 다른 ClickHouse 포맷(예: `FORMAT JSON`, `FORMAT CSV`)을 지정하면 오류가 발생합니다.

ClickHouse 데이터 타입은 직렬화 과정에서 Arrow 타입으로 매핑됩니다. Arrow Flight는 항상 정규 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`로 바꿀 수 있게 하면 모든 Flight SQL 드라이버에 대해 ClickHouse가 규격을 준수하지 못하게 되고, `table_schema` 안에서 ClickHouse가 알리는 테이블별 스키마까지 달라집니다.
* Flight 클라이언트는 스키마와 데이터를 별도의 호출로 가져옵니다(`GetFlightInfo` 또는 `GetSchema`, 이어서 `DoGet`). 스키마를 바꿀 수 있는 설정이 있다면, 그 사이에 세션이 변경될 경우 알려진 스키마와 실제 전달되는 스트림이 서로 어긋날 수 있습니다.

유일한 예외는 `JSON`, `Dynamic`, `QBit`, `AggregateFunction`처럼 Arrow에 대응되는 타입이 아예 없는 경우입니다. 기준으로 삼을 정규 매핑이 없으므로 ClickHouse가 표현 방식을 선택해야 하며, [`output_format_arrow_unsupported_types`](/ko/reference/settings/formats/output-format)로 이를 지정할 수 있습니다.

| Value | 동작 |
| - | - |
| `throw` | 쿼리가 거부됩니다. |
| `text` | 행마다 하나의 값을 텍스트 형식으로, Arrow `Utf8` 컬럼에 담아 반환합니다 — `CAST(col AS String)`이 반환하는 값과 동일합니다. |
| `binary` (기본값) | 행마다 하나의 값을 바이너리 형태로, Arrow `Binary` 컬럼에 담아 반환합니다 — `RowBinary`가 사용하는 인코딩입니다. |

`AggregateFunction` 컬럼은 `text` 모드에서도 Arrow `Binary` 컬럼으로 남는 유일한 타입입니다. 이 타입의 텍스트 형식은 원시 aggregate state인데, 이는 유효한 UTF-8이 아니며 Arrow `Utf8` 컬럼에는 유효한 UTF-8만 담을 수 있기 때문입니다. 읽을 수 있는 값이 필요하다면 `finalizeAggregation`을 사용하십시오.

같은 이유로 ClickHouse는 `text` 값에 포함된 모든 잘못된 UTF-8 시퀀스를 `Utf8` 컬럼에 기록하기 전에 U+FFFD(`�`)로 대체합니다. `String`을 담고 있는 `Dynamic`은 해당 바이트를 그대로 직렬화하고 그 값은 임의의 바이트일 수 있으므로, 이러한 처리가 없다면 컬럼이 Arrow 규격을 위반하여 엄격한 클라이언트에서 거부될 수 있습니다. 이미 유효한 텍스트가 아닌 값만 변경됩니다. 바이트를 정확히 보존해야 한다면 `binary` 모드를 사용하십시오.

`output_format_arrow_string_as_string`은 `FORMAT Arrow`에서도 이러한 컬럼에는 적용되지 않으며, 실제 `String` 및 `FixedString` 컬럼에만 작용합니다. 따라서 `clickhouse.opaque` 컬럼의 Arrow 타입은 항상 어떤 인코딩을 담고 있는지를 나타냅니다. 텍스트 형식이면 `Utf8`, 바이너리 형태면 `Binary`입니다.

이 때문에 `AggregateFunction` 컬럼과 달리 `Dynamic`에 담긴 aggregate state는 `text` 모드에서 손실이 발생합니다. 해당 컬럼의 타입은 `Dynamic`으로 정해지는데, 이 타입은 각 행이 무엇을 담고 있는지 전혀 알려주지 않으며, 스키마는 어떤 값을 확인하기도 전에 고정되므로 그 state에 별도의 `Binary` 컬럼을 부여할 수 없습니다. 값을 그대로 유지하려면 `binary` 모드를 사용하십시오. 반면 `Variant`는 가능한 타입들을 모두 나열하므로, 그중에 포함된 `AggregateFunction`은 자신만의 `Binary` 자식 컬럼을 갖게 되어 영향을 받지 않습니다.

이러한 컬럼은 그 외에는 실제 `Utf8`/`Binary` 컬럼과 구별할 수 없으므로 Arrow 확장 타입으로 선언됩니다. 필드 메타데이터에는 `ARROW:extension:name` = `clickhouse.opaque`가 담기고, 원래의 ClickHouse 타입 이름은 `ARROW:extension:metadata`에 담깁니다. 확장 이름을 인식하지 못하는 클라이언트는 Arrow 규격에 정의된 대로 plain storage 타입을 보게 됩니다. 중첩 컬럼은 각자의 필드에 태그가 지정되므로, 컨테이너 자체가 아니라 `Array(JSON)`의 자식이 태그를 갖고, `Map(JSON, ...)`의 키도 마찬가지로 태그를 갖습니다.

기존의 불리언 설정 `output_format_arrow_unsupported_types_as_binary`도 여전히 동작하며, `0`이면 `throw`, `1`이면 `binary`와 동일합니다. 이 설정은 `output_format_arrow_unsupported_types`가 기본값으로 유지된 경우에만 참조됩니다.

<h2 id="compatibility">
  호환성
</h2>

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가 특별히 필요한 경우가 아니라면 해당 커넥터를 사용하는 것이 좋습니다.

<h2 id="client-side">
  클라이언트 측 ArrowFlight 기능
</h2>

ClickHouse는 외부 Arrow Flight 서버에서 데이터를 읽는 Flight 클라이언트로도 사용할 수 있습니다. 다음을 참조하십시오.

* [ArrowFlight 테이블 엔진](/ko/reference/engines/table-engines/integrations/arrowflight)
* [arrowFlight 테이블 함수](/ko/reference/functions/table-functions/arrowflight)

<h2 id="see-also">
  관련 항목
</h2>

* [Apache Arrow Flight 명세](https://arrow.apache.org/docs/format/Flight.html)
* [Apache Arrow Flight SQL 명세](https://arrow.apache.org/docs/format/FlightSql.html)
* [ClickHouse의 Arrow 형식](/ko/reference/formats/Arrow/Arrow)
