LINUX.ORG.RU

B+-Tree: как там эффективнее всего решают задачу «дай число ключей от A до B»?

 


0

1

Как мировые лидеры-имплементаторы B+-Tree (авторы всяких там движков InnoDB или Postgres) успешнее всего решали задачи «дай число ключей в дереве между A и B»?

Могу сходу напридумывать способов облегчать такие запросы, простейший: в каждом PIVOT хранить число ключей во всём поддереве, на которое он показывает. Тогда находим просто 2 итератора на lower_bound(A) и upper_bound(B) и суммируем все такие чиселки на всех PIVOT между ними. Учитывая, что не обязательно ходить по PIVOT-ам в блоках уровня 1 (0 означает LEAF-блок), а можно часто пройтись и по блокам выше, суммируя числа крупнее (критерий: блок верхнего уровня покрывает весь диапазон блоков уровня ниже, по которым мы собирались пойти), то всё можно сделать быстро. Физически это будет выглядеть примерно так: сходили вглубь до LEAF для A, сходили вглубь до LEAF до B, потом поднялись-собрали, собрали по крыше, опустились-собрали. Трапеция такая. Ну вы поняли. Но недостаток тут в том, что тебе надо на каждый инсерт ключа инкрементить чиселки на всем пути выше, по которому ты пришёл в LEAF и в целом больше блоков в дереве будет грязными из-за этого, хотя наверное пофиг.

Это к «задаче про лайки». Допустим я решил хранить число лайков в комменту в какой-то наивной реализации B+-Tree. Коммент - это uint64 (id сущности, которую лайкнули - docid), лайк - тоже uint64 (кто лайкнул - userid). Инсертим в дерево ключ K = uint128 (конкатенация двух ID выше - (docid << 64 + userid)). Такой ключ может быть только один. Всё хорошо. Медленно - это достать число лайков к нашему docid. Для этого надо достать число ключей в подинтервале [ID:0, ID+1:0), что при паре лямов лайков к какому-то посту не-больно только при каком-то ненаивном подходе к этому, типа описанного выше.

Вопрос: чё там в постгресах-мускулях понаверчено физически в реальности для оптимизации такого?

Как хранить лайки иначе спрашивать не буду, и так знаю - во всяких фейсбуках просто пишут свою микро-субд, представляющую собой логически std::map< uint64_t, std::set<uint64_t> > и кайфуют. Вопрос был именно про кейс, когда Вася решил докопаться именно до B+-Tree для такой задачи. Мало ли где ещё и для чего кто-то хочется число ключей в подинтервале.



Последнее исправление: kilokolyan (всего исправлений: 4)
Ответ на: комментарий от kilokolyan

Более производителтная альтернатива в треде всё ещё не предложена.

