Pull to refresh

Comments 23

Вместо SQL базы данных типа SqLite я мог бы использовать документную базу данных

Хотелось бы уточнить, современная sqlite имеет поддержку https://www.sqlite.org/json1.html , чего-то не хватает чтобы ее использовать ее как "документную базу данных"?

Если у вас приложение на .NET, то преимущество LiteDB в том что нет необходимости создавать прослойку в виде SQL запросов или подключении ORM. Вы работаете сразу напрямую с вашими POCO делая запросы к коллекциям документов с Linq.

Нет никаких проблем с использованием SqLite. Просто хотелось чего-нибудь нового.

Я так понимаю, данные автора помещаются в память. Тогда логичный вопрос, зачем искать какой-то движек/либа БД, когда самый быстрый и удобный доступ к объектам CLR предоставляет сам C#?

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

На такие случаи придумали сериализацию

Сложно себе представить мобильное/десктопное приложение, которому требуется оперировать бОльшим количеством структурированных данных, чем есть доступной памяти.

Встроенные БД не дают каких-либо волшебных гарантий сохранения данных. Всё тоже самое достигается сериализацией в файл.

Встроенные БД не дают каких-либо волшебных гарантий сохранения данных. Всё тоже самое достигается сериализацией в файл.

Ну так про любую библиотеку можно сказать - "это библиотека ничего особого не делает, ее код можно написать самому в своей программе".

Если например файловая система журналируемая, но транзакции позволяет делать только для метаданных, то нужно заморачиваться с двумя файлами, в одном данные в другом последние изменения, и нужно вызвать в правильном порядке нужные системные вызовы, а главное отработать все возможные ошибки. Например можно почитать про "I/O Error Testing" для sqlite: https://www.sqlite.org/testing.html Все это конечно можно сделать самому, но стоит ли это того?

Но почему обязательно не помещается в память то. БД не обязательно про огромные размеры, а про удобство использование и уже собранные проблемы при реализации функционала сохранения.


Например обычные заметки. Их можно редактировать и сохранять. А так же они могут хотеть синхронизироваться из облака.


Можно конечно всё написать самому, а потом героически чинить множество проблем и мелочей, которые возникнут в процессе. А можно же взять готовую бд и получить тот же функционал из коробки.


Что она даст? Например возможность не думать о:


  • Синхронизации потоков. Может прилететь апдейт из облака в другом потоке и бд это разрулит сама;
  • Синхронизации использования файла на диске. Кто в него пишет, кто читает, всё целиком или частями;
  • Мерже изменений, если можно менять стейт асинхронно или параллельно;
  • Роллбек изменений, когда что то пошло не так;
  • Поиск по значениям полей или даже полнотекстовый поиск (не знаю умеет ли в него конкретно LiteDB);
  • О сохранности данных на диске в конце концов. Я знаю что если транзакция закоммичена, то даже если приложение крашнёт, то я его переоткрою и данные не потеряются. В случае с файлом есть много ньюансов, типа flush забыли позвать.

А если это десктопное приложение начинает уметь обрабатывать запросы от кого либо извне, то все эти проблемы увеличиваются многократно

Можно конечно всё написать самому, а потом героически чинить множество проблем и мелочей, которые возникнут в процессе. А можно же взять готовую бд и получить тот же функционал из коробки.

Если взять встроенную БД придётся аналогично чинить множество проблем, только уже чужих.
Ну и под написать самому, то тут есть нуансы. Каждый перечисленный пункт делается одной двумя строками кода либо дизайном.

Синхронизации потоков. Может прилететь апдейт из облака в другом потоке и бд это разрулит сама;

Мерже изменений, если можно менять стейт асинхронно или параллельно

Роллбек изменений, когда что то пошло не так

Решается immutable структурами данных. Хочешь менять, делай copy-on-write.
Хочешь применить изменения, удостоверься что ты менял последнюю версию. Для пессимистов есть блокировки. Если не копипастить все данные каждый раз, то проблемы большого мемори трафика нет.

