Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Why Akavache is Fast: how not to use SQLite3 Paul Betts (@paulcbetts)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

“An asynchronous, persistent key-value store”

Slide 5

Slide 5 text

Dictionary

Slide 6

Slide 6 text

What can we do with Byte Dictionaries? • Arbitrary objects via JSON.NET • Fetching and loading Images and URLs from the Internet • Storing and automatically encrypting User Credentials

Slide 7

Slide 7 text

Akavache uses SQLite3 to store data sqlite> .schema CREATE TABLE "CacheElement"( "Key" varchar primary key not null , "TypeName" varchar , "Value" blob , "Expiration" bigint , "CreatedAt" bigint );

Slide 8

Slide 8 text

Akavache uses SQLite3 to store its data • Users anecdotally report that once they move to Akavache, they get a huge app speedup vs SQLite3 • Yet, Akavache is built on SQLite3

Slide 9

Slide 9 text

??!??

Slide 10

Slide 10 text

I thought SQLite3 was Fast!

Slide 11

Slide 11 text

SQLite3 is fast when you: • Use transactions properly • Access SQLite3 on a single thread • Prepare commands ahead-of-time

Slide 12

Slide 12 text

SQLite3 is not fast when you: • Just issue commands (this means every command is a transaction) • Call into SQLite3 from a bunch of background threads at the same time

Slide 13

Slide 13 text

Accidentally using SQLite3 is easy

Slide 14

Slide 14 text

Accidentally using SQLite wrong is easy 1. Load Messages 2. Every message instantiates a MessageCellViewModel 3. MessageCellViewModel downloads avatar 4. Avatar gets saved in cache

Slide 15

Slide 15 text

There is only one disk.

Slide 16

Slide 16 text

Make SQLite3 faster, the easy way // NB: Setting journal_mode returns a row, nfi Connection.ExecuteScalar("PRAGMA journal_mode=WAL"); Connection.Execute("PRAGMA temp_store=MEMORY"); Connection.Execute("PRAGMA synchronous=OFF");

Slide 17

Slide 17 text

Make sure we prepare statements • Key-Value stores have pretty predictable query needs • Create them all on startup • Make sure to destroy them or you'll have problems!

Slide 18

Slide 18 text

Make sure we prepare statements var result = (SQLite3.Result)raw.sqlite3_prepare_v2( conn.Handle, "INSERT OR REPLACE INTO CacheElement VALUES (?,?,?,?,?)", out insertOp);

Slide 19

Slide 19 text

Make sure we prepare statements foreach (var v in insertList) { try { this.Checked(raw.sqlite3_bind_text(insertOp, 1, v.Key)); if (String.IsNullOrWhiteSpace(v.TypeName)) { this.Checked(raw.sqlite3_bind_null(insertOp, 2)); } else { this.Checked(raw.sqlite3_bind_text(insertOp, 2, v.TypeName)); } this.Checked(raw.sqlite3_bind_blob(insertOp, 3, v.Value)); this.Checked(raw.sqlite3_bind_int64(insertOp, 4, v.Expiration.Ticks)); this.Checked(raw.sqlite3_bind_int64(insertOp, 5, v.CreatedAt.Ticks)); this.Checked(raw.sqlite3_step(insertOp)); } finally { this.Checked(raw.sqlite3_reset(insertOp)); } }

Slide 20

Slide 20 text

Use less transactions

Slide 21

Slide 21 text

First, use transactions!

Slide 22

Slide 22 text

First, Akavache added Bulk Operations • Bulk operations let us insert / lookup multiple keys at once • We can insert all of the keys in a single transaction • If one fails, we have to fail them all in a single transaction

Slide 23

Slide 23 text

First, Akavache added Bulk Operations IDictionary Get( IEnumerable keys); void Insert( IDictionary keyValuePairs);

Slide 24

Slide 24 text

Concurrency still not solved

Slide 25

Slide 25 text

Use a Queue!

Slide 26

Slide 26 text

