Поддержка MySQL в Node.js: node-mysql-libmysqlclient

    Я довольно долго откладывал этот анонс, однако сейчас настало его время.

    Встречайте: node-mysql-libmysqlclient v0.0.7, коннектор к MySQL для Node.js, поддерживающий синхронное и асинхронное выполнение запросов к БД и имеющий API, близкое к API аналогичных коннекторов для PHP/Perl/Ruby etc.

    MySQL и Node.js: история отношений


    Node.JS разрабатывался в основном для написания быстрых асинхронных серверов (см. [1]), для обслуживая очередей сообщений, отдачи небольшого количества статики и других задач, оперирующих с небольшими объёмами данных. Поэтому первыми появились коннекторы к NoSQL базам данных и Memcached. На том этапе развития Node.JS казалось, что это покрывает большинство возможных применений системы. В случае, если требовалась работы с реляционными базами данных, писался демон на другом языке, который подготавливал данные для Node.js сервера. Как вы понимаете, это тормозило развитие Node.js, ведь многие привыкли к поддержке реляционных БД в стандартных библиотеках распространённых языков серверного программирования. Этого не отрицал и разработчик Node.js, планировавший включить подобные коннекторы в одну из поздних стабильных версий. Однако пока этого не произошло, а сторонние коннекторы к реляционным БД начали появляться. На момент начала написания node-mysql-libmysqlclient их было несколько:

    Yet another MySQL connector for Node.js?


    Как я считаю, у всех них были свои недостатки. Несомненно, удобно написать коннектор непосредственно на Javascript, как было сделано в node-mysql. Однако это довольно большой объём кода, который необходимо тщательно тестировать и скрупулёзно обновлять при изменении API Node.js. Также, как оказалось, реализация на C/C++ в 3-5 раз быстрее, чем на JavaScript. Для использования node.dbslayer.js необходимо использовать прослойку в виде DBsLayer для обеспечения асинхронности выполнения запросов. Node_postgres в своих первых реализациях была очень мало функциональна. И ни один из этих коннекторов не приближался к аналогичным в других языках по наличию утилитарных функций. Это и стало основной причиной, по которой я начал писать node-mysql-libmysqlclient.

    Node-mysql-libmysqlclient: возможности


    На данный момент реализованы биндинги для всех функций API библиотеки libmysqlclient, касающихся утилитарных функций, выполнения запросов и получения данных. Также коннектор позволяет выполнять запросы асинхронно, с callback-ом или без. Пример:

    /* http://gist.github.com/537870 */
    
    var mysql_libmysqlclient = require("mysql-libmysqlclient");
    var conn = mysql_libmysqlclient.createConnection(host, user, password, database);
    
    if (!conn.connected()) {
      sys.puts("Connection error: " + conn.connectErrno() + ", " + conn.connectError());
      process.exit(1);
    }
    
    var string = conn.escape("Sannis's code");
    
    /* Sync queries */
    var res = conn.query("CREATE TEMPORARY TABLE t1 (alpha INTEGER, beta VARCHAR(255), pi FLOAT);");
    sys.puts("'CREATE TABLE' result: " + sys.inspect(res));
    res = conn.query("INSERT INTO t1 VALUES (1, 'hello', 3.141);");
    sys.puts("LastInsertId: " + sys.inspect(conn.lastInsertId()));
    
    /* Async queries */
    conn.queryAsync("INSERT INTO t1 VALUES (2, 'world', 2.718);", function (res) {
      conn.queryAsync("SELECT * FROM t1;", function (res) {
        sys.puts("NumRows: " + res.numRows());
        var rows = res.fetchAll();
        sys.puts("Rows: " + sys.inspect(rows));
        conn.queryAsync("DELETE * FROM t1;");
      });
    });


    Ближайшие планы

    • Больше тестов, особенно для асинхронных запросов
    • Обновить документацию по API и написать примеры использования для всех функций
    • Написание бенчмарков для всех существующих на данный момент коннекторов к MySQL
    • Реализация prepared statements
    Реализация асинхронности

    Библиотека libmysqlclient нативно не поддерживает асинхронное выполнение запросов. Второй вызов mysql_query() до получения всех результатов после mysql_use_result() или вызова mysql_store_result() вызовет ошибку. Есть, правда, недокументированная функция mysql_send_query, которую использует разработчик коннектора mysql2 для Ruby, но я решил не идти этим путём.

    Для поддержки асинхронных I/O операций в состав Node.js включена библиотека libeio. Она не только поддерживает выполнения дисковых операций с коллбеками для результата, но и с помощью eio_custom позволяет выполнить любую функцию в отдельном потоке и вернуть результат её выполнения в коллбек. В libeio использует threads-pool, так что во избежание параллельного выполнения mysql_query она дополнительно обёрнута в мьютекс.

    Возможно, текущая реализация не выдержит конкуренции в случае высокой нагрузки на event-loop или libeio threads-pool и тогда я думаю вернутся к системе с очередью запросов, когда за последовательный запуск mysql_query будет отвечать очередь и не потребуется использование мьютекса.

    Фрагмент кода, отвечающий за асинхронные запросы: github.com/Sannis/node-mysql-libmysqlclient/blob/v0.0.7/src/mysql_bindings_connection.cc#L825-946

    Ссылки по теме

    [1] Сайт Node.js
    [2] Node.js на GitHub
    [3] Node-mysql-libmysqlclient на GitHub
    [4] Node-mysql-libmysqlclient API

    P.S. Мне вполне хватает кармы для публикации топика в тематический блог. Но я никак не могу решить, в каком ему будет лучше: JavaScript или Web-разработка?
    Поделиться публикацией

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

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

    Подробнее
    Реклама

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

      +2
      Javascript
        +8
        Наверное, пора открывать блог по node.js
          +3
          Давно пора, но нет возможности.
            0
            У меня тут есть один пустой коллективный блог, возможно администрация ответит на мой e-mail по поводу переименования.
          +1
          выглядит вкусно
            –5
            var conn = mysql_libmysqlclient.createConnection(host, user, password, database);


            секьюрно ли в коде JS хранить настройки базы данных?
              +5
              Да. Node.JS работает на серверной стороне, так что файлы скриптов не должны быть доступны из сети.
              0
              Может слить вместе две квери функции и определять асинхронность по наличию коллбэка в параметрах?
                0
                Скорее всего перейдём в следующей же версии к стандартному именованию, вместо query/queryAsync будет querySync/query. Мне пока кажется, что бывают случаи, когда коллбек не обязателен, а запрос можно выполнить асинхронно.

                Пока ещё есть сомнения, что текущая реализация обёртки с мьютексами для запуска асинхронных запросов лучше, чем диспетчер с очередью запросов. Как только буду в этом уверен, будет v0.1.0 с зафиксированным API.
                  0
                  Таки можно оставить обе функции, а в первой сделать проверку параметра, и если он есть, то переход на вторую. Мне так кажется удобным, но решать в итоге, конечно, вам :)
                    0
                    Буду думать об этом :)
                      0
                      Я за переименование… Всё блокрующее в Ноде стоит помечать окончанием Sync а не наоборот…
                        0
                        Можно считать это уже решённым.
                          +1
                          Да, или такой вариант. Потому что меня смущало именно то что использоваться обычно будет функция с более длинным именем.
                            0
                            Правда тогда остаётся открытым вопрос именования остальных функций. Большинство утилитарных функций предполагается использовать при инициализации приложения и у меня нет планов делать их асинхронными. Например такие функции как conn.setCharset(), conn.setSsl() и т.д. Мне кажется не стоит всем им приписывать Sync, если асинхронная версия не предполагается. Кроме того, для result.fetchAll() собираюсь сделать асинхронную версию, а вот для остальных fetch* собираюсь оставить синхронными для тех, кто переходит на Node.JS с PHP/Perl.
                              0
                              Стоит человеку переходящему с PHP/Perl не понимая того заюзать блокирующую функцию вне инициализации, как его проект начнёт дико медленно работать, и он будет во всю ругаться и кричать что Нода медленная, поэтому мне кажется Важно чётка Дать разработчику понять, что блокирует выполнение, а что нет.
                                0
                                Правильный аргумент. Чувствую грядёт массивная перетряска API.
                                  0
                                  Как же тогда существует в самой ноде такая вещь как fs.Stats? Я допуская правильность использования имён querySync, connectSync, но isConnectedSync (сейчас connected()) как-то не смотрится. Разве что делать её свойством, тогда оно будет использоваться только синхронно и вопрос именования снимается.
                          +1
                          Однозначно требуется стандартное именование, когда синхронная версия имеет суффикс Sync, а все коллбэки имеют параметр err.
                            0
                            Присоединяюсь, уже на автомате почуял неладно и перечитал код в топике и понял такое.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Обновил пример: gist.github.com/537870

                          Сам Node.js тестировали и много. Можно начать с записей разработчиков на этот счёт(http://four.livejournal.com/1018026.html, four.livejournal.com/1019177.html, four.livejournal.com/1020129.html, http://debuggable.com/posts/node_js:4ab4d9d7-b788-41d4-85c0-1b51cbdd56cb), за более поздними бенчмарками я не следил, но думаю качественно картина осталась такой же. Память течёт мало и всегда можно запустить GC. Что касается биндингов, то тут тестов по этой части мало.
                            0
                            Спасибо за замечание, в следующей версии обязательно расширю документацию, давно её не дописывал.
                              0
                              Первый тест который я написал — это простейший http-сервер и http-клиент. Клиент DOS-ил сервер запросами. На ноуте (старенький MacBook Pro) серверный скрипт откушал 43Мб, а клиентский 6Мб. Серверный кушал порядка 65% процессора, а клиентский порядка 35%. netstat показал 16390 сокетов на порту, на котором велись тесты. Тест выдал порядка 6000 запросов в сек. Я считаю, что это очень неплохой результат.
                                0
                                11-12 000 одновременно подключенных держит.
                                0
                                А можете подробнее написать, каким образом реализована асинхронность?
                                  0
                                  +1. Очень интересно, как это удалось сделать.

                                  Автору — спасибо!
                                    0
                                    Скорее всего каждый запрос в отдельном треде
                                    0
                                    Добавил раздел «Реализация асинхронности» в пост. Запросы выполняются в потоках на основе libeio, включённого в Node.JS.
                                      0
                                      Спасибо за ответ. Понятно. Что-то вроде этого и ожидал.

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

                                      Простите за мысли вслух. Просто у самого есть сейчас похожая задача, только для Perl'а. И пока еще не решил, что в итоге использовать: то ли Coro::Mysql попробовать, то ли DBSlayer (с ним, правда, тоже есть определенные неудобства, например то, что его придется поднимать для каждой базы отдельно. И если база, например, шардированная, то для каждого шарда отдельно. Это не очень удобно в эксплуатации), то ли свою http-прослойку наподобие DBSlayer'а написать.
                                    0
                                    Добавил в Gentoo-оверлей: github.com/monoid/gentoo-nodejs :)
                                      0
                                      Спасибо, разве что в DEscriptION обновить :-)
                                        0
                                        Спасибо, исправил :)
                                      +1
                                      Я обычно статьи по Node.JSкладу в Web-разработку. Просто потому, что к традиционной тематике блога Javascript серверсайд имеет мало отношения.
                                        0
                                        Ого, спасибо.
                                        Только не хватает инструкций о том, как это собирать. Просто на gcc или cpp скомпилировать?
                                          0
                                          $ node-waf configure build
                                          в папке проекта. В README это есть, насколько я помню ;-)

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

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