Драйвер для PostgreSQL на Node.js

Быстрый Node.js

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

К сожалению, в сообществе 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
Компания

Похожие публикации

Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 19

    +1
    с картинками вроде беда
      0
      Поправили, спасибо :)
      +4
      В вашем идеальном мире существует только 1 сервер postgresql? Как мне к двум подключиться? И где фейловер?
        –1
        Зачем усложнять драйвер и давать ему на обработку такую непростую логику? Это, наверняка, будет медленнее. Есть pgpool2 и прочее.
          –1
          Два сервера с разными данными — сложная логика? В статье написано «быстрый драйвер с возможностью failover’а», при чём тут pgpool?
            0
            А если серверов будет больше двух в вашей схеме?)

            Два сервера с одинаковыми данными — это уже сложная логика. Если еще и данные разные, то все еще хуже. Ну это не задача драйвера такие проблемы решать, тем более если есть претензии на «быстроту».

            Pgpool при том, что эти проблемы он легко решает
            — пул соединений и балансировка
            — почти настоящий failover
            — in-memory query cache в качестве приятного бонуса ))
              0
              > А если серверов будет больше двух в вашей схеме?)

              В моей схеме? Я никакую схему не показывал вроде бы.

              > Ну это не задача драйвера такие проблемы решать, тем более если есть претензии на «быстроту».

              Не задача драйвера уметь соединения к нескольким серверам в пределах одного процесса? Ок-ок.

              > Pgpool при том, что эти проблемы он легко решает

              В статье ни слова про pgpool. Статья про драйвер. В статье написано, что есть фейловер, я его не увидел. Pgpool не при чём.
          0
          Что вы подразумеваете под «идеальным» миром?
            0
            Мир, где все данные можно получить из одного сервера postresql. Или всё же ваш драйвер умеет несколько серверов в одном процессе?
              0
              Нет, наш драйвер работает только с одним сервером. Прошу заметить, мы нигде не указывали то что драйвер универсальный и т.п. Нам понадобился драйвер работающий быстро с одной базой и мы его написали. Если вам необходимо решение работающие с несколькими базами, то делайте pull request, будем только рады.
          0
          Не очень понятно. Если у меня в середине транзакции отвалится коннект, что сделает ваш драйвер? Или пострес позволяет подключиться к существующей транзакции?

          Вообще этот момент с автоматическим переподключением вызывает много вопросов, и самый главный — зачем это надо? В остальном здорово, хотя лично я не люблю излишнюю фрагментацию и предпочел бы видеть вашу работу в виде патчей к текущему стандартному (де факто) драйверу.
            0
            В данный момент наш драйвер не поддерживает транзакции.
            0
            По-моему, node.js слабо подходящая технология для активной работы с базой данных.
              0
              Да нормальная это технология. Чем по вашему nodejs приложения дергающее sql базу асинхронно будет отличаться от дергающего mongo асинхронно? Правильно ни чем.
              Это абсолютно беспочвенные домыслы, что для node.js sql базы не подходят, а надо обязательно юзать одно из 100500 nosql решений (и типо все сразу станет по феншую).

              PS. Я использую сам и sql и mongo в разных проектах.
                0
                Получается асинхронное дерганье подходит практически для любых баз. Ну, а если база будет графориентированная, то я думаю тоже ничего не изменится?
                  0
                  Да, все зависит только от реализации драйвера/клиента.
              0
              Умеет ли драйвер выдавать ответ с несколькими rowset-ами?
              Например из процедуры…

              или если выполнить одним запросом
              select * from test1; select * from test2;
              

                0
                Точно не знаю, но почти уверен, что ответ нет. Вы знаете способ чтобы из функции получить два курсора?
                  0
                  Нет, в результат вернется только последний.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое