1 of 20

Cloud Data Warehouse

for

Data Intensive Applications

Mosha Pasumansky - moshap@firebolt.io

NWDS 2024

2 of 20

Architecture

3 of 20

Architecture

3D Engine 💪

Sparse Index ☝

WAL Lookup 🫣

FireJoin🔥

Shuffle 💨

4 of 20

The Hash-Join Algorithm

Aim: Compute SELECT a, c FROM R JOIN S ON R.a = S.b

Hash-Join algorithm to the rescue!

  • Well known, proposed in the late 1970s
  • Most commonly used join algorithm in analytical DBs

Two Phases:

  1. Build
  2. Probe

5 of 20

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

6 of 20

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

7 of 20

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

8 of 20

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

9 of 20

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

10 of 20

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

11 of 20

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

12 of 20

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

13 of 20

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

14 of 20

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

15 of 20

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

16 of 20

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?

  • Stats-collection pass �For all rows, find max # bytes needed for each column

  • Here for all 10M rows: 3 bytes for column b, 1 byte for column c

5’000’000

42

17 of 20

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

18 of 20

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

19 of 20

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

20 of 20

Thank you for your time