Engineering Note

Apache Fluss 1.0, 무엇이 달라졌나: 데이터 조회부터 Go 클라이언트까지

Fluss 1.0의 배치 조회, Spark 통합 읽기, 보존 정책과 운영 개선을 기존 실습에 연결하고 fluss-go 0.2.0-beta.1의 지원 범위와 전환 순서를 정리합니다.

발행 · Pletor Engineering apache-flussgolangflinklakehousereleases
여러 정원으로 물길이 이어지는 따뜻한 종이 공예풍 풍경
최근 데이터 조회부터 과거 이력 분석까지, Fluss 1.0에서 넓어진 활용 범위를 살펴봅니다.

2026년 9월 21일 Apache Fluss 1.0이 공식 발표됐습니다. Apache 최상위 프로젝트로 졸업한 뒤 처음 맞는 메이저 릴리스입니다. 이어서 Go 클라이언트인 fluss-go도 Fluss 1.0을 지원하는 v0.2.0-beta.1을 공개했습니다.

Fluss는 최근 데이터를 실시간으로 읽고 쓰면서 데이터 레이크의 과거 이력과 연결하는 저장 시스템입니다. 기본 키 테이블(Primary Key Table, 이하 PK 테이블)은 고객별 현재 상태를, 로그 테이블(Log Table)은 주문처럼 계속 추가되는 이벤트를 담는 데 사용할 수 있습니다.

앞선 글에서는 이 두 모델로 데이터를 읽고 썼습니다. 이번에는 작은 상태 테이블 조회, Spark의 최근 데이터·과거 이력 분석, 장애와 용량 부족에 대한 대응이 1.0에서 어떻게 달라졌는지 살펴봅니다. 마지막에는 Go로 서버 측 스캔을 사용하는 예제를 연결합니다.

이 글은 2026년 9월 22일 기준의 릴리스와 1.0 소스 문서를 바탕으로 그 변화를 정리합니다. 이전 실습은 0.9.1 환경을 전제로 했으므로, 아래 예제를 실행하려면 서버와 Flink 커넥터를 포함한 별도의 1.0 실습 환경이 필요합니다.

먼저 볼 변화

하려는 작업1.0에서 살펴볼 기능먼저 확인할 범위
작은 고객 상태 테이블 조회서버 측 PK 스캔서버에 스캔 세션이 유지됨
최근 주문과 과거 주문 함께 분석Spark 배치 Union Read레이크 연동과 티어링 구성 필요
오래된 상태와 이력 정리PK 행 TTL, 로컬 로그 TTL행·로그·파티션·레이크의 보존 정책은 별개
클러스터 유지보수Coordinator HA, 저장 공간 보호복구 상태와 재시도 동작 확인
Go 서비스 연결fluss-go 0.2 계열0.9.1 프로토콜과 호환되지 않음

새 기능의 의미는 실제 접근 경로에서 드러납니다. 고객 한 명을 키로 조회하는 작업, 작은 고객 테이블을 훑는 작업, 과거 주문 전체를 분석하는 작업은 서로 다른 읽기 방식입니다.

현재 상태를 확인하는 방법이 늘었다

이전 Quickstart에서는 적재 결과를 COUNT(*), LIMIT, 기본 키 조회로 확인했습니다. Fluss 1.0에서는 Flink 배치 읽기가 확대되고, PK 테이블을 TabletServer에서 직접 스캔하는 선택지도 생겼습니다.

핵심 이점은 작은 상태 테이블을 확인할 때 클라이언트가 스냅샷 파일을 내려받고 변경 로그를 재생할 필요가 없어진다는 것입니다. 1.0에서도 기본 전략인 snapshot-merge는 스냅샷과 이후 변경 로그를 합칩니다. 선택 가능한 server-scan은 서버가 관리하는 키-값(KV) 상태를 직접 읽어 결과를 나눠 보냅니다. 고객 한 명의 기본 키 조회를 대체하는 것이 아니라, 관리 화면처럼 여러 고객의 현재 상태를 확인하는 작업에 선택지가 생긴 것입니다.

Fluss 1.0용 Flink Catalog에서, 레이크 스냅샷이 없는 작은 demo.customer_profile 테이블을 조회한다면 다음처럼 해당 쿼리에만 적용할 수 있습니다.

