비동기 삽입

비동기 삽입 (Asynchronous inserts, async_insert)

비동기 삽입은 클라이언트 측 배칭이 어려울 때 강력한 대안입니다. 특히 관측 가능성(observability) 워크로드에서 수백·수천 개의 에이전트가 로그·메트릭·트레이스를 지속적으로, 때로는 작은 실시간 페이로드로 보낼 때 유용해요.

출처: 문서

본문

ClickHouse의 비동기 삽입은 클라이언트 측 배칭이 어려울 때 강력한 대안을 제공합니다. 이것은 수백·수천 개의 에이전트가 로그·메트릭·트레이스를 지속적으로, 종종 작은 실시간 페이로드로 보내는 관측 가능성 워크로드에서 특히 가치 있습니다. 이런 환경에서 클라이언트 측 데이터 버퍼링은 복잡성을 높이며, 충분히 큰 배치를 보내기 위한 중앙 집중식 큐를 요구합니다.

동기 모드로 많은 작은 배치를 보내는 것은 권장되지 않습니다. 파트가 많이 만들어지기 때문입니다. 이것은 쿼리 성능 저하와 "too many part" 오류로 이어집니다.

비동기 삽입은 들어오는 데이터를 인메모리 버퍼에 쓴 다음 구성 가능한 임계값에 따라 스토리지로 플러시함으로써 배칭 책임을 클라이언트에서 서버로 옮깁니다. 이 접근 방식은 파트 생성 오버헤드를 크게 줄이고 CPU 사용을 낮추며, 높은 동시성에서도 수집이 효율적으로 유지되게 합니다.

핵심 동작은 async_insert 설정으로 제어됩니다.

비동기 삽입은 HTTP와 네이티브 TCP 인터페이스 모두에서 지원됩니다.

활성화되면(async_insert = 1) 삽입은 버퍼링되고 플러시 조건 중 하나가 충족될 때만 디스크에 기록됩니다:

어느 임계값이든 먼저 도달하는 것이 플러시를 트리거합니다.

이 배칭 과정은 클라이언트에 보이지 않으며 ClickHouse가 여러 소스의 삽입 트래픽을 효율적으로 병합하게 도와줍니다. 그러나 플러시가 발생할 때까지는 데이터를 쿼리할 수 없습니다. 중요하게, 삽입 형태와 설정 조합마다 여러 버퍼가 있고, 클러스터에서는 노드별로 버퍼가 유지됩니다 — 멀티테넌트 환경에서 세밀한 제어를 가능하게 합니다. 삽입 메커니즘은 그 외에는 동기 삽입에 설명된 것과 동일합니다.

반환 모드 선택

비동기 삽입의 동작은 wait_for_async_insert 설정으로 더 정제됩니다.

1(기본값)로 설정하면 ClickHouse는 데이터가 디스크로 성공적으로 플러시된 후에만 삽입을 인정합니다. 이것은 강력한 내구성 보장을 제공하고 오류 처리를 간단하게 만듭니다: 플러시 중 문제가 발생하면 오류가 클라이언트에 반환됩니다. 이 모드는 대부분의 프로덕션 시나리오, 특히 삽입 실패를 안정적으로 추적해야 할 때 권장됩니다.

벤치마크는 적응형 삽입과 안정적인 파트 생성 동작 덕분에 이 모드가 동시성과 함께 잘 확장됨을 보여 줍니다(200이든 500 클라이언트든).

wait_for_async_insert = 0으로 설정하면 "fire-and-forget" 모드를 활성화합니다. 이 모드에서 서버는 데이터가 버퍼링되는 즉시, 스토리지에 도달하기를 기다리지 않고 삽입을 인정합니다.

이것은 초저지연 삽입과 최대 처리량을 제공하며, 고속·저중요도 데이터에 이상적입니다. 그러나 트레이드오프가 있습니다: 데이터가 영속화될 보장이 없고, 오류는 플러시 중에만 표면화되며, 실패한 삽입을 위한 데드레터 큐가 없습니다 — 실패 추적은 사후에 서버 로그와 시스템 테이블을 검사해야 합니다. 데이터 손실을 용인할 수 있는 워크로드에서만 이 모드를 사용하세요.

벤치마크는 또한 버퍼 플러시가 드물 때(예: 30초마다) 상당한 파트 감소와 낮은 CPU 사용을 보여 주지만, 조용한 실패의 위험은 남아 있습니다.

비동기 삽입을 사용한다면 async_insert=1,wait_for_async_insert=1을 사용할 것을 강력히 권장합니다. wait_for_async_insert=0을 사용하는 것은 매우 위험합니다. INSERT 클라이언트가 오류가 있는지 인지하지 못할 수 있고, ClickHouse 서버가 쓰기를 늦추고 서비스 안정성을 보장하기 위해 역압(backpressure)을 만들어야 하는 상황에서 클라이언트가 계속 빠르게 쓰면 잠재적 과부하를 일으킬 수 있기 때문입니다.

적응형 비동기 삽입

버전 24.2부터 ClickHouse는 기본적으로 적응형 플러시 타임아웃을 사용합니다(async_insert_use_adaptive_busy_timeout). 고정된 플러시 간격 대신, 타임아웃이 들어오는 데이터 속도에 따라 최소(async_insert_busy_timeout_min_ms, 기본 50ms)와 최대(async_insert_busy_timeout_max_ms, 기본 200ms 또는 Cloud에서 1000ms) 사이에서 동적으로 조정됩니다.

