Перейти к основному содержимому

Query Cache

Query Cache — это мощная функция Selena, которая может значительно повысить производительность агрегатных запросов. Сохраняя промежуточные результаты локальных агрегаций в памяти, Query Cache может избежать ненужного доступа к диску и вычислений для новых запросов, которые идентичны или похожи на предыдущие. С помощью Query Cache Selena может предоставлять быстрые и точные результаты для агрегатных запросов, экономя время и ресурсы и обеспечивая лучшую масштабируемость. Query Cache особенно полезен в сценариях с высокой конкурентностью, где многие пользователи выполняют похожие запросы на больших и сложных наборах данных.

Эта функция поддерживается начиная с версии 1.5.0.

В версии 2.5 Query Cache поддерживает только агрегатные запросы к отдельным плоским таблицам. Начиная с версии 1.5.0, Query Cache также поддерживает агрегатные запросы к нескольким таблицам, соединенным в схеме звезды.

Сценарии применения

Мы рекомендуем использовать Query Cache в следующих сценариях:

  • Вы часто выполняете агрегатные запросы к отдельным плоским таблицам или к нескольким соединенным таблицам, которые связаны в схеме звезды.
  • Большинство ваших агрегатных запросов — это агрегатные запросы без GROUP BY и агрегатные запросы GROUP BY с низкой кардинальностью.
  • Ваши данные загружаются в режиме добавления по временным разделам и могут быть категоризированы как горячие данные и холодные данные на основе частоты доступа.

Query Cache поддерживает запросы, которые соответствуют следующим условиям:

  • Движок запросов — Pipeline. Чтобы включить движок Pipeline, установите переменную сессии enable_pipeline_engine в true.

    ПРИМЕЧАНИЕ

    Другие движки запросов не поддерживают Query Cache.

  • Запросы к нативным OLAP таблицам (с версии 2.5) или облачно-нативным таблицам (с версии 3.0). Query Cache не поддерживает запросы к внешним таблицам. Query Cache также поддерживает запросы, планы которых требуют доступа к синхронным материализованным представлениям. Однако Query Cache не поддерживает запросы, планы которых требуют доступа к асинхронным материализованным представлениям.

  • Запросы являются агрегатными запросами к отдельным таблицам или к нескольким соединенным таблицам.

    ПРИМЕЧАНИЕ

    • Query Cache поддерживает Broadcast Join и Bucket Shuffle Join.
    • Query Cache поддерживает две древовидные структуры, содержащие операторы Join: Aggregation-Join и Join-Aggregation. Shuffle joins не поддерживаются в структуре дерева Aggregation-Join, в то время как Hash joins не поддерживаются в структуре дерева Join-Aggregation.
  • Запросы не включают недетерминированные функции, такие как rand, random, uuid и sleep.

Query Cache поддерживает запросы к таблицам, которые используют любую из следующих политик разделения: Unpartitioned, Multi-Column Partitioned и Single-Column Partitioned.

Ограничения функции

  • Query Cache основан на вычислениях по tablet'ам движка Pipeline. Вычисление по tablet'ам означает, что драйвер pipeline может обрабатывать целые tablet'ы один за другим, а не обрабатывать часть tablet'а или множество tablet'ов вперемешку. Если количество tablet'ов, которые нужно обработать каждым отдельным BE для запроса, больше или равно количеству драйверов pipeline, которые вызываются для выполнения этого запроса, Query Cache работает. Количество вызванных драйверов pipeline представляет фактическую степень параллелизма (DOP). Если количество tablet'ов меньше количества драйверов pipeline, каждый драйвер pipeline обрабатывает только часть конкретного tablet'а. В этой ситуации результаты вычислений по tablet'ам не могут быть получены, и поэтому Query Cache не работает.
  • В Selena агрегатный запрос состоит как минимум из четырех этапов. Результаты вычислений по tablet'ам, генерируемые AggregateNode на первом этапе, могут быть кэшированы только тогда, когда OlapScanNode и AggregateNode вычисляют данные из одного и того же фрагмента. Результаты вычислений по tablet'ам, генерируемые AggregateNode на других этапах, не могут быть кэшированы. Для некоторых DISTINCT агрегатных запросов, если переменная сессии cbo_cte_reuse установлена в true, Query Cache не работает, когда OlapScanNode, который производит данные, и AggregateNode первого этапа, который потребляет произведенные данные, вычисляют данные из разных фрагментов и соединены ExchangeNode. Следующие два примера показывают сценарии, в которых выполняются оптимизации CTE и поэтому Query Cache не работает:
    • Выходные столбцы вычисляются с использованием агрегатной функции avg(distinct).
    • Выходные столбцы вычисляются несколькими DISTINCT агрегатными функциями.
  • Если ваши данные перемешиваются перед агрегацией, Query Cache не может ускорить запросы к этим данным.
  • Если столбцы group-by или столбцы дедупликации таблицы являются столбцами с высокой кардинальностью, для агрегатных запросов к этой таблице будут генерироваться большие результаты. В этих случаях запросы будут обходить Query Cache во время выполнения.
  • Query Cache занимает небольшое количество памяти, предоставляемой BE для сохранения результатов вычислений. Размер Query Cache по умолчанию составляет 512 МБ. Поэтому Query Cache не подходит для сохранения элементов данных большого размера. Кроме того, после включения Query Cache производительность запросов снижается, если коэффициент попаданий в кэш низкий. Поэтому, если размер результатов вычислений, генерируемых для tablet'а, превышает порог, указанный параметром query_cache_entry_max_bytes или query_cache_entry_max_rows, Query Cache больше не работает для запроса, и запрос переключается в режим Passthrough.

Как это работает

Когда Query Cache включен, каждый BE разделяет локальную агрегацию запроса на следующие два этапа:

  1. Агрегация по tablet'ам

    BE обрабатывает каждый tablet индивидуально. Когда BE начинает обрабатывать tablet, он сначала проверяет Query Cache, чтобы увидеть, находится ли промежуточный результат агрегации по этому tablet'у в Query Cache. Если да (попадание в кэш), BE напрямую извлекает промежуточный результат из Query Cache. Если нет (промах кэша), BE обращается к данным на диске и выполняет локальную агрегацию для вычисления промежуточного результата. Когда BE заканчивает обработку tablet'а, он заполняет Query Cache промежуточным результатом агрегации по этому tablet'у.

  2. Межтабletная агрегация

    BE собирает промежуточные результаты со всех tablet'ов, участвующих в запросе, и объединяет их в окончательный результат.

    Query cache - How it works - 1

