{"id":33793,"date":"2019-10-31T21:54:42","date_gmt":"2019-10-31T18:54:42","guid":{"rendered":"https:\/\/prohoster.info\/blog\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\/"},"modified":"2019-10-31T21:54:42","modified_gmt":"2019-10-31T18:54:42","slug":"bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","title":{"rendered":"Bitmap indexes in Go: searching at wild speed","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/86ef928e6741022b2c0e5885a031408a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h2>Opening Remarks<\/h2>\n<p>\nI presented this talk in English at GopherCon Russia 2019 in Moscow and in Russian at a meetup in Nizhny Novgorod. It discusses the bitmap index, which is less common than the B-tree but equally interesting. I share <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/WvlUH6MjUuI?list=PL3xVZC4USRNSO_kb2lh_J_no6C-KJ7Phg\">the recording<\/a><\/noindex> of my conference presentation in English and the text transcription in Russian.<\/p>\n<p>We will explore how the bitmap index works, when it is better, when it is worse than other indexes, and in which cases it can be significantly faster; we'll see in which popular DBMSs bitmap indexes already exist; and we will attempt to write our own in Go. As a 'dessert', we will use existing libraries to create our own super-fast specialized database.<\/p>\n<p>I really hope my efforts will be useful and interesting to you. Let's get started!<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Introduction<\/h2>\n<p>\n<center><div class=\"youtube-placeholder\" data-id=\"WvlUH6MjUuI\" onclick=\"loadVideo(this)\">\r\n        <img decoding=\"async\" src=\"https:\/\/img.youtube.com\/vi\/WvlUH6MjUuI\/hqdefault.jpg\" alt=\"Play video\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\n<noindex><a rel=\"nofollow\" href=\"http:\/\/bit.ly\/bitmapindexes\">http:\/\/bit.ly\/bitmapindexes<\/a><\/noindex><br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/mkevac\/gopherconrussia2019\">https:\/\/github.com\/mkevac\/gopherconrussia2019<\/a><\/noindex><\/p>\n<p>Hello everyone! It's six in the evening, and we are all super tired. What a wonderful time to talk about the boring theory of database indexes, right? Don\u2019t worry, I\u2019ll have a couple of lines of source code here and there. \ud83d\ude42<\/p>\n<p>Joking aside, the talk is packed with information, and we don\u2019t have much time. So let\u2019s get started.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/e778e13727700f0335a4b5558a0d8db3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nToday I will talk about the following:<\/p>\n<ul>\n<li>what indexes are;\n<\/li>\n<li>what a bitmap index is;\n<\/li>\n<li>where it is used and where it is NOT used and why;\n<\/li>\n<li>a simple implementation in Go and a bit of dealing with the compiler;\n<\/li>\n<li>a slightly less simple but much more performant implementation in Go assembly;\n<\/li>\n<li>the 'problems' of bitmap indexes;\n<\/li>\n<li>existing implementations.\n<\/li>\n<\/ul>\n<h2>So what are indexes?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/b80e5b990c44814afe9150a9a82351fd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAn index is a separate data structure that we maintain and update in addition to the primary data. It is used to speed up searches. Without indexes, searching would require a full scan of the data (a process called full scan), and this process has linear algorithmic complexity. However, databases typically contain vast amounts of data, and linear complexity is too slow. Ideally, we would like to achieve logarithmic or constant time complexity.<\/p>\n<p>This is a vast and complex topic filled with nuances and trade-offs, but after looking at decades of development and research in various databases, I am ready to assert that there are only a few widely used approaches to creating DB indexes.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/08a74dbb365035cd99bc94d72644a1b7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe first approach involves hierarchically reducing the search space by dividing it into smaller sections.<\/p>\n<p>We usually achieve this by using various types of trees. An example could be a large box with materials in your cabinet, containing smaller boxes that are categorized by different topics. If you need materials, you would likely look in the box labeled 'Materials' rather than the one labeled 'Cookies,' right?<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/423fac47c748980b18af434644af4dae.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe second approach is to directly identify the required element or group of elements. We do this using hash maps or inverted indexes. Using hash maps is very similar to the previous example, except instead of a box of boxes, you have a bunch of little boxes with final items in your cabinet.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/088fed0a2a0feea4a23edbf0ca654805.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe third approach is to eliminate the need for a search entirely. We achieve this with Bloom filters or cuckoo filters. The former provides an instant response, freeing you from the need to perform a search.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/493bc4f20baacc0a5dc0faf15cc46285.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe last approach is to fully utilize the capabilities that modern hardware provides us. This is precisely what we do in bitmap indexes. Yes, using them sometimes requires us to go through the whole index, but we do this super efficiently.<\/p>\n<p>As I mentioned, the topic of database indexes is vast and filled with trade-offs. This means that sometimes we can use multiple approaches simultaneously: if we need to speed up searches even further or if we need to cover all possible types of searches.<\/p>\n<p>Today I will talk about the least known approach from those mentioned\u2014the bitmap indexes.<\/p>\n<h2>Who am I to speak on this topic?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/42a9d17a507c392bc202254a92dfaf41.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nI work as a team lead at Badoo (you might be more familiar with our other product\u2014Bumble). We already have over 400 million users worldwide and many features designed to find the best match for them. We achieve this through custom services that utilize bitmap indexes, among other things.<\/p>\n<h2>So what exactly is a bitmap index?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/14f20f3697a20c02f6b3510dc7f0ae4d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBitmap indexes, as the name suggests, use bitmaps or bit sets to implement a search index. From a bird's eye view, this index consists of one or more bitmaps representing certain entities (like people) and their properties or parameters (age, eye color, etc.), along with an algorithm that utilizes bitwise operations (AND, OR, NOT) to respond to search queries.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/f815720330a51b1f0798e45d23160d3b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIt is said that bitmap indexes are best suited and extremely efficient for cases where searches involve queries across many columns with low cardinality (think 'eye color' or 'marital status' compared to something like 'distance from the city center'). However, I will later show that they work perfectly well for columns with high cardinality as well.<\/p>\n<p>Let\u2019s consider a simple example of a bitmap index.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/33fe23476e0931c10345d7175b83d68b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nImagine we have a list of Moscow restaurants with binary properties like these:<\/p>\n<ul>\n<li>near metro;\n<\/li>\n<li>has private parking;\n<\/li>\n<li>has terrace;\n<\/li>\n<li>accepts reservations;\n<\/li>\n<li>vegan friendly;\n<\/li>\n<li>expensive.\n<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/fcbad539ea12f79a9d06966ce308637e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nLet\u2019s assign a sequential number to each restaurant starting from 0 and allocate memory for 6 bitmaps (one for each characteristic). Then we will fill these bitmaps depending on whether the restaurant has the specific property or not. If restaurant 4 has a terrace, then bit number 4 in the 'has terrace' bitmap will be set to 1 (if there is no terrace, it will be 0).<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/3709c426617d4364f392ee6b68f92b00.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNow we have the simplest possible bitmap index, and we can use it to respond to queries like:<\/p>\n<ul>\n<li>'Show me restaurants that are vegan friendly';\n<\/li>\n<li>'Show me inexpensive restaurants with a terrace where reservations can be made.'\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/90acb0f890686bd52c3db1fc667f0b0b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/0fd0b7fe9f7b7039022ad5c79fe873bc.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHow? Let\u2019s take a look. The first query is very simple. All we need to do is take the bitmap 'vegan friendly' and convert it into a list of restaurants whose bits are set.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ef4a5cfd658ff4ef8bc0c638c4522d11.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/9cc175bae55c16018fdf5ff95517a61a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThe second query is a bit more complex. We need to use the NOT bitwise operation on the bitmap \"expensive\" to obtain a list of inexpensive restaurants, then AND it with the bitmap \"can book a table\" and AND the result with the bitmap \"has a terrace.\" The resulting bitmap will contain a list of establishments that meet all our criteria. In this example, it\u2019s only the restaurant \"Youth.\"<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/f1cdda0cbf7f15278553899cf876c17e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/126b50e0f622b6e36c461cd74e708c38.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThere\u2019s a lot of theory here, but don\u2019t worry, we\u2019ll see the code very soon.<\/p>\n<h2>Where are bitmap indexes used?<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/406132236c71a4f66ae79957b6e633b3.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIf you \"Google\" bitmap indexes, 90% of the answers will be somehow related to Oracle DB. But other DBMSs surely support such a cool feature, right? Not quite. <\/p>\n<p>Let\u2019s go through the list of main suspects.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/d18d66451a8a26b0ddf121f8ec0204cd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nMySQL does not yet support bitmap indexes, but there is a proposal to add this option (<noindex><a rel=\"nofollow\" href=\"https:\/\/dev.mysql.com\/worklog\/task\/?id=1524\">https:\/\/dev.mysql.com\/worklog\/task\/?id=1524<\/a><\/noindex>).<\/p>\n<p>PostgreSQL does not support bitmap indexes but uses simple bitmaps and bitwise operations to combine search results across several other indexes.<\/p>\n<p>Tarantool has bitset indexes, supporting simple searches on them.<\/p>\n<p>Redis has simple bit fields<noindex><a rel=\"nofollow\" href=\"https:\/\/redis.io\/commands\/bitfield\"> (https:\/\/redis.io\/commands\/bitfield<\/a><\/noindex>) without the ability to search on them.<\/p>\n<p>MongoDB still does not support bitmap indexes, but there is also a proposal to add this option. <noindex><a rel=\"nofollow\" href=\"https:\/\/jira.mongodb.org\/browse\/SERVER-1723\">https:\/\/jira.mongodb.org\/browse\/SERVER-1723<\/a><\/noindex><\/p>\n<p>Elasticsearch uses bitmaps internally<noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/blog\/frame-of-reference-and-roaring-bitmaps\"> (https:\/\/www.elastic.co\/blog\/frame-of-reference-and-roaring-bitmaps<\/a><\/noindex>).<\/p>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/232d335963603d6e6dec98839fc9f486.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ul>\n<li>But a new neighbor has appeared in our house: Pilosa. This is a new non-relational database written in Go. It contains only bitmap indexes and builds everything on them. We\u2019ll talk about it a bit later.\n<\/li>\n<\/ul>\n<h2>Implementation in Go<\/h2>\n<p>\nBut why are bitmap indexes used so rarely? Before answering this question, I would like to demonstrate to you an implementation of a very simple bitmap index in Go.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ae7f4c1a4a740fe709b05dfaee27ac9b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBitmaps are essentially represented as just pieces of data. In Go, let\u2019s use byte slices for this.<\/p>\n<p>We have one bitmap for one restaurant characteristic, and each bit in the bitmap indicates whether a specific restaurant has that property or not.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/1ab628ee15887d6b3c6c99855aa610c8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWe will need two helper functions. One will be used to fill our bitmaps with random data. Random, but with a certain probability that a restaurant possesses each attribute. For example, I believe that there are very few restaurants in Moscow where you cannot reserve a table, and I think that about 20% of establishments are suitable for vegetarians.<\/p>\n<p>The second function will convert the bitmap into a list of restaurants.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ab9c87a0ba63750116e2e7968f842a9a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/498cf7a33611d99b90197d4ee82e2834.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nTo respond to the query 'Show me inexpensive restaurants that have a terrace and where it is possible to reserve a table', we will need two bitwise operations: NOT and AND.<\/p>\n<p>We can simplify our code a bit by using the more complex operation AND NOT.<\/p>\n<p>We have functions for each of these operations. Both of them loop through the slices, take the corresponding elements from each, combine them with a bitwise operation, and place the result in the resulting slice.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ec5652a34f0370dfb03df199f341f153.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAnd now we can use our bitmaps and functions to respond to the search query.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/cf9500ccfe75995a6008191c16729689.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPerformance is not that high, even though the functions are very simple and we saved quite a bit by not returning a new resulting slice with each function call.<\/p>\n<p>After profiling a bit with pprof, I noticed that the Go compiler missed one very simple but crucial optimization: function inlining.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/9092f5ba3940d0a4f3fbfa90b364d716.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThe thing is that the Go compiler is terribly afraid of loops that go through slices and categorically refuses to inline functions that contain such loops.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/d1bddd62b61b367dd1f680f223fa0ce7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBut I'm not afraid and can trick the compiler by using goto instead of a loop, just like in the old days.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/5b1c3cb26b923972686047910ecd1c31.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/1ca31d12dc90931b674c6a86a4ea23bd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAnd, as you can see, now the compiler gladly inlines our function! As a result, we manage to save about 2 microseconds. Not bad!<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/bdd6735d16082573e600bffe2cdd5662.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe second bottleneck is not hard to spot if you take a careful look at the assembly output. The compiler added boundary checks for the slice right inside our hottest loop. The fact is that Go is a safe language, and the compiler is concerned that my three arguments (three slices) may have different sizes. There is, after all, a theoretical possibility of a buffer overflow.<\/p>\n<p>Let's calm the compiler by showing it that all slices have the same size. We can do this by adding a simple check at the beginning of our function.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/59b4ce687a11dd653e9f12fc3130d89a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSeeing this, the compiler happily skips the check, and in the end, we save another 500 nanoseconds.<\/p>\n<h2>Large batches<\/h2>\n<p>\nOkay, we've managed to squeeze some performance out of our simple implementation, but this result is actually much worse than what is possible with the current hardware.<\/p>\n<p>All we're doing are basic bitwise operations, and our processors perform them very efficiently. Unfortunately, we are 'feeding' our processor very small chunks of work. Our functions perform operations byte by byte. We can easily tune our code to work with 8-byte chunks by using slices of UInt64.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/8d1b60ba4b7046c836601631cadd0b6a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAs you can see, this small change accelerated our program eightfold by increasing the batch size eight times. The gain can be said to be linear.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/0fc663412d04dd38b927de5c8776f69d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Assembly implementation<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/b3ef133167b89da983356b1c7389aecd.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBut this is not the end. Our processors can work with chunks of 16, 32, and even 64 bytes. Such 'wide' operations are called single instruction multiple data (SIMD), and the process of transforming code so that it uses these operations is called vectorization.<\/p>\n<p>Unfortunately, the Go compiler is far from an expert in vectorization. Currently, the only way to vectorize code in Go is to take and manually lay out the data operations using Go assembly.<\/p>\n<p><img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/91278be45df67e1f9572d68fab7ebad1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nGo assembly is a strange beast. You probably know that assembly is something that is very architecture-specific for the computer you're writing for, but in Go, that's not the case. Go assembly is more like an intermediate representation language (IRL): it is almost platform-independent. Rob Pike gave a great <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=KINIAgRpkDA\">presentation<\/a><\/noindex> talk on this topic a few years ago at GopherCon in Denver.<\/p>\n<p>In addition to this, Go uses an unusual Plan 9 format that differs from the widely recognized AT&amp;T and Intel formats.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/526f75eb18edfc2f851f2725e9d6f69e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIt is safe to say that writing Go assembly manually is not the most enjoyable task.<\/p>\n<p>But fortunately, there are already two high-level tools that assist us in writing Go assembly: PeachPy and avo. Both utilities generate Go assembly from higher-level code written in Python and Go, respectively.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/b8a7aa2c585b805e9b1f2ada186b84e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThese utilities simplify tasks like register allocation, writing loops, and generally ease the process of entering the world of assembly programming in Go.<\/p>\n<p>We will use avo, so our programs will be almost ordinary Go programs.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/128e4adef14e5f2cf00fb5b6302ef58e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHere is what the simplest example of an avo program looks like. We have the main() function, which defines the Add() function within itself, whose purpose is to add two numbers. There are helper functions for obtaining parameters by name and for retrieving one of the free and suitable processor registers. Each processor operation has a corresponding function in avo, as seen with ADDQ. Finally, we see a helper function for storing the resulting value.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/01ccaa6aa6d394ef598ea2dbc9257d87.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBy calling go generate, we will run the avo program and end up with two generated files:<\/p>\n<ul>\n<li>add.s containing the resulting code in Go assembly;\n<\/li>\n<li>stub.go with function headers to bridge the two worlds: Go and assembly.\n<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/72a9443776ecf45eef6fb97a4e08acba.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNow that we have seen what avo does and how it works, let\u2019s look at our functions. I have implemented both scalar and vector (SIMD) versions of the functions.<\/p>\n<p>First, let's look at the scalar versions.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ec31dbb8b97b9d7c1012a120fa18cdaf.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAs in the previous example, we ask for a free and proper general-purpose register, we don\u2019t need to calculate offsets and sizes for the arguments. avo does all of this for us.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/88085e927dd943ea0f808a28fb3ccf9b.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPreviously, we used labels and goto (or jumps) for performance enhancement and to trick the Go compiler, but now we are doing this from the beginning. The thing is, loops are a higher-level concept. In assembly, we only have labels and jumps.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/9a4181248eb279d89c1445a820b06649.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThe remaining code should already be familiar and clear. We emulate loops with labels and jumps, take a small part of data from our two slices, combine them with a bitwise operation (AND NOT in this case), and then store the result in the resulting slice. That's all.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/19275ead27f63092fc6596bed38a8d03.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHere is what the final assembly code looks like. We did not need to calculate offsets and sizes (highlighted in green) or keep track of the registers being used (highlighted in red).<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ed68528a6f852634a5b536670c6315f0.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIf we compare the performance of the assembly implementation with the performance of the best implementation in Go, we will see that they are the same. And this is to be expected. After all, we didn't do anything special\u2014we simply reproduced what the Go compiler would do.<\/p>\n<p>Unfortunately, we cannot force the compiler to inline our functions written in assembly. The Go compiler does not currently have this capability, although there has been a request to add it for quite some time.<\/p>\n<p>That is why it is impossible to gain any advantages from small functions in assembly. We either have to write large functions, use the new math\/bits package, or avoid assembly altogether.<\/p>\n<p>Now let\u2019s take a look at the vectorized versions of our functions.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/7f67c3cc855908fb47c7900d6e5d7f54.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nFor this example, I decided to use AVX2, so we will work with operations that deal with 32-byte chunks. The structure of the code is very similar to the scalar version: loading parameters, asking for a free general-purpose register, and so on.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/021e0424a0d733ec7db9edeb98ce1f65.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nOne of the innovations is that wider vector operations use special wide registers. In the case of 32-byte chunks, these are registers prefixed with Y. That\u2019s why you see the YMM() function in the code. If I had used AVX-512 with 64-bit chunks, the prefix would have been Z.<\/p>\n<p>The second innovation is that I decided to use an optimization called loop unrolling, which means performing eight loop iterations manually before jumping back to the start of the loop. This optimization reduces the number of branches in the code, and it is limited by the number of free registers available.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/f54631d9e8c69f6f70ecace3133ae1e8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nBut what about performance? It is excellent! We achieved a speedup of about seven times compared to the best solution in Go. Impressive, right?<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/d7e85c933eecb243cfb49225b4d92c6c.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nHowever, even this implementation could potentially be further accelerated using AVX-512, prefetching, or a JIT (just-in-time compiler) for the query planner. But that is definitely a topic for another talk.<\/p>\n<h2>Issues with bitmap indexes<\/h2>\n<p>\nNow that we have examined the simple implementation of a bitmap index in Go and a much more performant one in assembly, let\u2019s finally discuss why bitmap indexes are so rarely used.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/83d36a5c92ba90fd680fe8afb6cfc11f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nOlder academic papers mention three problems with bitmap indexes, but more recent studies and I argue that they are no longer relevant. We won't delve deeply into each of these issues, but we'll take a superficial look at them.<\/p>\n<h2>The High Cardinality Problem<\/h2>\n<p>\nSo, we are told that bitmap indexes are suitable only for fields with low cardinality, meaning those with few values (such as gender or eye color). The reason is that the usual representation of such fields (one bit per value) will take up too much space in cases of high cardinality, and moreover, these bitmap indexes will be sparsely (rarely) populated.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/0787efc4d2cb4ea4d404ca7888b33697.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/44ab4bc7c25d14fe2f53caf5d9da399d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSometimes we can use a different representation, for example, the standard one we use for numerical representation. But it was the emergence of compression algorithms that changed everything. Over the last few decades, scientists and researchers have devised numerous compression algorithms for bitmaps. Their main advantage is that we do not need to decompress bitmaps to perform bitwise operations \u2014 we can conduct bitwise operations directly on compressed bitmaps.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/ecb54fbf15aa11271bbbab01ecbda880.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nRecently, hybrid approaches have emerged, such as roaring bitmaps. They use three different representations for bitmaps simultaneously \u2014 traditional bitmaps, arrays, and so-called bit runs \u2014 and balance between them to maximize performance and minimize memory consumption.<\/p>\n<p>You can find roaring bitmaps in some of the most popular applications. There are already a huge number of implementations for a variety of programming languages, including more than three implementations for Go.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/de2adfebc431ff48c996247b453f02ae.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAnother approach that can help us deal with high cardinality is called binning. Imagine you have a field representing a person's height. Height is a floating-point number, but we, humans, don\u2019t think of it in those terms. For us, there is no difference between a height of 185.2 cm and 185.3 cm.<\/p>\n<p>Thus, we can group similar values into bins within 1 cm.<\/p>\n<p>And if we also know that very few people have a height less than 50 cm and greater than 250 cm, we can essentially turn a field with infinite cardinality into a field with cardinality of about 200 values.<\/p>\n<p>Of course, if needed, we can perform additional filtering later.<\/p>\n<h2>The issue of high bandwidth<\/h2>\n<p>\nThe next issue with bitmap indexes is that updating them can be very costly.<\/p>\n<p>Databases must allow for updating data at the moment when potentially hundreds of other queries are searching through that data. We need locks to avoid problems with concurrent data access or other concurrency issues. And where there is one big lock, there is a problem \u2014 lock contention, when that lock becomes a bottleneck.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/0ae1bf925542d286f8b7b245c160a35e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThis issue can be solved or circumvented using sharding or versioned indexes.<\/p>\n<p>Sharding is simple and well-known. You can shard a bitmap index just as you would shard any other data. Instead of one large lock, you obtain many smaller locks and thus eliminate lock contention.<\/p>\n<p>The second way to solve the problem is by using versioned indexes. You may have one copy of the index that you use for searching or reading, and another for writing or updating. And every so often (e.g., every 100 ms or 500 ms), you duplicate them and switch them. Of course, this approach is applicable only when your application can work with a slightly outdated search index.<\/p>\n<p>These two approaches can be used simultaneously: you may have a sharded versioned index.<\/p>\n<h2>More complex queries<\/h2>\n<p>The last problem with bitmap indexes is that, as we are told, they are poorly suited for more complex types of queries, such as range queries.<\/p>\n<p>Indeed, if you think about it, bitwise operations like AND, OR, etc., are not very suitable for queries like \"Show me hotels with room rates from $200 to $300 per night.\"<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/7bc2e129cad46fb5875c2b3018c39ff7.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nA naive and very unreasonable solution would be to take the results for each dollar value and combine them using a bitwise OR operation.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/b9f8fc7945caa1866f8e0cfa8a04bd98.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nA somewhat more appropriate solution would be to use grouping. For example, in groups of $50. This would speed up our process by 50 times.<\/p>\n<p>But the problem is easily solved by using a representation specifically created for this type of query. In academic papers, it is called range-encoded bitmaps.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/644a420628b21f220a7af1ff15c4031f.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn such a representation, we don't just set one bit for a given value (for example, 200), but we set this value and everything above it. 200 and above. The same goes for 300: 300 and above. And so on.<\/p>\n<p>Using this representation, we can respond to this type of search query by scanning the index only twice. First, we get a list of hotels where the room cost is less than 300 dollars, and then we filter out those where the cost is below 199 dollars. Done.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/cc2bb58d7d7a51495c62ec7da81e2d12.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nYou will be surprised, but even geocoding queries are possible using bitmap indexes. The trick is to use a georepresentation that surrounds your coordinate with a geometric shape. For example, S2 from Google. The shape should be representable in the form of three or more intersecting lines that can be numbered. This way we can transform our geocoding query into several 'range' queries (along these numbered lines).<\/p>\n<h2>Ready-made solutions<\/h2>\n<p>\nI hope I have piqued your interest a bit and you now have another useful tool in your arsenal. If you ever need to do something similar, you'll know where to look.<\/p>\n<p>However, not everyone has the time, patience, and resources to create bitmap indexes from scratch. Especially more advanced ones, using SIMD, for example.<\/p>\n<p>Fortunately, there are several ready-made solutions that can help you.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/817b47cb189a758756b602ec9cf319e1.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Roaring bitmaps<\/h2>\n<p>\nFirst, there\u2019s the roaring bitmaps library that I mentioned earlier. It contains all the necessary containers and bitwise operations you will need to create a full-fledged bitmap index.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/57f61a51485c174666b52dc2063fabe8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nUnfortunately, at the moment, none of the Go implementations use SIMD, which means that Go implementations are less performant than implementations in C, for example.<\/p>\n<h2>Pilosa<\/h2>\n<p>\nAnother product that can help you is the Pilosa DB, which essentially only has bitmap indexes. It is a relatively new solution but is quickly winning hearts.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/d3870a979e1e73d093fe5d5e9bb71cd8.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPilosa uses roaring bitmaps internally and gives you the ability to utilize them, simplifying and explaining all the aspects I mentioned earlier: grouping, range-encoded bitmaps, the concept of fields, etc.<\/p>\n<p>Let\u2019s quickly take a look at an example of using Pilosa to answer a question you're already familiar with.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/dfee7abfd653cc19d9aa8b64c64f3e4e.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nThe example is very similar to what you've seen before. We create a client to the Pilosa server, establish an index and the necessary fields, then fill our fields with random data based on probabilities, and finally execute a familiar query.<\/p>\n<p>After that, we use NOT on the field 'expensive', then intersect the result (or AND it) with the field 'terrace' and the field 'reservations'. And finally, we get the final result.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/8d66c6d68019c2297b6c15b700f06a3a.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nI sincerely hope that in the near future, databases like MySQL and PostgreSQL will also feature this new type of index \u2014 bitmap indexes.<br \/>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/7a8e33d576c7fb6376a173ee038b4206.jpeg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<h2>Conclusion<\/h2>\n<p>\n<img decoding=\"async\" alt=\"Bitmap indexes in Go: searching at wild speed\" src=\"\/wp-content\/uploads\/2019\/05\/c62caa9ad6f2d96056c80326f4fa9a0d.jpeg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIf you haven't fallen asleep yet, thank you. I had to briefly touch on many topics because of limited time, but I hope the presentation was useful and perhaps even motivating.<\/p>\n<p>It's good to know about bitmap indexes, even if you don't need them right now. Let them be one more tool in your toolbox.<\/p>\n<p>We reviewed various tricks to enhance performance for Go and the issues that the Go compiler is still not handling very well. This is certainly useful knowledge for every Go programmer.<\/p>\n<p>That's all I wanted to share. Thank you!<br \/>\n<br \/>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/badoo\/blog\/451938\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441 \u044d\u0442\u0438\u043c \u0434\u043e\u043a\u043b\u0430\u0434\u043e\u043c \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u043d\u0430 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 GopherCon Russia 2019 \u0432 \u041c\u043e\u0441\u043a\u0432\u0435 \u0438 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u2014 \u043d\u0430 \u043c\u0438\u0442\u0430\u043f\u0435 \u0432 \u041d\u0438\u0436\u043d\u0435\u043c \u041d\u043e\u0432\u0433\u043e\u0440\u043e\u0434\u0435. \u0420\u0435\u0447\u044c \u0432 \u043d\u0451\u043c \u0438\u0434\u0451\u0442 \u043e bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u0435 \u2014 \u043c\u0435\u043d\u0435\u0435 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u043e\u043c, \u0447\u0435\u043c B-tree, \u043d\u043e \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u043c. \u0414\u0435\u043b\u044e\u0441\u044c \u0437\u0430\u043f\u0438\u0441\u044c\u044e \u0432\u044b\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f \u043d\u0430 \u043a\u043e\u043d\u0444\u0435\u0440\u0435\u043d\u0446\u0438\u0438 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u043e\u043c \u0438 \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0439 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c. \u041c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":25469,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-33793","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47Bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 Go: \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u0434\u0438\u043a\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:54:42+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:54:42+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Bitmap Indexes in Go: Search at Wild Speed | ProHoster","description":"Opening Remarks I presented with.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47Bitmap-\u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0432 Go: \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u0434\u0438\u043a\u043e\u0439 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 | ProHoster","og:description":"\u0412\u0441\u0442\u0443\u043f\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0441\u043b\u043e\u0432\u043e \u042f \u0432\u044b\u0441\u0442\u0443\u043f\u0438\u043b \u0441.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/bitmap-indeksy-v-go-poisk-na-dikoj-skorosti","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:54:42+00:00","article:modified_time":"2019-10-31T18:54:42+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"33793","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 16:43:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 02:33:25","updated":"2026-01-21 16:43:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/33793","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=33793"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/33793\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/25469"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=33793"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=33793"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=33793"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}