Синхронизации использования файла на диске. Кто в него пишет, кто читает, всё целиком или частями;

Читать/писать целиком. Писать в новый файл, старый заменять атомарным реплейсом. Читать естественно один раз на старте, дальше работать из памяти.

Поиск по значениям полей или даже полнотекстовый поиск (не знаю умеет ли в него конкретно LiteDB)

Аналогично любой встроенной БД надо будет делать разметку тех полей что индексируются. Можно сделать очень дешево, можно подороже и воткнуть полнотекстовый поиск любой сложности, а не то что "есть" в либе.

О сохранности данных на диске в конце концов.


Как выше указано, проблемы нет т.к. нет работы с файлами. Пишут и читают из памяти. На файловой системе остается только последняя копия, которая не может быть сломанной.

А теперь из плюсов:

  • Нет сторонних компонентов.

  • Непревзойденный перфоманс.

  • Знакомый всем разработчикам API работы с коллекциями и объектами.

  • Формат хранения данных, который может читаться человеком. Удобно отлаживать.

  • Схема данных объявлена там, где используется, а не в 2 местах.

  • Можно запрофилировать проблемы производительности и решить их.
    Можно найти баг и починить. В чужом компоненте это из разряда фантастики.


Как я писал выше, есть только две проблемы для которой подходят БД:

  • данные не помещаются в память.

  • данные надо обслуживать (бекапы, аудиты, интеграции), хотя это уже СУБД

Сколько с бд работал не встречал баги. И все же бд это про удобство и скорость разработки.

Если взять встроенную БД придётся аналогично чинить множество проблем, только уже чужих.

Я прекрасно понимаю желание написать функционал бд самому. Но я не понимаю желания на каждом проекте это делать по новой. Почему нельзя это оформить в nuget и переиспользовать? А если так, то вот и получается своя бд.


Решается immutable структурами данных. Хочешь менять, делай copy-on-write

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


class Item
{
    public string Title { get; set; }
    public string Description { get; set; }
    public List<Field> Fields { get; set; } = new List<Field>();
}

И сама она лежит в каком-нибудь:


class Table
{
    public List<Item> Fields { get; set; } = new List<Item>();
}

И вся эта табличка весит 1-2GB. Что вы тут будете менять copy-on-wirte, когда вам нужно будет новый item добавить? Всю таблицу копировать? Если да, то непревзойдённого перфоманса у вас не будет, если нет, то синхронизацию потоков это не обеспечит.


Читать/писать целиком. Писать в новый файл, старый заменять атомарным реплейсом. Читать естественно один раз на старте, дальше работать из памяти.

Я правильно понимаю, что если у меня приложение заметок с сохранёнными данными на 1-2Gb, то при каждом открытии/закрытии оно должно целиком весь файл перечитывать/перезаписывать (а по хорошему вообще на каждый набранный символ тригерить сейв в фоне)? Тут тоже возникают небольшие сомнения относительно скорости работы.


Как выше указано, проблемы нет т.к. нет работы с файлами. Пишут и читают из памяти. На файловой системе остается только последняя копия, которая не может быть сломанной.

Тут как раз возникает та проблема, про которую я говорил в начале. Когда я нажал кнопку добавить/сохранить и приложение сказало что оно всё сделало, то я хочу что бы данные уже на диске были, а не в памяти продолжали висеть. Иначе при любом краше я всё потеряю.


В общем мой поинт по прежнему в том что если в приложении не нужно ни возможность менять данные из разных потоков, ни изменение данных частями, ни гарантированность сохранности данных при краше, то конечно достаточно в 1 строку открыть файл и сериализовать в него json/xml. Но это ни разу не сравнимо по функционалу с тем что даёт нормальная встраиваемая база данных.

И вся эта табличка весит 1-2GB. Что вы тут будете менять copy-on-wirte, когда вам нужно будет новый item добавить? Всю таблицу копировать?

Конечно нет. Т.к. оригинал не изменяемый, то достаточно сделать новую неизменяемую List подобную коллекцию, где будут "запатчены" измененные элементы, в остальном она будет ссылаться на старую. В итоге будет занято немного новой памяти. Любой программист выше джуниора может накатать этот код за рабочий день.

