erlangのチャンネルプロセスでのTTL

ARASUKAのチャットシステムはerlangで0から自作しています。(msgdという名前にしてます)

ejabberdを使った方が開発面でも運用面でもコストが安そうではあるのですが、erlang大好きなので作っちゃいました。しょうがない。

erlang大好きとはいえ熟知しているわけではなく、「プログラミング言語Erlang」を買ってナナメ読みして必要最低限の知識でシステムを組んでいるだけなので、erlang関数型言語、あるいは並列処理に関して大部分の定石を無視した設計になっていると思います。

その一つが、プロセスのTTL処理。

msgdはクライアントによりチャンネル作成要求が発生すると、チャンネルプロセスがspawnされます。チャンネルプロセスには、チャンネルの管理を任せます。チャンネル内で行き交うメッセージの管理、クライアントの入出管理、過去ログをバッファリングしデータベースに定期的に書き込みを行う処理、などなど。

このチャンネルプロセス、無限に増えないように以下の条件を全て満たしたとき消滅するようにします。

  1. チャンネル内に誰もいなくなった。
  2. データベースに書き込むべきバッファデータをflushしていて、バッファが空。
  3. 1.2.を満たした状態(何もする仕事がない状態)で、3日経過した。

3.がなぜ必要かというと。msgdでは新規にチャンネルに入ってくるユーザーに対し、過去ログ(主にメッセージログ)を提供しています。この機能より、新規ユーザーで入ったとしてもある程度空気が読めるようになります。この過去ログは通常、チャンネルプロセスがオンメモリで格納しているバッファから吐き出されます。高速です。
もし3.の条件を含ませないとすると、誰もいないチャンネルにアクセスするたび、過去ログを取得するためにデータベースへ問い合わせを行わなくてはならなくなります。3日ほどオンメモリに待機してさせておけば、頻繁にアクセスされるチャンネルの、データベースに対する参照コストを抑えることができます。3日とは言わず、数時間でも良いかも知れません。

で、やっと本題。

どうやって3日経過したら自動消滅するようなプロセスを作るか。

  1. timer:send_after/2でtimeoutアトムなどを送る。
  2. receiveにafterつけて、定期的にtimer:now_diff/2を観測する。

これだけで済むはずなんですが、どうも関数型言語で「タイマー」「時刻をポーリング」を使うのはあんまり好きくありません。いや、どんな言語でもタイマー関係が好きじゃないだけなのかも知れませんが、、、

なので、タイマーを使わずに、言語仕様だけでTTLを実現してみました。

channel_process(0) -> void;
channel_process(TTL) ->
  receive
    ...
    channel_process(60 * 60 * 24 * 3)
  after 1000
    ...
    channel_process(TTL - 1)
  end.

のような。
大部分省いてますが。

・・・どうなんでしょう。「60 * 60 * 24 * 3」は3日間待機、の意味です。

TTL実装のためにafter、しかも1000(1秒)を使っているのは、無用の混乱を招きそうです。

変数TTLの意味を変えないのならば、afterは必ず1000でなければなりません。うっかり「これ意味なく回ってるから10000でも良いだろ」なんて思って値を変更したら、チャンネルプロセスが30日間生存してしまいます。

なんかこう(自分に)しっくりくるようなTTL処理はないだろうか〜。