Когда в будущем выдается похожий запрос, он может повторно использовать кэшированный результат предыдущего запроса. Например, запрос, показанный на следующем рисунке, включает три tablet'а (Tablets 0-2), и промежуточный результат для первого tablet'а (Tablet 0) уже находится в Query Cache. Для этого примера BE может напрямую извлечь результат для Tablet 0 из Query Cache вместо обращения к данным на диске. Если Query Cache полностью прогрет, он может содержать промежуточные результаты для всех трех tablet'ов, и таким образом BE не нужно обращаться к каким-либо данным на диске.

Query cache - How it works - 2

Для освобождения дополнительной памяти Query Cache использует политику вытеснения на основе Least Recently Used (LRU) для управления записями кэша в нем. Согласно этой политике вытеснения, когда объем памяти, занимаемый Query Cache, превышает его предопределенный размер (query_cache_capacity), наименее недавно используемые записи кэша вытесняются из Query Cache.

ПРИМЕЧАНИЕ

В будущем Selena также будет поддерживать политику вытеснения на основе Time to Live (TTL), основанную на которой записи кэша могут быть вытеснены из Query Cache.

FE определяет, нужно ли каждый запрос ускорять с помощью Query Cache, и нормализует запросы для устранения тривиальных литеральных деталей, которые не влияют на семантику запросов.

Чтобы предотвратить снижение производительности, вызванное плохими случаями Query Cache, BE использует адаптивную политику для обхода Query Cache во время выполнения.

Включение Query Cache

В этом разделе описываются параметры и переменные сессии, которые используются для включения и настройки Query Cache.

Переменные сессии FE

ПеременнаяЗначение по умолчаниюМожет быть динамически настроенаОписание
enable_query_cachefalseДаУказывает, включать ли Query Cache. Допустимые значения: true и false. true указывает на включение этой функции, а false указывает на отключение этой функции. Когда Query Cache включен, он работает только для запросов, которые соответствуют условиям, указанным в разделе "Сценарии применения" этой темы.
query_cache_entry_max_bytes4194304ДаУказывает порог для запуска режима Passthrough. Допустимые значения: 0 до 9223372036854775807. Когда количество байтов или строк из результатов вычислений конкретного tablet'а, к которому обращается запрос, превышает порог, указанный параметром query_cache_entry_max_bytes или query_cache_entry_max_rows, запрос переключается в режим Passthrough.
Если параметр query_cache_entry_max_bytes или query_cache_entry_max_rows установлен в 0, режим Passthrough используется даже тогда, когда из задействованных tablet'ов не генерируются результаты вычислений.
query_cache_entry_max_rows409600ДаТо же, что и выше.

Параметры BE

Вам нужно настроить следующий параметр в файле конфигурации BE be.conf. После перенастройки этого параметра для BE вы должны перезапустить BE, чтобы новая настройка параметра вступила в силу.

ПараметрОбязательныйОписание
query_cache_capacityНетУказывает размер Query Cache. Единица: байты. Размер по умолчанию составляет 512 МБ.
Каждый BE имеет свой собственный локальный Query Cache в памяти, и он заполняет и проверяет только свой собственный Query Cache.
Обратите внимание, что размер Query Cache не может быть меньше 4 МБ. Если емкость памяти BE недостаточна для обеспечения ожидаемого размера Query Cache, вы можете увеличить емкость памяти BE.

Разработан для максимального коэффициента попаданий в кэш во всех сценариях

Рассмотрим три сценария, где Query Cache все еще эффективен, даже когда запросы не идентичны буквально. Эти три сценария:

  • Семантически эквивалентные запросы
  • Запросы с перекрывающимися сканируемыми разделами
  • Запросы к данным с изменениями данных только для добавления (без операций UPDATE или DELETE)

Семантически эквивалентные запросы

