弱小Railsアプリケーションで1日毎のお手軽な集計方法のご提案
こちらのアンサーブログだと思ってもらえれば。
結論
結論からいうと、集計をするためのModelを作っておいてそこから集計を行う方式にして、 色々なModelの集計に対して対応できるようにした。
create_table "aggregaters", force: :cascade do |t| t.date "aggregate_date", null: false t.string "aggregate_type", limit: 255 t.float "aggregate_value", null: false end
ちょっと筋悪な感も否めないが、なにもしないよりは良いだろうと思っている。 タスクにして実行しないといけないのは当然なんだけど、そもそも弱小アプリケーションなんだから問題ないだろうと思ってる。
簡単な利用方法
バッチで結果を格納する側 (RailsのRakeタスク)
require 'active_record' require 'active_support' namespace :aggregate_record do desc 'コメント数の集計を行って、集計レコードを作成します。' task create_daily_comment: :environment do Aggregater.create(aggregate_date: Date.now, aggregate_type: 'comment_count', aggregate_value: Comment.size) end end
呼び出し方
# なんかここはうまいことやるのは解るけど、あくまで簡単なサンプルで。 daily_comment_count_obj = Aggregater.find_by(aggregate_date: Date.yesterday, aggregate_type: 'comment_count') daily_comment_count = daily_comment_count_obj.aggregate_value || 0
今後
弱小な間はしばらくはこの方式でなんとかなりそう。 今後もTresureDataやBigQuery等を利用した集計を行う為の外部サービスのクエリ結果をDBにキャッシュする(格納してRailsが取り出しやすい用に)という用途でも利用できそうなので、便利な感じはする。 ただ筋悪なのは否めないので、もっといい方法があれば教えていただきたい所存です。
Dockerで1つのイメージ複数のコマンドを切り替える方法
これをやりたい理由
作るDockeイメージは1つにしたいのだけど、デプロイする先によってコンテナの役割(例えばCronやJobワーカーを動かすコンテナとWebサーバーを実際に動かすコンテナ等)を分けたいときに利用する。
例えばAzureのWebApps for Container (Web Apps on Linux)で、 Webサーバーはスケールアウトしたいけど、Jobワーカーサーバーはスケールアウトしたくない(もしくはJobワーカーサーバーは任意のタイミングにしたい)とか 管理するコンテナを分けたいとかそういうときに利用すれば便利だと思っている。
やりかた
init.shという起動時の環境変数によって実行するコマンドを変更するスクリプトをCMDで実行してあげればOK。 これで実行時に環境変数を渡してあげればOK。
下記例はRubyアプリケーションを動かす際の自分のDockerfileの雛形のようなものである。
# Dockerfile --- FROM ruby:2.5 ENV LANG C.UTF-8 ENV RACK_ENV production EXPOSE 3000 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY Gemfile /usr/src/app/ COPY Gemfile.lock /usr/src/app/ RUN bundle install -j4 --without development test RUN mkdir tmp COPY . /usr/src/app RUN chmod u+x init.sh CMD "./init.sh"
# init.sh --- #!/bin/bash set -e if [[ "${RUN_WEBAPP}" != "" ]] then echo "Starting uniron ..." bundle exec unicorn -c config/unicorn.rb else [[ "${RUN_WEBJOB}" != "" ]] echo "Starting sidekiq ..." bundle exec sidekiq -r ./app.rb -C ./sidekiq.yml fi
以上の内容でDockerイメージをBuildして、docker runするときに環境変数を渡せばOK。
サンプルでWebサーバーを実行する場合
docker run -e RUN_WEBAPP=true xxx
サンプルでJobワーカーサーバーを実行する場合
docker run -e RUN_WEBJOB=true xxx
おわりに
ちなみにAWSのElasticBeanstalkあたりだとマルチコンテナーをそもそもサポートしているので、そっちを使うほうがまぁ普通だと思っている。 もしくはCronを利用しないで、SidekiqやDelatedJobあたりでワーカーがいくらスケールアウトしても問題ないようにするとか、 WebサーバーとWorkerの同居を許容してしまうとか、そういう方法で解決したほうが至極真っ当な感じはする。
余談
AzureからAWSのDynamoDBって利用すると遅いのかな。 ぶっちゃけ用途があえばCosmosDBでもよかったんだけど、メッセージングアプリっぽいの作りたくて、 MongoベースのAPIでまともに要件を満たせるのか感があって、不安...
URLのOGP情報をスクレイピングするだけのWebAPIを作った
サイトのOGP情報をスクレイプするのが面倒なので、OGP情報のスクレイピングし、JSONに変換して情報を返すだけのマイクロサービスを作った。 マイクロサービスというか、用途的にはDockerが向いていると思ったので、Dockerイメージにもしてあります。
- docker hub: https://hub.docker.com/r/webuilder240/ogp_parse_api/
- github: https://github.com/webuilder240/ogp_parse_api
- WebAPI (Herokuで雑に運用してる): https://powerful-sierra-79664.herokuapp.com
- テストで作っているものなので、落ちても、突然なくなっても怒らないでね。
WebAPIから利用する
$ curl https://powerful-sierra-79664.herokuapp.com/?url=http://ogp.me | jq { title: "Open Graph protocol", description: "The Open Graph protocol enables any web page to become a rich object in a social graph.", image: "http://ogp.me/logo.png", url: "http://ogp.me" }
リポジトリにあるDockerComposeから利用する
同梱されているDockerComposeを利用して簡単に使い始めることも可能です。
$ docker-compose up $ curl http://localhost:8080?url=http://ogp.me | jq { title: "Open Graph protocol", description: "The Open Graph protocol enables any web page to become a rich object in a social graph.", image: "http://ogp.me/logo.png", url: "http://ogp.me" }
Dockerイメージから利用する場合(Redisが必要です)
$ docker pull webuilder240/ogp_parse_api $ docker run -p 8080:8080 webuilder240/ogp_parse_api
注意事項
- Productionで利用する場合、サーバーサイドの前段でキャッシュを取ったりはしていないので、このMicroServiceの前にCDNを利用することを推奨しています。
- バックエンドにRedisを使っているので、Redisを事前に用意してください。*1
その他の詳しい情報についてはGithubあたりに置いているREADMEをご参照頂ければと。
なんか特徴があるとすれば
- Sinatra使ってる
- 本体のコードは100行未満で、めっちゃ短い
- ただ他のライブラリに依存していないとは言っていない。
- バックエンドにRedisを利用している
- OGPなんて消えてしまっても別に困らない。再度取得すればOK
- キャッシュしたデータがもりもりになっても困るし、7日くらいで勝手に削除されるようにしてる。*2
- 前段にNginxを噛ませてない
- なるべくインストールするものを減らしたかったというのと、前段にCDNを置いて運用する前提で作っていたのでそもそも不要と思ったからでした。
作った背景
- マイクロサービスの題材として最適だった。そのなかで色々な案件で使えそうな便利なものをと考えたらコレになった。
- 機能単位でWebAPIとして切り出していくメリットはなんとなく掴めた。
- サーバーサイドで利用するだけだったらGemだけでも良いけど、フロントエンドやRuby以外のサーバーサイドでも利用するのではないかと思うと、JSONでやり取りした方が良いのでは思ったから。
- あとはリンクを解析して、色々な情報を解析して返してくれるSaasもあるけどなんかべらぼうに髙いように感じたのと、やけに高機能だったのでシンプルにして実装し直した。 というのが作ってみたモチベーション。
コレであれば使われなくても捨てれるサイズだし、何かしらアウトプットしとくか位の気持ちでやっている。*3
今後の展望
self_updated_atがメソッド指定に対応しました
先日紹介した、拙作のself_updated_atなのですが、 この度v0.2.0へアップデートしたので、こちらの更新情報をお伝えします。
self_updated_atがどういうGemかよくわからんという人はこの記事を読んでみてください。 webuilder240.hatenablog.com
下記は更新内容について記載しました。
パラメータにmethodsを追加しました。
- methodsはcolumnsよりも後にチェックされるもので、指定したもののどれかが真を返す場合、指定したカラムの日付を更新します。
- methodsで指定されたものすべての結果が偽の場合は指定カラムの日付更新を行いません。
methodsのSyntaxについて
class Post self_updated_at, :service_updated_at, methods: [:true_method] private def true_method true end end
上記コード例の場合、 「true_method」が真だった場合に「service_updated_at」カラムの更新日が更新されます。
もちろん、methodsとcolumnsを組み合わせても利用できます。
class Post self_updated_at, :service_updated_at, columns: [:title], methods: [:true_method] private def true_method true end end
上記コード例の場合、 「title」カラムが更新された場合もしくは、「true_method」が真だった場合に「service_updated_at」カラムの更新日が更新されます。 式の評価は、columnsを優先して行います。
その他の細かい修正
- self_updated_atの指定カラムに作成時に日時を設定した場合、その日時を優先して反映するようにしました。作成時に指定カラムがnilだった場合は現在日時を設定します。
ActiveRecordのVersionが5.1以上の場合に発生するDEPRECATION WARNINGへの対応
元々、xxxx_charged?メソッドを利用してModelの変更を検知していたのですが、ActiveRecordが5.1以降の場合、 近い将来廃止予定らしく、DEPRECATION WARNINGが発生したので移行予定のメソッドを利用するように修正を行いました。
今後について
機能面はもうこれで自分のケースは満たしていると思ってて、あんまりProcとかLambdaをサポートする意欲はないかも。 Specをある程度リファクタリングして、READMEを英語で書いた段階で、v1.0.0をリリースしちゃうかも。
特定のカラムの更新日を管理するためのGemを作りました
self_updated_atという、ActiveRecordで指定したカラムに対して更新が発生したときに更新日時を書き換えるgemを作りました。
利用方法
ActiveRecordのModelに以下のように設定します。 下記のように設定すると、titleとbodyの内容に変更があったときのみ、service_updated_atの時間が更新されるというような具合です。
class Post < ActiveRecord::Base self_updated_at :service_updated_at, columns: [:title, :body] end
Gemを作った背景
業務やビジネス要件によって振る舞いを変更する「更新日」処理を簡単にするため
業務やビジネス要件によって振る舞いを変更したい「更新日」に対してActiveRecordが利用する updated_atは利用するべきではないと考えています。 なぜなら、「特定のカラムを更新したときにupdated_atは更新しない」を実現するのが現実的ではないと思っているからです。
例えば、updated_atを更新しないで特定のカラムを更新する方法として、update_columnというメソッドがActiveRecordにもありますが、確実に漏れると思っています。 誰かが運悪くupdateと書いてデータが更新されてしまった瞬間にupdated_atは更新されてしまい、今まで苦労は水泡に帰してしまいます。
そうならないために別のカラムを用意して、それを「サービスとしての更新日」として実装・利用するほうが良い思ったからです。*1
ただ、なんかスマートじゃないしカラム名だけ指定すればOKみたいに作れれば良いなぁ...というのが大きなモチベーションです。
どうしてupdated_atを直接改変しないのか
フレームワークで自動で更新されることがレールで敷かれており、業務やビジネス要件によって振る舞いを変更したい「サービスとしての更新日」に対して利用するべきではないと考えています。 それがフレームワークのデフォルトの振る舞いなので、そこを変に改変しないほうが良いと思っているからです。*2
TODO
ひとまずv0.0.1として出したけど、簡単なGemと言いつつもテストをまだ書けていない。さっさとテストを書く。このブログを書いてて思ったのは、hooks_columnsという記法が微妙なので、columnsへの変更。- シンプルはシンプルだけど、READMEもちゃんと書く
- 今はModelのカラムが更新されているかという条件しか指定出来ないので、さらにメソッド名かLambdaを渡せるオプションがあればうれしいですね
- この当たりができればv1.0.0としてシュッとリリースしたい。
最後に
このGemを使うか使わないかは置いておいて、updated_atを「ビジネス・仕様上の更新日」として利用する場合は、ActiveRecordが更新するupdated_atとは別のカラムで管理したほうが幸せになれる。ということを強く言いたい。
あと、もし類似するライブラリがあったら教えてください... 簡単に探してみたけどなさそうだったのでサクッと作ってしまったので...