---
metadata:
  - name: generator
    content: Diplodoc Platform v5.50.6
alternate:
  - https://ydb.tech/docs/en/dev/primary-key/column-oriented.md
  - https://ydb.tech/docs/ru/dev/primary-key/column-oriented.md
sourcePath: ru/core/dev/primary-key/column-oriented.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt

# Выбор ключей для максимальной производительности колоночных таблиц

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

## Ключ партиционирования

Ключ партиционирования должен являться непустым подмножеством столбцов первичного ключа. Хэш от ключа партиционирования определяет партицию, в которую попадёт строка. Ключ следует выбирать таким образом, чтобы данные распределялись равномерно по партициям. Обычно этого достигают путём включения в ключ партиционирования высококардинальных столбцов, например, временных меток с высоким разрешением (тип данных `Timestamp`). Если в качестве ключа партиционирования используется низкокардинальный ключ, данные могут распределиться по партициям неравномерно, что приведёт к перегрузке некоторых партиций. Перегрузка партиций вызывает неоптимальную производительность запросов и/или накладывает ограничения на максимальный поток вставляемых данных.

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

Для базовой оценки числа партиций можно использовать формулу `(количество узлов * 4`. Это позволит максимально утилизировать ресурсы кластера при выполнении параллельных запросов.

## Первичный ключ

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

## Пример

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

Основные сущности:

- `event_timestamp`: Время события (точность до микросекунд).
- `user_id`: Идентификатор пользователя.
- `campaign_id`: Идентификатор рекламной кампании.
- `ad_id`: Идентификатор конкретного рекламного объявления.
- `event_type`: Тип события (`'impression'`, `'click'`).
- `event_id`: Уникальный идентификатор события.

Типичные аналитические запросы:

1. Посчитать количество кликов по кампании `campaign_id = X` за последний час.
2. Найти все события для пользователя `user_id = Y`, отсортированные по времени.

### Выбор ключа партиционирования

Для обеспечения максимальной производительности необходимо равномерно распределить входящие данные по всем партициям.
Рассмотрим использование различных колонок в качестве ключа партиционирования.

Неудовлетворительные варианты:

- `event_timestamp` или `event_date`. Все поступающие данные за текущий момент времени будут отправляться в одну и ту же партицию, создавая высокую нагрузку на запись в одну партицию на одном узле, в то время как остальные партиции будут простаивать.
- `campaign_id`. Рекламные кампании имеют разную популярность. Одна крупная кампания может генерировать 80% всех событий, что приведет к перекосу данных и записи преимущественно в несколько партиций.

Оптимальные варианты:

- `user_id`. Идентификаторов пользователей очень много (высокая кардинальность), их активность, скорее всего, распределена случайным образом.
- композитный ключ, например, (`event_timestamp`, `user_id`). Для вычисления партиции данные колонок конкатенируются, и хеш-функция вычисляется уже по итоговой суммарной строке, обеспечивая уникальное значение.

Итог: оптимальным вариантом ключа партиционирования является либо колонка `user_id`, либо композитный ключ вида (`event_timestamp`, `user_id`).
Например, `PARTITION BY HASH(event_timestamp, user_id)`.

### Выбор первичного ключа

Выбор первичного ключа важен для следующих задач:

1. Обеспечение быстрой фильтрации по диапазону (особенно по времени).
2. Оптимизация записи, делая её максимально приближённой к последовательному добавлению в конец (append-only).
3. Обеспечение целостности данных.

Вернёмся к требованиям к системе:

1. Посчитать количество кликов по кампании `campaign_id = X` за последний час.
2. Найти все события для пользователя `user_id = Y`, отсортированные по времени.

Рассмотрим варианты выбора первичного ключа:

1. Для обеспечения выборок, связанных со временем, и для записи поступающих данных в конец таблиц, оптимально использовать `event_timestamp` как первую часть ключа.
2. В один и тот же момент времени могут поступать данные о действиях разных пользователей, поэтому в качестве второй части ключа используем `user_id`.
3. Пользователи могут совершать действия в разных кампаниях, поэтому в третью часть ключа добавим поле `campaign_id`.

Итоговая структура таблицы

```sql
CREATE TABLE ad_events (
    -- Ключевые колонки, участвующие в ключах
    user_id Utf8 NOT NULL,
    event_timestamp Timestamp NOT NULL,
    event_id Uint64,
    campaign_id Uint64 NOT NULL,

    -- Остальные колонки
    ad_id Uint64,
    event_type Utf8,
    PRIMARY KEY(event_timestamp, user_id, campaign_id)
)
PARTITION BY HASH(user_id, event_timestamp) -- Равномерно распределяем данные
WITH (
    STORE = COLUMN
);
```

Такая структура эффективно решает поставленные задачи: входящий поток данных равномерно распределяется по партициям благодаря `HASH(user_id, event_timestamp)`, а аналитические запросы по временным диапазонам работают быстро за счёт сортировки данных по `event_timestamp` внутри каждой партиции.