Когда два запроса похожи, что не означает, что они должны быть буквально эквивалентными, но означает, что они содержат семантически эквивалентные фрагменты в своих планах выполнения, они считаются семантически эквивалентными и могут повторно использовать результаты вычислений друг друга. В широком смысле два запроса семантически эквивалентны, если они запрашивают данные из одного источника, используют один и тот же метод вычисления и имеют один и тот же план выполнения. Selena применяет следующие правила для оценки того, являются ли два запроса семантически эквивалентными:

  • Если два запроса содержат несколько агрегаций, они оцениваются как семантически эквивалентные, если их первые агрегации семантически эквивалентны. Например, следующие два запроса, Q1 и Q2, оба содержат несколько агрегаций, но их первые агрегации семантически эквивалентны. Поэтому Q1 и Q2 оцениваются как семантически эквивалентные.

    • Q1

      SELECT
      (
      ifnull(sum(murmur_hash3_32(hour)), 0) + ifnull(sum(murmur_hash3_32(k0)), 0) + ifnull(sum(murmur_hash3_32(__c_0)), 0)
      ) AS fingerprint
      FROM
      (
      SELECT
      date_trunc('hour', ts) AS hour,
      k0,
      sum(v1) AS __c_0
      FROM
      t0
      WHERE
      ts between '2022-01-03 00:00:00'
      and '2022-01-03 23:59:59'
      GROUP BY
      date_trunc('hour', ts),
      k0
      ) AS t;
    • Q2

      SELECT
      date_trunc('hour', ts) AS hour,
      k0,
      sum(v1) AS __c_0
      FROM
      t0
      WHERE
      ts between '2022-01-03 00:00:00'
      and '2022-01-03 23:59:59'
      GROUP BY
      date_trunc('hour', ts),
      k0
  • Если оба запроса принадлежат к одному из следующих типов запросов, они могут быть оценены как семантически эквивалентные. Обратите внимание, что запросы, которые включают предложение HAVING, не могут быть оценены как семантически эквивалентные запросам, которые не включают предложение HAVING. Однако включение предложения ORDER BY или LIMIT не влияет на оценку того, являются ли два запроса семантически эквивалентными.

    • GROUP BY агрегации

      SELECT <GroupByItems>, <AggFunctionItems> 
      FROM <Table>
      WHERE <Predicates> [and <PartitionColumnRangePredicate>]
      GROUP BY <GroupByItems>
      [HAVING <HavingPredicate>]

      ПРИМЕЧАНИЕ

      В предыдущем примере предложение HAVING является необязательным.

    • GROUP BY DISTINCT агрегации

      SELECT DISTINCT <GroupByItems>, <Items> 
      FROM <Table>
      WHERE <Predicates> [and <PartitionColumnRangePredicate>]
      GROUP BY <GroupByItems>
      HAVING <HavingPredicate>

      ПРИМЕЧАНИЕ

      В предыдущем примере предложение HAVING является необязательным.

    • Агрегации без GROUP BY

      SELECT <AggFunctionItems> FROM <Table> 
      WHERE <Predicates> [and <PartitionColumnRangePredicate>]
    • DISTINCT агрегации без GROUP BY

      SELECT DISTINCT <Items> FROM <Table> 
      WHERE <Predicates> [and <PartitionColumnRangePredicate>]
  • Если любой из запросов включает PartitionColumnRangePredicate, PartitionColumnRangePredicate удаляется перед оценкой двух запросов на семантическую эквивалентность. PartitionColumnRangePredicate указывает один из следующих типов предикатов, которые ссылаются на столбец разделения:

    • col between v1 and v2: Значения столбца разделения попадают в диапазон [v1, v2], где v1 и v2 — константные выражения.
    • v1 < col and col < v2: Значения столбца разделения попадают в диапазон (v1, v2), где v1 и v2 — константные выражения.
    • v1 < col and col <= v2: Значения столбца разделения попадают в диапазон (v1, v2], где v1 и v2 — константные выражения.
    • v1 <= col and col < v2: Значения столбца разделения попадают в диапазон [v1, v2), где v1 и v2 — константные выражения.
    • v1 <= col and col <= v2: Значения столбца разделения попадают в диапазон [v1, v2], где v1 и v2 — константные выражения.
  • Если выходные столбцы предложений SELECT двух запросов одинаковы после их перестановки, два запроса оцениваются как семантически эквивалентные.

  • Если выходные столбцы предложений GROUP BY двух запросов одинаковы после их перестановки, два запроса оцениваются как семантически эквивалентные.

  • Если оставшиеся предикаты предложений WHERE двух запросов семантически эквивалентны после удаления PartitionColumnRangePredicate, два запроса оцениваются как семантически эквивалентные.

  • Если предикаты в предложениях HAVING двух запросов семантически эквивалентны, два запроса оцениваются как семантически эквивалентные.

Используйте следующую таблицу lineorder_flat в качестве примера:

CREATE TABLE `lineorder_flat`
(
`lo_orderdate` date NOT NULL COMMENT "",
`lo_orderkey` int(11) NOT NULL COMMENT "",
`lo_linenumber` tinyint(4) NOT NULL COMMENT "",
`lo_custkey` int(11) NOT NULL COMMENT "",
`lo_partkey` int(11) NOT NULL COMMENT "",
`lo_suppkey` int(11) NOT NULL COMMENT "",
`lo_orderpriority` varchar(100) NOT NULL COMMENT "",
`lo_shippriority` tinyint(4) NOT NULL COMMENT "",
`lo_quantity` tinyint(4) NOT NULL COMMENT "",
`lo_extendedprice` int(11) NOT NULL COMMENT "",
`lo_ordtotalprice` int(11) NOT NULL COMMENT "",
`lo_discount` tinyint(4) NOT NULL COMMENT "",
`lo_revenue` int(11) NOT NULL COMMENT "",
`lo_supplycost` int(11) NOT NULL COMMENT "",
`lo_tax` tinyint(4) NOT NULL COMMENT "",
`lo_commitdate` date NOT NULL COMMENT "",
`lo_shipmode` varchar(100) NOT NULL COMMENT "",
`c_name` varchar(100) NOT NULL COMMENT "",
`c_address` varchar(100) NOT NULL COMMENT "",
`c_city` varchar(100) NOT NULL COMMENT "",
`c_nation` varchar(100) NOT NULL COMMENT "",
`c_region` varchar(100) NOT NULL COMMENT "",
`c_phone` varchar(100) NOT NULL COMMENT "",
`c_mktsegment` varchar(100) NOT NULL COMMENT "",
`s_name` varchar(100) NOT NULL COMMENT "",
`s_address` varchar(100) NOT NULL COMMENT "",
`s_city` varchar(100) NOT NULL COMMENT "",
`s_nation` varchar(100) NOT NULL COMMENT "",
`s_region` varchar(100) NOT NULL COMMENT "",
`s_phone` varchar(100) NOT NULL COMMENT "",
`p_name` varchar(100) NOT NULL COMMENT "",
`p_mfgr` varchar(100) NOT NULL COMMENT "",
`p_category` varchar(100) NOT NULL COMMENT "",
`p_brand` varchar(100) NOT NULL COMMENT "",
`p_color` varchar(100) NOT NULL COMMENT "",
`p_type` varchar(100) NOT NULL COMMENT "",
`p_size` tinyint(4) NOT NULL COMMENT "",
`p_container` varchar(100) NOT NULL COMMENT ""
)
ENGINE=OLAP
DUPLICATE KEY(`lo_orderdate`, `lo_orderkey`)
COMMENT "olap"
PARTITION BY RANGE(`lo_orderdate`)
(PARTITION p1 VALUES [('0000-01-01'), ('1993-01-01')),
PARTITION p2 VALUES [('1993-01-01'), ('1994-01-01')),
PARTITION p3 VALUES [('1994-01-01'), ('1995-01-01')),
PARTITION p4 VALUES [('1995-01-01'), ('1996-01-01')),
PARTITION p5 VALUES [('1996-01-01'), ('1997-01-01')),
PARTITION p6 VALUES [('1997-01-01'), ('1998-01-01')),
PARTITION p7 VALUES [('1998-01-01'), ('1999-01-01')))
DISTRIBUTED BY HASH(`lo_orderkey`)
PROPERTIES
(
"replication_num" = "3",
"colocate_with" = "groupxx1",
"storage_format" = "DEFAULT",
"enable_persistent_index" = "false",
"compression" = "LZ4"
);

