Как парсить гигабайты 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
}
```