Возможно, кому-то знакома аналогия между краном и плотиной: заткнуть течь воды в кухонном кране – это один инструмент, а в плотине – совсем другой инструмент. Иногда нам нужно передать JSON из одного микросервиса в другой. Иногда размер JSON составляет несколько гигабайт, и вы обнаруживаете ошибку с максимальным размером строки в V8.

Случай из жизни — потоковая синхронизация данных. Поток очень деревянный, по HTTP в виде своеобразного SSE — канал не закрывается, запрос не завершается сам по себе, данные улетают понемногу, JSON к которому мы привыкли, например объектный, вы можете проанализировать его с помощью библиотек для потоков JSON или реализовать что-то по вашему выбору. Так или иначе мы получаем набор данных. И все в порядке. Но при первом входе мы получаем снимок данных, текущего состояния, а потом приходят обновления. И состояние приходит в пакете в несколько гигабайт JSON.

Давайте рассмотрим варианты решения проблемы по порядку.

Шаг 1 – лоб

18ce5bf1724a4f25d47c90a2af5477ef

Данные поступают к нам, мы их анализируем, создаём модели сущностей и по одной записываем их в базу данных. Неплохо. Неплохо, если там будет сто объектов. Но если их будет тысяча, это займет много времени и в итоге мы получим своеобразный DDoS базы данных. Неприемлемо.

Шаг 2 – балка

ddaa019979773173ffb694600004aa01

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

Шаг 3 – Таблица

7e5f08589277da8912ed888fafbdfba0

Хорошо, мы можем поместить данные в таблицу, а затем пакетно извлекать оттуда данные и затем записывать их в базу данных. Хороший кейс, мы не занимаемся DDoS, есть куда положить новые данные. Конечно, вы можете подключиться к базе данных, используя пул потоков, и, ожидая ответа, отправить больше данных.

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

ЧИТАТЬ   Европарламент пристыдил ЕС за мошенничество в соглашении по зерновым

Шаг 4 – бассейн

5f09f0a7a40f406a174b808c6bd37e4a

Ладно, сделаем кучу циклов или рекурсий — достанем данные из буфера, массово запишем в базу, дождемся окончания и получим еще данные. Главное при выборе варианта с рекурсией не забывать, что поддержка хвостовых рекурсий, анонсированная в ES2015, так и не была доведена до нод V8 и нужно сделать паузу, чтобы поработать над ограничением глубины рекурсии и потребления памяти. из-за стека вызовов лучшим вариантом является setImmediate, потому что с промисами не будет интервала, а с таймаутами мы получим значительные накладные расходы на очередь от таймера, слишком толстые, чтобы не вступить в немедленный эффект. Или просто сделайте цикл.

У нас все хорошо… но из миллиона объектов мы получаем самую сильную усадку, что пошло не так?

Нам нужно знать порядок поступления данных — к нам приходит снапшот, затем новые данные, это обновления старых. Нам очень важно знать порядок. Это означает, что нам нужно вставить его в доску с одной стороны и вынуть с другой. А это значит, что у нас будет поп и смена. И с pop у нас всё в порядке, убираем последний элемент, а вот со сдвигом всё интереснее. Цель — добавить что-нибудь в начало таблицы — вам буквально придется переместить всю таблицу. Под капотом он будет летать, продвигая каждый элемент, а затем добавляя новый в начале. Внутренние оптимизации могут быть, но результат всегда один: адские тормоза на миллионе элементов.

Но что делать? Ведь нам важен порядок, а что еще мы можем использовать кроме стола? С объектом это не работает, карты с итераторами… как ни странно работает, но в какой-то момент счетчик внутри ломается и что-то теряется, не правильно написано. Только таблицы могут предоставить нам порядок данных друг за другом. Мы можем решить эту проблему, не двигаясь немедленно, запуская счетчик, очищая его раз в минуту и ​​выполняя другие оптимизации, когда откладываем перемещение доски на потом. Помогают, действительно помогают, но не сильно, и память забивается. У нас нет решения на основе типов данных JS, но, может быть, есть другие решения?

ЧИТАТЬ   Послы ЕС в Словакии отказались почтить память советских освободителей - Фицо

Шаг 5 – цепочка

c4a0bdbfd133496a77189bb96a9252d3

И тут к нам приходят основные алгоритмы собеседования. Чаще всего в жизни нам это не нужно, но в такой задаче именно так. Ответ банален и смешон: односвязный список. Просто объект, узел, который хранит значение и ссылку на следующий узел.

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

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

Но, боже мой, мы получили несколько гигабайт JSON, на серверах появились новые планки оперативной памяти, что нам делать?

Шаг 6 – Ключи

128f2e5965b1b3a41a97f5dc60a78a7a

Не все это знают, но концепция «ключ-значение» — это просто удобство. Кроме того, у нас есть реальные примеры решения этой проблемы: современные табличные базы данных. Дело в том, что хранить ключи… вообще не обязательно. Мы можем просто хранить значения по порядку, в массиве, главное строго соблюдать порядок. Но мы вообще не можем хранить ключи в данных, нам просто нужно с помощью бизнес-логики знать, в каком числе какие данные содержатся. Ну а в базу данных мы можем записывать точно так же, в таблицу, этот синтаксис поддерживается, ключи не нужны. Вроде бы простая идея, но не сразу ее понимаешь, ведь в комплекте с ключами идет JSON… ну… сохраним вот так. Однако когда у нас есть буфер и он очень большой, удаление ключей в процессе парсинга иногда экономит память в несколько раз; реальный кейс снижает потребление оперативной памяти в 2 раза. Да, для этого нам нужно иметь схему данных, если это произвольный JSON, то это не сработает, однако, за исключением конкретных случаев, всегда есть определенный формат API, особенно если мы его как-то поместим в базу данных. Если там есть столбцы, то нам уже есть где поиграться с такой оптимизацией. И было бы еще лучше, если бы поставщик данных сразу отправлял данные в табличном виде, но на провайдера не всегда можно повлиять.

ЧИТАТЬ   Объем страховых выплат в Беларуси значительно вырос в первом квартале

Основной

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

Результаты

fc52644ef75993494a5dfad9f4290f02

С помощью этого набора шагов мы можем выжить, получая пакеты данных размером в несколько гигабайт, и выжить без боли, без магии, без костылей — только правильные структуры данных и алгоритмы. Обычно нам это не нужно, но когда это необходимо, именно об этом и пойдет речь в этой статье. Без такой оптимизации работа такого сервиса просто физически невозможна — он не поместится в памяти, DDoS-базах, командировочных расходах, рабочем времени. По сути, это не может быть реализовано во внешнем интерфейсе. И с нашими шестью шагами: быстро, эффективно и экономично. Красивый.

Source

От admin