VictoriaLogs принимает логи по куче протоколов — JSON Lines, Elasticsearch bulk, Loki push, OpenTelemetry, syslog и другим. Любой из них система переводит во внутренний формат: метка времени, набор именованных полей и идентификатор потока (stream identity). Уже на этом этапе можно повлиять на обработку — через параметры запроса или заголовки: отбросить ненужные поля, очистить цветовые коды, добавить служебные поля, указать, где лежит основное сообщение и метка времени.
Главное понятие во всей статье — stream identity. Логи с одинаковыми stream fields (например, pod и container) группируются в один поток. VicroriaLogs хранит их рядом на диске, что даёт отличное сжатие и позволяет запросам трогать только нужные потоки, а не всё подряд. Практическое правило для оператора: stream fields должны быть стабильными и низкокардинальными (host, app, pod, container), а высококардинальные вроде trace_id держать обычными полями.
Система не обрабатывает логи по одному. Они накапливаются в буфере, разделённом на шарды по числу CPU — каждый шард работает независимо. Примерно раз в секунду (или раньше, если буфер заполнен) батч сбрасывается в in-memory part — небольшую поисковую структуру в RAM. Если батч слишком велик, part пишется сразу на диск.
При сбросе каждый лог попадает в партицию — одну на календарный день (UTC). На диске это выглядит как директория partitions/20260109. Такой подход делает дешёвыми две операции: удаление старых логов (просто удаляется целая директория) и запросы с фильтром по времени (система открывает только нужные дни).
Parts бывают трёх видов. In-memory — создаются первыми, логи видны в запросах почти мгновенно. Small parts — сбрасываются на диск для сохранности. Big parts — результат объединения small parts со временем. Благодаря этому VictoriaLogs делает мало операций записи и чтения и может переваривать около гигабайта логов в секунду даже на медленных HDD.
Внутри part данные организованы хитро. Сначала — блочная структура: логи группируются в блоки по потоку и времени, внутри блока строки отсортированы по времени. Каждый блок несёт крошечный заголовок — какой поток, сколько строк, минимальный и максимальный timestamp. Эти заголовки хранятся отдельно от данных, поэтому VictoriaLogs может сканировать их, решая, какие блоки читать, и не трогать ни одной лишней строки.
Второй приём — колоночное хранение. Вместо того чтобы хранить каждую запись как строку со всеми полями, VictoriaLogs хранит каждое поле отдельной колонкой. Запрос читает только те колонки, которые реально нужны, а не все поля каждого совпавшего лога. Это особенно эффективно для широких логов с кучей полей. Плюс значения одного поля похожи друг на друга и отлично сжимаются — вот почему терабайты сырых логов могут уменьшиться до нескольких сотен гигабайт.
На диске part — это директория с набором файлов. Самый главный — metadata.json, человекочитаемый. Там указано число строк, блоков, размер в сжатом и несжатом виде, временной диапазон. Если запрос не попадает в этот диапазон, весь part пропускается сразу.
Таймстемпы хранятся отдельно в timestamps.bin — чтобы дешёво фильтровать и сортировать по времени. Имена полей мапятся на короткие числовые ID в column_names.bin. Потом VictoriaLogs решает, в какой шард положить данные каждой колонки — эта информация записана в column_idxs.bin. Сами значения лежат в values.binN (по одному файлу на шард), а рядом — bloom.binN с bloom-фильтрами.
Bloom-фильтр — это дешёвый префильтр. Он отвечает на вопрос «может ли это слово быть в блоке?» с гарантией: «точно нет» или «может быть». Ложноположительные ответы возможны, ложноотрицательных нет. Если слово error встречается в запросе, VictoriaLogs сначала проверяет bloom-фильтр каждого блока — и пропускает блоки, где слова точно нет, не читая их значения.
Для навигации по колонкам внутри блока используется columns_header.bin. Это карта: вот offset и размер value blob для определённой колонки в определённом блоке. А columns_header_index.bin — это индекс внутри этого файла, чтобы прыгать сразу к нужной колонке, не перебирая все.
Наконец, index.bin хранит заголовки всех блоков part, а metaindex.bin — это индекс поверх index, который VictoriaLogs держит в памяти. Два уровня позволяют с первого же чтения понять, какие блоки могут подойти под запрос, и прочитать только их.