某飲食店予約システムのインフラ構成を予想したい。

インフラのイの字もわからないのにインフラ系のことをせざるを得ない状況になっており、勉強のために某飲食店予約システムのインフラ構成を予想しようと思いました。ピンクくて丸いやつ向けです。

注意

記事の内容は、野球の試合をテレビで見ながらイチャモンをつけるおっさんくらい薄く、知識量に比例して正確性は皆無です。インフラをわかってない人が頑張るとこういう予想にたどり着くんだね、という参考にはなるかもしれません。

参考にしたページ

とりあえず情報を集めます。

トレタのインフラ運用、支えている道具(Packer, Terraform, Serverspec, Ansible, Roadworker, Circle CI)、考え方 – トレタ開発者ブログ
トレタのインフラ運用 – Speaker Deck
トレタのMySQL MySQL casual #8
記事が結構古いからだいぶ変わってそうですね。どっかに答え載ってるんじゃないかと思って開発者ブログを見てみたけど最近の記事はなかった…。SlideShareとかにはあるのかもしれないですね。

うっす~いよそう

実際に使ってみてのエラーの返り方やレスポンスの感じだと以下のような構成でしょうか…?

  • サーバ:Amazon EC2
  • ロードバランサ:ALB?NLB?
  • バックエンド:Ruby on Rails
  • DB:Amazon RDS for MySQL
  • 静的コンテンツ(json等):CloudFront
  • 静的コンテンツ(画像等):Amazon S3

default backend – 404(多分Nginx Ingress Controllerのデフォルトバックエンドのレスポンス)が時たま返ってきたことから予想すると、おそらくRoRはKubernetesのPodで動いてるかもしれません。AWSを使ってるなら、GKEを採用しているのかなぁと予想しました。(リバースプロキシもIngressを使用)

実際の挙動からのうっす~いよそう

予約用の内部APIは予約対象の店舗のIDごとに処理する仮想サーバが決まっているわけではないようで、一店舗に予約処理が集中すると他の店舗のリクエストも応答しなくなるという挙動でした。(実際、先月はそれで巻き添え食らったところが落ちていた)
EC2使ってるならスケールアウトするか、予行演習で前もってやばいのはわかってたので、ピンクいやつだけL7ロードバランサでRDSもRoRも専用のインスタンスに処理させるとかいくらでも手は打てたと思うのですが、どうしてこうしなかったのかはよくわかりません。

以下を読んだのですが、ELB自体は5万RPSかけても受けきれるっぽいのと、API叩いたときに502 Bad Gateway返ってきてたので、Ingress Controllerまでは問題なくて、バックがボトルネックになっているのかなぁと思いました。(小学生並みの感想)
[社内勉強会]ELBとALBと数万スパイク負荷テスト

感想

やっぱりよくわからない。

Metabaseを使ってポケモンGOで孵化したタマゴの情報を可視化する。

旦那さんとポケモンGOをプレイしているのですが、タマゴから孵るポケモンに偏りがあるかを知りたかったので、記録を取るようにしています。
記録の方法は以下の通り。

  1. タマゴが孵化したらLINEグループ宛に報告する
  2. 報告をLINE botが拾ってDB(SQLite3)に書き込む

LINE botと会話して統計を取れるようにはなっているのですが、テキストベースだと情報がデジタルでいまいちピンとこないため、Metabaseで情報を可視化してみることにしました。最初Grafanaが使いたかったのですが、SQLiteに対応していないので自動的にMetabase一択になりました。

動かし方

インストールは以下の記事を参考にしました。
OSSのデータ可視化ツール「Metabase」が超使いやすい
Dockerが多分一番楽に立ち上げられる気がします。私の環境はいろいろあってDockerが動かせないので、jarをそのままOpenJDK 8で動かす方法をとりました。使用したMetabaseのバージョンは0.30.1です。デフォルトだと3000番がMetabaseのポートになります。
セットアップはほんとに楽すぎて何も困ることなかったので割愛。

可視化したいDBの設定

DBの追加画面

ここから解析対象のDBまでのパスを入力すればすぐ解析してくれます。

DBの可視化結果とか

DBの可視化結果
ポケモンの円グラフとかホエルコのグラフとか

