Snowflake에서 ClickHouse로 마이그레이션하기

Snowflake에서 ClickHouse로 마이그레이션하기 (SQL 번역)

Snowflake에서 ClickHouse로 옮길 때 데이터 타입의 차이가 바로 두드러져요. ClickHouse는 숫자·문자열·반구조화 타입에서 더 세밀한 표현력을 제공해요. 이 문서는 두 시스템의 타입을 대응시키는 완전한 참조를 제공해요.

출처: Migrating from Snowflake to ClickHouse

본문

데이터 타입 (Data types)

숫자 타입 (Numerics)

ClickHouse와 Snowflake 사이에서 데이터를 옮기는 사용자들은 숫자 선언에 있어 ClickHouse가 더 세밀한 정밀도를 제공한다는 걸 바로 알게 돼요. 예를 들어 Snowflake는 숫자에 Number 타입을 제공해요. 이는 총 38자리까지 정밀도(전체 자릿수)와 스케일(소수점 오른쪽 자릿수)을 사용자가 지정해야 해요. 정수 선언은 Number와 동의어이며, 단순히 범위가 동일한 고정 정밀도와 스케일을 정의해요.

이 편의성은 가능한 이유는 Snowflake에서 정밀도(정수의 스케일은 0)를 수정해도 디스크의 데이터 크기에 영향을 주지 않기 때문이에요 — 마이크로 파티션 수준에서 쓰기 시점에 숫자 범위에 필요한 최소 바이트가 사용되거든요. 스케일은 저장 공간에 영향을 주며 압축과 상쇄돼요. Float64 타입은 정밀도 손실과 함께 더 넓은 값 범위를 제공해요.

이와 대조적으로 ClickHouse는 부동소수점과 정수에 대해 여러 부호 있는/없는 정밀도를 제공해요. 이를 통해 정수에 필요한 정밀도를 명시해서 저장·메모리 오버헤드를 최적화할 수 있어요. Snowflake의 Number 타입에 해당하는 Decimal 타입은 정밀도와 스케일이 두 배인 76자리를 제공해요. 유사한 Float64 값에 더해, ClickHouse는 정밀도가 덜 중요하고 압축이 중요한 경우를 위한 Float32도 제공해요.

문자열 (Strings)

ClickHouse와 Snowflake는 문자열 데이터 저장에 대조적인 접근을 취해요. Snowflake의 VARCHAR는 UTF-8의 유니코드 문자를 담으며 사용자가 최대 길이를 지정할 수 있어요. 이 길이는 저장이나 성능에 영향을 주지 않아요 — 문자열을 저장할 때 항상 최소 바이트 수를 사용하거든요 — 그래서 다운스트림 도구에 유용한 제약만 제공해요. Text, NChar 같은 다른 타입은 이 타입의 단순 별칭이에요.

반면 ClickHouse는 모든 문자열 데이터를 원시 바이트로 String 타입으로 저장해요 (길이 지정 불필요). 인코딩은 사용자에게 맡기고, 다양한 인코딩을 위한 쿼리 시간 함수를 제공하죠. 그 동기에 대해서는 "Opaque data argument"를 참고하세요. 그래서 ClickHouse String은 구현상 Snowflake의 Binary 타입에 더 가까워요. SnowflakeClickHouse 모두 "collation(조합)"을 지원해서, 문자열이 정렬되고 비교되는 방식을 사용자가 재정의할 수 있어요.

반구조화 타입 (Semi-structured types)

Snowflake는 반구조화 데이터에 VARIANT, OBJECT, ARRAY 타입을 지원해요. ClickHouse는 이에 상응하는 Variant, Object(현재는 네이티브 JSON 타입을 위해 deprecated), Array 타입을 제공해요. 추가로 ClickHouse는 deprecated된 Object('json') 타입을 대체하는 JSON 타입을 갖고 있으며, 다른 네이티브 JSON 타입과 비교해 특히 성능과 저장 효율이 좋아요.

ClickHouse는 또한 명명된 TupleNested 타입을 통한 Tuple 배열을 지원해서, 사용자가 중첩 구조를 명시적으로 매핑할 수 있어요. 이렇게 하면 계층 전체에 코덱과 타입 최적화를 적용할 수 있어요. 반면 Snowflake는 외부 객체에 OBJECT, VARIANT, ARRAY 타입을 사용해야 하고 명시적 내부 타이핑을 허용하지 않아요.