SET 'execution.runtime-mode' = 'batch';

SELECT *
FROM demo.customer_profile
/*+ OPTIONS('client.scanner.kv.batch-strategy' = 'server-scan') */;

조회 결과는 고객별 현재 행입니다. 변경 이력 전체가 나오는 쿼리는 아닙니다. 뒤의 실습에서는 별도 데이터베이스에 같은 스키마를 만들고 이 조회 방식을 직접 확인합니다.

서버 측 스캔은 수천 행 정도의 상태 확인처럼 작은 조회에 어울립니다. 버킷마다 스캔을 여는 시점이 달라질 수 있으므로 전체 테이블의 단일 시점 트랜잭션 스냅샷으로 해석해서는 안 됩니다. 대규모 분석이나 동시 조회가 많은 환경에서는 서버의 세션 비용도 고려해야 합니다. 레이크 스냅샷이 있는 PK 테이블의 배치 읽기는 Union Read를 사용하므로 이 옵션이 적용되지 않습니다. 1.0 Flink 읽기 문서

로그 조회에는 조건에 맞지 않는 데이터 묶음을 서버에서 건너뛰는 기능도 추가됐습니다. 예를 들어 주문 금액 조건을 만족할 수 없는 Arrow 로그 배치를 전송하지 않으면 네트워크 전송과 클라이언트의 역직렬화 작업이 줄어듭니다. 이 조건식 푸시다운은 열 통계를 활성화한 뒤 기록한 배치에 적용되며, 남은 배치 안에서 행별 조건을 확인하는 작업은 여전히 필요합니다. 뒤의 Go 예제와는 별개의 기능이며 fluss-go의 이번 베타에서는 지원하지 않습니다. 조건식 푸시다운

Spark에서도 최근 데이터와 과거 이력을 함께 읽는다

기존 개념 글에서 설명한 Union Read는 Fluss의 최근 데이터와 레이크의 과거 데이터를 하나의 논리적 테이블로 읽는 기능입니다. 1.0에서는 Spark의 배치 분석에도 이 읽기 경로가 추가됐습니다.

예를 들어 최근 주문이 아직 레이크에 반영되지 않았더라도, 적절히 구성한 Fluss 테이블을 Spark에서 읽으면 레이크 이력과 함께 분석할 수 있습니다. 레이크 파일만 직접 읽는 쿼리와 결과의 최신성이 달라질 수 있는 이유입니다. Union Read가 최근 데이터를 보완한다고 해서 레이크 파일 자체가 즉시 갱신되는 것은 아닙니다. 파일 반영은 티어링 주기에 따릅니다. Union Read 문서

레이크 활용 범위도 넓어졌습니다. Hudi 연동이 추가됐고, Paimon에는 Fluss에서 정리된 과거 파티션의 데이터를 다시 조회하거나 수정하는 경로가 생겼습니다. 늦게 도착한 주문 정정처럼 과거 날짜의 데이터를 다루는 작업과 연결됩니다. 다만 과거 파티션 접근은 단일 파티션 열을 가진 Paimon 자동 파티션 테이블 등 지원 조건이 있습니다. 이 기능을 Iceberg·Hudi에도 동일하게 적용할 수 있다고 읽어서는 안 됩니다. 1.0 파티션 문서

보존 기간은 데이터의 역할에 맞춰 나눈다

고객의 임시 상태, 주문 변경 로그, 로컬 디스크에 남겨 둘 최근 로그는 필요한 보존 기간이 다릅니다. TTL(Time To Live)은 데이터가 만료되기까지의 보존 기간입니다. 1.0에서 추가된 PK 행 TTL과 로컬 로그 TTL을 사용하면 이 기간들을 따로 정할 수 있습니다.

설정 또는 정책관리 대상
table.kv.ttlPK 테이블에 저장된 행
table.log.ttl로그의 보존 기간
table.log.local-ttl로컬에 남기는 로그의 기간
파티션 보존 정책오래된 파티션 전체
레이크 보존 정책티어링된 레이크 데이터

