NeDB (Node.js Embedded Database)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 09:09, 2 июня 2018.
NeDB (Node.js Embedded Database)
Разработчики: Сообщество
Постоянный выпуск: 1.8 / апрель 2018 года
Написана на: JavaScript
Операционная система: Кроссплатформенное программное обеспечение
Тип ПО: Встраиваемая база данных для языка JavaScript
Лицензия: Лицензия MIT
Веб-сайт github.com/louischatriot/nedb

NeDB (Node.js Embedded Database) - встраиваемая база данных для проектов на Node.js, принадлежащая подможеству MongoDB API. Данная СУБД написана на чистом JavaScript и не имеет других бинарных зависимостей кроме Node.js. Хранение данных NeDb обеспечивает в простом json-файле, который записан на диск.

Установка

Данная СУБД поддерживает установку с использованием менеджеров пакетов: npm и bower. Соотвественно перед установкой самой СУБД необходимо установить один из перечисленных с официального сайта производителя. Далее соотвественно можно приступить непосредственно к установке NeDB.

В случае npm:

npm install nedb --save

Или же можно подключить к проекту в виде зависимости в файле package.json:

{
	"name": "nedbProject",
	"version": "0.0.1",
	"dependencies": {
		"express": "^4.10.6",
		"nedb": "^1.8",
	}
}

В случае с bower все примерно так же:

bower install nedb

Создание базы данных

Хранилище (datastore) в NeDB - это эквивалент коллекции в MongoDB.[Источник 1]

NeDB поддерживает два типа хранения данных:

  • в оперативной памяти (in-memory)
  • постоянные (persistent)

Создание харанилища происходит с помощью конструктора:

new Datastore(options)

На месте options могут быть указаны дополнительный параметры, например, такие как:

Параметры подключения
Параметр Описание
filename Опциональный параметр задающий расположение файла для хранения данных хранилища. В случае пустого параметра хранилище создается в режиме хранения в оперативной памяти (in-memory)
inMemoryOnly Параметр указывающий тип хранения данных. Поумолчанию: false.
autoload Параметр отвечающий за автоматическую загрузку данных из файла при создании объекта подключения, при этом не нужно будет использовать метод loadDatabase(). Поумолчанию: false
onload В данном параметре можно указать функцию обработки завершения подключения в случае использования autoload

Рассмотрим несколько пример создания хранилищ NeDB. Прежде всего нужно подключить драйвер NeDB в скрипт, проект:

var Datastore = require('nedb');

Далее можем приступать к созданию хранилища:

var db = new Datastore();

В данном случае мы создали хранилище в оперативной памяти. Далее рассмотрим пример создания постоянного хранилища.

var Datastore = require('nedb');
var db = new Datastore({ filename: 'path/to/datafile' });
db.loadDatabase(function (err) {    
});

В данном случае из-за того, что мы не использовали параметр autoload нам пришлось вручную вызывать функцию загрузки данных db.loadDatabase(), которая принимает в качестве параметр функцию обратного вызова. Воспользуемся параметров автоматической загрузки данных (при этом не нужно вручную вызывать функцию загрузки):

var Datastore = require('nedb');
var db = new Datastore({ filename: 'path/to/datafile', autoload: true });

И конечно можно использовать несколько хранилищ, записав из в контейнер, например ассоциативный массив:

var Datastore = require('nedb');
var db = {};
db.datastore1 = new Datastore('path/to/datafile1');
db.datastore2 = new Datastore('path/to/datafile2');
db.datastore1.loadDatabase();
db.datastore2.loadDatabase();

Хранение постоянных данных

В целях повышения производительности NeDB использует так называемый "append-only" формат, который подразумевает, что команды обновления (update) или удаления (delete) записываются в конец файла. Так же в целях быстродействия NeDB автоматически переводит файл с данными в однострочный формат при подулючении или создании. Есть функция, позволяющая вручную запустить перевод в однострочный формат:

db.persistence.compactDatafile();

Также есть возможность запускать перевод в однострочный формат с жестко заданным интервалом с помощью:

var interval = 1000;
db.persistence.setAutocompactionInterval(interval);

В качестве параметра передается длина интервала в миллисекундах. Стоит отметить, что к выбору интервала стоит подойти с умом, так как данная операция требует времени, а все запросы приостанавливаются на время выполнения перевода в однострочный формат.

