Легковесная встраиваемая база данных LiteDB

Я часто пишу небольшие домашние проекты и постоянно приходится решать проблему хранения данных. Полноценный сервер БД это слишком сложно для такого проекта, а сериализация в файлы требует слишком много сил. На помощь приходят различные встраиваемые БД. Сегодня посмотрим на одну из них — LiteDB.
Авторы рекомендуют базу для небольших десктоп приложений и веб сервисов. При этом нужно учитывать следующие рекоммендации:
— одна база на одного пользователя/аккаунт
— небольшое количество конкурентных операций записи
Вся база данных хранится в одном файле.
LiteDB распостраняется в виде nuget пакета. Доступна в .Net Core 2.0.
Подключение к базе создается так:
using LiteDB; ... var db = new LiteDatabase(<path_to_database_file>)
При этом LiteDatabase реализует интерфейс IDisposable.
Все объекты в базе хранятся в типизированных именованных коллекциях
var games = db.GetCollection<GameInfo>(GAMES_COLLECTION_NAME)
Для добавления элемента используются методы Insert и InsertBulk, для обновления Update, для поиска Find. Метод EnsureIndex создает индекс если его еще не было.
Тест производительности
Я решил сделать синтетический тест, приближенный к ситуациям, характерным для моего проекта.
Тестовый запрос: фильтрация по одному свойству, затем сортировка по другому свойству и получение TOP 5 результатов.
Сначала создаем класс тестовой сущности и заполняем базу
public class TestModel
{
public string Name { get; set; }
public int Value { get; set; }
public int GroupId { get; set; }
}
static void GenerateData()
{
var faker = new Faker();
using (var db = new LiteDatabase(@"MyData.db"))
{
var models = db.GetCollection<TestModel>("models");
for (var i = 1; i <= 500; i++)
{
var list = new List<TestModel>();
for (var j = 1; j <= 1000; j++)
{
list.Add(new TestModel()
{
GroupId = i,
Name = faker.Name.FullName(),
Value = faker.Random.Int(10000, 99999)
});
}
models.InsertBulk(list);
}
}
}
Для генерации данных использую bogus
Созданная база содержит 500 000 строк и занимает 109 мегабайт.
Теперь замеряем усредненную длительность нужного запроса
static Random random = new Random();
static double TestQuery()
{
var groupId = random.Next(1, 500);
using (var db = new LiteDatabase(@"MyData.db"))
{
var models = db.GetCollection<TestModel>("models");
var t1 = DateTime.Now;
var q = models.Find(x => x.GroupId == groupId).
OrderByDescending(x => x.Value).
Take(5).
ToList();
return (DateTime.Now - t1).TotalMilliseconds;
}
}
static double Test()
{
TestQuery();
double total = 0;
int count = 30;
for (var i = 1; i <= count; i++)
{
total += TestQuery();
}
total = total / count;
return total;
}
Средняя длительность запроса составила 8.8513 мс. Первый вызов TestQuery() вне цикла нужен для прогрева базы, он на порядки дольше.
Теперь попробуем добавить индекс по GroupId и повторить измерение длительности запроса
static void addIndexByGroup()
{
using (var db = new LiteDatabase(@"MyData.db"))
{
var models = db.GetCollection<TestModel>("models");
models.EnsureIndex(x => x.GroupId);
}
}
Размер базы вырос до 136 мегабайт, а длительность запроса уменьшилась до 7.8323 мс.
Меня такая производительность устроила.