보존 기간이 지난 PK 행은 컴팩션 과정에서 정리됩니다. TTL을 정확한 시각의 즉시 삭제나 모든 저장 계층의 일괄 삭제로 이해해서는 안 됩니다. 파티션 자체가 만료되면 더 긴 행 TTL이 그 파티션을 보존해 주지도 않습니다. 보존 정책을 정할 때는 “어느 계층에서 무엇을 언제까지 읽을 수 있어야 하나”부터 결정해야 합니다. 1.0 TTL 문서

장애 복구와 저장 공간 관리도 달라졌다

테이블을 한 번 읽는 데 성공하는 것과 클러스터를 계속 운영하는 것은 다릅니다. 1.0에는 다음과 같은 운영 개선이 포함됐습니다.

운영 상황1.0에서 활용할 변화확인할 점
Coordinator 중단·재시작대기 Coordinator를 통한 자동 장애 전환리더 선출 중 관리 요청은 잠시 실패할 수 있음
TabletServer의 로컬 저장 공간 확장data.dirs로 여러 디스크 사용기존 데이터 디렉터리를 유지하며 설정 변경
디스크 부족·KV 쓰기 과부하쓰기 제한·거부를 통한 리소스 보호애플리케이션의 재시도와 저장 공간 경보 필요

예를 들어 대기 Coordinator는 장애 전환을 자동화하지만 모든 관리 요청의 무중단 처리를 보장하지는 않습니다. 리소스 보호 역시 용량을 늘리는 기능은 아닙니다. 요청이 지연되거나 거부되는 이유를 확인하고 재시도와 증설을 판단할 수 있어야 합니다. 운영 개선, 업그레이드 시 운영 설정

Konduo는 인프라 상태, 메트릭·로그 근거, 알림 대응과 운영 이력을 연결하는 통합 관리 운영 플랫폼입니다. Konduo는 fluss-go v0.2.0-beta.1을 활용해 Apache Fluss 1.0 클러스터의 관리·운영·모니터링을 제공합니다. 이번 Go 클라이언트의 1.0 지원은 예제 애플리케이션뿐 아니라 실제 클러스터를 운영하는 도구에도 활용됩니다.

HTTP와 여러 언어로 접근한다

새 Gateway는 메타데이터 조회, 테이블 관리, 일괄 쓰기를 HTTP로 제공합니다. 애플리케이션에서 네이티브 SDK를 쓰기 어려운 경우에 선택할 수 있는 접근 방법입니다. Rust·Python·C++ 클라이언트는 이번 버전부터 주 저장소에서 관리하며 Fluss와 함께 릴리스합니다. Java 외의 언어에서도 프로토콜 변경과 기능 추가를 같은 릴리스 기준으로 확인할 수 있게 됐습니다.

공식 발표가 AI를 강조하는 배경도 여기에 있습니다. 이벤트 처리로 갱신한 프로필이나 특징값을 애플리케이션에서 활용하려면 접근 방법이 필요하기 때문입니다. 다만 Gateway의 MCP·PGSQL·gRPC는 발표 시점의 향후 계획입니다. 현재 지원 기능과 구분해야 합니다. Fluss 1.0 발표

Go에서는 서버 측 PK 스캔을 사용한다

fluss-go는 Pletor가 개발하는 별도 Go 클라이언트이며 Apache 공식 SDK와 구분됩니다. 이번 버전도 Rust 코어를 감싼 바인딩이 아닌 순수 Go 구현입니다.

서버와 라이브러리는 다음 조합으로 맞춥니다.

Fluss 서버fluss-go 버전
0.9.1-incubatingv0.1.0-beta.11
1.0.0v0.2.0-beta.1

이전 블로그 예제는 v0.1.0-beta.10을 고정했습니다. 위 표의 beta.11은 새 마이그레이션 안내가 지정한 0.9.1 유지 버전입니다. 기존 글의 의존성만 0.2로 바꾸어 0.9.1 서버에 연결하면 안 됩니다. 마이그레이션 안내

현재 고객 상태를 JSON으로 출력하기

기존 Quickstart와 같은 고객 스키마를 사용하되, 기존 데이터를 건드리지 않도록 release_1_0_demo.customer_profile을 따로 만듭니다. Go 프로그램은 비파티션 PK 테이블을 읽기만 합니다.

준비물은 Docker와 Compose입니다. 아래에서는 Go도 컨테이너로 실행하므로 호스트에 따로 설치할 필요가 없습니다. 검증 환경은 Docker에 CPU 4개와 메모리 8GB를 할당했습니다.

