Сравнение форматов и
библиотек сериализации
Антон Рыжов, Qrator Labs
Задача
Сериализовать данные:
● Без потерь
● Компактно
● Быстро
● Удобно
JSON
✔ Привычно
✔ Удобно
✔ Человекочитаемо
[
  {
    "very": "simple"
  }
]
JSON
✔ Привычно
✔ Удобно
✔ Человекочитаемо
✗ Не компактно
[
  {
    "very": "simple"
  },
  {
    "very": "big"
  }
]
JSON — компактность
7              1 byte (1:1)→
JSON — компактность
7              1 byte (1:1)→
65535          5 bytes (5:2)→
1000 000 000   10 bytes (10:4)→
JSON — компактность
3 / 2   1.5 (3 byte)→
JSON — компактность
3 / 2   1.5 (3 byte)→
1 / 3   0.3333333333333333 (18 bytes)→
JSON — компактность
Hello   "Hello" (+2 bytes)→
JSON — компактность
Hello   "Hello" (+2 bytes)→
     "u0420u0418u0422" (×3)РИТ →
JSON — компактность
Hello   "Hello" (+2 bytes)→
     "u0420u0418u0422" (×3)РИТ →
     " " ( )РИТ → РИТ опция
JSON — компактность
[
  {"userId": 1, "userName": "John Smith"},
  {"userId": 2, "userName": "Jane Dow"}
]
JSON — компактность
[
  {"userId": 1, "userName": "John Smith"},
  {"userId": 2, "userName": "Jane Dow"}
]
JSON — компактность
[
  "keys": ["userId", "userName"],
  "values": [
    [1, "John Smith"],
    [2, "Jane Dow"]
  ]
]
JSON — целостность
dict(1: "foo")   { "1": "foo" }→
JSON — целостность
dict(1: "foo")   { "1": "foo" }→
object = { "type_id": 5 }
types = { "5": "Type name" }
JSON — целостность
Python   JSON:→
{ 1: "foo", "1": "bar" }   ?→
PHP   JSON:→
Array(1=> "foo", "1"=> "bar")   ?→
JSON — целостность
Python   JSON:→
{ 1: "foo", "1": "bar" }   {"1": "bar", "1": "foo"}→
PHP   JSON:→
Array(1=> "foo", "1"=> "bar")   {"1": "bar"}→
JSON — целостность
Python   JSON:→
{ 1: "foo", "1": "bar" }   {"1": "bar", "1": "foo"}→
PHP   → PHP:
Array(1=> "foo", "1"=> "bar")   → Array(1=> "bar")
JSON — binary
{"message": "0KDQmNCiKys="}  +30%
JSON — binary
{"message": "0KDQmNCiKys="}  +30%
{"message_bin": "0KDQmNCiKys="}
{"message": { "type": "bin", "value": "0KDQmNCiKys="}}
Альтернативы
Схема данных Binary Ключи map
BSON ✗ ✓ str
Msgpack ✗ ✓ any
Google Protobuf ✓ ✓ ✗
Apache Thrift ✓ ✓ any
Apache Avro ✓ ✓ str
Зачем схема?
✔Описывает данные
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
✗ Надо писать
Зачем схема?
✔Описывает данные
✔Уменьшает дублирование
✔Валидирует данные
✔Приводит типы
✗ Надо писать
✗ … а иногда ещё и компилировать
Protobuf
✔ Старое проверенное решение
✔ Разработан Google
✔ Хорошие результаты в тестах в интернете
✔ Хорошая документация
Protobuf
✔ Старое проверенное решение
✔ Разработан Google
✔ Хорошие результаты в тестах в интернете
✔ Хорошая документация
✗ Не всё можно описать
– Список списков, map
Protobuf — схема
message Point {
  required int32 x = 1;
  required int32 y = 2;
}
message PointsList {
  repeated Point points = 1;
}
Thrift
● Более новый формат
● Разработал Facebook, отдал в Apache
● Документация — справочник типов и BNF
● Есть “Thrift: The Missing Guide”
● RPC-клиент/сервер
Thrift — схема
struct PointsList {
    1: required list< list<i32> > points,
}
Apache Avro
● Ещё более новый формат
● Разработан для Apache Hadoop
● Документация на схему — подробная
● Кодогенерации нет
Avro — схема
{
    "namespace": "test.avro",
    "type": "array",
    "items": {
        "type": "array",
        "items": "int"
    }
}
Размер integer: «0»; 1-, 2-, 4-byte
Msgpack
Avro
Protobuf
JSON
Thrift
BSON*
0 2 4 6 8 10 12
Размер float: «0», 2.5, 1/3, 1e9/3
Avro
Protobuf
Msgpack
Thrift
BSON*
JSON
0 2 4 6 8 10 12 14 16 18 20
Размер string: "", "Z"×10, "Я"×10
Msgpack
Avro
Protobuf
JSON
Thrift
BSON*
0 5 10 15 20 25 30 35
Размер string: "Z"×1000, "Я"×1000
Msgpack
Avro
Protobuf
Thrift
BSON*
JSON
0 500 1000 1500 2000 2500
Размер 0, 10 bytes
Avro
Msgpack
Protobuf
Thrift
JSON
BSON*
0 5 10 15 20 25
Размер 1000 bytes
Avro
Msgpack
Protobuf
Thrift
BSON*
JSON
0 200 400 600 800 1000 1200 1400 1600
Размер array [0], [10]
Avro
Msgpack
Protobuf
JSON
Thrift
BSON*
0 5 10 15 20 25
Размер map [0], [10]
Msgpack
Avro
Protobuf*
JSON
BSON
Thrift
0 2 4 6 8 10 12 14 16 18 20
Размер struct vs map
Msgpack
Avro
Protobuf*
BSON
JSON
Thrift
0 20 40 60 80 100 120 140 160 180 200
Топ по размеру
1.Apache Avro
2.Msgpack
3.Google Protobuf
4.Apache Thrift
5.JSON
6.BSON
Методика бенчмарка
Характерные объекты — строки, байты, словари разных
размеров, массивы
Замер времени на сериализацию и десериализацию каждой
библиотекой в python2 и python3
Сравнение данных до и после
Железо:
Intel(R) Xeon(R) CPU E5-2683 v4 @ 2.10GHz
64 Gb RAM
Без схемы
Поддержка Установка Реализация Генерация кода
uJSON 2, 3 pip C-ext ✗
BSON 2, 3 pip Python ✗
Msgpack 2, 3 pip C-ext ✗
Без схемы — затраченное время
msgpack
ujson
json
bson
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Protobuf
Поддержка Установка Реализация Генерация кода
protobuf 2, 2to3 make + pip Python
✓
cprotobuf 3 protobuf + pip C-ext
✓
protobuf3 3 protobuf + pip Python
✓
Protobuf — пример кода protobuf, cprotobuf
data = test_pb2.PointsList(
    points=[
        test_pb2.Point(x=1, y=2),
        test_pb2.Point(x=3, y=4),
    ]
).SerializeToString()
pl = test_pb2.PointsList()
pl.ParseFromString(data)
print(pl.points[0].x)
Protobuf — пример для protobuf3
pl = test_pb2.PointsList()
po = test_pb2.Point()
po.x = 1; po.y = 2
pl.points.append(po)
data = pl.encode_to_bytes()
pl = test_pb2.PointsList()
pl.parse_from_bytes(data)
print(pl.points[0].x)
Protobuf — затраченное время
cprotobuf*
google_proto
protobuf3*
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Thrift
Поддержка Установка Реализация Генерация кода
(apache)
thrift
2 make + pip Python ✓
(facebook)
Thrift
2, 3 ☠nightmare☠ Python ✓
thriftpy 2, 3 pip Cython ✗
thriftrw 2, 3 pip Cython ✗
(Apache) thrift — пример кода
data = serialize(
    test_ttypes.PointsList([(1, 2)])
)
points = deserialize(
    test_ttypes.PointsList(),
    points_list,
).points
print(points[0][0])
(facebook)Thrift, thriftpy, thriftrw
Совместим с (Apache) thrift по API
Thrift — затраченное время
thriftrw
fb_thrift**
apache_thrift*
thriftpy+cython*
thriftpy*
Py2.7 Py3.5
Thrift — затраченное время
thriftrw
fb_thrift**
apache_thrift*
thriftpy+cython*
thriftpy*
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Avro
Поддержка Установка Реализация Генерация кода
(apache)
avro
2, 3
2 pip
3 src
Python ✗
fastavro 2, 3 pip Cython ✗
pyavroc 2, 3 shell + make libavro + wrapper ✗
(Apache) avro — пример упаковки
schema = avro.schema.parse(open('points_list.avsc', 
'r').read())
io_stream = io.BytesIO()
avro.io.DatumWriter(schema).write(
    [(1, 2), (3, 4)],
    avro.io.BinaryEncoder(io_stream),
)
data = io_stream.getvalue()
(Apache) avro — пример распаковки
schema = avro.schema.parse(open('points_list.avsc', 
'r').read())
io_stream = io.BytesIO(data)
points = avro.io.DatumReader(schema).read(
    avro.io.BinaryDecoder(io_stream),
)
print(points[0][0])
fastavro — пример упаковки
schema = json.loads(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO()
fastavro.schemaless_writer(
    io_stream,
    schema,
    [(1, 2), (3, 4)],
)
return io_stream.getvalue()
fastavro — пример распаковки
schema = json.loads(open('points_list.avsc', 'r').read())
io_stream = io.BytesIO(points_list)
points = fastavro.schemaless_reader(
    io_stream,
    schema,
)
print(points[0][0])
pyavroc — пример кода
schema = open('specs/points_list.avsc', 'r').read()
points_writer = _pyavroc.AvroSerializer(schema)
data = points_writer.serialize(
    [(1, 2), (3, 4)],
)
points_reader = _pyavroc.AvroDeserializer(schema)
points = points_reader.deserialize(data)
print(points[0][0])
Avro — затраченное время
pyavroc*
fastavro
avro
encode py2.7
decode py2.7
encode py3.5
decode py3.5
Топ — затраченное время
pyavroc*
msgpack
cprotobuf*
thriftrw
ujson
fastavro
thriftpy+cython*
Py2.7 Py3.5
Общие рекомендации
● Следить за новыми разработками
● Рассматривать альтернативы
● Референсная библиотека — не всегда самая лучшая
● Альтернативные — бывают ещё хуже
● Не верить бенчмаркам в интернете (В том числе
этому)
● Кодогенерация только мешает
Всё
ar@qrator.net
https://github.com/QratorLabs/ritfest2016

Сравнение форматов и библиотек сериализации / Антон Рыжов (Qrator Labs)