Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Úvodní slovo

Tento příspěvek jsem přednesl v angličtině na konferenci GopherCon Russia 2019 v Moskvě a v ruštině na meetupu v Nižním Novgorodě. Mluví o bitmapovém indexu, který je méně běžný než B-tree, ale ne méně zajímavý. Sdílím záznam svého vystoupení na konferenci v angličtině a textový přepis v ruštině.

Podíváme se na to, jak bitmapový index funguje, kdy je lepší a kdy horší než jiné indexy a v jakých případech je výrazně rychlejší než ony; uvidíme, v jakých populárních DBMS již existují bitmapové indexy; pokusíme se napsat svůj v Go. A na "závěr" využijeme hotové knihovny, abychom vytvořili naši superrychlou specializovanou databázi.

Hodně doufám, že mé práce budou pro vás užitečné a zajímavé. Jdeme na to!

Úvod

Přehrát video

http://bit.ly/bitmapindexes
https://github.com/mkevac/gopherconrussia2019

Ahoj všichni! Je šest hodin večer, jsme super unavení. Skvělý čas na to, abychom mluvili o nudné teorii databázových indexů, že? Nebojte se, mám tu a tam pár řádků zdrojového kódu. 🙂

Pokud však mluvíme vážně, tak je prezentace nabita informacemi a nemáme tolik času. Takže pojďme začít.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Dnes budu mluvit o následujícím:

  • co jsou indexy;
  • co je bitmapový index;
  • kde se používá a kde se NENÍ používá a proč;
  • jednoduchá implementace v Go a trochu boj s kompilátorem;
  • o něco složitější, ale mnohem výkonnější implementaci v Go-assembleru;
  • "problémy" bitmapových indexů;
  • existující implementace.

Tak co jsou indexy?

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Index je samostatná datová struktura, kterou uchováváme a aktualizujeme navíc k hlavním datům. Používá se k urychlení vyhledávání. Bez indexů by vyhledávání vyžadovalo úplné procházení dat (proces nazývaný full scan), a tento proces má lineární algoritmickou složitost. Ale databáze obvykle obsahují obrovské množství dat a lineární složitost je příliš pomalá. Ideálně bychom měli mít logaritmickou nebo konstantní.

Je to obrovské složité téma, přeplněné nuancemi a kompromisy, ale po desítkách let vývoje a zkoumání různých databází jsem připraven tvrdit, že existuje pouze několik široce používaných přístupů k vytváření indexů databáze.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

První přístup spočívá v hierarchickém zúžení oblasti hledání, rozdělení oblasti hledání na menší části.

Obvykle to děláme pomocí různých druhů stromů. Příkladem může být velká krabice s materiály ve vaší skříni, ve které se nacházejí menší krabice s materiály rozdělené podle různých témat. Pokud potřebujete materiály, určitě budete hledat v krabici popsáné jako „Materiály“, a ne v té, na které je napsáno „Slašť“.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Druhý přístup spočívá v okamžitém vymezení požadovaného prvku nebo skupiny prvků. Děláme to v hash mapách nebo v inverzních indexech. Použití hash map je velmi podobné předchozímu příkladu, jen místo jedné krabice s krabicemi máte ve skříni hromadu malých krabic s konečnými předměty.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Třetí přístup spočívá v odstranění potřeby hledání. To děláme pomocí Bloom filtrů nebo cuckoo filtrů. První poskytují odpověď okamžitě, čímž vás zbavují potřeby hledání.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Poslední přístup spočívá v plném využití všech možností, které nám moderní hardware nabízí. To děláme v bitmapových indexech. Ano, při jejich používání je občas nutné projít celý index, ale činíme to supers efektivně.

Jak jsem již řekl, téma databázových indexů je rozsáhlé a plné kompromisů. To znamená, že někdy můžeme použít několik přístupů současně: pokud potřebujeme ještě více urychlit vyhledávání nebo pokud je nutné pokrýt všechny možné typy hledání.

Dnes vám povím o nejméně známém přístupu z uvedených – o bitmapových indexech.

Kdo jsem, abych o tom mluvil?

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Pracuji jako vedoucí týmu v Badoo (možná lépe znáte náš jiný produkt – Bumble). Máme již více než 400 milionů uživatelů po celém světě a mnoho funkcí, které se zabývají tím, že pro ně hledají nejlepší partnera. Děláme to pomocí vlastních služeb, které využívají mimo jiné i bitmapové indexy.

