Ключевой задачей высоконагруженных WEB-систем является способность обработать большое число запросов. Решить эту проблему можно по-разному. В этой статье я предлагаю рассмотреть необычный метод оптимизации запросов к backend через технологию content-range (range). А именно — сократить их количество без потери качества системы путем эффективного кеширования.

Для начала, предлагаю изучить статью, где весьма емко и доходчиво изложена преамбула технологии с примером для S2S. Далее, желательно познакомиться с моей первой статьей об использовании этой технологии для оптимизации работы с market-data на проекте криптобиржи.

В этой статье, я хочу показать, что данная технология может быть использована шире, чем продемонстрировала первая статья. Напомню, там трейдинговая информация (свечи) получаются range-запросами к статическим файлам, которые заранее готовятся специальным сервисом. Теперь, я хочу рассмотреть запросы к полноценному backend на примере той же market-data, и для тех же свечей, без потери ключевых профитов.

Итак, что планируется достичь:

  1. Оптимизировать запросы к backend (уменьшить их количество);
  2. Повысить скорость доставки контента конечному пользователю;
  3. Оптимизировать трафик.

Еще раз, особо выделю то, что технология range является стандартом (RFC 2616). Она нативно поддерживается браузерами и они способны кешировать полученные порции данных. Следовательно, очередной запрос из браузера, при наличии актуального кеша запрашиваемой порции, реализуется на клиенте, не потревожив ваши сервера.

Если между клиентом и серверами добавить CDN, можно получить еще одну, мощную прослойку кеширования. И если в первом случае, кеширование будет происходить для конкретного, конечного клиента, то в паре с CDN, вы получаете возможность кешировать данные уже для группы конечных клиентов.

Таким образом, чтобы состоялся реальный запрос к серверу, запросу необходимо преодолеть два эшелона кеширования, каждый из которых снижает объем запросов к конечному серверу. Конечно, финальным “редутом”, на пути запроса, может стать ваш сервер со своим кешем.

Из особенностей использования технологии range нужно учесть, что она работает с порциями байтов. Т.е. с бинарными данными. И запрашивать мы можем именно интервалы байт. Отвечать должны, соответственно — бинарным блоком.

Думаю, вводных достаточно. Давайте перейдем к частному случаю, и уже на примере разберемся как мы можем все это “счастье” использовать в частной задаче — запрос свечей за заданный интервал с заданной экспозицией.

Для начала, т.к. мы должны работать с бинарными структурами, давайте закодируем нашу свечу. Для этого примем, к примеру, такую структуру:

moment: int32 // Момент времени
min: float64 // Минимальная цена
max: float64 // Максимальная цена
open: float64 // Цена открытия
close: float64 // Цена закрытия
volume: float64 // Объем
average: float64 // Средняя цена

Таким образом, наша структура займет 52 байта. Примем ее как квант — минимальный бинарный блок.

Далее, реализуем контроллер, который будет принимать GET запросы и парсить заголовок range. При этом, мы будем переводить запрашиваемый интервал в кванты путем простого деления без остатка, т.е. например, запрос с интервалом:

Range: 5200-52000

Должен нами интерпретироваться в размерности нашего кванта как:

Range: 100-1000

По сути, это будет offset и limit нашего запроса к БД.

С определением экспозиции совсем просто, мы ее можем поместить в url. К примеру:

/api/v1/candles/7m

Т.е. мы будем запрашивать свечи с экспозицией 7 минут. Естественно, мы предполагаем, что это параметр может меняться по желанию frontend.

Теперь, зная требуемую экспозицию, номер первой свечи и номер последней свечи, которую запрашивает frontend, мы можем выполнить соответствующий запрос к БД.

В целом, очень напоминает классическую задачу с пагинацией.

Остались мелочи. Каждая строка результата запроса конвертируется в бинарную структуру (тот самый квант) и полученный бинарный массив выводится как результат запроса, а в заголовок ответа отдается content-range:

Content-Range: [запрошенный интервал] / [[количество записей в БД] * [размер кванта]]

Стоп. А как же фрон сможет запросить нужный временной интервал, да еще и в байтовом интервале? Откуда он знает какие-то там номера свечей? Тут тоже все придумано. Range поддерживает относительное смещение, например,

Range: -52

Запросит 52 байта с конца. Т.е. последнюю свечу. Теперь, зная последний момент времени последней свечи, зная, из ответа, общий размер “файла”, можно вычислить общее количество свечей, а отсюда и определить байтовый интервал для запроса нужной временной экспозиции.

Если вам вдруг захотелось задать вопрос — зачем такие сложности? Прошу вернуться к поставленным целям. Данная технология “замаскировала” аналитические запросы к БД в бинарные файлы для CDN и браузера. Это позволяет большую часть повторяющихся запросов переложить на CDN и конечного клиента.

Возможно, возникнет еще вопрос — а почему бы не использовать простое кеширование GET запросов? Хорошо. Давайте разбираться. Если мы выполним вот такой запрос в классическом REST:

GET /api/v1/candles/7m?from=01-03-2018&to=31-03-2018

Мы получим уникальный кэш для этого запроса. Выполнив следующий запрос:

GET /api/v1/candles/7m?from=15-03-2018&to=20-03-2018

Мы получим еще один уникальный кэш…. хотя, обратите внимание, второй запрос запрашивает данные входящие в ответ первого.

