Комментарии 29
Dart прекрасный язык, Flutter отличная технология, но блин :(
Совсем не круто, когда список всего нескольких тысяч элементов, при удалении одного из них или при поиске, начинает тормозить.
Решение есть! Hive — noSql база, написанная на чистом Dart, очень быстрая.
Решение это UI нормально сделать, а не тащить в проект noSql. Затем изучить как работают БД и понять, что быстрее искать и читать можно только из памяти или в немногочисленных специальных случаях.
Наверное я не очень подробно расписал, что данная проблема возникает, когда мы с кешем работаем.
Совсем не круто, когда список всего нескольких тысяч элементов, при удалении одного из них и далее записи в кеш или при поиске по кешу — начинает тормозить.
Вот так написал, чтоб не бросать тень на Dart\Flutter — они реально очень быстрые.
А какой кейс покрывает красивый график с тысячью чтений из бд?
Если брать для примера заметочник — это будет один запрос при загрузке приложения который все заметки загрузит, и потом по одной записи на изменение.
Либо загрузка заметок батчами если их много, но это все равно не тысячи запросов.
Согласно графикам, SQLite на один запрос чтения потратит около 2мс, а на запись около 10мс. Это настолько мало, что не обязательно даже делать асинхронно.
В каких реальных задачах на телефоне нужен высокий RPS к БД? Это же не сервер с кучей подключений.
Например, когда идет обновление нескольких десятков анализов юзера по связанным формулам.
В этом случае возникают сотни\тысячи запросов с индексами, которые начинают (согласно графика в начале статьи) выполняться до нескольких секунд.
Думаю много таких кейсов в реальной разработке.
У нас есть.
В этом случае возникают сотни\тысячи запросов с индексами, которые начинают (согласно графика в начале статьи) выполняться до нескольких секунд.
Это некорректное сравнение, потому что hive — это k-v база данных, и чтобы получить запись по ключу вам не надо конструировать запрос, вы в коде бенчмарка просто делаете put
/get
, а для SQLite вызываете билдер запросов каждый раз. Не знаю, заинлайнит ли это AOT компилятор, но скорее всего нет. Есть ли в sqflite
prepared statements или что-то подобное я не знаю, но возможно сырые SQL запросы были бы быстрее.
К тому же, если вы бенчмаркаете абстрактные get
/put
, и в проекте у себя вы используете точно такой же подход, это не значит что те же анализы нельзя было бы сделать эффективнее используя нормальные возможности sql, а не переносить их в виде одиночных вызовов select
/insert
.
Поэтому сравнивать sqlite и hive нельзя так как это делаете вы, с SQL никто так не работает.
Поэтому сравнивать sqlite и hive нельзя так как это делаете вы, с SQL никто так не работает.
Код бенчмарка не я делал, я просто ссылку на него дал. Насколько я вижу, там используются встроенные в пакет SqfLite методы
github.com/hivedb/hive_benchmark/blob/master/lib/sqlite_store.dart
Если сравнивать, как это принято в рамках github и готового теста на быстродействие, то вы можете форкнуть бенчмарк и доказать свою позицию в цифрах.
Так это вы же пишете о том, что hive быстрее sqlite, но что вы хотели этим доказать? Что вставка/чтение по ключу в дубовое k-v хранилище быстрее, чем аналогичная работа с реляционной базой данных?
Ну как бы да, это и без бенчмарков понятно.
Проблема не в этом, а в том, что вы бенчмаркаете фигню. Сырая скорость вставки/чтения интов в одно хранилище по уникальному ключу никому не нужна. Если бы вы привели пример "сотни\тысячи запросов с индексами" с использованием hive/sqlite, то вполне вероятно, что сценарий использования sql в данном случае был бы совершенно другой, нежели тысячи единичных select
и insert
.
Называть его дубовым — некорректно.
Остановимся на том, что для разных случаев нужны разные решения.
Холивар между SQL и NoSQL базами за рамками данной статьи и примера.
Для приложений (да и для бэков) довольно часто используются NoSQL базы.
PS Соблюдайте культуру дискуссии :)
Называть его дубовым — некорректно.
Дубовое — не означает негативную оценку. Таким же "дубовым" хранилищем будет Rocks, LMDB, Bolt, и пр. встраиваемые k-v хранилища, которые никаких возможностей кроме хранения данных не предоставляют.
Hive — это максимально простое хранилище, какое только возможно придумать, там даже транзакций нет, судя по документации. Плохо ли это? Нет, не плохо. И да, для разных случаев нужны разные решения. Но вы всё равно зачем-то противопоставляете ему полноценную (пусть и встраиваемую) реляционную БД.
Холивар между SQL и NoSQL базами за рамками данной статьи и примера
Обоснование мотивации выбора k-v хранилища для определённой задачи и рациональность такого выбора для конкретного примера — это не есть холивар. Заметьте, я не говорю что hive — это плохо, но указываю но очевидную проблему вашей аргументации в пользу hive в рамках конкретной задачи, на что вы приводите пример (довольно абстрактный) из совсем другой области.
Производительность хранилища в рамках вашей статьи вообще никакой роли не играет. К тому же для работы именно с такого рода задачами SQLite может быть более удобен в т.ч. потому, что у него есть встроенный движок для FTS, к примеру, что для заметок довольно полезно.
Для приложений (да и для бэков) довольно часто используются NoSQL базы.
Но это не значит что они всегда используются уместно, к тому же NoSQL != key-value.
Вы сами не понимаете о чём пишете, вам культурно указывают на это, а вы начинаете "соблюдайте культуру".
Ускорение, которое может вам дать Hive оно не от скорости Hive, а от другой схемы хранения данных, когда все, что надо читается в один запрос. Но никто не мешает также хранить данные и в SQLite. И у этого подхода есть серьезные недостатки, о которых нужно знать. Особенно когда у вас нет транзакций.
Разница между Hive и SQLite может быть в скорости вставки, потому что в SQLite есть транзакции и оно гарантирует запись на диск. Если Hive делает вставку сильно быстрее, то только потому, что пишет в память (например в memory mapped file). Это быстро, но черевато потерей данных и поломанной базой. При записи SQLite добавляет данные в WAL и дожидается пока этот WAL запишется на диск. Быстрее надежно данные на диск записать нельзя. Единственное возможное ускорение — писать в N реплик, чтобы если одна упадет, данные в другие все же попали на диск, но это не сценарий для мобилки.
То есть Hive либо пишет в память и надеется, что повезет и ОС сбросит данные на диск быстрее чем они пропадут, либо Hive пишет с такой же скоростью как SQLite. Опять же, ускорение тут может быть за счет модели данных, за счет денормализации.
Я пытаюсь донести простую мысль — не бывает надежных средств хранения существенно быстрее RDBMS. Быстрее RDBMS это запись в memory mapped file, но это не одно и то же. Почти всегда вам совершенно все равно писать в WAL или в mmap. И почти всегда выбор RDBMS это лучше чем noSQL. А если вы выбираете noSQL, то вам нужно а) знать как хранит и обновляет данные обычная SQL база б) знать как это делает noSQL база в) понимать чем noSQL пожертвовал и где вы огребете.
Future<void> write(List<int> bytes) {
_buffer.add(bytes);
if (_buffer.length >= _maxBufferSize) {
return flush();
}
return Future.value();
}
Круто да? Вы что-то записали, а оно в памяти лежит, пока буфер не заполнится. Это даже хуже чем mmap, которых хотя бы гарантирует, что данные на диск попадут пока ОС жива. Тут данные будут потеряны при падении приложения.
А если вы посмотрите сюда, то увидите как замечательно ваши данные пишутся и что будет с базой, если посреди метода writeFrames выскочит исключение.
Далее смотрим сюда и видим как Hive ищет данные
final IndexableSkipList<dynamic, Frame> _store;
Выходит индекс на основе skip list и весь индекс лежит в памяти! Знаете что будет когда индекс станет большим? А еще в такой индекс крайне тяжело вставлять данные и читать его одновременно. Знаете что будет под нагрузкой одновременно на чтение/запись?
Итого — Hive дает хорошее и удобное API, заточен под небольшой объем данных (сотни мегабайт), работу с одним пользователем и денормализоваными данными. Может терять данные и всю базу целиком, поэтому хранить ценные данные в нем не стоит. Как замена shared preferences сойдет, также подойдет как локальный кеш данных с сервера.
С выводом согласен, никак иначе его и не позиционировал в своих проектах.
Вы неправы.
Hive использует другой механизм. Вся запись происходит вот тут, и она вполне надежная: https://github.com/hivedb/hive/blob/413ae3594f7142eb8b4b428f60c5c090d12d0799/hive/lib/src/backend/vm/storage_backend_vm.dart#L131.
То, что цитируете вы (https://github.com/hivedb/hive/blob/master/hive/lib/src/io/buffered_file_writer.dart) - это механизм, используемый только для операции compact. Вот код операции compact: https://github.com/hivedb/hive/blob/413ae3594f7142eb8b4b428f60c5c090d12d0799/hive/lib/src/backend/vm/storage_backend_vm.dart#L145. Тут видно, что запись через этот ненадежный writeBuffer идет сначала в temp файл и только в случае полной успешной записи происходит ренейм файла. Т.е. если что-то упадет, то основной файл не покораптится.
Приведённая вами строка выглядит так
await writeRaf.writeFrom(writer.toBytes());
Тут происходит запись в файловый буфер ОС. Ничего на диск не попадает пока ОС не соизволит сбросить кеш на диск.
Заметил также, что этот файл append only, а значит неизбежно будет compact. Подозреваю, что compact заблокирует и читателей и писателей, возможно на продолжительное время.
Вы хотите сказать, что чтобы записать на диск экземпляр RandomAccessFile я должен сделать await raf.flush(), а await raf.writeFrom() недостаточно?
А где бы посмотреть нативный код, который вызывает raf.writeFrom(), чтобы убедиться в таком поведении, не подскажете?
Да, именно так и есть. Где посмотреть реализацию writeFrom я не знаю, но это не так важно. Все платформы, которые я встречал, делают flush только если их попросить. Причина простая - только программист знает когда flush нужен, а в большинстве случаев он не нужен вовсе. Исключение это БД, где WAL файлы обычно открываются в режиме O_DIRECT или аналогичном, который инструктирует ОС не кешировать данные. Это значительно замедляет запись, в таком режиме скорость будет не выше, а скорее значительно ниже, чем скорость БД типа PG или SQlite.
Судя по обсуждению по ссылке как раз наоборот. Чувак говорит, что куча пользователей хотят запросы, а прикручивать их к текущей архитектуре библиотеки он считает невозможным — поэтому перепишет всё на Rust. У Rust есть интероп с C, у Dart есть интероп с C. Все будут счастливы (но это не точно, там ещё что-то про магию LMDB).
От себя добавлю: не знаю, хотят ли куча пользователей библиотеки запросы. Я точно знаю, чего они не хотят — думать. Если инструмент выполняет свою функцию хорошо — то его надо использовать. Если же будет Hive 2.0 с расширенной функциональность без обратной совместимости — пусть. Один инструмент для одних задач, другой — для других.
У Dart FFI далеко не быстрый, и с кроссплатформенностью будут проблемы, потому что библиотеку на расте придётся собирать отдельно для каждой платформы.
Но у нее очень посредственная кодогенерация и объекты которые собираешься хранить — не должны быть иммутабельными.
Hive — быстрая локальная база для Flutter, Dart