2020年4月5日 星期日

redis 的 cache stampede

之前就大概有稍微看了一下 cache stampede 的文章,也大概知道了 xfetch 這個東西,
但是一直都沒有仔細下去看,因為覺得問題不大,

直到剛好看到這篇

https://kkc.github.io/2020/03/27/cache-note/


裡面大概講到幾種避免的方式:

  1. 弄個 cronjob/worker 定期去更新,這就犯了一個常見的問題。
    1. cache 到底是 cache 還是 storage?
    2. 如果是 cache,那他應該是 on demand 的才對吧?
    3. cache fail 了,程式應該要知道怎麼 fallback!
    4. 既然是兩個地方都要處理同樣的邏輯,程式的架構是寫死在一個地方,還是寫成 component?
  2. lock 機制
    1. 當大家同時遇到某個 cache expire 了,就用 lock 來搶吧!
    2. 所以只有搶到 lock 的那個 request 可以真的 query db,其他的就在那邊 sleep!
    3. 那要等多久?等到天荒地老?要不要設定一個 timeout?如果 timeout 到了,還是沒辦法從 redis 拿到資料該怎麼辦?一樣大家進去操 db?
  3. xfetch 機制
    1. 會在 query db 的時候把要花多久存下來(aka delta),之後在每次要 get cache 的時候,一次把 cache value/ttl & delta 都拿出來,先比對 delta 如果 >= ttl 的話,就預先做一次 query db,然後把資料存回來。
    2. 那如果同時好幾個人都 get cache,然後大家都要去 query db 呢?
    3. xfetch 用了一個 random 來判斷!
    4. 沒錯,用 random,所以最差就是大家一起死,不然就是等到大家都 cache expire 了,再一起死!
這些方法,聽起來都好像可以,但是真的實作起來,其實都還是怪怪的。

  1. cron job 如果程式架構可以,要處理的 hot spot 也夠明確,的確能立即解決問題!
  2. 但是在 cloud 的時代,每個 instance 都應該是沒有狀態在上面,也不應該在上面跑 cronjob 才對。不然大家都一起跑,還不是一樣大家都戳一次!
  3. lock 好像也很好,但是在很多 instance 很多 process 的狀況下,該怎麼 lock 呢?
  4. 有的人會說,redis 有 redlock,對,的確可以用,不過就算 lock/unlock 的時間點抓得夠好,還是會有其他的 request 在那邊等的狀況發生(不過原本 request 在等,有可能是 query db,所以無論如何都在等,無所謂)
  5. xfetch 看起來是比較聰明的,但是蠢就在,他用 random 去判斷誰該做事,random ㄟ,random ㄟ!沒有更聰明的?

經過一段沈思跟試驗實作之後,我覺得真正簡單又可以動的版本應該是這樣的:

xfetch + lock

要提前 query db,但是又要想辦法讓同時間只有一個 query db 的動作!

那要怎樣做到這些事情?
要感謝 redis 後來有 server side lua 了,可以讓你在 redis 裡面先做一些判斷,
client 只要一個 packet 就可以拿到他要的結果!