お互いが孵したポケモンの円グラフを作ってみました。本当はグラフの横に凡例と値が出力できるのですが、GUIのバグで凡例が多すぎるとグラフが表示できなくなるため、非表示にしています。カーソルを当てると内容がわかるのですが、不便なので次バージョンに期待します。

DBのフィルタリング結果

フィルター機能で期間やタマゴの距離を絞り込むこともできます。めちゃくちゃ楽です。

使ってみた感想

私がQuestionをあまり上手に使いこなせていないので、今の所Kibanaのように息を吸って吐くレベルのグラフ作成には到達できていません。が、最悪SQL文を書いてもグラフは作れるので、可視化するには困らなさそうです。ただ動かすまでの手軽さは、ElasticSearchとFluentdのハードルが高い分、圧倒的にMetabaseのほうが勝ります。java jarコマンド打つだけだったので本当に手軽でした。アクセスログを解析するぐらいであれば、Metabaseで十分用が足りると思います。GUI周りのバグについては今後の修正に期待したいと思います。

あと、旦那さんが使っているブラウザがモダンブラウザではないので、表示がバグったり動作しないスクリプトがあったりするのが悩みどころです。他人とダッシュボードを共有するときは、モダンブラウザの使用ルール化することをおすすめします。

Metabase v0.30.1 でSQLite3のテーブルのFILTERED BYが動作しない

あらまし

Metabase v0.30.1でSQLite3のテーブルを読み込んだのですが、DB追加時にMetabaseが実施してくれるExploreが500を返し続けて永遠に終わらなかったり、FILTERED BYを使おうとしてもfieldの候補が表示されないので原因を調べました。Sample datasetだと問題ないのでインストール時の問題ではなさそうです。

FILTERED BYが選択できない

FILTERED BYが選択できない

調査

logに以下の警告が出ています。Settingsからログが見られるのはありがたいですね。

Aug 26 12:50:00 WARN metabase.driver.generic-sql :: Don't know how to map column type '' to a Field base_type, falling back to :type/*.
(中略)
Aug 26 07:50:04 ERROR metabase.sync.util :: Error running sync step: class java.lang.ClassCastException
[]

テーブル作成時にカラムのデータ型を指定しなかったので、typeが空文字で返ってきてしまって、どの型にキャストしていいのかわからずにエラーを起こしているみたいですね。DBの仕様に合わせてその辺りよろしくやってくれると嬉しいのですが。

対処

カラムの型を定義したテーブルを作り直してMetabaseに登録し直します。SQLiteは途中でカラムの定義が変えられないようなので、create tableし直したあとにinsert intoで元のテーブルの内容をそっくりそのまま挿入する方法でどうにかしました。

結果

無事Exploreが動作してFILTERED BYも選択できるようになりました。SQLiteの時刻表記の文字列が時刻として扱われるのか不安でしたが、きちんと時刻として認識されました。よかったです。

悟空のきもちの予約フォームには致命的ではないが色々問題がある。

悟空のきもちというマッサージ屋さんが全然予約取れないらしいのと、予約フォームのソース見たらかなりひどい簡単に予約自動化できそうだったので、スクリプトを組んで予約してみました。
…のですが、「システム不具合で予約が取れなかった」とお店から連絡がきました。なぜ不具合が起こったか調査してるそうなので(本当に調査してるのかは別として)、私も調べてみることにしました。
ソースコードを見ればすぐわかるような、スクリプトキディレベルのことしか書いてないです。
詳細書きすぎると問題になりそうなのであえて書いてませんが、普通にHTMLとPOSTリクエストの組み方がわかる人なら10分ソースを眺めればわかることばかりです。

詳細

予約フォームは以下のURLですが、
https://goku-nokimochi.com/reservation.html
実際の予約本体は以下のURLを使用しています。
https://form.goku-nokimochi.com/form/reservation/index3.php?shop_id=

恐らくこちらの製品を使用しているようです。
メールフォームプロCGI/UTF-8対応・クレジット決済機能対応 | 無料素材

予約が空いているかどうかは、店舗名とコースを選択した時点でGETリクエストにより問い合わせが行われ、selectタグに反映されます。
例えば、銀座店で60分コースを選択すると以下にリクエストが飛び、JSON形式でレスポンスが返ってきますね。
https://form.goku-nokimochi.com/form/getReservation4.php?shop_id=4&course_id=157