다음 세 파일을 새 디렉터리에 내려받습니다.

이 환경은 Fluss 1.0.0과 Flink Quickstart 1.20-1.0.0 이미지를 사용합니다. 레이크 연동 없이 서버 측 스캔만 확인하며, 원격 저장소 대신 컨테이너들이 공유하는 로컬 볼륨을 사용합니다. 단일 머신 실습용 구성이지 운영 배포 구성이 아닙니다.

docker compose up -d --wait --wait-timeout 180 &&
  docker compose run --rm sql-client -f /examples/prepare.sql

--wait는 Fluss 서버의 RPC 포트, Flink REST 응답, TaskManager의 슬롯 등록 상태를 확인합니다. 준비 확인에 성공한 경우에만 && 뒤의 SQL을 실행합니다. 180초 안에 준비되지 않으면 SQL을 실행하지 않고 실패합니다. 이 검사는 실습 시작 조건이며 클러스터의 모든 기능이 정상임을 보장하는 검사는 아닙니다.

실패하면 docker compose ps와 docker compose logs coordinator-server tablet-server jobmanager taskmanager로 원인을 확인한 뒤 같은 명령을 다시 실행합니다. --wait 옵션을 지원하는 Docker Compose v2가 필요합니다.

준비 SQL은 아래 세 고객을 삽입하고, 서버 측 스캔으로 조회합니다. 테이블이 이미 있으면 생성 단계에서 중단하므로 반복 실행으로 기존 데이터를 덮어쓰지 않습니다.

customer_idnamemembershipaccount_balance
101MinaSTANDARD125.50
102JoonVIP240.00
103SaraSTANDARD75.25

Go 예제도 같은 Compose 네트워크 안에서 실행하면 별도의 호스트 이름 매핑이 필요 없습니다. 아래 명령은 다운로드한 디렉터리에서 실행합니다. 최초 실행에는 Go 의존성 다운로드 시간이 추가됩니다.

docker run --rm --network fluss-release-demo_default \
  -v "$PWD:/work" -w /work \
  -e FLUSS_BOOTSTRAP=coordinator-server:9123 \
  golang:1.25.7 sh -c '
    test -f go.mod || go mod init example.com/fluss-scan
    go get github.com/pletorco/fluss-go@v0.2.0-beta.1 &&
    go mod tidy &&
    go run scan-profiles.go
  '
호스트에서 직접 실행하려면

호스트에서 직접 실행한다면 Go 1.25.0 이상이 필요합니다. 라이브러리의 go.mod는 go 1.25.0을 요구하고 개발용 도구 체인으로 go1.26.6을 지정합니다. 의존 모듈의 toolchain 지시문이 애플리케이션의 Go 버전을 자동으로 고정하는 것은 아닙니다.

호스트에서 직접 실행하려면 같은 모듈 준비 명령 후 FLUSS_BOOTSTRAP=<접속 주소> go run scan-profiles.go를 사용합니다. 단, 위 Compose는 외부 포트를 열지 않습니다. 직접 실행할 환경에서는 Coordinator뿐 아니라 서버가 클라이언트에 알려 주는 TabletServer 접속 주소도 호스트에서 접근할 수 있도록 구성해야 합니다.

전체 코드는 버킷마다 다음 API로 스캔 세션을 엽니다.

scanner, err := client.NewBatchScanner(
    ctx, table, bucket, fgo.WithBatchSizeBytes(64*1024),
)
if err != nil {
    return 0, err
}
defer scanner.Close()

이후 Poll(ctx)로 응답을 나눠 받고 Done까지 읽습니다. 빈 응답만으로 종료하지 않으며, 마지막 응답의 행도 출력합니다. 열 이름과 값을 연결해 JSON으로 변환한 뒤 Release()로 응답 메모리를 정리합니다. DECIMAL은 소수 정밀도를 보존하도록 문자열로 표시합니다.

출력되는 한 행의 형태는 다음과 같습니다. 아래는 출력 형식 예시이며, 행 순서나 응답별 행 수는 고정하지 않습니다.

{
  "account_balance": "125.50",
  "customer_id": 101,
  "membership": "STANDARD",
  "name": "Mina"
}