Запись в хранилище

NeDB поддерживает следующие типы данных:

  • String
  • Number
  • Boolean
  • Date
  • null

В случае если поле undefined, оно не будет сохранено, в отличие от MongoDB, который приводит тип к null. Еще одна особенность - если документ не содержит "_id" поле, то СУБД создаст его автоматически, который будет представлять из себя строку длины 16. Стоит отметить, что имя полей не может начинаться с символа "$" и содержать точку. Рассмотрим пример добавления документа (своего рода аналого строка (row) в реляционных БД, но имеющие произвольную структуру) в хранилище.

var doc = { field1: 'field1Value'
               , field2: 5
               , field3: new Date()
               , field4: true
               , field5: null
               , notToBeSaved: undefined 
               };

db.insert(doc, function (err, newDoc) {   
});

Сначала мы создаем документ, указывая необходимые поля и значения. Обратим внимание на то, что поле "notToBeSaved" имеет значение undefined. Это означает, что данное поле не будет сохранено. Далее мы записываем документ в хранилище с помощью метода insert. Данный метод принимает в качестве параметров документ для вставки и функцию обратного вызова, которая является необязательным параметром.

Поиск данных

Для осуществления поиска данных реализован методы find() и findOne() для одного документа. Для составления запроса можно использовать следующие операции:

  • $lt - меньше;
  • $lte - меньше или равно;
  • $gt - больше;
  • $gte - больше или равно;
  • $in - проверка на содержание в массиве значений;
  • $nin - проверка на непринадлежность к элементу массива значений;
  • $ne - операция неравенства;
  • $exists - операция проверки существования поля в документе;
  • $regex - проверка на соотвестиве регулярному выражению;

И следующие логические операции:

  • $or
  • $and
  • $not
  • $where

Рассмотрим пример запросов. Допустим у нас в хранилище есть следующие записи:

{ _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] }
{ _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } }
{ _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false }
{ _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } }
{ _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }

Давайте найдем записи по значению какого-нибудь поля:

