ГоловнаСтаттіГучні Біти: Стиснений Бітсет, Який Живить Пошукові Двигуни

Гучні Біти: Стиснений Бітсет, Який Живить Пошукові Двигуни

Коли ви фільтруєте пошуковий індекс, запускаєте запит до бази даних з кількома критеріями або просите систему знайти рядки, що відповідають більше ніж одному критерію, потрібно дуже швидко об'єднувати великі набори ідентифікаторів цілих чисел. Наївний підхід полягає у збереженні кожного набору як простого бітсету: один біт для кожного можливого ідентифікатора, з яким вмикається біт, якщо ідентифікатор належить до набору. Це надзвичайно швидко для об'єднання наборів, але витрачає величезну кількість пам’яті, коли набір розріджений, оскільки більшість бітів знаходиться у вимкненому стані. Абсолютно протилежний наївний підхід – відсортований масив або хеш-набір фактичних чисел – компактний для розріджених наборів, але стає повільним і вимогливим до пам’яті, коли набір стає великим і щільним, оскільки обчислення об'єднань та перетинів означає ходьбу або злиття довгих списків. Roaring Bitmap – це структура даних, розроблена для того, щоб дати вам найкраще з обох світів одночасно. Вона ділить весь діапазон 32-бітних цілих чисел на 65 536 рівномірно розміщених частин і для кожної частини автоматично вибирає формат внутрішнього зберігання, який найкраще відповідає ступеню заповнення цієї частини. Це призводить до представлення набору, яке залишається компактним незалежно від того, чи є дані розрідженими або щільними, а також підтримує операції з наборами, які виконуються контейнером за контейнером, а не бітом за бітом. Саме тому Roaring Bitmap знаходиться в центрі систем, таких як Apache Lucene, Apache Spark, ClickHouse, Elasticsearch і Apache Druid.

mysimulator teamОновлено — червень 2026≈ 8 хв читання▶ Відкрити симуляцію

Розбиття Діапазону Цілих Чисел на Частини

Розумний Біт починається з простої ідеї, запозиченої від того, як комп'ютери вже представляють числа. Будь-яке 32-бітне непідписане ціле число може бути розділене на дві 16-бітні половини: верхню та нижню. Верхні 16 біт можуть мати будь-які з 65 536 різних значень, тому їх використовують для вибору, до якої групи контейнерів належить число. Нижні 16 біт, які також можуть мати 65 536 різних значень, описують положення цього числа всередині свого блоку. Іншими словами, весь діапазон з більш ніж чотирьох мільярдів 32-бітних цілих чисел розділений на 65 536 контейнерів, кожен з яких відповідає точно 65 536 можливим значенням. Важливо, що блок створюється та зберігається лише тоді, коли принаймні одне число, яке належить до нього (тобто нижні 16 біти), насправді присутнє в наборі. Якщо ваші дані використовують лише цілі числа менше ста тисяч, то лише перші два блоки ніколи не повинні існувати, і всі інші блоки споживають нульовий об'єм пам’яті. Ця ленива, за запитом виділення блоків пам’яті, є одним із основних джерел ефективності Roaring Bitmap: вона ніколи не нараховує витрати на пам’ять для діапазонів чисел, які повністю порожні. Всередині кожного активного блоку числа, які належать до нього (тобто нижні 16 біти), потрібно зберігати якось, і цей формат зберігання вибирається незалежно для кожного окремого блоку. Ця двошарова структура, розріджений верхній рівень ідентифікаторів блоків у поєднанні з незалежно оптимізованими контейнерами нижче, дозволяє Roaring Bitmap плавно адаптуватися до дуже різних розподілів чисел, від кількох розкиданих ідентифікаторів до майже безперервних діапазонів, що охоплюють мільйони значень, все в одній єдиній структурі.

Три Типи Контейнерів для Трьох Видів Даних

Всередині кожного фрагменту Roaring Bitmap підтримує контейнер, який зберігає шістнадцять найнижчих бітів кожної знахідки, і обирає між трьома форматами контейнерів на основі того, скільки значень фактично міститься в ньому. Перший – це контейнер масиву, який використовується, коли фрагмент розріджений, зазвичай містить не більше приблизно чотирьох тисяч значень. Він просто перераховує найнижчі біти у відсоркованому порядку, кожен з яких зберігається компактно. Другий – це контейнер-бітова мапа, який використовується, коли фрагмент щільний. Він виділяє фіксований блок восьми тисяч дев’ятисот двадцять вісім байт, по одному біту для кожного можливого значення в фрагменті, і просто перемикає біти на або з вимкненого стану. Коли фрагмент містить більше кількох тисяч значень, бітова мапа стає більш компактною, ніж відсортований список окремих чисел, оскільки вартість зберігання кожного значення в контейнері масиву починає перевищувати фіксовану вартість одного біта на кожне можливе положення. Третій – це контейнер з узагальненням довжини пробілів, який використовується, коли фрагмент містить довгі безперервні ділянки послідовних цілих чисел. Замість зберігання кожного значення в пробілі окремо, він зберігає лише початкове значення та довжину кожного пробілу. Таким чином, фрагмент, що представляє, наприклад, десять тисяч послідовних ідентифікаторів документів, може бути описаний лише однією маленькою парою чисел замість десяти тисячі окремих записів. Roaring Bitmap постійно оцінює та динамічно перетворює між будь-який із цих трьох форматів, який би займав найменше місця для фактичного вмісту фрагменту, тому структура ніколи не платить за невиправдану вартість, коли дешевший формат може зробити те саме.

