Node.JS
19 August 2010

Поддержка 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-разработка?

+38
13.8k 71
Comments 36