TwiCal開発日記
はじめに
私は今個人でTwiCal https://twical.herokuapp.com/ というWebアプリを作成しています。自分のツイートをカレンダー形式で閲覧できるアプリです。
環境は、Rails 4.2.7.1 / Ruby 2.2.2です。
作ろうとしたきっかけは単純なもので、自分が1日に何ツイートしているのか気軽にわかればいいな、と思ったことです。そうして、2016年の9月から2017年の5月頃までかけて開発し、リリースするに至りました。
作ってリリースしてみたはいいものの、メンテナンスに関する知識はありませんでした。私にとってこれが初めての自作Webサービスのリリースだったので、当たり前と言えば当たり前です。しかし、herokuからはエラーメッセージがきます。けれども対処法がわからなかったため、正直しばらく放置をしていました。
しかしながら、最近になってようやくまたこのWebアプリをちゃんと面倒を見ようという気になりました。どうしてそう思ったのかはたまたまです。風が吹いたからかな。そして、まずherokuの管理画面を覗きに行きました。
すると、無料で使えるレコード数である10,000レコードが満杯になっていました。
原因を考えてみると単純で、このTwiCalは1ツイートに1レコード使うデータ設計になっています。多い人はひと月に何百ツイートもするわけですから、これでは全然足りるわけがありません。なので、私に突きつけられた選択肢は以下になりました。
- 保存されたツイートはキャッシュとしての意味合いしか持たないので、手動で定期的にDBから削除する
- 省レコード設計になるようにアプリを作り直す
なので、とりあえずの対処療法として1を実行し、恒久的な対策として2を取ることにしました。加えて、最後にアクセスされてから時間が経ったツイートは自動で削除する、という機能も盛り込むことにしました。
この開発日記は、これらの改造を実装していく過程を記録するものです。
2018 / 04 / 23
まずはrework_dbという新しいブランチを切りました。
そして環境をある程度更新するところから始めます。GitHubには、このコードには脆弱性があるという旨の警告をいただいていたので、それを解消するところからです。
最初はGemを更新しました。そうしてherokuにデプロイすると、本番環境でのみアプリが動かなくなりました。原因を探っていくと、PostgreSQLあたりで何か起こっているようです。
調べてみたところ、現在のRailsはPostgeSQL 1.0.0をサポートしていないのに、Gemを更新したことによってPostgreSQL 1.0.0を使うような指定になってしまったため、それを使う本番環境のみ影響が出たようでした。なので、PostgreSQLのバージョン指定をダウングレードしました。
この辺りの顛末は、 https://qiita.com/rotelstift/items/d3ce70f912f81863f29a にメモを残しておきました。
しかし今度は、rakeがうまく動きません。
これを調べるとrake12以上とRspec3.4.4未満を組み合わせるとエラーが出る模様。なのでRspecをバージョンアップして、無事動くようになりました。
これらの作業はmasterブランチとrework_dbブランチとで行っていたのですが、両者を揃えたところから作業を行うべきだと思ったので、最後にmasterブランチをrework_dbブランチにマージしました。
ここに書いたこと以外でも、先走って新しいDBを作ってしまったりしていたのでだいぶ混乱してしまいましたが、この日はここまで。
2018 / 04 / 24
省レコード設計にするため、ひとりのひと月のツイートを1レコードに持たせることにしました。そして、最後にアクセスされてから時間が経ったデータを削除するために、last_accessed_atというカラムも追加することにしました。
なので、モデル名はTweetsByMonthにすることにしましたが、これを書いている4/25現在すでに後悔しています……。ツイートが入ったDBはこれ以外に作らないので、TweetDbにした方がフラットに扱える感じがします。カラムにtweets_by_monthを作ってしまったことも致命的です。
一応今のところ、このDBは以下のようなカラム設計になっています。
create_table "tweets_by_months", force: :cascade do |t|
t.datetime "tweeted_month", null: false
t.datetime "last_accessed_at", null: false
t.integer "user_id", null: false
t.binary "tweets_by_month"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
このbinaryになるtweets_by_monthにはツイートのデータを二重配列にして入れる予定です。
tweets_by_month[day][n] = {tweet_id, tweet_url, tweeted_at}
このdayは1〜last_of_month.dayを添え字に使い、tweetes_by_month[0]は欠番にします。
ツイートをAPIから取ってきて、このtweetes_by_month[day][n]にしまい、さらにそれをデータベースに保存するところまで書いて、あとは表示部分での関数呼び出しのところの関数名を新しいものに揃えたところまでやり、とりあえず動かしてみた。
そうすると、当然エラーメッセージが出て動かないのだけれど、それらを一つずつ潰していき、そしてbinaryにtweets_by_month[day][n]が入らないよ、と言われたところでこの日は終了です。
たぶんtweets_by_month[day][n]を配列からバイナリ値に変換しなければいけないんだろうなぁ、とはこの時点であたりをつけています。
2018 / 04 / 25
配列をバイナリ値に変換しなければ問題でしたが、まずArray.packを調べていると、横から見ていた夫(エンジニアです)がアドバイスをくれました。
その用途だと.packよりもMarshalを使った方がいい、と。
というわけでまずMarshalを導入して、その他のエラーメッセージをビシバシ片付け、とりあえずトップページだけは表示されるように作り直しました。
そして、昨日ダメだと言っていたTweetsByMonthというモデル名ですが、非常にわかりづらいのでTweetDbというシンプルなものにリネームしました。
とりあえずこの日はここまで。
2018 / 04 / 26
この日はコードは書かず、今後の設計の課題の洗い出しなどをしていました。
今までの1ツイート1レコードのデータ構造は、無駄は多いものの様々な物事がシンプルに終わるものでした。しかし、ひと月を1レコードにしたからにはもう少し話は複雑になるのだということが見えてきました。
例えば、last_accessed_atが2018/03/20 20:15:44で、2018/03/15にしかツイートが存在しない2018/03のレコードに、2018/04/26 14:56:23に2018/03/10を表示してくれという命令でアクセスをした場合、いったいどのようにすればいいのか?などということを考えていました。
- どのようにレコード呼び出しをするべきか?
- last accessed at以降の3月分のツイートがあるかないかはどう確認すべきか?
- もしlast accessed at以降2018/03/31 23:59:59までに新しいツイートがあったらどうレコードに追加すべきか?
などが課題です。
明日以降はこれらの課題を手を動かして解決していきたいです。
2018 / 04 / 27
WIP