TGViewer
Daria’s room Daria’s room @dariasroom · 1.2K subscribers
Post #102 1.12K
Ribbon Filter: Part 1 - сжатая альтернатива Bloom и Cuckoo

Ribbon filter - это, пожалуй, один из самых недооцененных фильтров, который меньше по памяти чем Bloom, и в моих тестах показал более низкий false positive rate, чем Cuckoo. Разберем, как он работает, и сделаем выводы.

Ribbon
- статический вероятностный фильтр, который проверяет наличие элемента, кодируя весь набор ключей как систему XOR-уравнений.

В отличии от Bloom, где мы просто помечаем биты, и Cuckoo, где храним fingerprints ключей в бакетах, Ribbon для каждого ключа строит локальное XOR-уравнение внутри окна cells[start : start+w]. Фильтр статический, поэтому все такие уравнения решаются один раз при построении фильтра, а во время поиска мы просто вычисляем XOR по тем же позициям и проверяем, совпадает ли результат.

За счёт того, что во время поиска каждый ключ работает только внутри своего окна, фильтр получается очень «кеш френдли» и хорошо укладывается в память. Звучит пока абстрактно, поэтому надо смотреть на структуру.

В моей реализации фильтр хранит не набор ключей, а массив ячеек cells, плюс параметры окна и seed.

type RibbonFilter struct {
m uint32
w uint32
seed uint64
span uint32
cells []uint16
built bool
}


Главное поле - это cells []uint16: именно его мы в итоге строим, а потом используем для поиска. Поле w - ширина локального окна, внутри которого будут составные части уравнения для каждого ключа; span - диапазон допустимых стартов окна.

Тут нет классического Add()метода, потому что Ribbon - это статический фильтр, то есть он строится сразу целиком, когда известны все ключи.

Что происходит с каждым ключом

Для каждого ключа фильтр собирает набор для уравнения:

type row struct {
start uint32
mask uint64
fingerprint uint16
}

Из одного ключа мы получаем:
start - где начинается его локальное окно,
mask - какие ячейки внутри окна участвуют в XOR,
fingerprint - какой результат этот XOR должен дать.

Через makeRow собираем все необходимое:

func (rf *RibbonFilter) makeRow(item []byte) row {
h := hash64(item, rf.seed)
start, mask, fp := derive(h, rf.span, rf.w)
return row{
start: start,
mask: mask,
fingerprint: fp,
}
}

Внутри derive(...) один хеш раскладывается на три независимых части через mix64(). Что, кстати, куда дешевле, чем делать по 3 хеша. (поэтому можно подтянуть отдельно метод derive и пользоваться)

Итого:
каждый ключ в Ribbon filter превращается не в запись, а в XOR-уравнение над кусочком массива cells


Роль окон в уравнении

Одна из самых приятных вещей в Ribbon, это локальность по памяти. Если убрать детали, то в уравнении:

XOR(cells[start : start+w]) = fingerprint(key)

ключ резервирует не весь массив, а только локальное окно cells[start : start+w]. Внутри этого окна маска определяет, какие позиции реально участвуют в XOR - это видно из start, w, mask и из того, как Contains проходит по установленным битам маски. То есть каждый ключ работает только внутри своего окна. У такого подхода есть практический плюс: за счёт окон lookup работает локально по памяти, а не прыгает по всем cells; но при этом построение у Ribbon заметно дороже, чем у Bloom.

Локальная позиция внутри окна (mask) превращается в глобальный индекс через start + bitPos, и именно за счёт этого Ribbon получается cache-friendly.

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

Вместо этого он один раз строит систему XOR-уравнений, которая кодирует весь набор ключей, а при поиске просто подставляет ключ в это уравнение и сравнивает результат с fingerprint. За счёт того, что все вычисления происходят внутри небольшого окна, lookup обычно укладывается в одну кеш линию и требует минимум памяти.

В следующей части будет описан сам алгоритм сборки фильтра и вывод из сравнения бенчей

Дополнительно про ribbon:
- https://rocksdb.org/blog/2021/12/29/ribbon-filter.html
- https://engineering.fb.com/2021/07/09/core-infra/ribbon-filter/
- https://github.com/RibbonFilter/ribbonGo

Потыкать ribbon можно по примеру из ридми

#fts #perf
GitHub fts-engine/pkg/filter/ribbon.go at master · dariasmyr/fts-engine Modular full-text search engine in Go with pluggable indexes, filters, and customizable text processing pipelines. You can instantly index your docs (radix, HAMT), apply probabilistic filters, and ...
  • 👍 14
  • 🔥 7
  • ❤ 4
More from @dariasroom
  1. Sep 15, 2026Автоматическое сжатие на клиенте vs ручное на сервере Стандартный клиент http.Transport са…
  2. Sep 5, 2026Причина перекосов в уровне балансировки L4-балансировщик выбирает серверную ноду при созда…
  3. Aug 28, 2026Итераторы… TL;DR: в iter.Seq итератор сам передаёт следующие элементы в код внутри range,…
  4. Aug 17, 2026Что на самом деле нужно сохранять при сериализации сложной структуры? TL;DR: Важно отделит…
  5. Aug 13, 2026Привет! Вас стало больше, так что пора наконец представиться 🙂 Я Даша, давно пишу на Go,…
  6. Aug 12, 2026HNSW: как устроен графовый индекс для векторного поиска One million years later, я наконец…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →