CREATE DICTIONARY ... LAYOUT Cache

CREATE DICTIONARY ... LAYOUT Cache

cached 딕셔너리 레이아웃 타입은 고정된 수의 셀을 가진 캐시에 딕셔너리를 저장해요. 이 셀들은 자주 사용되는 요소를 담습니다.

출처: 문서

본문

cached 딕셔너리 레이아웃 타입은 고정된 수의 셀을 가진 캐시에 딕셔너리를 저장해요. 이 셀들은 자주 사용되는 요소를 담습니다.

딕셔너리 키는 UInt64 타입이에요.

딕셔너리를 검색할 때 캐시가 먼저 검색돼요. 각 데이터 블록에 대해 캐시에서 찾지 못했거나 오래된 모든 키는 SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...)를 사용해 소스에서 요청됩니다. 받은 데이터는 캐시에 기록됩니다.

이것은 키를 조회하는 경우 — dictGet 및 기타 딕셔너리 함수 — 에 적용돼요. SELECT ... FROM <dictionary>로 테이블처럼 딕셔너리를 읽는 것은 다릅니다. 캐시는 어떤 키가 존재하는지 기록하지 않기 때문에, 읽기는 그 순간 캐시에 상주하면서 값을 가진 셀만 열거하고, 키에 대한 WHERE는 가져올 키 목록이 아니라 그 셀들에 대한 일반적인 필터예요. 캐시에 없는 키는 WHERE가 뭐라고 하든 이 방식으로 발견될 수 없어요. 조회되었지만 소스에서 찾지 못한 키도 보이지 않습니다. 캐시는 그 빗나감(miss)을 기본 셀(default cell)로 기억하고, 테이블 읽기는 기본 셀을 건너뛰기 때문이에요. 상주 셀도 소스에서 자유롭지 않습니다. 만료된 셀은 dictGet과 같은 경로로 읽히므로 소스에서 다시 요청됩니다 — 동기적으로, 또는 allow_read_expired_keys가 활성화되어 있으면 비동기적으로요.

CREATE DICTIONARY cache_dict (id UInt64, data String) PRIMARY KEY id
SOURCE(CLICKHOUSE(TABLE 'cache_src')) LIFETIME(MIN 0 MAX 900) LAYOUT(CACHE(SIZE_IN_CELLS 1000));

-- nothing is cached yet, so nothing comes back and the source is not queried
SELECT count() FROM cache_dict WHERE id IN (1, 2, 3);
0

-- looking the keys up populates the cache
SELECT dictGet('cache_dict', 'data', toUInt64(number + 1)) FROM numbers(3);

-- and now the same read sees them
SELECT count() FROM cache_dict WHERE id IN (1, 2, 3);
3

따라서 캐시 딕셔너리는 딕셔너리 함수를 통해 사용하도록 만들어졌어요. 임의 키의 조회가 항상 소스에 도달해야 한다면, 매 조회마다 소스를 쿼리하고 아무것도 캐시하지 않는 direct 레이아웃과 함께 dictGet를 사용하세요. direct 딕셔너리의 테이블 읽기도 키 기반 fetch가 아니라는 점에 유의하세요. SELECT ... FROM <dictionary> WHERE key IN (...)은 키 필터를 딕셔너리로 푸시하지 않기 때문에 전체 소스를 로드하고 이후에 필터링합니다. 딕셔너리를 테이블로 읽으려면 flat이나 hashed처럼 전체를 보유하는 레이아웃을 사용하세요.

키가 딕셔너리에서 발견되지 않으면 업데이트 캐시 작업이 생성되어 업데이트 큐에 추가돼요. 업데이트 큐 속성은 설정 max_update_queue_size, update_queue_push_timeout_milliseconds, query_wait_timeout_milliseconds, max_threads_for_updates로 제어할 수 있어요.

캐시 딕셔너리의 경우 캐시 안 데이터의 만료 lifetime을 설정할 수 있어요. 셀에 데이터가 로드된 후 lifetime보다 더 많은 시간이 지나면 셀의 값은 사용되지 않고 키가 만료됩니다. 그 키는 다음에 사용해야 할 때 다시 요청돼요. 이 동작은 설정 allow_read_expired_keys로 구성할 수 있어요.

이것은 딕셔너리를 저장하는 모든 방법 중 가장 비효율적이에요. 캐시의 속도는 올바른 설정과 사용 시나리오에 크게 의존합니다. 캐시 타입 딕셔너리는 적중률이 충분히 높을 때(권장 99% 이상)만 잘 동작해요. 평균 적중률은 system.dictionaries 테이블에서 볼 수 있어요.

설정 allow_read_expired_keys가 1로 설정되면(기본값 0) 딕셔너리는 비동기 업데이트를 지원할 수 있어요. 클라이언트가 키를 요청했는데 모두 캐시에 있고 일부는 만료되었으면, 딕셔너리는 만료된 키를 클라이언트에 반환하고 소스에서 비동기적으로 다시 요청합니다.

캐시 성능을 개선하려면 LIMIT가 있는 서브쿼리를 사용하고, 함수를 외부에서 딕셔너리로 호출하세요.

모든 종류의 소스가 지원됩니다.

설정 예시:

  • DDL
  • Configuration file
LAYOUT(CACHE(SIZE_IN_CELLS 1000000000))
<layout>
    <cache>
        <!-- The size of the cache, in number of cells. Rounded up to a power of two. -->
        <size_in_cells>1000000000</size_in_cells>
        <!-- Allows to read expired keys. -->
        <allow_read_expired_keys>0</allow_read_expired_keys>
        <!-- Max size of update queue. -->
        <max_update_queue_size>100000</max_update_queue_size>
        <!-- Max timeout in milliseconds for push update task into queue. -->
        <update_queue_push_timeout_milliseconds>10</update_queue_push_timeout_milliseconds>
        <!-- Max wait timeout in milliseconds for update task to complete. -->
        <query_wait_timeout_milliseconds>60000</query_wait_timeout_milliseconds>
        <!-- Max threads for cache dictionary update. -->
        <max_threads_for_updates>4</max_threads_for_updates>
    </cache>
</layout>

충분히 큰 캐시 크기를 설정하세요. 셀 수를 선택하려면 실험이 필요해요.

  1. 어떤 값을 설정한다.
  2. 캐시가 완전히 찰 때까지 쿼리를 실행한다.
  3. system.dictionaries 테이블로 메모리 소비를 평가한다.
  4. 필요한 메모리 소비에 도달할 때까지 셀 수를 늘리거나 줄인다.

ClickHouse는 이 레이아웃의 소스로 권장되지 않아요. 딕셔너리 조회는 무작위 포인트 읽기를 요구하는데, 이는 ClickHouse가 최적화된 접근 패턴이 아니에요.

더 알아보기 (Learn more)