다른 쓰기가 없는 새 실습 테이블에서는 고객 세 명의 값이 입력 데이터와 일치하고 마지막에 Completed: 3 rows가 나오는지 확인합니다. 출력은 고객 ID 순 정렬을 보장하지 않습니다. 빈 테이블의 0행은 정상이며, 이 예제는 성능이나 버킷 전체의 단일 시점 스냅샷을 검증하지 않습니다.

2026년 9월 22일 위 Compose 구성과 Go 1.25.7 컨테이너에서 테이블 생성, 세 행 적재, Flink 서버 측 스캔, Go JSON 조회를 실행해 세 고객의 값과 최종 3행을 확인했습니다. Spark·레이크 연동, 장애 전환과 성능 측정은 이 실습의 검증 범위에 포함하지 않습니다.

JSON 출력 변환 테스트

출력 변환 테스트는 소수 금액, NULL, 빈 결과와 행 길이 불일치를 검사합니다. 같은 디렉터리에 내려받아 go test ./...로 실행할 수 있습니다.

실습 후 docker compose down으로 컨테이너를 종료합니다. 이 구성은 재기동 후 테이블 복구를 위한 영속 배포 구성이 아닙니다. 테스트 데이터까지 정리하려면 이 실습 디렉터리에서만 docker compose down --volumes를 실행합니다. 실행 중인 환경에서 준비 SQL을 다시 실행하려면 먼저 실습 테이블인지 확인한 뒤 명시적으로 삭제해야 합니다.

지원 범위도 함께 확인한다

이번 베타는 PK 스캔 세션과 관리 API 등을 확장했지만, Arrow 조건식 푸시다운과 레이크의 과거 물리 파티션 라우팅은 지원하지 않습니다. 서버에서 가능한 기능을 Go API 전체가 제공하는 것으로 해석하지 않아야 합니다. 릴리스별 지원 범위

업그레이드 순서는 클라이언트마다 확인한다

여기에는 중요한 차이가 있습니다. Apache의 일반 업그레이드 안내는 클라이언트·커넥터 → 티어링 서비스 → 클러스터 순서를 제시합니다. 새 레이크 테이블의 물리 스키마를 구버전 리더가 읽지 못하는 문제도 고려한 순서입니다. 반면 fluss-go 0.2는 0.9.1 프로토콜을 지원하지 않으므로 1.0 서버 준비 후 Go 애플리케이션을 전환해야 합니다.

따라서 모든 구성 요소에 “서버부터 올린다”는 한 가지 순서를 적용하면 안 됩니다. 기존 환경을 바꾸기 전 다음 항목을 확인합니다.

  • Flink·Spark 커넥터, 레이크 플러그인, 티어링 서비스의 버전과 전환 순서
  • Go 애플리케이션을 1.0 환경으로 넘기는 시점과 되돌릴 환경
  • fluss-go 루트 모듈과 사용 중인 어댑터의 동일 버전 고정
  • 권한 변경으로 기존 테이블이 보이지 않는 경우의 DESCRIBE 권한
  • 새 형식으로 기록된 데이터와 새 레이크 테이블의 구버전 읽기 가능 여부

특히 table.statistics.columns를 켜면 이후 Arrow 로그는 확장된 V1 형식으로 기록됩니다. 기존 리더의 업데이트가 끝나기 전에 활성화하지 않습니다. 나중에 설정을 끈다고 이미 기록된 형식이 되돌아가는 것도 아닙니다. Apache 1.0 업그레이드 안내

어디부터 확인하면 좋을까

기존 실습을 해 봤다면 작은 PK 테이블의 서버 측 스캔부터 확인하는 것이 좋습니다. 다음으로 실제 응용에 필요한 Spark 통합 읽기나 보존 정책을 검증하면 됩니다. Go 서비스는 별도 1.0 환경에서 연결·조회·종료 처리를 확인한 뒤 전환할 수 있습니다.

Fluss 1.0은 현재 상태 조회, 과거 이력 분석, 데이터 보존과 클러스터 운영을 함께 검토할 계기입니다. 이번 릴리스의 가치는 자신의 데이터 경로에서 불편했던 작업이 얼마나 단순해지는지로 판단하면 됩니다.

함께 읽기 좋은 글