Чому адаптивні контейнери перевершують прості бітмапи та масиви

Мотивація для цього трьохстороннього адаптивного дизайну стає очевидною, коли ви враховуєте можливі збої двох простіших альтернатив, які він замінює. Простий, незрощений бітмап по всій тридцятидворатковій діапазоні потребував би п’ятьсот дванадцять мегабайт лише для існування, незалежно від того, скільки чисел містить набір або десять мільйонів, оскільки кожне можливе значення вимагає зарезервованого біта, незалежно від того, чи використовується воно чи ні. Це величезний, часто неможливий, фіксований витратний бюджет для представлення розріджених наборів даних, які надзвичайно поширені в реальних робочих навантаженнях, таких як відповідність пошуковим запитам із вузьким фільтром. З іншого боку, простий відсортований масив або хеш-набір уникає цього фіксованого витратного бюджету та зберігає лише числа, які насправді присутні, але платить іншу ціну, коли набір стає великим або коли потрібно об’єднати кілька наборів. Обчислення перетину або об’єднання двох великих відсортованих масивів зазвичай потребує порівняння та злиття елементів один за одним, операція, вартість якої зростає зі збільшенням загальної кількості елементів, а хеш-набори погіршують ситуацію, відмовляючись від відсортованого порядку, який робить швидке злиття можливим у першому місці. Roaring Bitmap обходить обидві проблеми, ніколи не застосовуючи єдину стратегію впродовж всього діапазону. Розріджені регіони простору чисел обробляються компактним контейнером масиву, тому немає відходів пам’яті на порожнечі. Щільні регіони обробляються безпосереднім контейнером бітмапу, щоб уникнути надмірної витрати ресурсів на елемент за елементом. Регіони з довгими послідовними пробілами, які є поширеними в робочих навантаженнях, таких як часові ідентифікатори або безперервні діапазони рядків, обробляються контейнером зі стисненням довжини пробілу, який може представляти мільйони значень у невеликій фіксованій кількості місця. Оскільки вибір робиться незалежно для кожного блоку, одне Roaring Bitmap одночасно може містити деякі блоки, які майже порожні, деякі, що майже повні, і деякі довгі безперервні проміжки, кожен з яких зберігається у форматі, який найкраще підходить для нього, без будь-якої глобальної компромісної складності.

Швидкі Операції з Набору Даних, Контейнер за Контейнером

Реальна вигода від цього дизайну проявляється, коли потрібно об'єднувати набори даних – операція, яку постійно виконують пошукові системи та бази даних, наприклад, перетин набору документів, що містять один пошуковий запит, з набором документів, що містять інший. Roaring Bitmap виконує операцію об'єднання, перетин або різницю шляхом проходження ідентифікаторів блоків обох бітмапів у відсоркованому порядку, подібно до злиття двох відсортованих списків, та обробки сумісних блоків разом. Якщо ідентифікатор блоку існує лише в одному з бітмапів, для об'єднання весь блок просто копіюється без змін, а для перетину він повністю пропускається, оскільки не може бути жодних відповідних значень в відсутньому блоці іншого бітмапу. Це само по собі усуває величезну кількість непотрібної роботи порівняно з підходом біт за бітом або елемент за елементом, оскільки цілих 65 536-значущих блоків вирішується одним порівнянням. Коли ідентифікатор блоку існує в обох бітмапах, два відповідні контейнери об'єднуються, а точний метод залежить від типів контейнерів, що беруть участь. Два контейнера типу бітмапу можна об'єднати за допомогою надзвичайно швидких інструкцій процесора, які працюють з багатьма бітами одночасно. Два контейнера типу масив можна об'єднати так само, як два відсортованих списки, в часі пропорційному їх сумарній величині. Контейнер з розширенням кількості пробілів можна об’єднати з іншим контейнером шляхом порівняння пробілів і діапазонів безпосередньо, часто торкаючись набагато менше окремих значень, ніж фактично представляє контейнер. Оскільки кожен формат контейнера зберігає вміст у передбачуваному та відсортованому внутрішньому порядку, ці злиття на рівні контейнерів ніколи не потребують повернення до повільного загального порівняння, а вартість операції відстежує справжню складність даних замість теоретичного розміру діапазону чисел. Це дозволяє системам, що обробляють мільярди ідентифікаторів, обчислювати перетини та об'єднання наборів даних за тисячі частки секунди.

