TokoPoints, at first, used redis and memcache to do caching. But the memcache library we were using was buggy and ate a lot of memories. We removed it before Ramadan Ekstra, and we relied only on redis. Everything was OK at that time.
At Tokopedia Birthday, we decided to launch new kind of flash sale for TokoPoints: Flash Coupon. Some coupons are discounted to be redeemed. After load tested for Tukar Points page and redeem coupon button, there’s a huge gap between actual and target RPS. We should change our caching method.
In Go, map is a hash table. It’ll require key to save or return some value(s).
You can read more at Go’s official blog: https://blog.golang.org/go-maps-in-action
Because we’re using global vars, how to clear those cache?
We’re using 2 method to clear cache:
- Using cron, for every n-secs it will destroy the mapcache and re-initialized.
- Using cron, listening to redis every n-secs. If unix timestamp in redis bigger than app’s timestamp, it’ll destroy mapcache and re-initialized, and then renew app’s timestamp.
So far so good. Until we re-did load test. Concurrent error happens and our app is in panic.
So we revert our build, and searched how to prevent this. Go provides sync.Map to reduce this kind of error. With sync.Map, Go will lock that map using mutex so hopefully it won’t concurrent anymore.
So we changed from map to sync.Map and re-did again the load test. The result was expected: faster than redis, lighter than third-party lib memcache.
Because we’re caching at main application, there’s possibility cached data will be growing and maybe main app will be dying because of runs out of memory, so we should make sure what kind of cache that we can store as a map.
Mohamad Raditya Putra
August 19th, 2018