Следующие два запроса, Q1 и Q2, к таблице lineorder_flat семантически эквивалентны после их обработки следующим образом:

  1. Переставить выходные столбцы оператора SELECT.
  2. Переставить выходные столбцы предложения GROUP BY.
  3. Удалить выходные столбцы предложения ORDER BY.
  4. Переставить предикаты в предложении WHERE.
  5. Добавить PartitionColumnRangePredicate.
  • Q1

    SELECT sum(lo_revenue), year(lo_orderdate) AS year,p_brand
    FROM lineorder_flat
    WHERE p_category = 'MFGR#12' AND s_region = 'AMERICA'
    GROUP BY year,p_brand
    ORDER BY year,p_brand;
  • Q2

    SELECT year(lo_orderdate) AS year, p_brand, sum(lo_revenue)
    FROM lineorder_flat
    WHERE s_region = 'AMERICA' AND p_category = 'MFGR#12' AND
    lo_orderdate >= '1993-01-01' AND lo_orderdate <= '1993-12-31'
    GROUP BY p_brand, year(lo_orderdate)

Семантическая эквивалентность оценивается на основе физических планов запросов. Поэтому буквальные различия в запросах не влияют на оценку семантической эквивалентности. Кроме того, константные выражения удаляются из запросов, а выражения cast удаляются во время оптимизации запросов. Поэтому эти выражения не влияют на оценку семантической эквивалентности. В-третьих, псевдонимы столбцов и отношений также не влияют на оценку семантической эквивалентности.

Запросы с перекрывающимися сканируемыми разделами

Query Cache поддерживает разделение запросов на основе предикатов.

Разделение запросов на основе семантики предикатов помогает реализовать повторное использование частичных результатов вычислений. Когда запрос содержит предикат, который ссылается на столбец разделения таблицы, и предикат указывает диапазон значений, Selena может разделить диапазон на несколько интервалов на основе разделения таблицы. Результаты вычислений из каждого отдельного интервала могут быть отдельно повторно использованы другими запросами.

Используйте следующую таблицу t0 в качестве примера:

CREATE TABLE if not exists t0
(
ts DATETIME NOT NULL,
k0 VARCHAR(10) NOT NULL,
k1 BIGINT NOT NULL,
v1 DECIMAL64(7, 2) NOT NULL
)
ENGINE=OLAP
DUPLICATE KEY(`ts`, `k0`, `k1`)
COMMENT "OLAP"
PARTITION BY RANGE(ts)
(
START ("2022-01-01 00:00:00") END ("2022-02-01 00:00:00") EVERY (INTERVAL 1 day)
)
DISTRIBUTED BY HASH(`ts`, `k0`, `k1`)
PROPERTIES
(
"replication_num" = "3",
"storage_format" = "default"
);

Таблица t0 разделена по дням, и столбец ts является столбцом разделения таблицы. Среди следующих четырех запросов Q2, Q3 и Q4 могут повторно использовать части результатов вычислений, кэшированных для Q1:

  • Q1

    SELECT date_trunc('day', ts) as day, sum(v0)
    FROM t0
    WHERE ts BETWEEN '2022-01-02 12:30:00' AND '2022-01-14 23:59:59'
    GROUP BY day;

    Диапазон значений, указанный предикатом ts between '2022-01-02 12:30:00' and '2022-01-14 23:59:59' запроса Q1, может быть разделен на следующие интервалы:

    1. [2022-01-02 12:30:00, 2022-01-03 00:00:00),
    2. [2022-01-03 00:00:00, 2022-01-04 00:00:00),
    3. [2022-01-04 00:00:00, 2022-01-05 00:00:00),
    ...
    12. [2022-01-13 00:00:00, 2022-01-14 00:00:00),
    13. [2022-01-14 00:00:00, 2022-01-15 00:00:00),
  • Q2

    SELECT date_trunc('day', ts) as day, sum(v0)
    FROM t0
    WHERE ts >= '2022-01-02 12:30:00' AND ts < '2022-01-05 00:00:00'
    GROUP BY day;

    Q2 может повторно использовать результаты вычислений в следующих интервалах Q1:

    1. [2022-01-02 12:30:00, 2022-01-03 00:00:00),
    2. [2022-01-03 00:00:00, 2022-01-04 00:00:00),
    3. [2022-01-04 00:00:00, 2022-01-05 00:00:00),
  • Q3

    SELECT date_trunc('day', ts) as day, sum(v0)
    FROM t0
    WHERE ts >= '2022-01-01 12:30:00' AND ts <= '2022-01-10 12:00:00'
    GROUP BY day;

    Q3 может повторно использовать результаты вычислений в следующих интервалах Q1:

    2. [2022-01-03 00:00:00, 2022-01-04 00:00:00),
    3. [2022-01-04 00:00:00, 2022-01-05 00:00:00),
    ...
    8. [2022-01-09 00:00:00, 2022-01-10 00:00:00),
  • Q4

    SELECT date_trunc('day', ts) as day, sum(v0)
    FROM t0
    WHERE ts BETWEEN '2022-01-02 12:30:00' and '2022-01-02 23:59:59'
    GROUP BY day;

    Q4 может повторно использовать результаты вычислений в следующих интервалах Q1:

    1. [2022-01-02 12:30:00, 2022-01-03 00:00:00),

Поддержка повторного использования частичных результатов вычислений варьируется в зависимости от используемой политики разделения, как описано в следующей таблице.

Политика разделенияПоддержка повторного использования частичных результатов вычислений
UnpartitionedНе поддерживается
Multi-Column PartitionedНе поддерживается
ПРИМЕЧАНИЕ
Эта функция может быть поддержана в будущем.
Single-Column PartitionedПоддерживается

Запросы к данным с изменениями данных только для добавления

Query Cache поддерживает многоверсионное кэширование.

По мере загрузки данных генерируются новые версии tablet'ов. Следовательно, кэшированные результаты вычислений, которые генерируются из предыдущих версий tablet'ов, становятся устаревшими и отстают от последних версий tablet'ов. В этой ситуации механизм многоверсионного кэширования пытается объединить устаревшие результаты, сохраненные в Query Cache, и инкрементальные версии tablet'ов, хранящиеся на диске, в окончательные результаты tablet'ов, чтобы новые запросы могли нести последние версии tablet'ов. Многоверсионное кэширование ограничено типами таблиц, типами запросов и типами обновления данных.