Use a Queue! • Every Akavache cache gets a single dedicated worker thread • That thread is the only thread to touch SQLite3 and processes items off the Queue • The queue is a list of "Operation to run + TaskCompletionSource to notify" • Bingo Bango, no concurrency problems

Slide 27

Slide 27 text

The Power of TaskCompletionSource • Tasks don't have to have an associated thread running • Tasks != Threads! • They're really just a box for a future value

Slide 28

Slide 28 text

The Power of TaskCompletionSource var tcs = new TaskCompletionSource(); return tcs.Task; // Later in another part of town... tcs.TrySetValue(true); // Or tcs.TrySetException(new Exception("Didn't Work!"));

Slide 29

Slide 29 text

Use a (Blocking) Queue! 1. Try to fetch a single item and block if there is none 2. If we succeed, try to non-blocking fetch 31 more items at most 3. Create a transaction 4. Run all 32 commands 5. Drop the transaction, complete the associated Tasks

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Let’s think about those 32 operations

Slide 32

Slide 32 text

Operation Coalescing Write “bar” Read “foo” Read “bar” Read “baz”

Slide 33

Slide 33 text

Operation Coalescing Write “bar” Read “foo” Read “bar” Read “baz” Just return the value we wrote in #1

Slide 34

Slide 34 text

Operation Coalescing Read “bar” Delete “foo” Write “bar”

Slide 35

Slide 35 text

Operation Coalescing Write “foo” Read “bar” Delete “foo” Just read bar, we don’t need to do the write at all

Slide 36

Slide 36 text

Operation Coalescing Gone Wrong Read “bar” Delete “bar” Write “bar”

Slide 37

Slide 37 text

Operation Coalescing Gone Wrong Write “foo” Failed Read “foo” Delete “foo” This Read should have worked, but incorrect coalescing made it fail!

Slide 38

Slide 38 text

We must respect request ordering

Slide 39

Slide 39 text

Key-Value Stores only have data dependencies between identical keys

Slide 40

Slide 40 text

What Akavache actually does 1. Write “Bar” 2. Read “Foo” 3. Read “Bar” 4. Read “Foo” 5. Read “Baz” 6. Delete “Bamf” { Bar: [Write Bar, Read Bar], Foo: [Read Foo, Read Foo], Baz: [Read Baz], Bamf: [Invalidate Bamf] } Group by the key, but maintain original order

Slide 41

Slide 41 text

What Akavache actually does { Bar: [Write Bar, Read Bar], Foo: [Read Foo, Read Foo], Baz: [Read Baz], Bamf: [Invalidate Bamf] } { Bar: [Write Bar, Read Bar], Foo: [Read Foo, Read Foo], Baz: [Read Baz], Bamf: [Invalidate Bamf] } Dedupe operations that do the same thing in a row

Slide 42

Slide 42 text

What Akavache actually does { Bar: [Write Bar, Read Bar], Foo: [Read Foo], Baz: [Read Baz], Bamf: [Invalidate Bamf] } 1. Read [Foo, Baz] 2. Write [Bar] 3. Invalidate [Bamf] Remove the First item in every list, and try to smush them together

Slide 43

Slide 43 text

What Akavache actually does { Bar: [Read Bar], Foo: [], Baz: [], Bamf: [] } 1. Read [Bar] Keep doing this, until we run out of items

Slide 44

Slide 44 text

Final Result 1. Write "Bar" 2. Read "Foo" 3. Read "Bar" 4. Read "Foo" 5. Read "Baz" 6. Invalidate "Bamf" 1. Read [Foo, Baz] 2. Write [Bar] 3. Invalidate [Bamf] 4. Read [Bar] Six commands down to four, not bad!

Slide 45

Slide 45 text

Some Graphs, for Science

Slide 46

Slide 46 text

Some Graphs, for Science

Slide 47

Slide 47 text

Your app takes advantage of bulk operations Automagically

Slide 48

Slide 48 text

Your app takes advantage of transactions Automagically

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

Thanks! https://github.com/akavache/akavache @paulcbetts on Twitter / GitHub / etc