Co je vlastně bitmapový index?

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmap indexy, jak již naznačuje název, používají bitmapy nebo bitové sady k implementaci vyhledávacího indexu. Z ptačí perspektivy se tento index skládá z jednoho nebo několika bitmapů, které představují určité entity (například lidi) a jejich vlastnosti nebo parametry (věk, barva očí atd.), a z algoritmu, který používá bitové operace (AND, OR, NOT) k zodpovězení vyhledávacího dotazu.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Říká se nám, že bitmap indexy jsou nejvhodnější a velmi výkonné pro případy, kdy existuje vyhledávání, které kombinuje dotazy přes několik sloupců s nízkou kardinalitou (představte si „barvu očí“ nebo „rodinný stav“ oproti něčemu jako „vzdálenost od centra města“). Ale později ukážu, že fungují výborně i u sloupců s vysokou kardinalitou.

Zvažme nejjednodušší příklad bitmap indexu.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Představte si, že máme seznam moskevských restaurací s binárními vlastnostmi, jako jsou tyto:

  • nedaleko metra (near metro);
  • má soukromé parkoviště (has private parking);
  • má terasu (has terrace);
  • akceptuje rezervace (accepts reservations);
  • je přátelské k vegetariánům (vegan friendly);
  • drahé (expensive).

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pojďme každé restauraci přiřadit pořadové číslo počínaje 0 a vyčlenit paměť pro 6 bitmapů (jeden pro každou vlastnost). Poté tyto bitmapy vyplníme v závislosti na tom, zda restaurace má danou vlastnost nebo ne. Pokud restaurace 4 má terasu, bude bit č. 4 v bitmapě „má terasu“ nastaven na 1 (pokud terasa není, tak na 0).
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Teď máme nejjednodušší bitmap index, jaký je možný, a můžeme ho použít k odpovědím na dotazy jako:

  • „Ukaž mi restaurace vhodné pro vegetariány“;
  • „Ukaž mi levné restaurace s terasou, kde lze rezervovat stůl.“

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Jak? Pojďme se podívat. První dotaz je velmi jednoduchý. Všechno, co potřebujeme, je vzít bitmapu „je přátelské k vegetariánům“ a převést ji na seznam restaurací, jejichž bity jsou nastaveny.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Druhý dotaz je o něco složitější. Musíme použít bitovou operaci NOT na bitmapu „drahých“, abychom získali seznam levných restaurací, poté je ANDovat s bitmapou „rezervace stolu dostupná“ a výsledek ANDovat s bitmapou „má verandu“. Výsledná bitmapa bude obsahovat seznam zařízení, která splňují všechna naše kritéria. V tomto příkladu je to pouze restaurace „Junost“.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Tady je hodně teorie, ale nebojte se, velmi brzy uvidíme kód.

Kde se používají bitmapové indexy?

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pokud si „googlíte“ bitmapové indexy, 90 % odpovědí bude tak či onak spojeno s Oracle DB. Ale ostatní DBMS také jistě podporují tuto skvělou funkci, že?

