Легковесная встраиваемая база данных 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 мс.
Меня такая производительность устроила.