Pull to refresh

Comments 29

Dart прекрасный язык, Flutter отличная технология, но блин :(


Совсем не круто, когда список всего нескольких тысяч элементов, при удалении одного из них или при поиске, начинает тормозить.

Решение есть! Hive — noSql база, написанная на чистом Dart, очень быстрая.

Решение это UI нормально сделать, а не тащить в проект noSql. Затем изучить как работают БД и понять, что быстрее искать и читать можно только из памяти или в немногочисленных специальных случаях.

Так тут UI как раз не причем совсем. Есть разные варианты хранения данных и у них разная скорость чтения\записи.
Наверное я не очень подробно расписал, что данная проблема возникает, когда мы с кешем работаем.
Совсем не круто, когда список всего нескольких тысяч элементов, при удалении одного из них и далее записи в кеш или при поиске по кешу — начинает тормозить.

Вот так написал, чтоб не бросать тень на Dart\Flutter — они реально очень быстрые.

А какой кейс покрывает красивый график с тысячью чтений из бд?


Если брать для примера заметочник — это будет один запрос при загрузке приложения который все заметки загрузит, и потом по одной записи на изменение.
Либо загрузка заметок батчами если их много, но это все равно не тысячи запросов.


Согласно графикам, SQLite на один запрос чтения потратит около 2мс, а на запись около 10мс. Это настолько мало, что не обязательно даже делать асинхронно.


В каких реальных задачах на телефоне нужен высокий RPS к БД? Это же не сервер с кучей подключений.

Вот тут код бенчмарка github.com/hivedb/hive_benchmark
Например, когда идет обновление нескольких десятков анализов юзера по связанным формулам.
В этом случае возникают сотни\тысячи запросов с индексами, которые начинают (согласно графика в начале статьи) выполняться до нескольких секунд.
Думаю много таких кейсов в реальной разработке.
У нас есть.
В этом случае возникают сотни\тысячи запросов с индексами, которые начинают (согласно графика в начале статьи) выполняться до нескольких секунд.

Это некорректное сравнение, потому что 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.

Повторюсь, бенчмарк написал не я, а разработчики классного Open Source решения, которое уже стабильно много где используется и довольно популярно github.com/hivedb/hive
Называть его дубовым — некорректно.

Остановимся на том, что для разных случаев нужны разные решения.
Холивар между SQL и NoSQL базами за рамками данной статьи и примера.
Для приложений (да и для бэков) довольно часто используются NoSQL базы.

PS Соблюдайте культуру дискуссии :)
Называть его дубовым — некорректно.

Дубовое — не означает негативную оценку. Таким же "дубовым" хранилищем будет Rocks, LMDB, Bolt, и пр. встраиваемые k-v хранилища, которые никаких возможностей кроме хранения данных не предоставляют.


Hive — это максимально простое хранилище, какое только возможно придумать, там даже транзакций нет, судя по документации. Плохо ли это? Нет, не плохо. И да, для разных случаев нужны разные решения. Но вы всё равно зачем-то противопоставляете ему полноценную (пусть и встраиваемую) реляционную БД.


Холивар между SQL и NoSQL базами за рамками данной статьи и примера

Обоснование мотивации выбора k-v хранилища для определённой задачи и рациональность такого выбора для конкретного примера — это не есть холивар. Заметьте, я не говорю что hive — это плохо, но указываю но очевидную проблему вашей аргументации в пользу hive в рамках конкретной задачи, на что вы приводите пример (довольно абстрактный) из совсем другой области.


Производительность хранилища в рамках вашей статьи вообще никакой роли не играет. К тому же для работы именно с такого рода задачами SQLite может быть более удобен в т.ч. потому, что у него есть встроенный движок для FTS, к примеру, что для заметок довольно полезно.


Для приложений (да и для бэков) довольно часто используются NoSQL базы.

Но это не значит что они всегда используются уместно, к тому же NoSQL != key-value.

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

Вы сами не понимаете о чём пишете, вам культурно указывают на это, а вы начинаете "соблюдайте культуру".

А что вас заставляет делать сотни/тысячи запросов? Если Hive делает 100 запросов, то SQLite всегда может делать 100 или менее, это просто теорема. Причем каждый запрос на чтение в SQLite стоит лишь немного дороже, дополнительное время уходит на парсинг SQL. Само чтение и там и там это кеш + чтение из чего-то похожего на btree. Сделать этот процесс принципиально быстрее невозможно.

Ускорение, которое может вам дать 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 пожертвовал и где вы огребете.
Смотрим в исходник и видим причину быстрой вставки buffered

  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.

Звучит логично. Но всё же эту часть вашей претензии к hive удалось ниспровергнуть:

Это даже хуже чем mmap, которых хотя бы гарантирует, что данные на диск попадут пока ОС жива.

С Hive именно тот что для Dart, всплыла проблема с int ключами в виде timestamp, длинны хватает только на секундный timestamp, а вот миллисекундный уже не влезает, конечно можно использовать String для ключей, но тогда надо приводить типы в каждой фильтрации, но мне это показалось некрасивым.

Судя по обсуждению по ссылке как раз наоборот. Чувак говорит, что куча пользователей хотят запросы, а прикручивать их к текущей архитектуре библиотеки он считает невозможным — поэтому перепишет всё на Rust. У Rust есть интероп с C, у Dart есть интероп с C. Все будут счастливы (но это не точно, там ещё что-то про магию LMDB).

От себя добавлю: не знаю, хотят ли куча пользователей библиотеки запросы. Я точно знаю, чего они не хотят — думать. Если инструмент выполняет свою функцию хорошо — то его надо использовать. Если же будет Hive 2.0 с расширенной функциональность без обратной совместимости — пусть. Один инструмент для одних задач, другой — для других.

У Dart FFI далеко не быстрый, и с кроссплатформенностью будут проблемы, потому что библиотеку на расте придётся собирать отдельно для каждой платформы.

HIVE — просто волшебная штука.
Но у нее очень посредственная кодогенерация и объекты которые собираешься хранить — не должны быть иммутабельными.

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

Sign up to leave a comment.

Articles