Поддержка многоверсионного кэширования варьируется в зависимости от типов таблиц и типов запросов, как описано в следующей таблице.

Тип таблицыТип запросаПоддержка многоверсионного кэширования
Duplicate Key table
  • Запросы к базовым таблицам
  • Запросы к синхронным материализованным представлениям
  • Запросы к базовым таблицам: поддерживается во всех ситуациях, кроме случаев, когда инкрементальные версии tablet'ов содержат записи удаления данных.
  • Запросы к синхронным материализованным представлениям: поддерживается во всех ситуациях, кроме случаев, когда предложения GROUP BY, HAVING или WHERE запросов ссылаются на столбцы агрегации.
Aggregate tableЗапросы к базовым таблицам или запросы к синхронным материализованным представлениямПоддерживается во всех ситуациях, кроме следующих: Схемы базовых таблиц содержат агрегатную функцию replace. Предложения GROUP BY, HAVING или WHERE запросов ссылаются на столбцы агрегации. Инкрементальные версии tablet'ов содержат записи удаления данных.
Unique Key tableН/ДНе поддерживается. Однако Query Cache поддерживается.
Primary Key tableН/ДНе поддерживается. Однако Query Cache поддерживается.

Влияние типов обновления данных на многоверсионное кэширование следующее:

  • Удаления данных

    Многоверсионное кэширование не может работать, если инкрементальные версии tablet'ов содержат операции удаления.

  • Вставки данных

    • Если для tablet'а генерируется пустая версия, существующие данные tablet'а в Query Cache остаются действительными и все еще могут быть извлечены.
    • Если для tablet'а генерируется непустая версия, существующие данные tablet'а в Query Cache остаются действительными, но их версия отстает от последней версии tablet'а. В этой ситуации Selena читает инкрементальные данные, сгенерированные от версии существующих данных до последней версии tablet'а, объединяет существующие данные с инкрементальными данными и заполняет объединенные данные в Query Cache.
  • Изменения схемы и усечения tablet'ов

    Если схема таблицы изменяется или конкретные tablet'ы таблицы усекаются, для таблицы генерируются новые tablet'ы. В результате существующие данные tablet'ов таблицы в Query Cache становятся недействительными.

Метрики

Профили запросов, для которых работает Query Cache, содержат статистику CacheOperator.

В исходном плане запроса, если pipeline содержит OlapScanOperator, имена OlapScanOperator и агрегатных операторов имеют префикс ML_, чтобы обозначить, что pipeline использует MultilaneOperator для выполнения вычислений по tablet'ам. CacheOperator вставляется перед ML_CONJUGATE_AGGREGATE для обработки логики, которая контролирует, как Query Cache работает в режимах Passthrough, Populate и Probe. Профиль запроса содержит следующие метрики CacheOperator, которые помогают понять использование Query Cache.

МетрикаОписание
CachePassthroughBytesКоличество байтов, сгенерированных в режиме Passthrough.
CachePassthroughChunkNumКоличество chunks, сгенерированных в режиме Passthrough.
CachePassthroughRowNumКоличество строк, сгенерированных в режиме Passthrough.
CachePassthroughTabletNumКоличество tablet'ов, сгенерированных в режиме Passthrough.
CachePassthroughTime:Количество времени вычислений, затраченного в режиме Passthrough.
CachePopulateBytesКоличество байтов, сгенерированных в режиме Populate.
CachePopulateChunkNumКоличество chunks, сгенерированных в режиме Populate.
CachePopulateRowNumКоличество строк, сгенерированных в режиме Populate.
CachePopulateTabletNumКоличество tablet'ов, сгенерированных в режиме Populate.
CachePopulateTimeКоличество времени вычислений, затраченного в режиме Populate.
CacheProbeBytesКоличество байтов, сгенерированных для попаданий в кэш в режиме Probe.
CacheProbeChunkNumКоличество chunks, сгенерированных для попаданий в кэш в режиме Probe.
CacheProbeRowNumКоличество строк, сгенерированных для попаданий в кэш в режиме Probe.
CacheProbeTabletNumКоличество tablet'ов, сгенерированных для попаданий в кэш в режиме Probe.
CacheProbeTimeКоличество времени вычислений, затраченного в режиме Probe.

Метрики CachePopulateXXX предоставляют статистику о промахах Query Cache, для которых Query Cache обновляется.

Метрики CachePassthroughXXX предоставляют статистику о промахах Query Cache, для которых Query Cache не обновляется, потому что размер сгенерированных результатов вычислений по tablet'ам большой.

Метрики CacheProbeXXX предоставляют статистику о попаданиях в Query Cache.

В механизме многоверсионного кэширования метрики CachePopulate и метрики CacheProbe могут содержать одинаковую статистику tablet'ов, а метрики CachePassthrough и метрики CacheProbe также могут содержать одинаковую статистику tablet'ов. Например, когда Selena вычисляет данные каждого tablet'а, он попадает в результаты вычислений, сгенерированные на исторической версии tablet'а. В этой ситуации Selena читает инкрементальные данные, сгенерированные от исторической версии до последней версии tablet'а, вычисляет данные и объединяет инкрементальные данные с кэшированными данными. Если размер результатов вычислений, сгенерированных после объединения, не превышает порог, указанный параметром query_cache_entry_max_bytes или query_cache_entry_max_rows, статистика tablet'а собирается в метрики CachePopulate. В противном случае статистика tablet'а собирается в метрики CachePassthrough.

Операции RESTful API

  • metrics |grep query_cache

    Эта операция API используется для запроса метрик, связанных с Query Cache.

    curl -s  http://<be_host>:<be_http_port>/metrics |grep query_cache

    # TYPE starrocks_be_query_cache_capacity gauge
    starrocks_be_query_cache_capacity 536870912
    # TYPE starrocks_be_query_cache_hit_count gauge
    starrocks_be_query_cache_hit_count 5084393
    # TYPE starrocks_be_query_cache_hit_ratio gauge
    starrocks_be_query_cache_hit_ratio 0.984098
    # TYPE starrocks_be_query_cache_lookup_count gauge
    starrocks_be_query_cache_lookup_count 5166553
    # TYPE starrocks_be_query_cache_usage gauge
    starrocks_be_query_cache_usage 0
    # TYPE starrocks_be_query_cache_usage_ratio gauge
    starrocks_be_query_cache_usage_ratio 0.000000
  • api/query_cache/stat

    Эта операция API используется для запроса использования Query Cache.

    curl  http://<be_host>:<be_http_port>/api/query_cache/stat
    {
    "capacity": 536870912,
    "usage": 0,
    "usage_ratio": 0.0,
    "lookup_count": 5025124,
    "hit_count": 4943720,
    "hit_ratio": 0.983800598751394
    }
  • api/query_cache/invalidate_all

    Эта операция API используется для очистки Query Cache.

    curl  -XPUT http://<be_host>:<be_http_port>/api/query_cache/invalidate_all

    {
    "status": "OK"
    }