db.find({ system: 'solar' }, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

Соотвественно в функции обратного вызова мы сможем получить значение через второй параметр - docs и информацио об ошибке, если таковая случилась через err. Значения хранилеща возвращаются в виде массива. Далее давайте найдем значение по подстроке в поле

db.find({ planet: /ar/ }, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

Для получения всех документов нужно выполнить следующее:

db.find({}, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

А для получения конкретного документа, например по id воспользуемся findOne():

db.findOne({ _id: 'id1' }, function (err, doc) {
  if(!err) {
    console.log(doc);
  }
});

В данном случае у нас в переменной doc будет сохранен объект. Если же указанного объекта нет в хранилище doc будет равен null. Рассмотрим синтаксис логических операций поиска. В общем случае они выглядят как: ( {операция: массив запросов}, функция обратного вызова})

db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

db.find({ $not: { planet: 'Earth' } }, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

Несколько отличаются запросы для операции $where. Здесь мы передаем не массив запросов, а функцию поиска:

db.find({ $where: function () { return Object.keys(this) > 6; } }, function (err, docs) {
  if(!err) {
    console.log(docs);
  }
});

В данном случае мы ищем записи с числом полей больше 6. Также в NeDB есть возможность получить число документов, удовлетворяющих запросу:

db.count({ system: 'solar' }, function (err, count) {
  if(!err) {
    console.log(count);
  }
});

Обновление документов

Для обновления данных в хранилище реализован метод:

db.update(query, update, options, callback);

На вход он получает запрос, такой же как и при операциях поиска, указания проводимых изменений. Далее идут дополнительные опции, такие как:

Дополнительные опции
Опция Описание
multi Принимает значение true или false и разрешает модификацию нескольких документов. По умолчанию: false.
upset Принимает значение true или false и позволяет создать документ, если по запросу не был найдет подходящий для модификации. По умолчанию: false.
returnUpdatedDocs Принимает значение true или false и позволяет вернуть обновленные документы. По умолчанию: false.

И последним параметром идет функция обратного вызова. Например изменим название планеты для Юпитера:

db.update({ planet: 'Jupiter' }, { planet: 'Pluton'}, {}, function (err, numReplaced) {
  if(!err) {
    console.log("updated");
  }
});

Стоит обратить внимание на то, что при обновлении записей используются так называемые модификаторы, которые определяют тип изменений:

  • $set - изменяет значение, добавляет поле, если его до этого не было
  • $unset - удаляет поле
  • $inc - инкремент
  • $min/$max - изменение на минимальное или максимальное значение соотвественно
  • $push - операции добавления в массив
  • $pop - взять последний элемент массива
  • $addToSet - добавление в поле, если элемента там не было
  • $pull - удаляет их массива, все значения поля, которые удовлетворяют запросу
db.update({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {}, function () {
  if(!err) {
    console.log("updated");
  }
});

db.update({ planet: 'Mars' }, { $unset: { planet: true } }, {}, function () {
  if(!err) {
    console.log("updated");
  }
});

db.update({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true }, function () {
  if(!err) {
    console.log("updated");
  }
});

db.update({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {}, function () {
  if(!err) {
    console.log("updated");
  }
});

db.update({ _id: 'id6' }, { $pop: { fruits: 1 } }, {}, function () {
  if(!err) {
    console.log("updated");
  }
});

db.update({ _id: 'id6' }, { $pull: { fruits: $in: ['apple', 'pear'] } }, {}, function () {
  if(!err) {
    console.log("updated");
  }
});

Удаление записей

Для удаления из NeDB реализован метод

db.remove(query, options, callback)

В качестве параметра можно указать нужно ли это действия применять для всех документов с помощью опции: multi. По умолчанию она выключена.

db.remove({ _id: 'id2' }, {}, function (err, numRemoved) {
  if(!err) {
    console.log("removed "+ numRemoved+" records");
  }
});

В качестве результата данный метод возвращает число удаленных документов.

Индексация

NeDb поддерживает индексацию. Индексация - это маркировка, которая учитывается для построение поисковых деревьев, дающая прирост в скорости по проиндексированным полям. NeDbB позволяет добавить индекс ко всем полям, включая даже вложенные. В текущей версии индексы дают прирост в производительности в запросах с использованием[Источник 2]:

  • $in
  • $lt
  • $lte
  • $gt
  • $gte

Для добавления и удаления используются методы ensureindex и removeIndex соотвественно.

db.ensureIndex({ fieldName: 'somefield' }, function (err) {
  if(!err) {
    console.log("ok");
  }
});
db.removeIndex('somefield', function (err) {
  if(!err) {
    console.log("ok");
  }
});

Производительность

На средей статистической машине и размере коллекции в 10000 документов с использованием индексации получаются следующие данные:

  • запись: 10680 операций в секунду;
  • поиск: 43290 операций в секунду;
  • обновление значений: 8000 операций в секунду;
  • удаление: 11750 операций в секунду.

Пример использования NeDB

В данном видеоматериале показаны:

  • процесс установки через npm для консоли и для проекта;
  • процесс создания хранилища в двух режимах;
  • процесс записи, поиска и удаления данных;
  • вид и изменения файла постоянного хранилища в ходе работы с базой данных.


Выводы

NeDB - достаточно удобная NoSQL база данных для JavaScript проектов. Хотя она не позиционирует себя как полноценная замена для крупных проектов, она хорошо подходит для небольших и средних проектов, которым не нужна внешняя база данных. Также к плюсам можно отнести:

  • отсутствие необходимости настройки самой СУБД;
  • два режима хранения данных, что позволяет использовать NeDB, как кэш-хранилище для разгрузки сервера основной базы данных;
  • логика на основе MongoDB API, что позволяет легко освоить данное решение на основе опыта работы с другими СУБД.

Так как задачи производительности и использования для большых проектов не ставились разработчиками как основные, поэтому к минусам можно отнести:

  • невозможность кластеризации.

Однако как встроенная база данных для небольших и средних NeDB является хорошим решением.

Источники

Примечания

  1. NeDB. [20013-2018]. Дата обновления: 15.04.2018. URL: https://github.com/louischatriot/nedb ( Дата обращения: 14.05.2018.)
  2. NeDB indexing. [20013-2018]. Дата обновления: 15.04.2018. URL: https://github.com/louischatriot/nedb#indexing ( Дата обращения: 14.05.2018.)