데이터가 자주 도착하면 타임아웃은 최소에 가깝게 유지되어 더 일찍 플러시하고 종단 간 지연을 줄입니다. 데이터가 드물면 최대 쪽으로 커져 더 큰 배치를 축적합니다. 이것은 기본 모드(wait_for_async_insert=1)에서 특히 유용한데, 고정된 높은 타임아웃은 데이터가 플러시될 준비가 되었을 때조차 클라이언트를 전체 간격 동안 블록시키기 때문입니다.

오류 처리

스키마 검증과 데이터 파싱은 삽입을 받을 때가 아니라 버퍼 플러시 중에 일어납니다. 삽입 쿼리의 행 하나라도 파싱 또는 타입 오류가 있으면 그 쿼리의 데이터는 전혀 플러시되지 않습니다 — 쿼리 전체 페이로드가 거부됩니다. 기본 모드(wait_for_async_insert=1)에서는 오류가 클라이언트에 반환됩니다. fire-and-forget 모드에서는 오류가 서버 로그와 system.asynchronous_inserts 테이블에 기록됩니다.

각 플러시는 버퍼에 있는 구분된 파티션 키 값마다 최소 하나의 파트를 만듭니다. 파티션 키가 없는 테이블에서도 버퍼링된 데이터가 max_insert_block_size(기본 약 100만 행)를 초과하면 단일 플러시가 여러 파트를 만들 수 있습니다.

비동기 삽입을 사용해도 파티셔닝 키의 카디널리티가 높으면 여전히 "too many parts" 오류를 만날 수 있습니다.

중복 제거와 안정성

버전 26.2부터 ClickHouse는 동기 삽입뿐 아니라 비동기 삽입에도 자동 중복 제거를 수행하여, 중복 제거 로그를 유지하는 테이블에서 재시도가 안전하게 됩니다. *ReplicatedMergeTree 엔진은 기본적으로 하나를 유지하고, 일반 MergeTree 테이블은 non_replicated_deduplication_window를 양수 값으로 설정해야 합니다. 두 삽입 타입 모두 deduplicate_insert로 제어되며, 기본값은 enable입니다. 종속 머티얼라이즈드 뷰가 지원됩니다.

실무에서 같은 삽입이 — 예를 들어 타임아웃이나 네트워크 끊김 때문에 — 재시도되면 ClickHouse는 중복을 안전하게 무시할 수 있습니다. 이는 멱등성을 유지하고 데이터 이중 쓰기를 피하게 도와줍니다. 비동기 삽입의 경우 중복 제거는 플러시된 배치 단위가 아니라 사용자 쿼리 단위로 동작하므로, 중복 쿼리가 그것과 함께 배칭된 다른 쿼리를 버리지 않습니다.

자세한 내용과 비동기 삽입에서 머티얼라이즈드 뷰에 적용되는 한 가지 제약은 "재시도 시 삽입 중복 제거"를 참고하세요.

비동기 삽입 활성화

비동기 삽입은 특정 사용자 또는 특정 쿼리에 대해 활성화할 수 있습니다:

  • 사용자 수준에서 비동기 삽입 활성화. 이 예제는 default 사용자를 사용합니다. 다른 사용자를 만들었다면 그 사용자 이름으로 대체하세요:
ALTER USER default SETTINGS async_insert = 1
  • 삽입 쿼리의 SETTINGS 절로 비동기 삽입 설정을 지정할 수 있습니다:
INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
  • ClickHouse 프로그래밍 언어 클라이언트를 사용할 때 연결 파라미터로 비동기 삽입 설정을 지정할 수도 있습니다. 예를 들어 ClickHouse Java JDBC 드라이버로 ClickHouse Cloud에 연결할 때 JDBC 연결 문자열에서 이렇게 할 수 있습니다:
"jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"

비동기 삽입은 INSERT INTO ... SELECT 쿼리에는 적용되지 않습니다. 삽입에 SELECT 절이 포함되면 async_insert 설정과 무관하게 쿼리는 항상 동기적으로 실행됩니다.

종료 시 버퍼 플러시

대기 중인 모든 비동기 삽입 버퍼를 플러시하려면 — 예를 들어 우아한 종료 중이거나 유지보수 전에 — 실행하세요:

SYSTEM FLUSH ASYNC INSERT QUEUE

이렇게 하면 서버가 멈추기 전에 버퍼링된 데이터가 스토리지에 기록됩니다.

Buffer 테이블과의 비교

비동기 삽입은 Buffer 테이블의 현대적 대체품입니다. 주요 차이점:

  • DDL 변경 불필요. 비동기 삽입은 투명합니다 — 테이블을 추가로 만들지 않고 설정을 활성화하기만 합니다.
  • 형태별 버퍼링. 비동기 삽입은 고유한 쿼리 형태와 설정 조합마다 별도 버퍼를 유지해 세밀한 플러시 정책을 가능하게 합니다. Buffer 테이블은 타깃 테이블당 단일 버퍼를 사용합니다.
  • 내구성. 기본 모드(wait_for_async_insert=1)에서는 클라이언트가 승인을 받기 전에 데이터가 디스크에서 확인됩니다. Buffer 테이블은 fire-and-forget처럼 동작합니다 — 버퍼링된 데이터는 크래시 시 손실됩니다.
  • 클러스터 동작. 클러스터에서 비동기 삽입 버퍼는 노드별로 유지됩니다. Buffer 테이블은 각 노드에서 명시적 생성이 필요합니다.

더 알아보기 (Learn more)