Locality groups
If we need to read columns of a specific column family for many rows (using a common prefix), scan performance will degrade as column families increase in size.
Consider the webtable
example:
If we wanted to get the language of all com.* pages, we would need to scan following column families:
anchor
, which can be a very wide column familylanguage
contents
, which is always huge because it stores raw HTML
language
is just 2 bytes (alpha2 country code, e.g. DE, EN, …), but every row may require multiple kilobytes of data to be retrieved to get just the language. This heavily decreases read throughput of OLAP-style scans of large ranges.
To combat this, we can define a locality group, which can house multiple column families. Each locality group is stored in its own LSM-tree (a single partition inside the storage engine), but row mutations across column families stay atomic.
The data inside the locality group can likely be compressed much more efficiently, if the data is of the same type and similar in some way.
One downside of partitioning using locality groups is the increased read latency if we need to access column families that are not part of the same locality group.
Example: Without locality groups
Setup
First, let’s create a table no-locality-example
:
and two column families, title
and language
:
By listing our table, we can see the column families have been created and are not part of any locality groups:
All data is stored in the _dat_no-locality-example
partition.
Ingest data
Let’s ingest some data and query it (body is truncated for brevity):
Query data
Let’s query our entire table using a scan with empty prefix, but
only return the column title:
:
Smoltable returns (again, body truncated for brevity):
Note, how we scanned 1 KB of data, and 16 cells, but only returned 8 cells (because we filtered by the title
column family). That means we have a read amplification of about 2
.
Example: With locality groups
Setup
First, let’s create a table with-locality-example
:
and two column families, title
and language
, but move title
into a locality group:
By listing our table, we can see the column families have been created, and title
is moved into a locality group:
Column families that are not title
are stored in the _dat_with-locality-example
partition, and title
data is moved into the _lg_ij0SIQ_z0Ys9Qx_wMWyt6
partition.
Ingest data
Ingest the same data as before into with-locality-example
.
Query data
which returns (truncated):
We get the exact same result, however, we reduced scanned bytes down to 680 bytes, and halved scanned cells, achieving a read amplification of 1
!
Example: Scanning another column family
Let’s scan the language
column instead, which is still stored in the default partition.
no-locality-example
(no locality groups) returns:
with-locality-example
returns:
From 984
bytes down to 374
, that’s a 62% decrease!