Cloud Data Warehouse
for
Data Intensive Applications
Mosha Pasumansky - moshap@firebolt.io
NWDS 2024
Architecture
Architecture
3D Engine 💪
Sparse Index ☝
WAL Lookup 🫣
FireJoin🔥
Shuffle 💨
The Hash-Join Algorithm
Aim: Compute SELECT a, c FROM R JOIN S ON R.a = S.b
Hash-Join algorithm to the rescue!
Two Phases:
Hash-Join: 1) Build Phase
Idea: Go over S row-by-row and build a “hashtable”
= data structure that can map key => value
Right table: S Conceptually Details hashtable
column b | column c |
1 | 47 |
2 | 11 |
… | … |
5’000’000 | 42 |
… | … |
10’000’000 | 49 |
key | => value |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
Hash-Join: 1) Build Phase
Idea: Go over S row-by-row and build a “hashtable”
= data structure that can map key => value
Right table: S Conceptually Details hashtable
column b | column c |
1 | 47 |
2 | 11 |
… | … |
5’000’000 | 42 |
… | … |
10’000’000 | 49 |
key | => value |
1 | => 47 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
hash_bucket(� 1)� �=> 1’001
1 | 47 |
Hash-Join: 1) Build Phase
Idea: Go over S row-by-row and build a “hashtable”
= data structure that can map key => value
Right table: S Conceptually Details hashtable
column b | column c |
1 | 47 |
2 | 11 |
… | … |
5’000’000 | 42 |
… | … |
10’000’000 | 49 |
key | => value |
1 | => 47 |
2 | => 11 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
hash_bucket(� 2)� �=> 1’234’567
1 | 47 |
2 | 11 |
Hash-Join: 1) Build Phase
Idea: Go over S row-by-row and build a “hashtable”
= data structure that can map key => value
Right table: S Conceptually Details hashtable
column b | column c |
1 | 47 |
2 | 11 |
… | … |
5’000’000 | 42 |
… | … |
10’000’000 | 49 |
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
hash_bucket(� 5’000’000)� �=> 1’001
1 | 47 |
2 | 11 |
Hash-Join: 1) Build Phase
Idea: Go over S row-by-row and build a “hashtable”
= data structure that can map key => value
Right table: S Conceptually Details hashtable
column b | column c |
1 | 47 |
2 | 11 |
… | … |
5’000’000 | 42 |
… | … |
10’000’000 | 49 |
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
hash_bucket(� 10’000’000)� �=> 5’555’555
1 | 47 |
2 | 11 |
10’000’000 | 49 |
Hash-Join: 2) Probe Phase
Idea: Go over R row-by-row and probe the “hashtable”
= data structure that can map key => value
Left table: R Conceptually Details hashtable
Result:
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
1 | 47 |
2 | 11 |
10’000’000 | 49 |
column a |
5’000’000 |
column a | column c |
Hash-Join: 2) Probe Phase
Idea: Go over R row-by-row and probe the “hashtable”
= data structure that can map key => value
Left table: R Conceptually Details hashtable
Result:
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
1 | 47 |
2 | 11 |
10’000’000 | 49 |
column a |
5’000’000 |
column a | column c |
5’000’000 | 42 |
Hash-Join: 2) Probe Phase
Idea: Go over R row-by-row and probe the “hashtable”
= data structure that can map key => value
Left table: R Conceptually Details hashtable
Result:
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
hash_bucket(� 5’000’000)� �=> 1’001
1 | 47 |
2 | 11 |
10’000’000 | 49 |
column a |
5’000’000 |
column a | column c |
5’000’000 | 42 |
Hash-Join: 2) Probe Phase
Idea: Go over R row-by-row and probe the “hashtable”
= data structure that can map key => value
Left table: R Conceptually Details hashtable
Result:
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
hash_bucket(� 5’000’000)� �=> 1’001
1 | 47 |
2 | 11 |
10’000’000 | 49 |
column a |
5’000’000 |
column a | column c |
5’000’000 | 42 |
Hash-Join: 2) Probe Phase
Idea: Go over R row-by-row and probe the “hashtable”
= data structure that can map key => value
Left table: R Conceptually Details hashtable
Result:
key | => value |
1 | => 47 |
2 | => 11 |
… | … |
5’000’000 | => 42 |
… | … |
10’000’000 | => 49 |
Bucket |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 |
hash_bucket(� 5’000’000)� �=> 1’001
1 | 47 |
2 | 11 |
10’000’000 | 49 |
column a |
5’000’000 |
column a | column c |
5’000’000 | 42 |
Encoding of Tuples
How to encode ?
“Vanilla”, as 8 byte ints: 40 4B 4C 00 00 00 00 00, 2A 00 00 00 00 00 00 00
=> 16 bytes
5’000’000 | 42 |
Encoding of Tuples
How to encode ?
“Vanilla”, as 8 byte ints: 40 4B 4C 00 00 00 00 00, 2A 00 00 00 00 00 00 00
=> 16 bytes
FireHashJoin: 40 4B 4C, 2A
=> 4 bytes
Why?
5’000’000 | 42 |
Memory-Layout of the Hashtable
Common “chaining” hashtable
=> 830 MB (with RowRefs)
Bucket Ptrs |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 | nullptr |
1 | 47 | |
2 | 11 | nullptr |
10’000’000 | 49 | nullptr |
8 byte ptr
8 byte ptr
8 byte int
8 byte int
Memory-Layout of the Hashtable
Common “chaining” hashtable FireHashTable
=> 830 MB (with RowRefs)
Bucket Ptrs |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 | nullptr |
1 | 47 | |
2 | 11 | nullptr |
10’000’000 | 49 | nullptr |
8 byte ptr
8 byte ptr
8 byte int
8 byte int
Data: �byte array | |
… | … |
… | … |
|
|
1 | 47 |
5’000’000 | 42 |
… | … |
… | … |
2 | 11 |
… | … |
… | … |
10’000’000 | 49 |
… | … |
Tuples laid out back-to-back
in a byte-array��Each tuple as �“4-bytes blob”�in the compact
encoding.
Bucket 1’001:�01 00 00 2F
40 4B 4C 2A
Bucket 1’001
Bucket 1’234’567
Bucket 5’555’555
Memory-Layout of the Hashtable
Common “chaining” hashtable FireHashTable
=> 830 MB (with RowRefs) => 90 MB
Bucket Ptrs |
0 |
1 |
… |
1’001 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
5’000’000 | 42 | nullptr |
1 | 47 | |
2 | 11 | nullptr |
10’000’000 | 49 | nullptr |
8 byte ptr
8 byte ptr
8 byte int
8 byte int
Buckets: Offsets |
0 |
1 |
… |
1’001 |
1’002 |
… |
1’234’567 |
… |
5’555’555 |
… |
33’554’432 |
Data: �byte array | |
… | … |
… | … |
|
|
1 | 47 |
5’000’000 | 42 |
… | … |
… | … |
2 | 11 |
… | … |
… | … |
10’000’000 | 49 |
… | … |
~1.5 bytes
3 bytes
1 byte
Tuples laid out back-to-back
in a byte-array��Each tuple as �“4-bytes blob”�in the compact
encoding.
Sorted by hash/bucket
Thank you for your time