Я правильно понимаю, что если у меня приложение заметок с сохранёнными данными на 1-2Gb, то при каждом открытии/закрытии

Что за заметки на 1-2Gb? Десктопное приложение обычно оперирует пользовательскими данными, с большим натягом пользователь сгенерирует 1-2Mb данных. Это передёргивание.

Но это ни разу не сравнимо по функционалу с тем что даёт нормальная встраиваемая база данных.

В булочную через дорогу можно ездить на авто. Это даже удобно, но не практично. У каждой проблемы есть оптимальное решение.

Т.к. оригинал не изменяемый, то достаточно сделать новую неизменяемую List подобную коллекцию, где будут "запатчены" измененные элементы, в остальном она будет ссылаться на старую.

А как об этом новом list узнает остальной код программы? Как помёржить изменения, если 2 разных потока сделали 2 RO копии одновременно? Если там создаются новые листы то всё равно надо всё будет блокировкой оборачивать, т.к. при оптимистичном подходе вы можете какие то операции по нескольку раз делать, что далеко не всегда приемлемо.


Что за заметки на 1-2Gb? Десктопное приложение обычно оперирует пользовательскими данными, с большим натягом пользователь сгенерирует 1-2Mb данных. Это передёргивание.

Обычные заметки, как в существующих приложениях. Там же не только текст, но и картинки и видосики вполне успешно сохраняются. Ни разу не передёргивание. 1-2Gb успешно влазят в память любого PC даже 10 летней давности и вполне подходят под ваше определение.


Но даже если и 1-2Мб. Добавить nuget пакет на любую популярную db и в 2-3 строчки начать её использовать получается на порядок проще и удобнее чем сделать сериализацию в файлик, а потом по ходу развития проекта постоянно подпирать в нём всё новые проблемные случаи.


Конечно есть случае когда бд не нужна и не незачем тащить мидлвар, но далеко не все случае такие.

А как об этом новом list узнает остальной код программы? Как помёржить изменения, если 2 разных потока сделали 2 RO копии одновременно?

Узнают по тому что теперь новая ссылка на list. Как узнать о конфликте? Это по завершению изменений тот list с которым начали изменения не соответствует актуальному. Можно заново начинать работу. Либо оптимистичный подход, который будет работать с редкими изменениями. Либо пессимистичный, если высокая конкуренция. Но высокая конкуренция от каких асинхронных событий? Один источник это user input от одного человека/устройства, он редкий, второй это всё что приходит с сервера, частота разная. Ну так.

Обычные заметки, как в существующих приложениях. Там же не только текст, но и картинки и видосики вполне успешно сохраняются. Ни разу не передёргивание.

Хранить BLOB'ы в БД? Есть такая штука, придуманная в 80-х, для хранения BLOB'ов.

Но даже если и 1-2Мб. Добавить nuget пакет на любую популярную db и в 2-3 строчки начать её использовать

Взять качественное и проверенное решение и делать схемы, мапперы и транслировать запросы в SQL. Потом возиться с платфомами и архитектурами.
Либо взять nuget пакет с 100 звездами и модной in-memory DB и терять данные из за неправильной синхронизации с ФС :) Альтернативы ИМХО хуже, чем своё написать.

Узнают по тому что теперь новая ссылка на list. Как узнать о конфликте? Это по завершению изменений тот list с которым начали изменения не соответствует актуальному.

Это и есть один из способов работы с многопоточным кодом. И вот этим нужно заниматься самому, вместо того что бы просто вызывать нужный метод, который всё это инкапсулирует внутри и уже кем то написан и проверен.


Взять качественное и проверенное решение и делать схемы, мапперы и транслировать запросы в SQL. Потом возиться с платфомами и архитектурами.
Либо взять nuget пакет с 100 звездами и модной in-memory DB и терять данные из за неправильной синхронизации с ФС :) Альтернативы ИМХО хуже, чем своё написать.