Была. hash_map< docid, set<uid>* >. Стоимость нахождения числа лайков для данного docid - O(1). И даже вариант который Вы сами упомянули в описании с std::map будет лучше чем B+tree с compound key - там O(log(count(docid)).

bugfixer ★★★★★
()
Ответ на: комментарий от kilokolyan

любитель всё свое писать

кто бы эту структуру не писал.

Я к тому что я не знаю стандартного плюсового контейнера реализующего эту структуру. Да и выбор структуры крайне неудачный - то что хорошо работает для каких нибудь файловых систем где операция «вычитать N блоков файла X начиная с Y» скорее типична здесь мало применимо так как Ваши use-cases совсем другие.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Была. hash_map< docid, set* >. Стоимость нахождения числа лайков для данного docid - O(1). И даже вариант который Вы сами упомянули в описании с std::map будет лучше чем B+tree с compound key - там O(log(count(docid)).

Требовалось это хранить в Btree. У меня нет столько ОЗУ, чтобы запихать все лайки всех пролайканных объектов в хештаблицы в памяти. Ну это надо городить выгружалку-загружалку каждой хештаблицы или её региона хотя-бы. А при ресайзе хештаблицы придётся переписывать её ВСЮ, что жопа.

Незнаю насколько O(log(count(docid)) дешевле сильноветвистого B+-Tree, надо мерять.

kilokolyan
() автор топика
Последнее исправление: kilokolyan (всего исправлений: 1)
Ответ на: комментарий от bugfixer

Я к тому что я не знаю стандартного плюсового контейнера реализующего эту структуру.

Ну бывает. Его и нет наверное.

вычитать N блоков файла X начиная с Y

Разговор про список блоков начался с того, что кто-то сказал, что в B+-Tree я не могу эффективно последовательно длинно пробежать по ключам в порядке сортировки, а я могу из-за списка блоков на нижнем уровне. Зачем мне бегать по этим ключам я тоже не знаю, я и не бегаю.

kilokolyan
() автор топика
Ответ на: комментарий от kilokolyan

Требовалось это хранить в Btree. У меня нет столько ОЗУ, чтобы запихать все лайки всех пролайканных объектов в хештаблицы в памяти.

Хешать надо только «пролайкленные объекты», их скорее всего в разы/на порядки меньше чем лайков.

bugfixer ★★★★★
()
Ответ на: комментарий от kilokolyan

B+-Tree я не могу эффективно последовательно длинно пробежать по ключам в порядке сортировки

Я неправильно понял Ваш изначальный посыл и думал Вы в std::map это складывать собрались.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Хешать надо только «пролайкленные объекты», их скорее всего в разы/на порядки меньше чем лайков.

Хешать надо все лайки. Каждый лайк. Иначе можно поставить 2 лайка одному пролайканному объекту случайно.

kilokolyan
() автор топика
Ответ на: комментарий от kilokolyan

Хешать надо все лайки. Каждый лайк.

Вы меня не слышите: хеш на статьи, как вы будете хранить лайки - это дело десятое. Это может быть set, это может быть flat_set, это может быть hash_set, это могут быть Ваши любимые B+Tree (если Вы думаете оно будет компактнее). Важно что структура должна быть многоуровневой и дуплицировать docid на каждый лайк мягко говоря неэффективно, и по памяти в том числе.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Вы меня не слышите: хеш на статьи,

Зачем мне хеш на статьи. А, ок, вроде понял, ну допустим. Ну ладно.

kilokolyan
() автор топика
Последнее исправление: kilokolyan (всего исправлений: 1)

тебе надо на каждый инсерт ключа инкрементить чиселки на всем пути выше, по которому ты пришёл в LEAF

И? При любом масштабе тебе это всё равно придётся делать. Счётчик лайков — классический пример вещей, которые придётся денормализовать ещё задолго до того, как задумаешься о шардировании и вообще распределённых базах данных.

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

Ну и собственно вопрос: что чаще происходит — запись в пост (считая те, что ниже уровнем) или чтение?

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

x3al ★★★★★
()
Ответ на: комментарий от kilokolyan

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

Считать ключи независимо от сорта дерева — O(n) как минимум. Прочитать одно поле — O(1). Идти по таблице с лайками чтобы их посчитать — не для продакшна.

x3al ★★★★★
()
Ответ на: комментарий от kilokolyan

оптимизация: блоки имеют рядом идущие id и все влезли в озу, т.е. впределе это непрерывный кусок памяти

Непрерывный кусок памяти в БД? Серьёзно? Тредсейфово? В условиях MVCC?!

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

x3al ★★★★★
()
Ответ на: комментарий от kilokolyan

Вопрос был именно про кейс, когда Вася решил докопаться именно до B+-Tree для такой задачи.

Ответ: Васе на этапе пуллреквеста объясняют почему btree для этой задачи не нужны.

Как хранить лайки иначе спрашивать не буду, и так знаю - во всяких фейсбуках просто пишут свою микро-субд, представляющую собой логически std::map< uint64_t, std::set<uint64_t> > и кайфуют.

Никакого std::set для счётчика лайков там нет, там именно цифра со счётчиком. Сет хранится отдельно.

x3al ★★★★★
()
Последнее исправление: x3al (всего исправлений: 1)
Ответ на: комментарий от kilokolyan

Это всё понятно конечно. Но как-то не по конкретной теме бтри.

Деревья не дадут тебе ничего в этой задаче, денормализация и запись счётчика в отдельное поле — дадут.

Вам, кстати, очень правильные вещи говорят, просто в терминах немножко отличающихся от моих. Я бы прислушался.

bugfixer ★★★★★
()
Ответ на: комментарий от x3al

задолго до того, как задумаешься о шардировании

Я просто термин никогда не слышал. Я могу поинтересоваться что это?

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Я просто термин никогда не слышал. Я могу поинтересоваться что это?

Деление одной базы на много с доступом к той, в которой подинтервал ключей, содержащий нужный.

kilokolyan
() автор топика
Ответ на: комментарий от x3al

Деревья не дадут тебе ничего в этой задаче

Я понимаю, что читать число лайков я в идеале хочу прямо из сообщения, которое пролайкано. И знаю как я это сделаю: запрос вида «заинкременть инт по такому-то смещению, если удалось вставить лайк в сет лайков». А дерево B+Tree даст оптимальность по диску. Дерево b+tree тут не как спасение, а как дефолтная структура для хранения всего, ибо на диск/в сеть бы нам хотелось идти блоками.

kilokolyan
() автор топика
Ответ на: комментарий от x3al

Непрерывный кусок памяти в БД? Серьёзно? Тредсейфово? В условиях MVCC?!

У меня простая однопоточная базуня для шардинга, которя не многопоточная и потому не умеет вертикально масштабироваться - уметь жрать много ядер процессора при доступе в одну шарду. Такое решаю репликами, если приспичит. Транзакций нет, мвцц нет. Просто WAL на диске + b+tree в памяти и периодические «чекпоинты» всего дерева на диск.

kilokolyan
() автор топика
Последнее исправление: kilokolyan (всего исправлений: 1)
Ответ на: комментарий от x3al

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

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

kilokolyan
() автор топика
Последнее исправление: kilokolyan (всего исправлений: 1)

храни агрегированное состояние и ничего не сканируй

quester ★★
()
Ответ на: комментарий от kilokolyan

Деление одной базы на много с доступом к той, в которой подинтервал ключей, содержащий нужный.

Стоило мне конечно загуглить прежде чем спрашивать. Это чем то отличается от partitioning by static criteria? Сорьки - ни в одном глазу не SQL person, не моё оно - медленное слишком для наших задач, с терминологией не знаком.

bugfixer ★★★★★
()
Ответ на: комментарий от bugfixer

Шардинг к SQL и к СУБД отношения не имеет прямого, пошардить можно что угодно. Например ты генерал и хочешь сохранить набор телефонов. Строишь 100 солдат и каждого заставляешь выучить только те телефоны, которые кончаются на его номер в строю. +79697295031 запомнен 31-м солдатом.

kilokolyan
() автор топика
Последнее исправление: kilokolyan (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.