Перейти к содержанию

Пакетная вставка и подтверждение доставки

Таблица events_lab.deliveries описывает все физические доставки. Добавим подготовленные строки в формате JSONEachRow: каждая строка файла является отдельным JSON-объектом. Результат урока — воспроизводимый входной пакет и понятный путь подтверждения, без утверждения, что учебные события уже отправлены серверу.

В архиве находятся data/events.jsonl с шестнадцатью строками, схема и SQL-запросы. Используем вымышленные UUID, три материала и четыре условных браузера. Во время подготовки не выполнялись INSERT, HTTP-запросы к ClickHouse или загрузка настоящей посещаемости. Дальнейшие числа — ожидаемые свойства этого набора.

Одна строка и одна доставка

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

Минимальная SQL-вставка показывает два события посетителя a. Это самостоятельный пример для чистой учебной таблицы, а не добавление поверх полного файла, иначе вы намеренно создадите дополнительные доставки.

INSERT INTO events_lab.deliveries FORMAT JSONEachRow
{"schema_version":1,"event_id":"00000000-0000-4000-8000-000000000001","anon_id":"00000000-0000-4000-8000-00000000000a","lesson_id":"html-start","course_id":"web","event_name":"lesson_view","occurred_at":"2026-10-01 09:00:00.000","received_at":"2026-10-01 09:00:02.000","version":1}
{"schema_version":1,"event_id":"00000000-0000-4000-8000-000000000002","anon_id":"00000000-0000-4000-8000-00000000000a","lesson_id":"html-start","course_id":"web","event_name":"lesson_complete","occurred_at":"2026-10-01 09:10:00.000","received_at":"2026-10-01 09:10:02.000","version":1}

После такой отдельной вставки ожидаем две доставки. Это не число посетителей: оба события принадлежат одному anon_id. count() и число уникальных идентификаторов отвечают разным вопросам. Формат JSONEachRow описан в документации ClickHouse.

В полном наборе одна строка повторяет event_id завершения посетителя a, но имеет более позднее received_at и большую version. Это не второе завершение. Позднее событие посетителя b, напротив, получает собственный event_id, потому что описывает самостоятельное действие, произошедшее раньше доставки.

Пакет вместо запроса на каждое событие

Много маленьких вставок создают лишнюю работу по приёму и формированию частей. Сборщик обычно объединяет события в пакет по размеру и времени ожидания. Большой пакет уменьшает число операций, но увеличивает задержку отчёта и объём повторной отправки при неопределённом результате. Эти требования нужно согласовать, а не выбирать исключительно максимальное число строк.

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

Отправитель должен сохранять события до подтверждения или иметь оговорённое правило потери. Если удалить пакет сразу после начала передачи, разрыв сети сделает данные невосстановимыми. Если повторять без сохранённых идентификаторов, цифры вырастут. Поэтому контракт доставки начинается раньше SQL: отправитель хранит event_id вместе с содержимым партии.

HTTP-путь без браузерных секретов

HTTP-интерфейс ClickHouse позволяет передавать запрос и тело. Пример ниже предназначен будущему локальному оператору или серверному сборщику. Он не вставляется в браузерный JavaScript: пароль хранилища должен оставаться на серверной стороне.

curl --fail-with-body --request POST \
  --data-binary @data/events.jsonl \
  'http://127.0.0.1:8123/?query=INSERT%20INTO%20events_lab.deliveries%20FORMAT%20JSONEachRow'

Это команда для предполагаемого локального сервера без включённой аутентификации в закрытой учебной среде. Внешняя среда потребует HTTPS, ограниченную учётную запись и безопасную передачу секрета отдельно от URL. Здесь сервер и аккаунт не создавались. Официальное описание HTTP-интерфейса объясняет передачу тела и особенности ответа.

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

Подтверждение и неопределённый исход

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

Если используете асинхронные вставки, способ подтверждения зависит от настроек ожидания. В этой серии их не включаем: обычный синхронный путь легче объясняет исходный пакет. Даже при синхронной вставке неопределённость сети остаётся, поэтому она не устраняет необходимость идентификаторов.

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

Сохранение исходника для разбора

Локальный JSONL нужен, чтобы повторно разобрать данные при изменении SQL. Внешний исходный журнал требует собственного срока хранения и контроля доступа; бесконечное сохранение «на всякий случай» противоречит заданной минимизации. Учебный набор вымышленный, а настоящие пользовательские данные нельзя переносить в публичный архив урока.

Неудачный пакет и журнал отправителя

Если одна строка имеет неверный тип, отправитель должен сохранить сведения о неудачной партии и безопасную причину отказа. Не следует бесконечно повторять заведомо повреждённый JSON с той же ошибкой. Нужен отдельный путь отклонённых событий и понятная возможность исправления производителя.

При этом журнал отказа не должен автоматически содержать весь пользовательский запрос. Для разбора часто достаточно версии контракта, номера строки, идентификатора партии и класса ошибки. Если требуется исходное содержимое, доступ и срок такой копии задаются отдельно.

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

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

Следующий шаг уточнит ключ сортировки и партиции. Затем создадим таблицу логических событий, которая учитывает повтор доставки. Не начинайте строить отчёт завершений по deliveries.count() только потому, что загрузка выглядит законченной: физические строки ещё не соответствуют предметному числу действий.

Оглавление серии