brainのmergeが内容もタイミングもヤバイという話を読んでたら、ちょうどタイミングよくbrainが爆発した。


redis-brainに保存する度にエラーがでて、保存失敗する。hubotのbrainはオンメモリにキャッシュされているので、一見保存できてて動いているんだけどプロセス再起動するとデータ巻き戻ってる、という事態に見舞われた。

node_redis: no callback to send error: OOM command not allowed when used memory > 'maxmemory'.
[Fri Jan 09 2015 16:12:33 GMT+0900 (JST)] ERROR Error: OOM command not allowed when used memory > 'maxmemory'.
at ReplyParser. (/app/node_modules/hubot-scripts/node_modules/redis/index.js:279:27)
at ReplyParser.emit (events.js:95:17)
at ReplyParser.send_error (/app/node_modules/hubot-scripts/node_modules/redis/lib/parser/javascript.js:296:10)
at ReplyParser.execute (/app/node_modules/hubot-scripts/node_modules/redis/lib/parser/javascript.js:181:22)
at RedisClient.on_data (/app/node_modules/hubot-scripts/node_modules/redis/index.js:504:27)
at Socket. (/app/node_modules/hubot-scripts/node_modules/redis/index.js:82:14)
at Socket.emit (events.js:95:17)
at Socket. (_stream_readable.js:765:14)
at Socket.emit (events.js:92:17)
at emitReadable_ (_stream_readable.js:427:10)
at emitReadable (_stream_readable.js:423:5)
at readableAddChunk (_stream_readable.js:166:9)
at Socket.Readable.push (_stream_readable.js:128:10)
at TCP.onread (net.js:529:21)


原因

RedisToGoのサーバー側のmaxmemoryを超えた大きさのデータを書き込みにいっているのが悪いらしい。
nanoプランを使っているんだけど、その時の使用量はちょうど31%(約1.5MB)になっていた。

研究室のwikiの更新差分を通知するためにページの本文を比較的たくさん保存していて、そのせいでだんだん容量増えていたんだけどそれでもまだたった1.5MBしか保存してない。それでも書き込みエラーがでまくった。

実装を読んでみると、hubotのredis-brainはRedisの1つのキーに全データをシリアライズして保存している事に気づいた。そのせいで1回の通信でRedisと全データのやりとりが行われて、maxmemoryを超えるらしい。

最初、Redisじゃなくmongoにでも乗り替えるか・・Heroku+mongolab無料枠(500MBぐらい)も何度か使ったことあるし・・・
と思ってたんだけど、ちょっと気になってredis-brainの実装読んだら1キーに全部保存してる事に気づいた。
そしてmongo-brainもmongolab-brainも、1つのdocumentに全データ保存してた。何故そうする・・


解決方法


最初からついてるredis-brainを捨てて、hubot-brain-redis-hashに乗り換えた。

hubot-brain-redis-hashはちゃんとRedisをKVSとして使ってて、更新のあったキーだけ書き込むとかやってくれてるように見える。
とりあえずちゃんと動いてる。


redis-brainのデータ消す


移行する必要もなかったのでとりあえず全部消した

'use strict'

Redis = require 'redis'
Url = require 'url'

info = Url.parse process.env.REDISTOGO_URL
client = Redis.createClient(info.port, info.hostname)
prefix = info.path?.replace('/', '') or 'hubot'

if info.auth
client.auth info.auth.split(":")[1]

client.on 'error', (err) ->
console.error err

client.on 'connect', ->
console.log 'connect!'

client.get "#{prefix}:storage", (err, reply) ->
return console.error err if err

if reply
data = JSON.parse reply.toString()
console.log Object.keys data._private

client.del "#{prefix}:storage", (err) ->
return console.error err if err
console.log 'deleted'