LiteDB как раз тут третья альтернатива. Не требует таких усилий как SQL базы, но и не in-memory и не 100 звёздочек. Хочется больше звёздочек — можно взять BoltDB или RocksDB. Там звёздочек больше чем в dotnet/runtime.


Ну и если у вас есть уже хотя бы раз написанное решение, которое по вашему не обладает таким количеством недостатков и багов, то почему него не выложить его на GitHub, что бы и другие тоже могли его использовать?


За лично мой опыт работы с LiteDB она данные не теряла ни разу и позволила потратить пол дня на то что бы полностью закрыть вопрос с хранением всех данных в проекте.


Но она конечно далека от идеала. Например нет async методов. Да и индексы объявляются достаточно громоздко. Но делать db руками, это явно не то сравнимая по времени написания/поддержки альтернатива.

Когда мы выбирали механизм хранения локальных данных для нашего ПО, чтобы уйти от локального SQL - мы проверяли и Sqlite и LiteDB и сериализацию с сохранением в файлы.

К удивлению, LiteDB с существенным отрывом обошла ручное сохранение файлов (не помню точных цифр, давно это было, но она была в 2-3 раза). Экспериментировали и с сжатием, и без сжатия, и просто записью бинарного потока данных, чтобы избавиться от накладных расходов на сериализацию.

Там достаточно много низкоуровневых оптимизаций работы с файловой системой, которые позволяют этого добиться. Повторить все это самим конечно можно, но надо ли если кто-то уже это сделал?

Ложка дегтя - как выяснилось в эксплуатации в production - её хранение данных не слишком надежно. На наших масштабах (~10000 установок, 3-5 RPM на запись в среднем) - мы ловили по несколько сломавшихся БД в месяц. Попытки достучаться до разработчика и раскопать в чем проблема - не приводили ни к чему, игнор, потом "обновитесь на последнюю версию", потом повторение ситуации. Сложный конечно вопрос, кто виноват, LiteDB или проблемы с файловой системой у клиентов, но SQL (LocalDB) это переживал нормально.

В итоге остановились на том, что в LiteDB перенесли большие, часто меняемые, но не слишком критичные данные и относились к нему как к персистентному кешу. Критичные к потере данные - оставили в SQL

По-моему, вы излишне усложнили структуру классов. Или в статье неудачный пример. Я бы сделал проще:

internal class Field
{
  public string Text { get; set; }
}

internal sealed class TextField : Field {  }
internal sealed class PasswordField : Field {  }
internal sealed class DescriptionField : Field {  }

Description и Password – это тоже текстовое значение. Если Text нельзя перенести в класс Field, лучше всё равно использовать одно имя свойства, это упростит условие поиска

($.Fields[*].Text ANY LIKE @0) OR ($.Fields[*].Description ANY LIKE @0) OR ($.Fields[*].Password ANY LIKE @0)

Вероятно, вы правы, и пример не из лучших. Но целью статьи было рассмотрение возможностей LiteDb. Кроме того, не хотелось бы подгонять структуру классов под структуру хранения. Имя свойства Password на мой взгляд более говорящее, чем общее Text. Поэтому такого выноса свойств в базовый класс я не люблю.

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

И второе - она не развивается. Проект по сути заброшен. Количество открытых ишью на гитхабе уже пошло на шестую сотню. Два с половиной года выходят только патч-фиксы к пятой версии. Месяц назад выпустили 5.0.12, а 5.0.11 вышел больше года назад. У основного разработчика нет времени и желания заниматься бесплатным проектом

https://github.com/mbdavid/LiteDB/discussions/2107

Он некоторое время назад начал пилить что-то новое на основе старого LiteDb. Возможно, сделает что-то более коммерчески-ориентированное, но пока до этого далеко.

https://github.com/mbdavid/LiteDB-vNext

Было бы интересно услышать ещё об альтернативах — открытое встраиваемое решение написанное на C#. RocksDB/BoltDb более популярны, но они не на C#. RavenDB — платный и не понятно насколько лучше во встраиваемом варианте.

Sign up to leave a comment.

Articles