ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Clickhouse JSON 칼럼
    개발/기술 2025. 10. 27. 17:12

     

     

    How we built a new powerful JSON data type for ClickHouse

    We’re excited to introduce our new and significantly enhanced JSON data type, purpose-built to deliver high-performance handling of JSON data. Our core engineer, Pavel Kruglov, dives into how we built this feature on top of ClickHouse's columnar storage.

    clickhouse.com

    공식 블로그 글을 읽고 공부했던 내용을 정리했다


    Variant

     

    Variant 칼럼이란게 있다.

     

    기존 구조에서는 칼럼에는 하나의 데이터 타입만 둘 수 있었다면 Variant Type 에서는 여러가지 데이터 타입을 가질 수 있는 기능. 하나의 칼럼에 여러가지 데이터 타입의 값을 저장할 수 있다.

     

    아래 사진을 보면 하나의 칼럼에 Int64, String, Array (String or Int64) 이렇게 다양한 타입을 저장할 수 있도록 선언했다

     

    Variant Type 은 하나의 칼럼으로 보이지만 실제로 저장될 때는 같은 데이터 타입인 값들 끼리 별도의 서브 칼럼 형태로 저장된다. 위의 예시에서 C.Int64 는 정수형 데이터, C.string 은 문자열이 저장되는 방식이다.

     

    For such a column, ClickHouse stores all values with the same concrete data type in separate subcolumns (type variant column data files, which by themselves look almost identical to the column data files in the previous example)

     

    Variant 칼럼의 경우 타입에 해당하는 값만 읽어오도록 할 수 있다. Int64 타입의 데이터만 읽어오고 싶은 경우에는 아래처럼 선언해주면 된다

     

    SELECT C.Int64 -- Int64 타입의 데이터만 읽어오는 방법
    FROM test;
    
       ┌─C.Int64─┐
    1. │      42 │
    2. │    ᴺᵁᴸᴸ │
    3. │    ᴺᵁᴸᴸ │
    4. │      43 │
    5. │    ᴺᵁᴸᴸ │
    6. │    ᴺᵁᴸᴸ │
    7. │      44 │
    8. │    ᴺᵁᴸᴸ │
    9. │    ᴺᵁᴸᴸ │
       └─────────┘

     

    Dynamic 

     

    Variant 타입의 진화된 버전. 칼럼에 다양한 데이터 타입을 저장하면서 동시에 사용 가능한 데이터 타입의 수도 저장이 가능하다.

     

    Variant Type 에서는 데이터 타입을 지정해줘야 했던 반면에 Dynamic 에선 별도의 데이터 타입을 지정하지 않고도 여러가지 타입의 데이터를 저장할 수 있다. 

     

    디스크 형태에 저장되는 방식은 Variant column 과 동일하고 추가적인 정보가 같이 저장된다. 위 그림에서 C.dynamic_structure.bin 파일이 있는 점이 Variant type 과 다른 점이다. 

     

    Dynamic type 에서는 추가할 수 있는 타입의 개수에 제한을 둘 수 있다. 하지만 제한을 초과한다고 저장을 못하는건 아니다. 제한을 초과한 데이터들은 공유 칼럼에 하나의 데이터로 모두 들어가게된다. 아래 그림에서 C.SharedVariant.bin 여기에 정보가 저장된다

     

    Clickhouse JSON Type

     

    JSON 데이터 타입은 Variant 와 Dynamic 타입을 조합해서 칼럼 데이터 타입이다. JSON 타입이 다양한 자료구조를 지원하고 있기 때문에 Variant, Dynamic 타입을 조합하는 식으로 구현한 모양이다 (이렇게 안하면 다른 방법이 없기도 하고)

     

    JSON 칼럼 선언 방법은 아래와 같다

    <column_name> JSON(
      max_dynamic_paths=N, 
      max_dynamic_types=M, 
      some.path TypeName, 
      SKIP path.to.skip, 
      SKIP REGEXP 'paths_regexp')
    • max_dynamic_paths: 서브 칼럼으로 저장할 수 있는 json key 의 개수. 만약에 제한을 초과하면 key 들은 동일한 서브 칼럼에 저장되게 된다. 
    • max_dynamic_types: 같은 키인데 서로 다른 데이터 타입가 서브 칼럼으로 저장될 수 있는 수. 제한이 초과되면 마찬가지로 싱글 칼럼으로 저장되게 된다.
    • some.path TypeName 특정 JSON 경로에 대한 타입 힌트를 의미한다. 해당 경로로 저장되는 데이터는 특정 서브 칼럼으로 분류되기 때문에 성능이 보장된다
    • skip path.to.skip 특정 JSON 경로에 대해서는 저장하지 않는다. 무시할 경로에 대해서 사용하면 편함
    • skip regex 'path_regexp' 특정 정규식 조건의 JSON 에 대해서는 저장하지 않도록 설정한다.

    JSON 칼럼 Storage 를 다이어그램으로 아래와 같이 표현할 수 있다.

     

    a.b, a.c path 의 경우에는 타입힌트로 분류되었기 때문에 별도의 서브 칼럼으로 저장된다. a.d, a.d, a.e 의 경우에는 타입 힌트로 분류되지 않았으나 현재 칼럼이 max_dynamic_paths 조건을 초과하지 않았기 때문에 서브 칼럼으로 분류가 됐다.

     

    여기에서 서브 칼럼의 존재를 눈여겨볼 필요가 있는데 타입힌트의 경우 dynamic 보다 데이터를 빠르게 조회할 수 있는 이유가 서브칼럼 때문이다. 개발자 시각에선 하나의 칼럼에 데이터를 저장하는것처럼 보이지만 실제론 path 별로 해당하는 데이터를 별도의 서브 칼럼에 저장했고 path 별로 스캔할 양이 달라지기 때문

     

    max_dynamic_paths 에서도 서브 칼럼이 사용되며 max_dynamic_paths 범위를 벗어나는 경우에는 공유 칼럼을 사용하고 성능의 이점도 떨어지게 된다

     

    c.f path 에 해당하는 데이터의 경우 dynamic paths 를 초과하는 케이스인데 저장하는 데이터 칼럼을 공유하며 내부에 저장되는 데이터 타입도 같이 저장된다

     

    JSON 칼럼의 경우에는 python, javascript 처럼 ‘.’ 을 이용해서 json 경로 데이터에 접근하고 읽을수 있다

    SELECT C.a.b
    FROM test;

     

    저장된 데이터의 서브 칼럼 타입도 같이 확인할 수 있는데 Type hint 로 따로 지정하지 않았다면 아래처럼 Dynamic 으로 표시가 된다. 다른 타입에선 어떻게 나올지는 나중에 테이블을 만들어봐야 할듯함

    SELECT
        C.a.d,
        toTypeName(C.a.d)
    FROM test;
    
       ┌─C.a.d───┬─toTypeName(C.a.d)─┐
    1. │ 42      │ Dynamic           │
    2. │ 43      │ Dynamic           │
    3. │ ᴺᵁᴸᴸ    │ Dynamic           │
    4. │ foo     │ Dynamic           │
    5. │ [23,24] │ Dynamic           │
    6. │ ᴺᵁᴸᴸ    │ Dynamic           │
       └─────────┴───────────────────┘

     

    최적화

     

    대부분 시나리오에선 dynamic json 경로가 동일한 타입을 갖고 있을 것이라고 예상할 수 있는데 이런 경우 Dynamic 타입 구분자 파일이 동일한 값을 갖게 될거고 별도의 압축이 없다면 불필요한 저장공간을 차지하게 될 것이다. discriminator 는 아래 그림처럼 행별로 타입을 구분해주는 칼럼을 의미함

     

    비슷하게 유니크 하지만 다양한 json paths 를 저장하게 되면 각 경로의 discriminator 파일은 대부분 255 (NULL 을 의미) 값을 갖게 될 것임.

     

    두가지 경우 모두 discriminators 파일은 압축될 것이지만 대부분의 행이 같은 값을 가지기 때문에 redundant 할 것이다.

     

    이걸 최적화시키기 위해서 컴팩트하게 discriminator 직렬화 시키기 위한 포맷을 만들었다. discriminator 를 보통의 UInt8 로 작성하기보다 discriminator 가 target 수치와 동일하다면 세가지 데이터만 직렬화를 시킨다고 한다

    댓글

Designed by Tistory.