Розумні Біти: Оптимізований Бітсет для Пошукових Дій

Розумні Біти вперше були описані у науковому дослідженні 2016 року та швидко перейшли від академічної концепції до практичного застосування в деяких із найбільш поширених систем даних у світі. Apache Lucene, пошукова бібліотека, яка лежить в основі Elasticsearch і Apache Solr, використовує структуру «Розумні Біти» для представлення списків публікацій, набір ідентифікаторів документів, пов’язаних із кожним індексованим терміном. Це дозволяє швидко об'єднувати результати запитів з кількох термінів, навіть якщо індекси містять сотні мільйонів документів. Apache Spark використовує «Розумні Біти» для відстеження, які розділи або рядки вже були оброблені під час великих розподілених обчислень, уникаючи проблеми пам’яті та навантаження при злитті списків. Аналітичні системи даних, такі як ClickHouse і Apache Druid, використовують їх для індексування бітмапів категоріальних стовпців, де запит може одночасно знайти всі рядки, що відповідають кільком умовам фільтрації, перетинаючи кілька таких бітмапів. У кожній з цих систем основна робота має однакову структуру: ідентифікатори приходять у величезному числовому діапазоні, їх розподіл непередбачуваний і може варіюватися від надзвичайно розрідженого до надзвичайно щільного, а система повинна швидко об'єднувати багато таких наборів під жорсткими обмеженнями затримки. Поєднання адаптивної стиснення на основі чанків та швидких операцій з об’єднанням контейнерних наборів «Розумні Біти» безпосередньо відповідає всім трьом вимогам одночасно, що пояснює, чому вони стали майже стандартом вибору, де потрібно ефективно індексувати, зберігати та поєднувати великі набори цілих чисел, а не нішевою технікою, обмеженою одним продуктом.

Часті запитання

Чому розділено цілий числовий діапазон на частини по шістдесят п’ять тисяч п’ятсот тридцять шість значень?

Цей розмір безпосередньо випливає з поділу 32-бітного цілого числа на дві рівні 16-бітні половини. Шістнадцять біт може представляти 65 536 різних значень, тому використання верхньої половини для вибору частини та нижньої половини для вибору положення в межах цієї частини рівномірно ділить повний 32-бітовий діапазон на цю кількість рівновеликих частин, використовуючи прості та швидкі бітові операції замість арифметичного ділення.

Як Roaring Bitmap вирішує, який тип контейнера використовувати для частини?

Реалізації відстежують, скільки значень містить частина, і як ці значення розподілені, а також оцінюють вартість зберігання кожного формату контейнера для цього конкретного вмісту. Частина з лише кількома значеннями використовує компактний масив контейнера, частина, яка переважно заповнена, переходить до контейнера фіксованого розміру, а частина, що домінує над довгими послідовними пробілами, переходить до контейнера зі стисненням довгих пробілів. Багато реалізацій періодично переоцінюють і конвертують контейнер частини, коли її вміст змінюється з часом.

Чи завжди Roaring Bitmap менший за простий бітмапс?

У найгіршому випадку повністю щільна частина, збережена як контейнер у вигляді бітмапу, коштує приблизно стільки ж, скільки еквівалентна ділянка простого бітмапу, тому тут немає значного переваги в пам’яті. Реальні вигоди полягають у не зайнятих або порожніх областях числового діапазону, які майже нічого не коштують у Roaring Bitmap, але коштують ту ж фіксовану суму, що й будь-яка інша область у простому бітмапі. У реалістичних, нерівномірно розподілених даних це зазвичай дає дуже велику загальну економію.

Чи уповільнює стиснення даних операції об’єднання?

Ні, і це центральне дизайнерське досягнення Roaring Bitmap. Оскільки кожен контейнер зберігає свої значення в передбачуваному відсортованому порядку, а формат контейнера відомий заздалегідь, операції об’єднання та перетину можуть виконуватися безпосередньо на стиснутому представленні, часто використовуючи швидкі бітові інструкції процесора, не потребуючи попереднього розпакування даних у простий бітмапс.

Які типи застосувань найбільше виграють від використання Roaring Bitmap?

Будь-яке застосування, яке зберігає великі набори цілочислових ідентифікаторів і потребує часто їх комбінувати, приносить користь, тому пошукові машини, аналітичні бази даних та системи розподіленої обробки даних широко використовують її. Типові приклади включають представлення документів, що відповідають терміну пошуку, рядків, що задовольняють умову фільтрації, або розділів розподіленого завдання, які завершили роботу, особливо коли кількість можливих ідентифікаторів величезна, але фактичні розміри наборів варіюються непередбачувано.

Спробуйте наживо

Усе, що вище, працює прямо у вашому браузері — відкрийте Roaring Bitmap: The Compressed Bitset That Powers Search Engines і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.

▶ Відкрити симуляцію Roaring Bitmap: The Compressed Bitset That Powers Search Engines

Що ви знайшли?

Додати кроки відтворення (опційно)