1 of 25

Design a modern

Memory Cache

Joway Wang

iftech 2019.11

2 of 25

Why we need a local cache?

3 of 25

Definition of “Modern”

Concurrent 💥

High throughput 🌊

High cache hit ratio 🎳

Memory Limit 🔒

4 of 25

Definition

Concurrent 💥

High throughput 🌊

High cache hit ratio 🎳

Memory Limit 🔒

5 of 25

Simplest Map

Golang

cache := map[string]string{}

cache["a"] = "b"

Problem

  1. No thread safety

6 of 25

Thread Safe Map

Problem

  • Poor performance

Golang

type SafeMap struct {

lock sync.Mutex

store map[string]string

}

7 of 25

Segmented Safe Map

Problem

  • Not friendly for hot key

Question

  1. How to reduce lock competition?
  2. How to implement lock-free?

Golang

type SafeMap struct {

locks []*sync.Mutex

store []map[string]string

}

8 of 25

Golang: sync.Map

9 of 25

Definition

Concurrent 💥

High throughput 🌊

High cache hit ratio 🎳

Memory Limit 🔒

10 of 25

Eviction strategy

11 of 25

Eviction strategy: LRU

What’s the problem?

12 of 25

Eviction strategy: LFU

What’s the problem?

13 of 25

Eviction strategy: TinyLFU

What’s the problem?

14 of 25

Eviction strategy: W-TinyLFU

Why it called 「TinyLFU」?

15 of 25

Data Structure: Count-Min Sketch

16 of 25

💥 Question:

How to push access log into TinyLFU?

Problem:

  1. Write amplification
  2. Lock competition

17 of 25

Roadmap

  1. Create an lossy queue
  2. Make sure it’s thread safe

18 of 25

RingBuffer

19 of 25

Roadmap

  • Create an access log queue
  • Make sure it’s thread safe

20 of 25

💥 Question:

Could we use segmented buffer?

Why?

21 of 25

Goroutine Model

Thread

M : Machine | Physical Processor

P : Logic Processor

G : Goroutine

Physical CPU

G

P

M

G

G

G

G-P-M Model

CPU

Thread

OS

Golang

22 of 25

Golang: sync.Pool

23 of 25

RingBuffer with sync.Pool

Golang

type ringBuffer struct {

stripes []*ringStripe

pool *sync.Pool

}

24 of 25

Architecture

25 of 25

Q&A