Запрос большого набора данных в базе данных Oracle из NodeJS

В настоящее время я работаю над проектом с работы, где у меня есть таблица базы данных Oracle 10 с примерно 310K строк, которые дают или принимают 10-30K строк.

Цель состоит в том, чтобы отобразить эти строки в угловом интерфейсе, однако возврат всех из них через NodeJS занимает много времени.

Учитывая, что я впервые использую и NodeJS, и oracledb, я предполагаю, что я что-то упустил?

var oracledb = require('oracledb');
var config = require(__dirname+'/../db.js');

function get(req,res,next)
{
var table = req.query.table;
var meta;

oracledb.getConnection(config.oracle)
.then( function(connection)
{
    var stream = connection.queryStream('SELECT * FROM '+table);

    stream.on('error', function (error) 
    {
        console.error(error);
        return next(err);
    });

    stream.on('metadata', function (metadata) {
        console.log(metadata);
    });

    stream.on('data', function (data) {
        console.log(data);
    });

    stream.on('end', function () 
    {
      connection.release(
        function(err) {
          if (err) {
            console.error(err.message);
            return next(err);
          }
        });
    });
})
.catch(function(err){
    if(err){
        connection.close(function(err){
            if(err){
                console.error(err.message);
                return next(err);
            }
        });
    }
})
}

module.exports.get = get;

1 ответ

Решение

30 МБ - это много данных для загрузки в интерфейс. Он может работать в некоторых случаях, например, в настольных веб-приложениях, где преимущества "кэширования" данных компенсируют время, необходимое для их загрузки (и увеличение устаревших данных - это нормально). Но это не будет работать хорошо в других случаях, таких как мобильный.

Помните, что 30 МБ необходимо перенести из БД в Node.js, а затем из Node.js в клиент. Сетевые соединения между ними сильно влияют на производительность.

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

Во-первых, если вы используете веб-сервер, вы должны использовать пул соединений, а не выделенные / одноразовые соединения. Как правило, вы создаете пул соединений в вашем index/main/app.js и запускаете веб-сервер после того, как это будет сделано и готово.

Вот пример:

const oracledb = require('oracledb');
const express = require('express');
const config = require('./db-config.js');
const thingController = require('./things-controller.js');

// Node.js used 4 background threads by default, increase to handle max DB pool.
// This must be done before any other calls that will use the libuv threadpool.
process.env.UV_THREADPOOL_SIZE = config.poolMax + 4;

// This setting can be used to reduce the number of round trips between Node.js
// and the database.
oracledb.prefetchRows = 10000;

function initDBConnectionPool() {
  console.log('Initializing database connection pool');

  return oracledb.createPool(config);
}

function initWebServer() {
  console.log('Initializing webserver');

  app = express();

  let router = new express.Router();

  router.route('/things')
    .get(thingController.get);  

  app.use('/api', router);

  app.listen(3000, () => {
    console.log('Webserver listening on localhost:3000');
  });
}

initDBConnectionPool()
  .then(() => {
    initWebServer();
  })
  .catch(err => {
    console.log(err);
  });

Это создаст пул, который будет добавлен во внутренний кеш пула в драйвере. Это позволяет легко получить к нему доступ из других модулей (пример позже).

Обратите внимание, что при использовании пулов соединений обычно рекомендуется увеличить пул потоков, доступный для Node.js, чтобы позволить каждому соединению в пуле работать одновременно. Пример этого включен выше.

Кроме того, я увеличиваю значение oracledb.prefetchRows. Этот параметр напрямую связан с вашим вопросом. Сетевые обходы используются для перемещения данных между БД и Node.js. Этот параметр позволяет вам регулировать количество строк, извлекаемых с каждым циклом. Таким образом, поскольку prefetchRows становится выше, требуется меньше циклов и увеличивается производительность. Только будьте осторожны, вы не поднимаетесь до максимума в соответствии с памятью, которая у вас есть на вашем сервере Node.js.

Я запустил общий тест, который издевался над размером набора данных 30 МБ. Когда для oracledb.prefetchRows было установлено значение по умолчанию 100, тест завершился через 1 минуту 6 секунд. Когда я увеличил это до 10000, это закончилось за 27 секунд.

Ладно, перейдем к "things-controller.js", который основан на вашем коде. Я обновил код, чтобы сделать следующее:

  • Утвердите, что таблица является допустимым именем таблицы. Ваш текущий код уязвим для внедрения SQL.
  • Используйте цепочку обещаний, которая эмулирует блок try/catch/finally, чтобы закрыть соединение только один раз и вернуть первую обнаруженную ошибку (если необходимо).
  • Работай, чтобы я мог запустить тест.

Вот результат:

const oracledb = require('oracledb');

function get(req, res, next) {
    const table = req.query.table;
    const rows = [];
    let conn;
    let err; // Will store the first error encountered

    // You need something like this to preven SQL injection. The current code
    // is wide open.
    if (!isSimpleSqlName(table)) {
        next(new Error('Not simple SQL name'));
        return;
    }

    // If you don't pass a config, the connection is pulled from the 'default'
    // pool in the cache.
    oracledb.getConnection() 
        .then(c => {
            return new Promise((resolve, reject) => {
                conn = c;

                const stream = conn.queryStream('SELECT * FROM ' + table);

                stream.on('error', err => {
                    reject(err);
                });

                stream.on('data', data => {
                    rows.push(data); 
                });

                stream.on('end', function () {
                    resolve();
                });
            });
        })
        .catch(e => {
            err = err || e;
        })
        .then(() => {
            if (conn) { // conn assignment worked, need to close/release conn
                return conn.close();
            }
        })
        .catch(e => {
            console.log(e); // Just log, error during release doesn't affect other work
        })
        .then(() => {
            if (err) {
                next(err);
                return;
            }

            res.status(200).json(rows);
        });
}

module.exports.get = get;

function isSimpleSqlName(name) {
  if (name.length > 30) {
    return false;
  }

  // Fairly generic, but effective. Would need to be adjusted to accommodate quoted identifiers,
  // schemas, etc.
  if (!/^[a-zA-Z0-9#_$]+$/.test(name)) {
    return false;
  }

  return true;
}

Надеюсь, это поможет. Дайте мне знать, если у вас есть вопросы.

Другие вопросы по тегам