index3.phpが出力しているHTMLのsetUpTimeメソッドを見ると、恐らく配列内には時刻が4ケタの数字で格納されているだろうことがわかります。(実際そうでした。)

value.substr(0,2) + "時"+ value.substr(2,2) + "分"...

フォームにはトークン等も設定されていないので、getReservation4.phpに定期的に問い合わせを送り、自分の都合の良い日時の空きがあれば、予約用のリクエストボディを組み立てて../sendmail2.php宛にcurlかなにかでPOSTしてしまえば良いことがわかります。

../sendmail2.php 宛にPOSTしても、実際にはまだ予約が完了にならず、customer_idが発行されるだけになります。どうやらsendmail2.php宛にリクエスト投げた時点で予約できてしまうようです。
customer_idが発行されると、以下のURLにジャンプします。
https://form.goku-nokimochi.com/form/reservation/sheet.php?customer_id=

sheet.phpのフォーム

トークン等は存在しないので、予約用のリクエストボディを組み立てて ../sendsheet.php にPOSTしてしまえば予約が取れてしまいますね。

このシステムで微妙な部分

予約が埋まっている時間帯を指定してリクエストを組み立ててもエラーが返ってきてしまいますが、エラーが返ってこなくなるまでリクエストを投げ続ければ、キャンセルで空きが出た瞬間に予約が取れますね。簡単に予約代行できそうです。
このマッサージ屋さんに限ったことではなく、似たような予約システムを抱えているところには同じ問題がいえますね。

このシステムで問題のある箇所

index3.php(予約フォームの本体)で、好きな日付を入力した上で時刻指定(r_time)の値を指定しなかった場合、customer_idが発行できてしまいます。
何かの拍子でr_timeが設定されていないのにリクエストが飛んだ場合、そのまま予約が(時刻指定なしのまま)とれてしまいますね。
私はコレにひっかかったのかも。
あと、コースIDが予測できるものなので、隠しコースとか予約できてしまいますね。実際予約できたんでこのままでいいや…。お金払うし…。

フロントのソースコードが中学生が作った掲示板レベルなので、調べたらもっといっぱい穴がありそうでオラワクワクしてきたぞ(悟空のきもちだけに)。
バックエンドもきっとヤバそうな気がします。

【未解決→解決】WPtouch Mobile Pluginが適用されたモバイルページでContact Form 7が動作しない。

未解決です。このサーバでしか起こらない問題かどうかすらもわかってません…。
Contact Form 7のv4.8とWPtouch Mobile Pluginのv4.3.18を導入している環境で、かつモバイルページからContact Form 7を使用して問い合わせを送信した場合、メールが送信されません。
WordPressのバージョンは4.8です。

最初nginxのキャッシュフラグの設定がおかしいかと思ったのですが、curlコマンドでPOSTした場合は正しく問い合わせが送信できるためWPtouchとの組み合わせが悪いと判断しました。(curlでUAをモバイル系にすると送信されないが、適当な文字列にすると送信できる)

php-fpmのログにもnginxのログにも何も出てないし、POSTを送信すると200OKしか返ってこないのでデバッグが面倒です。いま適用しているテーマはありがたいことにレスポンシブデザインなので、とりあえずWPtouchを無効にしました。

この現象がいつから起こっていたか把握できないため、過去スマホ・タブレット経由でcontactからお問い合わせを送信された方につきましては、お手数ですが再度送信をお願いいたします。すみません…。

追記(2017/08/08)

普通にContact Form 7用のjsがWPtouch用のテンプレートで読み込まれていないからでした。仕方ないので、footerに以下のコードを挿入して解消しました。
js無いと動かないんですね。そりゃそうか。

<script type='text/javascript'>
/* <![CDATA[ */
var wpcf7 = {"apiSettings":{"root":"https:\/\/retrorocket.biz\/wp-json\/contact-form-7\/v1","namespace":"contact-form-7\/v1"},"recaptcha":{"messages":{"empty":"\u3042\u306a\u305f\u304c\u30ed\u30dc\u30c3\u30c8\u3067\u306f\u306a\u3044\u3053\u3068\u3092\u8a3c\u660e\u3057\u3066\u304f\u3060\u3055\u3044\u3002"}}};
/* ]]> */
</script>
<script type='text/javascript' src='https://retrorocket.biz/wp-content/plugins/contact-form-7/includes/js/scripts.js?ver=4.8.1'></script>