Параметры в предыдущих операциях API следующие:

  • be_host: IP-адрес узла, на котором находится BE.
  • be_http_port: номер HTTP-порта узла, на котором находится BE.

Меры предосторожности

  • Selena нужно заполнить Query Cache результатами вычислений запросов, которые инициируются в первый раз. В результате производительность запросов может быть немного ниже ожидаемой, а задержка запросов увеличивается.
  • Если вы настроите большой размер Query Cache, количество памяти, которое может быть выделено для оценки запросов на BE, уменьшается. Мы рекомендуем, чтобы размер Query Cache не превышал 1/6 емкости памяти, выделенной для оценки запросов.
  • Если количество tablet'ов, которые нужно обработать, меньше значения pipeline_dop, Query Cache не работает. Чтобы включить работу Query Cache, вы можете установить pipeline_dop на меньшее значение, такое как 1. Начиная с версии 1.5.0, Selena адаптивно настраивает этот параметр на основе параллелизма запросов.

Примеры

Набор данных

  1. Войдите в ваш кластер Selena, перейдите в целевую базу данных и выполните следующую команду для создания таблицы с именем t0:

    CREATE TABLE t0
    (
    `ts` datetime NOT NULL COMMENT "",
    `k0` varchar(10) NOT NULL COMMENT "",
    `k1` char(6) NOT NULL COMMENT "",
    `v0` bigint(20) NOT NULL COMMENT "",
    `v1` decimal64(7, 2) NOT NULL COMMENT ""
    )
    ENGINE=OLAP
    DUPLICATE KEY(`ts`, `k0`, `k1`)
    COMMENT "OLAP"
    PARTITION BY RANGE(`ts`)
    (
    START ("2022-01-01 00:00:00") END ("2022-02-01 00:00:00") EVERY (INTERVAL 1 DAY)
    )
    DISTRIBUTED BY HASH(`ts`, `k0`, `k1`)
    PROPERTIES
    (
    "replication_num" = "3",
    "storage_format" = "DEFAULT",
    "enable_persistent_index" = "false"
    );
  2. Вставьте следующие записи данных в t0:

    INSERT INTO t0
    VALUES
    ('2022-01-11 20:42:26', 'n4AGcEqYp', 'hhbawx', '799393174109549', '8029.42'),
    ('2022-01-27 18:17:59', 'i66lt', 'mtrtzf', '100400167', '10000.88'),
    ('2022-01-28 20:10:44', 'z6', 'oqkeun', '-58681382337', '59881.87'),
    ('2022-01-29 14:54:31', 'qQ', 'dzytua', '-19682006834', '43807.02'),
    ('2022-01-31 08:08:11', 'qQ', 'dzytua', '7970665929984223925', '-8947.74'),
    ('2022-01-15 00:40:58', '65', 'hhbawx', '4054945', '156.56'),
    ('2022-01-24 16:17:51', 'onqR3JsK1', 'udtmfp', '-12962', '-72127.53'),
    ('2022-01-01 22:36:24', 'n4AGcEqYp', 'fabnct', '-50999821', '17349.85'),
    ('2022-01-21 08:41:50', 'Nlpz1j3h', 'dzytua', '-60162', '287.06'),
    ('2022-01-30 23:44:55', '', 'slfght', '62891747919627339', '8014.98'),
    ('2022-01-18 19:14:28', 'z6', 'dzytua', '-1113001726', '73258.24'),
    ('2022-01-30 14:54:59', 'z6', 'udtmfp', '111175577438857975', '-15280.41'),
    ('2022-01-08 22:08:26', 'z6', 'ympyls', '3', '2.07'),
    ('2022-01-03 08:17:29', 'Nlpz1j3h', 'udtmfp', '-234492', '217.58'),
    ('2022-01-27 07:28:47', 'Pc', 'cawanm', '-1015', '-20631.50'),
    ('2022-01-17 14:07:47', 'Nlpz1j3h', 'lbsvqu', '2295574006197343179', '93768.75'),
    ('2022-01-31 14:00:12', 'onqR3JsK1', 'umlkpo', '-227', '-66199.05'),
    ('2022-01-05 20:31:26', '65', 'lbsvqu', '684307', '36412.49'),
    ('2022-01-06 00:51:34', 'z6', 'dzytua', '11700309310', '-26064.10'),
    ('2022-01-26 02:59:00', 'n4AGcEqYp', 'slfght', '-15320250288446', '-58003.69'),
    ('2022-01-05 03:26:26', 'z6', 'cawanm', '19841055192960542', '-5634.36'),
    ('2022-01-17 08:51:23', 'Pc', 'ghftus', '35476438804110', '13625.99'),
    ('2022-01-30 18:56:03', 'n4AGcEqYp', 'lbsvqu', '3303892099598', '8.37'),
    ('2022-01-22 14:17:18', 'i66lt', 'umlkpo', '-27653110', '-82306.25'),
    ('2022-01-02 10:25:01', 'qQ', 'ghftus', '-188567166', '71442.87'),
    ('2022-01-30 04:58:14', 'Pc', 'ympyls', '-9983', '-82071.59'),
    ('2022-01-05 00:16:56', '7Bh', 'hhbawx', '43712', '84762.97'),
    ('2022-01-25 03:25:53', '65', 'mtrtzf', '4604107', '-2434.69'),
    ('2022-01-27 21:09:10', '65', 'udtmfp', '476134823953365199', '38736.04'),
    ('2022-01-11 13:35:44', '65', 'qmwhvr', '1', '0.28'),
    ('2022-01-03 19:13:07', '', 'lbsvqu', '11', '-53084.04'),
    ('2022-01-20 02:27:25', 'i66lt', 'umlkpo', '3218824416', '-71393.20'),
    ('2022-01-04 04:52:36', '7Bh', 'ghftus', '-112543071', '-78377.93'),
    ('2022-01-27 18:27:06', 'Pc', 'umlkpo', '477', '-98060.13'),
    ('2022-01-04 19:40:36', '', 'udtmfp', '433677211', '-99829.94'),
    ('2022-01-20 23:19:58', 'Nlpz1j3h', 'udtmfp', '361394977', '-19284.18'),
    ('2022-01-05 02:17:56', 'Pc', 'oqkeun', '-552390906075744662', '-25267.92'),
    ('2022-01-02 16:14:07', '65', 'dzytua', '132', '2393.77'),
    ('2022-01-28 23:17:14', 'z6', 'umlkpo', '61', '-52028.57'),
    ('2022-01-12 08:05:44', 'qQ', 'hhbawx', '-9579605666539132', '-87801.81'),
    ('2022-01-31 19:48:22', 'z6', 'lbsvqu', '9883530877822', '34006.42'),
    ('2022-01-11 20:38:41', '', 'piszhr', '56108215256366', '-74059.80'),
    ('2022-01-01 04:15:17', '65', 'cawanm', '-440061829443010909', '88960.51'),
    ('2022-01-05 07:26:09', 'qQ', 'umlkpo', '-24889917494681901', '-23372.12'),
    ('2022-01-29 18:13:55', 'Nlpz1j3h', 'cawanm', '-233', '-24294.42'),
    ('2022-01-10 00:49:45', 'Nlpz1j3h', 'ympyls', '-2396341', '77723.88'),
    ('2022-01-29 08:02:58', 'n4AGcEqYp', 'slfght', '45212', '93099.78'),
    ('2022-01-28 08:59:21', 'onqR3JsK1', 'oqkeun', '76', '-78641.65'),
    ('2022-01-26 14:29:39', '7Bh', 'umlkpo', '176003552517', '-99999.96'),
    ('2022-01-03 18:53:37', '7Bh', 'piszhr', '3906151622605106', '55723.01'),
    ('2022-01-04 07:08:19', 'i66lt', 'ympyls', '-240097380835621', '-81800.87'),
    ('2022-01-28 14:54:17', 'Nlpz1j3h', 'slfght', '-69018069110121', '90533.64'),
    ('2022-01-22 07:48:53', 'Pc', 'ympyls', '22396835447981344', '-12583.39'),
    ('2022-01-22 07:39:29', 'Pc', 'uqkghp', '10551305', '52163.82'),
    ('2022-01-08 22:39:47', 'Nlpz1j3h', 'cawanm', '67905472699', '87831.30'),
    ('2022-01-05 14:53:34', '7Bh', 'dzytua', '-779598598706906834', '-38780.41'),
    ('2022-01-30 17:34:41', 'onqR3JsK1', 'oqkeun', '346687625005524', '-62475.31'),
    ('2022-01-29 12:14:06', '', 'qmwhvr', '3315', '22076.88'),
    ('2022-01-05 06:47:04', 'Nlpz1j3h', 'udtmfp', '-469', '42747.17'),
    ('2022-01-19 15:20:20', '7Bh', 'lbsvqu', '347317095885', '-76393.49'),
    ('2022-01-08 16:18:22', 'z6', 'fghmcd', '2', '90315.60'),
    ('2022-01-02 00:23:06', 'Pc', 'piszhr', '-3651517384168400', '58220.34'),
    ('2022-01-12 08:23:31', 'onqR3JsK1', 'udtmfp', '5636394870355729225', '33224.25'),
    ('2022-01-28 10:46:44', 'onqR3JsK1', 'oqkeun', '-28102078612755', '6469.53'),
    ('2022-01-23 23:16:11', 'onqR3JsK1', 'ghftus', '-707475035515433949', '63422.66'),
    ('2022-01-03 05:32:31', 'z6', 'hhbawx', '-45', '-49680.52'),
    ('2022-01-27 03:24:33', 'qQ', 'qmwhvr', '375943906057539870', '-66092.96'),
    ('2022-01-25 20:07:22', '7Bh', 'slfght', '1', '72440.21'),
    ('2022-01-04 16:07:24', 'qQ', 'uqkghp', '751213107482249', '16417.31'),
    ('2022-01-23 19:22:00', 'Pc', 'hhbawx', '-740731249600493', '88439.40'),
    ('2022-01-05 09:04:20', '7Bh', 'cawanm', '23602', '302.44');