Projdeme si seznam hlavních podezřelých.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
MySQL ještě nepodporuje bitmapové indexy, ale existuje návrh na přidání této možnosti (https://dev.mysql.com/worklog/task/?id=1524).

PostgreSQL nepodporuje bitmapové indexy, ale používá jednoduché bitmapy a bitové operace pro kombinaci výsledků vyhledávání z několika jiných indexů.

Tarantool má bitset indexy, podporuje jednoduché vyhledávání podle nich.

Redis má jednoduché bitové pole (https://redis.io/commands/bitfield) bez možnosti vyhledávání podle nich.

MongoDB ještě nepodporuje bitmapové indexy, ale také existuje návrh na přidání této možnosti. https://jira.mongodb.org/browse/SERVER-1723

Elasticsearch používá bitmapy uvnitř (https://www.elastic.co/blog/frame-of-reference-and-roaring-bitmaps).

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

  • Ale v našem domě se objevil nový soused: Pilosa. Je to nová nerelační databáze napsaná v Go. Obsahuje pouze bitmapové indexy a vše zakládá na nich. O ní si povíme trochu později.

Implementace v Go

Ale proč jsou bitmapové indexy tak vzácně používány? Než na tuto otázku odpovím, chtěl bych vám ukázat implementaci velmi jednoduchého bitmapového indexu v Go.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapy jsou v podstatě reprezentovány jako kusy dat. V Go si pro to udělejme použití sliceů bytů.

Máme jednu bitmapu pro jednu charakteristiku restaurace, a každý bit v bitmapě říká, zda daná restaurace toto vlastnost má nebo nemá.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Budeme potřebovat dvě pomocné funkce. Jedna se bude používat k naplnění našich bitmap náhodnými daty. Náhodnými, ale s určitou pravděpodobností, že restaurace má každou vlastnost. Například si myslím, že v Moskvě je velmi málo restaurací, kde nelze rezervovat stůl, a mám pocit, že přibližně 20 % podniků je vhodných pro vegetariány.

Druhá funkce převede bitmapu na seznam restaurací.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Abychom mohli odpovědět na dotaz „Zobraz mi levné restaurace, které mají verandu a kde lze rezervovat stůl“, budeme potřebovat dvě bitové operace: NOT a AND.

Můžeme náš kód trochu zjednodušit použitím složitější operace AND NOT.

Máme funkce pro každou z těchto operací. Obě procházejí řezem, berou odpovídající prvky z každého a kombinují je bitovou operací a ukládají výsledek do výsledného řezu.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
A teď můžeme použít naše bitmapy a funkce pro odpověď na vyhledávací dotaz.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Výkon není tak vysoký, i když jsou funkce velmi jednoduché a ušetřili jsme značné množství prostředků tím, že jsme při každém volání funkce nevraceli nový výsledný řez.

Po trochu profilování s pprof jsem si všiml, že kompilátor Go vynechal jednu velmi jednoduchou, ale velmi důležitou optimalizaci: inlining funkcí.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pokud jde o to, kompilátor Go se hrozně bojí cyklů, které procházejí řezem, a kategoricky odmítá inline funkce, které obsahují takové cykly.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Ale já se nebojím a můžu oklamat kompilátor použitím goto místo cyklu, jako ve starých dobrých časech.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

A jak vidíte, nyní kompilátor s radostí inline naši funkci! V důsledku toho se nám podaří ušetřit asi 2 mikrosekundy. Není to špatné!

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Druhou úzkou hrdlo není obtížné vidět, pokud se důkladně podíváte na assemblerový výstup. Kompilátor přidal kontrolu na hranice řezu přímo dovnitř našeho nejvíce používaného cyklu. To proto, že Go je bezpečný jazyk, kompilátor se obává, že moje tři argumenty (tři řezy) mají různou velikost. Proto by teoreticky mohlo dojít k takzvanému přetečení bufferu.

Uklidněme kompilátor tím, že mu ukážeme, že všechny slice mají stejnou velikost. Toho můžeme dosáhnout tím, že na začátek naší funkce přidáme jednoduchou kontrolu.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Když to uvidí, kompilátor ochotně přeskočí kontrolu a my tím ušetříme dalších 500 nanosekund.

Velké dávky

Dobře, podařilo se nám vytáhnout nějaký výkon z naší jednoduché implementace, ale toto je ve skutečnosti mnohem horší výsledek, než co je možné s aktuálním hardwarem.

Vše, co děláme, jsou základní bitové operace a naše procesory je provádějí velmi efektivně. Bohužel ale našemu procesoru dodáváme velmi malé kousky práce. Naše funkce provádějí operace po bytech. Můžeme velmi snadno upravit náš kód tak, aby pracoval s 8-člennými kusy, pomocí slices UInt64.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Jak vidíte, tato malá změna zrychlila naši aplikaci osmkrát díky zvýšení dávky osmkrát. Zisk je možné říci lineární.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Implementace v assembleru

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Ale to není konec. Naše procesory mohou pracovat s kousky o velikosti 16, 32 a dokonce i 64 byte. Tyto `široké` operace se nazývají single instruction multiple data (SIMD; jedna instrukce, mnoho dat) a proces transformace kódu tímto způsobem, aby používal takové operace, se nazývá vektorizace.

Bohužel, Go kompilátor není právě hvězda ve vektorizaci. V současnosti je jediný způsob, jak vektorizovat kód v Go, vzít a ručně vložit data operací pomocí assembleru Go.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Assembler Go je zvláštní zvíře. Určitě víte, že assembler je něco, co je silně vázané na architekturu počítače, pro který píšete, ale v Go tomu tak není. Assembler Go je více podobný IRL (intermediate representation language) nebo mezijazyku: je téměř platformově nezávislý. Rob Pike měl skvělý přednáškou přednášku na toto téma před několika lety na GopherCon v Denveru.

Kromě toho Go používá neobvyklý formát Plan 9, který se liší od uznávaných formátů AT&T a Intel.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
S jistotou lze říci, že psaní assembleru Go ručně není zrovna zábavná činnost.

Ale naštěstí již existují dva vysoceúrovňové nástroje, které nám pomáhají při psaní Go assembleru: PeachPy a avo. Obě utility generují Go assembler z vyššího kódu napsaného v Pythonu a Go.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Tyto utility zjednodušují věci jako je alokace registrů (výběr procesorového registru), psaní smyček a celkově usnadňují vstup do světa assemblerového programování v Go.

Budeme používat avo, takže naše programy budou téměř běžnými programy v Go.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Takto vypadá ten nejjednodušší příklad programu v avo. Máme funkci main(), která definuje uvnitř sebe funkci Add(), jejímž smyslem je sečíst dvě čísla. Zde jsou pomocné funkce pro získání parametrů podle jména a pro získání jednoho z volných a vhodných procesorových registrů. Každá procesorová operace má odpovídající funkci v avo, jak ukazuje ADDQ. A nakonec vidíme pomocnou funkci pro uložení výsledné hodnoty.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Zavoláním go generate spustíme program v avo a nakonec budou vygenerovány dva soubory:

  • add.s s výsledným kódem v Go-assembleru;
  • stub.go s hlavičkami funkcí pro propojení dvou světů: Go a assembleru.

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Nyní, když jsme viděli, co a jak dělá avo, podívejme se na naše funkce. Implementoval jsem jak skalární, tak vektorové (SIMD) verze funkcí.

Nejprve se podívejme na skalární verze.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Stejně jako v předchozím příkladu, žádáme o poskytování volného a správného registru obecného použití, nemusíme počítat posuny a velikosti argumentů. To všechno avo dělá za nás.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Dříve jsme používali labely a goto (nebo skoky) pro zlepšení výkonu a abychom oklamali kompilátor Go, ale nyní to děláme od začátku. Jde o to, že smyčky jsou vysoceúrovňový pojem. V assembleru máme pouze labely a skoky.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Zbytek kódu by měl být již známý a srozumitelný. Emulujeme smyčku pomocí labelů a skoků, vezmeme malý kousek dat z našich dvou řezů, sloučíme je bitovou operací (AND NOT v tomto případě) a poté uložíme výsledek do výsledného řezu. To je všechno.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Takto vypadá finální kód v assembleru. Nemuseli jsme počítat posuny a velikosti (vyznačeno zeleně) ani sledovat používané registry (vyznačeno červeně).
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pokud porovnáme výkon implementace v assembleru s výkonem nejlepší implementace v Go, vidíme, že jsou si rovny. A to je očekávané. Nic zvláštního jsme nedělali – pouze jsme reprodukovali to, co by dělal kompilátor Go.

Bohužel nedokážeme donutit kompilátor, aby naimplementoval naše funkce napsané v assembleru. Go-kompilátor tuto možnost v současnosti nemá, přestože žádost o její přidání existuje už docela dlouho.

A právě proto nelze získat žádné výhody z malých funkcí v assembleru. Musíme buď psát velké funkce, nebo použít nový balíček math/bits, nebo se vyhnout assembleru.

Teď se podívejme na vektorové verze našich funkcí.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pro tento příklad jsem se rozhodl použít AVX2, a proto budeme používat operace pracující se 32-bajtovými bloky. Struktura kódu je velmi podobná skalární variantě: načítání parametrů, žádost o uvolněný společný registr atd.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Jednou z novinek je, že širší vektorové operace používají speciální široké registry. V případě 32-bajtových bloků jsou to registry s prefixem Y. Proto v kódu vidíte funkci YMM(). Kdybych použil AVX-512 s 64-bitovými bloky, pak by prefix byl Z.

Druhá novinka se týká toho, že jsem se rozhodl použít optimalizaci zvanou rozbalování cyklu, tj. provést osm operací cyklu ručně, než skočím na začátek cyklu. Tato optimalizace snižuje počet branchů (větvení) v kódu a je omezena počtem volných registrů, které jsou k dispozici.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
A co výkon? Je skvělý! Dosáhli jsme zrychlení přibližně sedmkrát oproti nejlepší variantě v Go. Působivé, že?
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Ale i tuto implementaci by bylo možné potenciálně zrychlit využitím AVX-512, přednačítáním nebo JIT (just-in-time compiler) pro plánovač dotazů. To už ale určitě patří do samostatného skládání.

Problémy bitmapových indexů

Teď, když jsme už prozkoumali jednoduchou implementaci bitmapového indexu v Go a mnohem výkonnější verzi v assembleru, pojďme konečně mluvit o tom, proč se bitmapové indexy používají tak zřídka.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Ve starých vědeckých pracích se zmiňují tři problémy bitmapových indexů, ale novější vědecké práce a já tvrdíme, že jsou již neaktuální. Nebudeme se do každého z těchto problémů ponořovat do hloubky, ale povrchně je zvážíme.

Problém vysoké kardinálnosti

Takže nám říkají, že bitmapové indexy jsou vhodné pouze pro pole s nízkou kardinálností, tedy taková, která mají málo hodnot (například pohlaví nebo barva očí), a důvodem je, že běžné zobrazení takových polí (jeden bit na hodnotu) v případě vysoké kardinálnosti zabere příliš mnoho místa a navíc budou tyto bitmapové indexy slabě (zřídka) zaplněny.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Někdy můžeme použít jiné zobrazení, například standardní, které používáme pro reprezentaci čísel. Ale právě vznik kompresních algoritmů všechno změnil. V posledních desetiletích vymysleli vědci a výzkumníci řadu kompresních algoritmů pro bitmapy. Jejich hlavní výhodou je, že není nutné dekomprimovat bitmapy pro provádění bitových operací — můžeme provádět bitové operace přímo na komprimovaných bitmapách.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
V poslední době se objevují také hybridní přístupy, jako například roaring bitmapy. Ty používají současně tři různá zobrazení pro bitmapy — vlastně bitmapy, pole a takzvané bit runs — a balancují mezi nimi, aby maximalizovaly výkon a minimalizovaly spotřebu paměti.

Můžete se setkat s roaring bitmapami v nejoblíbenějších aplikacích. Již existuje velké množství implementací pro různé programovací jazyky, včetně více než třech implementací pro Go.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Další přístup, který nám může pomoci vypořádat se s vysokou kardinálností, se nazývá skupinování (binning). Představte si, že máte pole, které představuje výšku člověka. Výška je číslo s plovoucí desetinnou čárkou, ale my, lidé, na to takto nepřemýšlíme. Pro nás není rozdíl mezi výškou 185,2 cm a 185,3 cm.

Tedy můžeme seskupit podobné hodnoty do skupin v rozsahu 1 cm.

A pokud ještě víme, že velmi málo lidí má výšku pod 50 cm a nad 250 cm, můžeme v podstatě proměnit pole s nekonečnou kardinálností na pole s kardinálností přibližně 200 hodnot.

Samozřejmě, pokud bude potřeba, můžeme provést další filtrování i později.

Problém s velkým průchodem

Další problém bitmapových indexů spočívá v tom, že jejich aktualizace může být velmi nákladná.

Databáze musí umožňovat aktualizaci dat v okamžiku, kdy potenciálně stovky dalších požadavků provádějí dotazy na tato data. Potřebujeme zámky, abychom se vyhnuli problémům s konkurenčním přístupem k datům nebo jiným problémům sdíleného přístupu. A tam, kde je jeden velký zámek, tam je problém – „lock contention“, kdy se tento zámek stává úzkým místem.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Tento problém lze vyřešit nebo obejít pomocí shardingu nebo použití verzionovaných indexů.

Sharding je jednoduchá a běžně známá věc. Můžete shardovat bitmapový index stejně, jako byste shardovali jakákoli jiná data. Místo jednoho velkého zámku dostanete spoustu malých zámků a tím se zbavíte lock contention.

Druhý způsob, jak vyřešit problém, spočívá v použití verzionovaných indexů. Můžete mít jednu kopii indexu, kterou používáte pro vyhledávání nebo čtení, a jednu – pro zápis nebo aktualizaci. A jednou za určité období (například každých 100 ms nebo 500 ms) je duplikujete a vyměníte. Tento přístup se samozřejmě uplatňuje pouze v případech, kdy vaše aplikace může pracovat s mírně opozdilým vyhledávacím indexem.

Tyto dva přístupy lze použít současně: můžete mít shardovaný verzionovaný index.

Složitější dotazy

Poslední problém bitmapových indexů spočívá v tom, že, jak nám říkají, nejsou příliš vhodné pro složitější typy dotazů, například dotazy „v rozmezí“.

A skutečně, pokud o tom přemýšlíme, bitové operace typu AND, OR apod. nejsou příliš vhodné pro dotazy ala „Ukažte mi hotely s cenou pokoje od 200 do 300 dolarů za noc“.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Naivním a velmi nerozumným řešením by bylo vzít výsledky pro každou dolarovou hodnotu a sloučit je bitovou operací OR.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
O něco správnějším řešením by bylo používat skupiny. Například ve skupinách po 50 dolarech. To by urychlilo náš proces 50krát.

Ale problém je také snadno řešitelný použitím reprezentace vytvořené speciálně pro tento typ dotazů. V odborných pracích se tomu říká range-encoded bitmaps.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
V takové reprezentaci nenastavujeme jen jeden bit pro nějakou hodnotu (například 200), ale nastavujeme tuto hodnotu a všechno, co je nad ní. 200 a výše. Totéž platí pro 300: 300 a výše. A tak dále.

Použitím této reprezentace můžeme odpovědět na takovýto typ vyhledávacího dotazu, projdeme indexem pouze dvakrát. Nejprve získáme seznam hotelů, kde cena pokoje je nižší než 300 dolarů, a poté vyřadíme ty, kde cena pokoje je nižší než 199 dolarů. Hotovo.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Budete se divit, ale dokonce i geo-dotazy jsou možné za použití bitmapových indexů. Zásadní je použít geo-representaci, která obklopuje vaši souřadnici geometrickou figurou. Například S2 od Googlu. Figurku musí být možné představit ve formě tří nebo více vzájemně se křížících přímek, které lze očíslovat. Tímto způsobem budeme moci převést náš geo-dotaz na několik dotazů „v rozmezí“ (podle těchto očíslovaných čar).

Hotová řešení

Doufám, že jsem vás trochu zaujal a že jste do svého arzenálu přidali ještě jeden užitečný nástroj. Pokud budete někdy potřebovat udělat něco podobného, budete vědět, kterým směrem se podívat.

Nicméně ne každý má čas, trpělivost a zdroje, aby vytvořil bitmapové indexy od nuly. Zvlášť ty pokročilejší, využívající SIMD například.

Naštěstí existuje několik připravených řešení, které vám mohou pomoci.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Roaring bitmaps

Za prvé, je tu ta ona knihovna roaring bitmaps, o které jsem již hovořil. Obsahuje všechny potřebné kontejnery a bitové operace, které budete potřebovat k vytvoření plnohodnotného bitmapového indexu.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Bohužel v současnosti žádná z Go implementací nevyužívá SIMD, což znamená, že Go implementace jsou méně výkonné než implementace na C například.

Pilosa

Další produkt, který vám může pomoci, je databázový systém Pilosa, který v podstatě pracuje pouze s bitmapovými indexy. Je to relativně nové řešení, ale rychle si získává srdce uživatelů.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pilosa uvnitř sebe využívá roaring bitmaps a dává vám možnost je použít, zjednodušuje a vysvětluje všechny ty věci, o kterých jsem mluvil výše: agregaci, range-encoded bitmaps, pojem pole atd.

Rychle se podívejme na příklad použití Pilosa k odpovědi na otázku, kterou už znáte.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Příklad je velmi podobný tomu, co jste viděli dříve. Vytváříme klienta pro server Pilosa, vytváříme index a potřebná pole, poté zaplňujeme naše pole náhodnými daty s pravděpodobnostmi a nakonec provádíme známý dotaz.

Poté používáme NOT na pole „expensive“, následně propojíme výsledek (nebo AND) s polem „terrace“ a s polem „reservations“. A nakonec získáváme závěrečný výsledek.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Opravdu doufám, že v dohledné době se v databázích jako MySQL a PostgreSQL také objeví tento nový typ indexů — bitmapové indexy.
Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí

Závěr

Bitmapové indexy v Go: vyhledávání neuvěřitelnou rychlostí
Pokud jste ještě neusnuli, děkuji. Musel jsem se ve zhruba dotknout mnoha témat z důvodu časového omezení, ale doufám, že byl můj příspěvek užitečný a možná i motivující.

O bitmapových indexech je dobré mít přehled, i když je aktuálně nepotřebujete. Ať jsou dalším nástrojem ve vaší výbavě.

Prozkoumali jsme různé triky ke zvýšení výkonu pro Go a věci, s nimiž si kompilátor Go zatím příliš dobře neporadí. A to je rozhodně něco, co by měl znát každý programátor v Go.

To je vše, co jsem chtěl říct. Děkuji!

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster