Технологии

Как парсить гигабайты JSON в секунду на Go

Краткое резюме

В статье описан метод эффективного анализа больших объёмов данных в формате JSON с использованием языка программирования Go и библиотеки go-faster/jx. Для оптимизации процесса декодирования предлагается создать соответствующую структуру данных в Go и использовать тип Map для полей Attributes и Resource..

В статье рассматривается метод эффективного анализа больших объёмов данных в формате JSON с использованием языка программирования Go. Основное внимание уделяется библиотеке go-faster/jx, которая является облегчённым вариантом jsoniter и предназначена для высокопроизводительной работы с JSON на низком уровне. **Исходные данные** Для примера берётся JSON-объект, представляющий запись лога из модели данных OpenTelemetry: ``` { "Timestamp": "1586960586000000000", "Attributes": { "http.status_code": 500, "http.url": "http://example.com", "my.custom.application.tag": "hello" }, "Resource": { "service.name": "donut_shop", "service.version": "2.0.0", "k8s.pod.uid": "1138528c-c36e-11e9-a1a7-42010a800198" }, "TraceId": "13e2a0921288b3ff80df0a0482d4fc46", "SpanId": "43222c2d51a7abe3", "SeverityText": "INFO", "SeverityNumber": 9, "Body": "20200415T072306-0700 INFO I like donuts" } ``` **Представление в Go** Для эффективного анализа JSON необходима соответствующая структура данных в Go, которая позволит сократить накладные расходы при декодировании. Используя знания о модели данных, можно создать такую структуру. Например, известно, что TraceId всегда представлен как 32-символьная шестнадцатеричная строка, а SeverityText может быть опущен, если известно числовое значение SeverityNumber. Пример такой структуры: ``` type OTEL struct { Timestamp jx.Num Attributes Map Resource Map TraceID [16]byte SpanID [8]byte Severity byte Body Raw } ``` **Тип Map** Важное значение для эффективного декодирования имеет тип Map, который используется для полей Attributes и Resource. Сначала рассматривается тип Bytes, который хранит срез байтов и позиции ключей/значений внутри этого среза. Это представление []string без лишних аллокаций: ``` type Pos struct { Start int End int } type Bytes struct { Buf []byte Pos []Pos } ``` Bytes позволяет хранить массив строк в компактном и локальном по памяти виде. Тип Pos указывает на начало и конец каждой строки внутри буфера Buf. Определены методы для работы с Bytes: * Elem позволяет получить i-й элемент. * ForEachBytes итерирует по всем элементам Bytes, используя коллбек и избегая аллокаций. * Append добавляет новый элемент в Bytes. * Reset очищает содержимое. Теперь можно определить тип Map, который использует Bytes для хранения ключей и значений: ``` type Map struct { Keys Bytes Values Bytes } ```

Фильтры и сортировка