Примеры запросов

Статистика метрик, связанных с Query Cache, в этом разделе является примерами и предназначена только для справки.

Query Cache работает для локальных агрегаций на этапе 1

Это включает три ситуации:

  • Запрос обращается только к одному tablet'у.
  • Запрос обращается к нескольким tablet'ам из нескольких разделов таблицы, которая сама составляет colocated group, и данные не нужно перемешивать для агрегаций.
  • Запрос обращается к нескольким tablet'ам из одного раздела таблицы, и данные не нужно перемешивать для агрегаций.

Пример запроса:

SELECT
date_trunc('hour', ts) AS hour,
k0,
sum(v1) AS __c_0
FROM
t0
WHERE
ts between '2022-01-03 00:00:00'
and '2022-01-03 23:59:59'
GROUP BY
date_trunc('hour', ts),
k0

Следующий рисунок показывает метрики, связанные с Query Cache, в профиле запроса.

Query Cache - Stage 1 - Metrics

Query Cache не работает для удаленных агрегаций на этапе 1

Когда агрегации на нескольких tablet'ах принудительно выполняются на этапе 1, данные сначала перемешиваются, а затем агрегируются.

Пример запроса:

SET new_planner_agg_stage = 1;

SELECT
date_trunc('hour', ts) AS hour,
v0 % 2 AS is_odd,
sum(v1) AS __c_0
FROM
t0
WHERE
ts between '2022-01-03 00:00:00'
and '2022-01-03 23:59:59'
GROUP BY
date_trunc('hour', ts),
is_odd

Query Cache работает для локальных агрегаций на этапе 2

