В ходе рефакторинга нашей криптобиржи, было решено пересмотреть концепцию работы с маркет-датой. В классическом случае маркет-дата распространяется двумя методами:
1. REST-интерфес;
2. WEBSocket broadcast subscription.
Метод REST зачастую используется для получения исторических данных, в то время, как по WEBSocket рассылается актуальная информация в режиме online. В некоторых случаях WEBSocket вообще не используется, а обновление происходит регулярными запросами через REST.
И вроде все довольны. Но, при более детальном рассмотрении, становится очевидными огромные накладные расходы на такую концепцию. Их основная масса ложится на REST. Для обеспечения функционирования REST-интерфейса мы должны создать backend отвечающий требованиям высоконагруженных систем. Естественно, тут можно выбирать различные варианты решения от PHP до нынче модного Golang.
Также требуется создать высокодоступную инфраструктуру, реализовать такие мелочи как CI/CD для сервисов, обеспечить все это нужными спецами по разработке, сопровождению, и т.д., и т.п.
При этом, именно историческая маркет-дата занимает основной объем дискового пространства. Хранится она обычно в БД. Это с одной стороны, позволяет решить вопрос кластеризации, но при критических размерах, становится невыносимой тяжестью и ставит перед командой DevOps и разработчиков нетривиальные задачи.
В общем… кажущаяся простота и логичность данной концепции вдребезги разбивается о суровый быт.
Отдельно нужно заметить особенность маркет-даты. Она всегда накапливается (прирастает). Т.е. говоря языком программиста БД мы всегда инсертим и потом селектим. Но не делитим. Т.е. замечательная функциональность БД по систематизации, оптимизации и т.п. хранимых данных становится маловостребованной.
Еще одним важным свойством маркет-даты является ее четко определенная структура. Например свеча в графике имеет всего восемь параметров:
1. Момент времени;
2. Экспозиция;
3. Максимальная цена;
4. Минимальная цена;
5. Цена открытия;
6. Цена закрытия;
7. Объем;
8. Средняя цена.
Тоже можно сказать о сделках.
Опираясь на эти предпосылки, а также вооружившись опытом, я достаточно быстро пришел к выводу, что правильнее рассматривать маркет-дату как структурированный поток.
Например, поток со свечами, можно рассмотреть как массив бинарных структур:
moment: int32
exposition: int32
min: float64
max: float64
open: float64
close: float64
volume: float64
average: float64
Итого: 56 байт.
Для примера, такая свеча в JSON будет описана так:
{
"date":1501004700,
"high":0.08053391,
"low":0.08020004,
"open":0.08030001,
"close":0.0803542,
"volume":48.62169347,
"weightedAverage":0.08038445
}
Итого: 167 байт.
Профит по размеру очевиден. А это меньше трафик, выше скорость доставки до клиента.
И тут, конечно, приходит на ум BSON. Но он не решает проблемы необходимости наличия backend и в общем-то не решает проблему в корне. К тому же, он не поддерживается браузерами нативно.
Я посмотрел в иную сторону. В сети есть масса ресурсов работающих с потоками. Это аудио и видео ресурсы. Они демонстрируют все те признаки, которые необходимы:
- работают с большими объемами данных;
- прекрасно справляются с высокой нагрузкой;
- способны в online поставлять контент, но при этом, дают возможность обращаться к историческим данным.
Я слегка углубился в потоковое видео, что позволило кардинально решить все вышеозначенные проблемы по маркет-дате. Вся магия, как выяснилось, скрывается в технологии Content-Range которая из коробки поддерживается, например, Nginx. Она позволяет обращаться к любой области статического ресурса (файлу на сервере) не используя backend. В целом, это происходит так: вы обращаетесь к URL указывая в заголовке экспозицию, которую хотите вернуть. Например: range: 100-200. Не буду останавливаться на тонкостях, все нюансы технологии вы можете найти в профильных статьях. Суть думаю понятна.
Фактически, теперь, на стороне фронта можно организовать обращение к необходимому участку файла, например, содержащего свечи. А так как, точно известно сколько байт занимает одна свеча (56 байт), мы легко можем определить нужное нам смещение. Правда, мы еще должны знать момент времени, с которого график начинается. И это весьма легко решается добавлением к файлу заголовка, размер которого также является константой.
Т.е. первым делом, фронт обращается к файлу с нулевой позиции, и получает
заголовок. Одновременно с этим nginx отдаст размер файла. Это позволит определить общее количество свечей в файле и момент начала отсчета.
Теперь, зная начальную точку времени, имея четкое представление о количестве свечей, мы можем получить любое их количество за любой промежуток времени из фронта, не используя backend.
Ах, да… еще момент… у нас есть такой параметр как экспозиция свечи. Тут решение тоже простое — мы держим сразу несколько файлов для разных экспозиций. Как дополнительный мелкий бонус, сокращается размер структуры свечи еще на 4 байта.
В целом, решение уже было достаточно интересным для реализации, но выяснились еще, не постесняюсь сказать — очень крутые профиты. Дело в том, что браузеры умеют кэшировать данные полученные методом range. Т.е. при очередном запросе фронта к серверу, если этот участок файла был получен браузером ранее, он не полезет к вам на сервер, а возьмет его из кэша.
Но даже это не все. Используя CDN, кеширование может быть настроено еще и его средствами. Т.е. по итогу, можно получить систему, которая в состояние отдавать большие объемы маркет-даты минимально нагружая инфраструктуру и сервера.
Нужно ли говорить, что сомнений в верности идеи уже не было? Теперь остались сущие мелочи… нужно сделать генерацию этих самых файлов.
Как уже было сказано выше, обычно, биржа эксплуатирует два средства доставки маркет-даты: REST и WEBSocket. Последний транслирует online актуальную маркет-дату. Обычно это отдельный сервис. Как показала практика, доработка этого сервиса для того, чтобы он дописывал данные в необходимые файлы не представляет труда и решается буквально парой часов работы разработчика. Можно сказать, что он одновременно с рассылкой ведет лог.
Вопрос доставки файлов на ноды — классическая проблема синхронизации файловой системы. Команда DevOps решает ее легко и непринужденно. Например используя rsync.
Вот теперь, можно смело сказать — БИНГО!
Возможно, возникнет вопрос — а почему все крипто-биржи делают иначе? На этот вопрос у меня нет достоверного ответа. Но есть мысли:
- биржи создавались сочувствующим крипте девелоперами. Возможно, они не имели представления о работе классических бирж, не учитывали их опыт и пользовались легкодоступными решениями, чтобы получить быстрый результат. А это, шаблонные решения: тот самый REST и сопутствующий JSON;
- как говорят в народе — Работает? Не трогай. — Т.к. ключевыми биржами уже были созданы технологические тренды, вновь возникающие биржи заимствовали их.
Если тема окажется интересной сообществу, продолжу статьями о других нестандартных решениях внедренных на нашем проекте. Как это работает на практике, можно посмотреть на сайте биржи (см. мой профиль). Особо предлагаю обратить внимание на работу графика, который наглядно демонстрирует все профиты данной технологии.