Как известно, скорость любого каравана ограничена сверху скоростью самого медленного верблюда. В мире программирования, мы, то и дело, сталкиваемся с этим принципом, разрабатывая сложную, многокомпонентную систему взаимодействующих модулей. Оптимизировав свои внутренние алгоритмы, мы сталкиваемся с ограничениями верблюдов-драйверов, предоставляющих нам интерфейс к сторонним сервисам: базам данным, менеджерам очередей сообщений, и т.д.
К сожалению, в сообществе node.js, на данный момент сложилась такая ситуация, что подавляющее большинство драйверов к распространённым сервисам имеет ряд существенных недостатков, не позволяющих приложениям достигать заслуженных высот эффективности и стабильности. Вы наверняка слышали все эти ужасающие истории о том, что «node.js течёт», оно “игрушечное”, не предназначенное для применения в настоящей высоконагруженной среде. Однако, как мы убедились на собственном опыте, с умом написанное ПО для ноды блестяще справляется со всеми испытаниями суровых боевых реалий. И здесь мы приходим к главному вопросу: что же мешает среднестатистическому node.js-драйверу нормальной работе? Полномасштабному обзору этих препятствий можно посвятить отдельную статью, однако, уже сейчас можно выделить наиболее типичные признаки проблемного продукта:
1) медлительность (отсутствие учёта особенностей v8)
2) некорректная обработка входящих чанков (отсутствие учёта эффектов tcp-сегментации)
3) утечки памяти (обработчики событий и их своевременное очищение)
4) отсутствие фейловера и других полезных плюшек.
Одним из первых нам понадобился драйвер для базы данных PosgreSQL. Поэкспериментировав с имеющимся решениями, мы пришли к выводу, что разумнее будет написать свой драйвер. В дальнейшем, сталкиваясь с необходимостью взаимодействовать всё с большим количеством сервисов, мы неизбежно возвращались к тому же самому решению.
Итак, мы написали свой многопоточный драйвер для PosgreSQL на c+, в котором реализовали механизм failover. Когда рвётся соединение с сервером, мы больше не думаем о том, потеряются ли запросы. Наш драйвер хранит их в оперативной памяти и при восстановлении соединения возобновляет запросы.
Проектируя интерфейс, мы руководствовались принципом “ничего лишнего и только по делу”.
Для того, чтобы выполнить запрос, надо сначала инициализировать пул соединений. Это делается очень просто с помощью функции pg.init, в которую необходимо передать количество одновременных соединений и настройки соединения:
pg.init(20, {
'user': 'postgres',
'dbname': 'postgres',
'hostaddr': '127.0.0.1',
'password': '123'
});
После чего можно выполнить запрос и обработать полученный результат:
var query = "SELECT 1 AS value";
pg.exec(query, function(table) {
console.log('Result table:', table);
}, console.error);
Также можно выполнить заранее подготовленный запрос:
var preparedQuery = "SELECT $word1 AS word1, $word2 AS word2";
pg.execPrepared(preparedQuery, {
'word1': 'hello',
'word2': 'world'
}, function(table) {
console.log('Result table:', table);
}, console.error);
После выполнения всех необходимых действий можно разорвать соединение с помощью драйвера, просто вызвав функцию pg.destroy();
Как видно, всё очень просто и прозрачно. Можно, не тратя много времени на ознакомление с интерфейсом драйвера, начать разрабатывать.
Также мы написали теста сравнения скорости и сравнили наш драйвер с одним из самых популярных, а именно node-postgres. Мы делали запросы пакетов данных в 1байт и 1Кбайт и сравнивали затраченные время и память. Результаты ниже представлены в полулогарифмическом масштабе:
Как видно из графика, после 100000 запросов время ожидания результата стремительно растёт. На 500000 запросах приходится ждать более 25 минут! Наш драйвер обрабатывает такое количество запросов за полминуты.
Как видно из графика, представленного ниже, при количестве запросов 10000 — 100000 разница во времени обработки существенна.
Похожая ситуация и при пакете данных в 1Кбайт. Результаты ниже представлены в полулогарифмическом масштабе.
Также мы сравнили результаты по расходуемой памяти. Как видно из графиков, здесь наш драйвер тоже выигрывает. Далее все графики представлены в полулогарифмическом масштабе.
В итоге мы разработали простой, быстрый драйвер с возможностью failover’а.
Вы можете установить его с помощью npm, выполнив в консоли команду:
npm install livetex-node-pg
и подключив его в своём приложении:
var pg = require(‘livetex-node-pg’);
Также вы можете зафоркать проект на github, поиграться, предложить свои изменения:
https://github.com/LiveTex/Node-Pg
Автор: LiveTex