Это включает три ситуации:

  • Агрегации на этапе 2 запроса компилируются для сравнения одного типа данных. Первая агрегация — это локальная агрегация. После завершения первой агрегации результаты, сгенерированные из первой агрегации, вычисляются для выполнения второй агрегации, которая является глобальной агрегацией.
  • Запрос — это запрос SELECT DISTINCT.
  • Запрос включает одну из следующих DISTINCT агрегатных функций: sum(distinct), count(distinct) и avg(distinct). В большинстве случаев агрегации выполняются на этапе 3 или 4 для такого запроса. Однако вы можете выполнить set new_planner_agg_stage = 1, чтобы принудительно выполнить агрегации на этапе 2 для запроса. Если запрос содержит avg(distinct) и вы хотите выполнить агрегации на этапе, вам также нужно выполнить set cbo_cte_reuse = false, чтобы отключить оптимизации CTE.

Пример запроса:

SELECT
date_trunc('hour', ts) AS hour,
v0 % 2 AS is_odd,
sum(v1) AS __c_0
FROM
t0
WHERE
ts between '2022-01-03 00:00:00'
and '2022-01-03 23:59:59'
GROUP BY
date_trunc('hour', ts),
is_odd

Следующий рисунок показывает метрики, связанные с Query Cache, в профиле запроса.

Query Cache - Stage 2 - Metrics

Query Cache работает для локальных агрегаций на этапе 3

Запрос — это GROUP BY агрегатный запрос, который включает одну DISTINCT агрегатную функцию.

Поддерживаемые DISTINCT агрегатные функции: sum(distinct), count(distinct) и avg(distinct).

УВЕДОМЛЕНИЕ

Если запрос включает avg(distinct), вам также нужно выполнить set cbo_cte_reuse = false, чтобы отключить оптимизации CTE.

Пример запроса:

SELECT
date_trunc('hour', ts) AS hour,
v0 % 2 AS is_odd,
sum(distinct v1) AS __c_0
FROM
t0
WHERE
ts between '2022-01-03 00:00:00'
and '2022-01-03 23:59:59'
GROUP BY
date_trunc('hour', ts),
is_odd;

Следующий рисунок показывает метрики, связанные с Query Cache, в профиле запроса.

Query Cache - Stage 3 - Metrics

Query Cache работает для локальных агрегаций на этапе 4

Запрос — это агрегатный запрос без GROUP BY, который включает одну DISTINCT агрегатную функцию. Такие запросы включают классические запросы, которые удаляют дублированные данные.

Пример запроса:

SELECT
count(distinct v1) AS __c_0
FROM
t0
WHERE
ts between '2022-01-03 00:00:00'
and '2022-01-03 23:59:59'

Следующий рисунок показывает метрики, связанные с Query Cache, в профиле запроса.

Query Cache - Stage 4 - Metrics

Кэшированные результаты повторно используются для двух запросов, чьи первые агрегации семантически эквивалентны

Используйте следующие два запроса, Q1 и Q2, в качестве примера. Q1 и Q2 оба включают несколько агрегаций, но их первые агрегации семантически эквивалентны. Поэтому Q1 и Q2 оцениваются как семантически эквивалентные и могут повторно использовать результаты вычислений друг друга, сохраненные в Query Cache.

  • Q1

    SELECT
    (
    ifnull(sum(murmur_hash3_32(hour)), 0) + ifnull(sum(murmur_hash3_32(k0)), 0) + ifnull(sum(murmur_hash3_32(__c_0)), 0)
    ) AS fingerprint
    FROM
    (
    SELECT
    date_trunc('hour', ts) AS hour,
    k0,
    sum(v1) AS __c_0
    FROM
    t0
    WHERE
    ts between '2022-01-03 00:00:00'
    and '2022-01-03 23:59:59'
    GROUP BY
    date_trunc('hour', ts),
    k0
    ) AS t;
  • Q2

    SELECT
    date_trunc('hour', ts) AS hour,
    k0,
    sum(v1) AS __c_0
    FROM
    t0
    WHERE
    ts between '2022-01-03 00:00:00'
    and '2022-01-03 23:59:59'
    GROUP BY
    date_trunc('hour', ts),
    k0

Следующий рисунок показывает метрики CachePopulate для Q1.

Query Cache - Q1 - Metrics

Следующий рисунок показывает метрики CacheProbe для Q2.

Query Cache - Q2 - Metrics

Query Cache не работает для DISTINCT запросов, для которых включены оптимизации CTE

После выполнения set cbo_cte_reuse = true для включения оптимизаций CTE результаты вычислений для конкретных запросов, которые включают DISTINCT агрегатные функции, не могут быть кэшированы. Несколько примеров следующие:

  • Запрос содержит одну DISTINCT агрегатную функцию avg(distinct):

    SELECT
    avg(distinct v1) AS __c_0
    FROM
    t0
    WHERE
    ts between '2022-01-03 00:00:00'
    and '2022-01-03 23:59:59';

Query Cache - CTE - 1

  • Запрос содержит несколько DISTINCT агрегатных функций, которые ссылаются на один и тот же столбец:

    SELECT
    avg(distinct v1) AS __c_0,
    sum(distinct v1) AS __c_1,
    count(distinct v1) AS __c_2
    FROM
    t0
    WHERE
    ts between '2022-01-03 00:00:00'
    and '2022-01-03 23:59:59';

Query Cache - CTE - 2

  • Запрос содержит несколько DISTINCT агрегатных функций, каждая из которых ссылается на разный столбец:

    SELECT
    sum(distinct v1) AS __c_1,
    count(distinct v0) AS __c_2
    FROM
    t0
    WHERE
    ts between '2022-01-03 00:00:00'
    and '2022-01-03 23:59:59';

Query Cache - CTE - 3

Лучшие практики

При создании таблицы укажите разумное описание раздела и разумный метод распределения, включая:

  • Выберите один столбец типа DATE в качестве столбца раздела. Если таблица содержит более одного столбца типа DATE, выберите столбец, значения которого продвигаются вперед по мере инкрементального поступления данных и который используется для определения интересующих временных диапазонов ваших запросов.
  • Выберите подходящую ширину раздела. Данные, поступившие совсем недавно, могут изменить последние разделы таблицы. Поэтому записи кэша, включающие последние разделы, нестабильны и склонны к аннулированию.
  • Укажите количество bucket'ов, которое составляет несколько десятков в описании распределения оператора создания таблицы. Если количество bucket'ов чрезмерно мало, Query Cache не может вступить в силу, когда количество tablet'ов, которые нужно обработать BE, меньше значения pipeline_dop.