이 내부 타이핑은 ClickHouse에서 중첩 숫자에 대한 쿼리를 단순화해 주기도 해요 — 캐스팅 없이 인덱스 정의에서 사용할 수 있거든요. ClickHouse에서는 코덱과 최적화된 타입을 하위 구조에도 적용할 수 있어요. 이는 중첩 구조에서의 압축이 평탄화(flattened)된 데이터와 비슷하게 우수하게 유지된다는 추가 이점을 제공해요. 반대로 Snowflake는 하위 구조에 특정 타입을 적용할 수 없기 때문에 최적 압축을 위해 데이터 평탄화를 권장해요. Snowflake는 또한 이러한 데이터 타입에 크기 제한을 두기도 해요.

타입 참조 (Type reference)

Snowflake ClickHouse 비고
NUMBER Decimal ClickHouse는 Snowflake보다 두 배의 정밀도와 스케일을 지원해요 — 76자리 대 38자리.
FLOAT, FLOAT4, FLOAT8 Float32, Float64 Snowflake의 모든 float는 64비트예요.
VARCHAR String
BINARY String
BOOLEAN Bool
DATE Date, Date32 Snowflake의 DATE는 ClickHouse보다 넓은 날짜 범위를 제공해요. 예를 들어 Date32의 최소값은 1900-01-01, Date1970-01-01이에요. ClickHouse의 Date는 더 비용 효율적인(2바이트) 저장을 제공해요.
TIME(N) 직접 대응은 없지만 DateTimeDateTime64(N)으로 표현 가능. DateTime64는 동일한 정밀도 개념을 사용해요.
TIMESTAMPTIMESTAMP_LTZ, TIMESTAMP_NTZ, TIMESTAMP_TZ DateTimeDateTime64 DateTimeDateTime64는 컬럼에 선택적으로 TZ 파라미터를 정의할 수 있어요. 없으면 서버 시간대가 사용돼요. 추가로 클라이언트에 --use_client_time_zone 파라미터가 있어요.
VARIANT JSON, Tuple, Nested JSON 타입은 ClickHouse에서 experimental이에요. 이 타입은 삽입 시점에 컬럼 타입을 추론해요. Tuple, Nested, Array로 명시적으로 타입된 구조를 만드는 대안으로도 사용할 수 있어요.
OBJECT Tuple, Map, JSON OBJECTMap 모두 ClickHouse의 JSON 타입과 유사해요 — 키가 String이죠. ClickHouse는 값이 일관되고 강하게 타입되어야 하지만 Snowflake는 VARIANT를 사용해요. 즉 다른 키의 값이 다른 타입일 수 있어요. ClickHouse에서 이게 필요하면 Tuple로 계층을 명시적으로 정의하거나 JSON 타입에 의존하세요.
ARRAY Array, Nested Snowflake의 ARRAY는 요소에 VARIANT — 수퍼 타입 — 을 사용해요. 반면 ClickHouse에서는 강하게 타입됩니다.
GEOGRAPHY Point, Ring, Polygon, MultiPolygon Snowflake는 좌표계(WGS 84)를 부과하지만 ClickHouse는 쿼리 시점에 적용해요.
GEOMETRY Point, Ring, Polygon, MultiPolygon

추가 ClickHouse 타입:

ClickHouse 타입 설명
IPv4IPv6 IP 전용 타입. Snowflake보다 더 효율적인 저장이 가능할 수 있어요.
FixedString 고정 길이 바이트 사용을 허용. 해시에 유용해요.
LowCardinality 모든 타입을 사전(dictionary) 인코딩할 수 있게 함. 카디널리티가 < 100k일 것으로 예상될 때 유용해요.
Enum 명명된 값을 8비트 또는 16비트 범위로 효율적으로 인코딩.
UUID UUID를 효율적으로 저장.
Array(Float32) 벡터를 지원되는 거리 함수와 함께 Float32의 Array로 표현할 수 있어요.

마지막으로 ClickHouse는 집계 함수의 중간 상태(state)를 저장하는 고유한 능력을 제공해요. 이 상태는 구현별이지만, 집계 결과를 저장하고 나중에 (대응하는 merge 함수로) 쿼리할 수 있게 해 줘요. 보통 이 기능은 매터리얼라이즈드 뷰를 통해 사용되며, 아래에서 보여 주듯 삽입된 데이터에 대한 쿼리의 증분 결과를 저장해서 최소 저장 비용으로 특정 쿼리의 성능을 개선할 수 있어요.

더 알아보기 (Learn more)