Так вот, в случае выше предложенной реализации (range), второй запрос не сформирует отдельного кэша, а использует уже полученные данные из первого запроса. И не полезет на сервер. А это, экономия размера кешей и уменьшение количества обращений к серверу.

Есть ли минусы у данной технологии? Да. Они очевидны:

  1. Эта технология плохо подходит для меняющихся со временем данных, т.к. базируется на тотальном кешировании.
  2. CDN CloudFlare кэширует файлы только целиком. Это значит, что если конечный клиент запросит интервал скажем, с 1 по 100 байт, то CloudFlare реально запросит с сервера весь файл. Т.е. в нашем случае, он загрузит все свечи с определенной экспозицией. Положит у себя и будет уже раздавать сам. Это можно было бы считать даже плюсом, если бы не ограничения по месту. И если у вас могут формироваться “тяжелые” ответы, а параметров множество, то… В общем понятно, что место кончится. Возможно, мы так и не смогли его правильно настроить. Но пока результат таков.
  3. Требуется с умом управлять кешами. Для этого есть достаточные механизмы, но они требуют тюнинга.
  4. Frontend должен уметь парсить бинарные данные и иметь на борту что-то аля dataset для работы с range запросами.

Я бы сформулировал целесообразность реализации этой стратегии так — когда она вам понадобится, вы поймете. Если сейчас есть сомнения, полезно знать о ней, но не стоит заморачиваться.

Комментарии (11)


  1. Moxa
    26.09.2018 00:35

    Сколько у вас данных в базе, что нужно заморачиваться таким экзотическим кешированием? Не проще все в память положить?


    1. rpiontik Автор
      26.09.2018 00:55

      Мы используем механизм из первой статьи. Он очень прост. А этот для нас избыточен. Возможно, он будет применен в аналитической подсистеме в будущем. Данная статья — развитие идеи. Она возникла по итогу комментариев к первой. Там мне пытались доказать, что это "прибитое к nginx гвоздями решение" и что оно не имеет запаса "гибкости". В какой-то степени, просто гештальт закрыл для себя.


      1. Moxa
        26.09.2018 12:40

        так сколько у вас данных? неужели действительно не влезет в память?


  1. bcmob
    26.09.2018 12:45

    Несмотря на кучу негатива в комментариях к первой статье, я просто зашёл биржу с профиля и увидел разницу в работе с другими аналогичными. И точно такая система, описання теперь, у меня реализовалась в мозгу после прочтения предыдущей статьи.


    1. rpiontik Автор
      26.09.2018 12:52

      +100 Мне казалось, что эта статья по лишняя. Что первой вполне достаточно для предоставления базы, которую уже может развить любой, в нужно направлении. Но… как вы правильно отметили, комментарии к прошлой статье показали обратное. Я откровенно рад, если наш опыт кому-то окажется полезен.


      1. Moxa
        26.09.2018 17:32

        поделитесь пожалуйста ссылкой, никак не могу найти


  1. rpiontik Автор
    26.09.2018 12:47

    Возможно я не понимаю Вас, но причем тут память? Вы статику в память хотите запихнуть? Зачем? Есть CDN который сам решает как ему эти файлы хранить. В памяти или на диске. Мы находимся за ним.

    Закешировать запросы? Ну так я, мне кажется наглядно на это дал пояснения в статье. И положив что-то в память, это никак не решит вопрос.

    Уж тем более это никак не решит вопрос количества запросов. Даже если предположить, что данные находящиеся в памяти решают проблему времени доступа к ним, то что с количеством соединений?

    Плз, посмотрите цели которые я ставлю в статье, и если вы видите как их достичь просто положив что-то в память, я с удовольствием отражу это в статье как альтернативное решение. Ну или вообще признаю статью несостоятельной для приведенных условий.

    to Moxa


    1. Moxa
      26.09.2018 17:28

      если данные лежат в памяти, то никакие cdn не нужны, слабенкая впска за 10 баксов сможет обрабатывать десятки тысяч подключений


  1. rpiontik Автор
    26.09.2018 17:46

    Т.е. если на дохлой VPS (2400core, 512кб ОЗУ, 10Гб HD) с каналом 100мб, мы запихнем все в память… и можно отказаться от CDN? Верно? Доступ к данным будет для всех одинаково скор, хоть в Африке, хоть в Европе? Ну и DDoS (шквальную нагрузку запросами) мы легко переживем? А падение этого сервера невероятно, т.к. все лежит в памяти. Я ничего не упустил?


  1. maydjin
    27.09.2018 12:30

    Но замеров — так и не представили...


    Говорят, что очевидно, что mergesort лучше quicksort, а поддерживать отсортированный массив пар цена/количество это убого по сравнению с красночёрным деревом и уж тем более хэш таблицей. И эти утверждения даже строгие доказательства имеют.


    Но на практике, c++ реализация quicksort, проходит тесты для mergesort для остальных языков на том же stepic'e. А за хранение аггрегированной буки до 10000 элементов в хэш таблице, в приличном обществе, можно и в глаз получить.


  1. rpiontik Автор
    27.09.2018 14:37

    Будет время — сделаю.

    Но на практике, c++ реализация quicksort, проходит тесты для mergesort для остальных языков на том же stepic'e. А за хранение аггрегированной буки до 10000 элементов в хэш таблице, в приличном обществе, можно и в глаз получить.


    Мне кажется, это как определять степень влажности по итогу сравнения объемов мокрого с теплым… в целом я не против такого механизма, если его используют другие.