SQSで処理を非同期化したらストレスフリーになった

f:id:cocoeyes02:20180725091923j:plain

こんにちは。サグーワークス開発チームに所属している大津です。

サグーワークス開発で Amazon Simple Queue Service (以下 SQS と呼ぶ)をサービスに導入したので、SQS の紹介と実際に導入した例を紹介したいと思います!

今回は他のメッセージキューイングサービスなどには触れず、あくまで SQS にテーマをしぼってお話ししていきます。

SQS とは?

SQS は AWS ( Amazon Web Services )が提供しているメッセージキューイングサービスです。

キューへメッセージを送信、あるいはキューからメッセージを受信・削除をすることができるシンプルなサービスになります。

キューを介してメッセージを送受信することで、異なるシステム同士でデータの送受信ができます。

メッセージキューイングサービスのメリット

メッセージキューイングサービスの最大のメリットは、システム同士が同期していなくてもメッセージのやりとりができることにあります。

それぞれシステムが好きなタイミングでメッセージを送受信することができます。

SQSを導入 -導入背景-

CRM からサグーワークスへ自動で情報を登録したい

サグーワークスには運営者用の管理機能があります。

それとは別に、CRMでも情報を管理しています。

サグーワークス上でも CRM で管理している顧客情報が必要だったので、CRM からサグーワークスへ手動で顧客情報を登録していました。

以下がその手順と図になります。

f:id:cocoeyes02:20180904095301p:plain

  1. CRM から サグーワークス DB に保存したい顧客情報を確認する
  2. 管理画面から手動で顧客情報をサグーワークス DB に登録する

手動で顧客情報をサグーワークス DB に登録するため、この方法では手間がかかるという問題がありました。

そこで、自動化も含めて運営者用の管理機能をリニューアルしようという話になりました。

仕様変更の影響を減らしたい

自動で顧客情報をサグーワークス DB へ登録するにあたって1つ問題がありました。

CRM でデータの持ち方に関する仕様変更があったとき、サグーワークスのシステムでもあわせて変更する必要がありました。

どう解決するか悩みましたが、「ロジック」と「タイミング」2つのアプローチで仕様変更の影響を減らすことに決めました。

ロジックを分けて疎結合にする

CRM 周りのロジックは「 CRM から顧客情報を取得するロジック」と「取得した情報を保存するロジック」の2つに分けて疎結合にします。

もし CRM 側で仕様変更があっても、「 CRM から顧客情報を取得するロジック」だけ修正すれば対応することができます。

タイミングを分けて処理を非同期にする

CRM への顧客情報を取得する処理とそれ以外のサグーワークスの処理を非同期にします。

そうすることで、CRM への顧客情報を取得する処理でエラーがあっても、それ以外のサグーワークスの処理は実行されます。

これで、他の処理まで実行できなくなるという事態を避けることができます。

条件を満たしたい

「ロジックを分けて疎結合にする」「タイミングを分けて処理を非同期にする」を満たす方法を調べました。

すると SQS を利用すれば上記の条件を満たせることがわかり、SQS を利用することに決めました!

SQSを導入 -利用例-

サグーワークスの運営者用の管理機能を使って顧客情報を登録するとき、手動で CRM に顧客情報を確認しなくても良いように、顧客情報を取り込むバッチを作成しました。

以下がその手順と図になります。

f:id:cocoeyes02:20180904095035p:plain

  1. 管理画面から顧客を指定し、メッセージ(顧客 ID )を SQS に送る
  2. バッチが SQS からメッセージ(顧客 ID )を取得する
  3. バッチが顧客 ID を使って顧客情報を CRM から取得する
  4. バッチが顧客情報をサグーワークス DB に登録する
  5. サグーワークス DB に登録に成功した時は、バッチが登録成功した(あるいは既に登録されている)メッセージ(顧客 ID )を SQS から削除する

バッチと SQS を使うことで、CRM 関連の処理とそれ以外の処理を非同期化することができました。

終わりに

CRM など外部からの仕様変更の影響を減らしたい時には、SQS はとても有効です。

皆さんも是非、プロダクトに導入してみてはいかがでしょうか!

責任範囲の拡大!インターンの経験を活かした入社3ヶ月とこの先の目標

こんにちは。 2018年にウィルゲートにエンジニアとして入社した林です。 現在コンテンツマーケティング事業を支えるシステムの開発・運用保守を行っているチームに配属されています。

入社して早3か月が経ちました。 自分はウィルゲートに入社を決めた後、大学の夏季休業中約2ヶ月ほど現在の配属先と同じチームでインターンをしていました。 インターンの期間と入社後の期間が同じのため、今回のブログではインターン時代と入社後の業務内容を比較し、振り返ってみようと思います。

入社経緯

まず、自分は今回初のブログ投稿でもあるため、簡単に入社経緯を書こうと思います。

自分が在籍した大学の学類では、幅広い技術の基本を学ばせた後は、そこから先は個人の自由にやっていく風潮がありました。 そのため、大学時代で触れた言語だけでもjavaC言語OCamlR言語アセンブラ言語といった計10近くは触れていたと思います。 講義でも、機械学習や組み込み技術、CGとバックエンドの技術をメインに学びました。

学外での活動ではゲームを作成したり、Arduinoで遊んだりとwebとは全く関係ないことをしてきました。

大学時代に様々なことをやっていた自分ですが、大学入学前はゲームエンジニアを目指していました。 大学入学後、講義を通して様々な技術に触れていくうちに、将来の職としてゲームエンジニアと絞らずに、色んなことが経験でき、顧客との距離が近く、お互いに成長していけるような会社で働きたいと変化しました。 そのため、就活時にはゲーム会社のほかに、セキュリティ会社やweb会社といった幅広い分野の会社を見ました。 その際に会社を見る指針として設定したのが、 ・自社でメインに開発を行っている ・二つ以上の分野に手を伸ばしている といった2点でした。

そういう指針をもって就職活動をしている中で、最終的にウィルゲートに入社することを決めた大きな理由として

  1. 開発において全て自社内で行っているため、幅広い経験ができそう
  2. 社長研修や上長との1 on 1といった社員一人一人に向き合った制度があり、内定をもらった中で最も自分が楽しく、成長できる
  3. 兼任制度や新規事業の提案制度があり、自分がやりたいことを実現できる
  4. コンテンツマーケティング事業、メディア事業の2つが存在する

といった4つの理由があり入社を決めました。

インターン時代

業務内容

自分がウィルゲートでインターンを開始した時期は、チーム内で大きなシステムの社内リリースを控えていました。 そのため、インターンの最初からプロダクト開発メンバーの一人として参入し、業務はバグの除去を任されました。 最初は、システムを理解することもあり、リストアップされたバグを簡単なものから順に取り除くことに力を注ぎました。 途中からは自分でバグを見つけて、上長に確認し、それらを修正していくこともしていました。 バグの種類としては、簡単なものではデザイン修正・表示の出し分け、 少し大変だったものでは、画面遷移時に前の画面情報をもとにして初期表示の変更・自分で仕様変更による処理を考えて実装などがありました。 以上のようなバグを約50個、インターンの期間中に取り除くことができました。

その後は、簡単な実装を任せてもらえました。 その内の一つにVue.jsを用いてselect2を実装するというのがありました。 select2とはjQueryプラグインで、selectタグを使いやすく高機能にしたものです。 この実装では描画自体は順調でしたが、選択時のイベントが動作しないということがありました。 調べたり、先輩に話を聞いたりしてなんとか実装を完了しました。 現在でも、そのとき書いたコードが残ってたりするので毎回その部分を見ると少し心がほんわかしたりします。

以上のことを行って、自分は約2ヶ月間のインターンを終えました。

学んだこと

自分はウィルゲートでインターンを行うまで、webの開発もそうでしたが、チームでシステムの開発を行うことをやったことがありませんでした。 最初は、他のメンバーの進捗状況を考えずに、様々なファイルに修正を加えるプルリクエストを出してしまうことがありました。 また、コード規約に沿っていないといったことがあり、プルリクエストのコメントが何十件になったこともありました。 その際に、チームの人にプルリクエストの単位を小さくすること、読みやすいコーディングの必要性を教えていただきました。 教えてもらった後からはただ実装するだけでなく、どうしたら読みやすいのか、他の人が出したプルリクエストを見て参考にしたりと注意を行うようにしました。 そのおかげもあり、今まで目の前のことしか見えていなかったものが、少しずつ視野を広く持つようになり、自分が行う修正が他の人にどのような影響があるのかを見れるようになったと思います。

f:id:rinrin_wg:20180824185517j:plain

入社後

業務内容

インターンの時は、一つのシステムのバグ改修がメインでした。 入社後では、チームが持っているシステム全般の開発・運用保守をメインに行っています。

現在、自分のチームで扱っているシステムは全部で9個程あります。 自分はそれらすべてのシステムの運用保守を行いながら、どういうシステムか、どのような処理手順かといった理解を進めています。 最初は上長から直接エラーの対応を任されていました。 しかし、そのまま上長から直接作業を任される状態に慣れてしまうと、自分の成長も望めません。 更にチームへの貢献も多くできないとも思いました。 その考えが出てからは、エラーが出ていることに気づいたら、事前に原因がなんだったのかを調べて、上長に自分が対応する旨を伝えるようにしました。 最初は、エラーとは的外れの場所を調査することがありましたが、何度も対応していくうちにエラーの原因箇所をすぐに割り出せるようになっていきました。 また、その際に行ったことや手順などをドキュメントに残し、属人化とならないように注意しています。

開発に関しては、現在開発中のシステムへの機能の追加をメインに行っています。 この現在開発中のシステムというのが、社内の中でも今後「コンテンツマーケティング事業を支えるシステム」の根幹となるものでもあります。 基本的には機能追加に最長3日もあれば実装が完了するものを割り当てられています。 大学時代では個人で開発することを行っていたため、チームで開発するときの留意点や大変さを感じることができ楽しいです。 また、現在任されている主要システムの内一つに、他のチームも使い、かつ社内でも核となるシステムがあります。 チームに配属されたばかりの頃、このシステムの改修を任されました。 このシステムに不具合や修正があった場合は当然ながら社内全体に影響が出ます。 そのため、インターン時に任された業務と比較すると想像つかないほど緊張しながら開発・テストをしていました。 しかし、その分とてもやりがいもありましたし、テスト時に注意すべき観点といった得るものが多くあったと思います。

f:id:rinrin_wg:20180824185601j:plain

インターン時代との変化

正社員となり、インターン時代と異なって大変になったことが2点あります。

一つ目は責任範囲の拡大です。 インターン時代も自分が修正した部分に責任を持っていました。 その際は、大きなプロダクト開発の一メンバーとして、システムを早期に理解し、バグをたくさんつぶして活躍をしていました。 社員となったことで、扱うシステムの範囲も広がり、基幹システムの修正を任されたり、基本設計から携わったりと、他のメンバー、チームとも触れる業務が増えました。 まだ慣れていないことで見落としがあり指摘されることはありますが、任されているということが実感できています。

二つ目は外部の仕様変更による影響範囲の認識です。 入社してから機能の実装部分に携わることがありました。 入社して最初に任された最初の業務では、社内で基幹となっているシステムに機能を追加しました。 テストも行い無事リリースとなりましたが、その数週間後にエラーが大量に発生することがありました。 自分が任された部分でもあったため、自分がミスをしたと思い心臓がバクバクでした。 調査を通して何が原因だったのか追求しましたが、最終的にエラーの原因は外部APIの仕様が突然変更となったことでした。 しかし、この現象が起きたおかげで、その後の運用保守や実装の際に外部APIのことも考えながら作業を進めることができるようになりました。

まとめ

現在自分は、業務では重要なシステムの運用保守や開発に関わることが多いです。 そういう役割を任されているのも、インターン時代にインターン生という気持ちを捨て、自ら問題を見つけようとしたり、システムの理解をしようとしたため、信用を得るこができていたからだとも思っています。 また、インターン時代には、実装時の注意点や周囲のチームメンバーを考えるといったエンジニアとして注意する中で基本となる部分を身に着けることができました。 入社してからも、インターン時代に得た経験が更にブラッシュアップを行っていますが、まだ依然として先輩方から何かを得ている段階であります。 今後は誰かに自分がインターン時代や入社して得た経験を普及したり、取り組み方の手本となれるように頑張っていきたいと思います。

今後

自分のチームでは突発的に扱っているシステムに対して相談や依頼がくることがあります。 それらの窓口は現在上長となっており、その対応も上長が行っています。 この窓口と対応を行う流れを自分が担えるようにすることを第一の目標としています。 そのために、まずは現在稼働している社内システムを他人に説明できるぐらいに理解を進め、 運用保守や依頼があった際には自分が率先して行うようにしていかなければならないと思っています。

また、開発の部分では現在小さいタスクが割り振られています。 少しずつ大きなタスクが任されるように信頼を得て、基本設計や要件定義などの上流に関われるようになりたいと思っています。

短期的な目標としては、早く他のメンバーと同じぐらい社内システムを理解し、基本設計から関われるように今足りない力を手に入れていきたいと思っています。

長期的な目標としてはPMとなり、プロダクトを回していけるようになることを目標としています。そのために、エンジニアとしての技術力を身に着けることはもちろん、思考の柔軟性、スケジューリング力をつけていきたいと思っています。

「敢闘賞」&「ブログ賞」記念ランチに行ってきました!

こんにちは。2017年度第4クオーターの「ブログ賞」を受賞しました、宮西です。

先日、開発室内でクオーター毎に決定している「敢闘賞」および「ブログ賞」の記念ランチに行ってきました。 この記念ランチでは、弊社執行役員の鶴飼から、豪華なランチをプレゼントしてもらうことができます! 今回のブログでは、各賞の紹介と、記念ランチの模様をお伝えしたいと思います。

「敢闘賞」とは?

ウィルゲートでは、社員の功績を称えて行動の指針とするための取り組みとして、 クオーター毎に社員表彰が行われています。 それに加えて開発室では「敢闘賞」という賞を設け、 開発室内で最も成果に貢献した人物を表彰する取り組みが行われています。

敢闘賞では、まず月毎にノミネート者が選抜されます。 その後ノミネート者の中から、そのクオーターの敢闘賞が選ばれます。

2017年度下期は、第3クオーターに三島 正三、f:id:miyanishi-yuki:20180802162330j:plain第4クオーターに岡田正平が選ばれました。 f:id:miyanishi-yuki:20180802162325j:plain

「ブログ賞」とは?

「ブログ賞」とは、この WILLGATE tech blog に投稿された記事に与えられる賞で、 期間内に投稿された記事が選抜対象となります。 選抜基準は2種類あり、

となっています。

この賞は、 WILLGATE tech blog がはてなブログに移行した、2017年第4クオーターに新設された賞となります。 私は、3月に投稿した「JavaScript のグラフライブラリ比較 - 私達がChart.jsからHighchartsに乗り換えた理由 -」の記事でバズ賞&ロングテール賞の2つを受賞することができました。

tech.willgate.co.jp

とんかつに舌鼓!ランチの様子

今回お邪魔したのは、会社からほど近くの「かつ吉 渋谷店」さん。

www.bodaijyu.co.jp

こちらはとんかつ専門店。 食べログの評価も3.59(2018/08/10 時点)と大変高く、特にお昼は予約をしないと入れない人気店です。

f:id:miyanishi-yuki:20180802163655j:plain

こちらは鶴飼執行役員が注文した「特製厚切りかつ丼」です。 写真では分かりにくいですが、丼がとても深く、ボリューム満点です。

f:id:miyanishi-yuki:20180802163719j:plain

こちらは、岡田、三島、私が注文した「国産銘柄豚ひれかつ定食」です。 お肉がとっても柔らかく、衣もさっくりしていてしつこさを感じません。 通常の白米と、青しそまぜごはんを選べるのもありがたいです。

ごはんを食べながら、賞を取った時の取り組みや考えていたこと、今後どんなことをやっていきたいかなどを話しました。 真面目な話から他愛もない話まで、いろんな話ができた1時間半でした。

f:id:miyanishi-yuki:20180802163931j:plain

ごちそうさまでした!

【スライドあり】LT会 兼 交流会「Hacker's GATE Beer Bash!!!!」の様子をご紹介します!!【写真あり】

こんにちは!新卒3年目のエンジニア、宮西です。今年度に入ってからは、社内勉強会の運営メンバーとしても活動しています。

ウィルゲートの社内勉強会は「Hacker's GATE」という名前が付けられており、 毎月1回開催している社内LT会を中心に活動を行っています。

tech.willgate.co.jp

また社内に留まらず、社外の方をお招きしてのイベントも今までに2度開催してきました。

tech.willgate.co.jp

tech.willgate.co.jp

今回のブログでは、7月19日に開催したLT会 兼 交流会「Hacker's GATE Beer Bash!!!!」の様子をご紹介します!

初のオープン開催!「Hacker's GATE Beer Bash!!!!」について

「Hacker's GATE Beer Bash!!!!」では新しく挑戦したことが2つありました。

1つは、LTよりも交流をメインにしたことです。今まで社外向けに開催したHacker's GATEのイベントはLTが中心の「LT会」でした。 しかし、LTを真剣に聞いている時間が長く、せっかくの交流の機会を長く取れないという悩みがありました。 そこで今回は、LTの件数を3件に絞ることで交流の時間を長く作るようなタイムスケジュールを組みました。

もう1つの挑戦は、オープンに集客を行うことです。今までHacker's GATEとして開催したLT会はすべてクローズドなイベントで、 参加者の方はウィルゲート社員とつながりのある方だけでした。 今回はconnpassでイベントページを作成し集客を行ったことで、今までウィルゲートを知らなかった方々にも来ていただくことができました。

そして、「Hacker's GATE Beer Bash!!!!」でこだわったのは会のテーマです。 今回は「あのサービス誕生秘話を聞いてみよう」というテーマを設定しました。 これは、単純な技術の話ではなく「サービスの中で生きている技術」についての情報交換ができることを期待して設定しました。 その期待通り、より踏み込んだ内容や「サービス」に焦点を当てた発表が多く、新鮮なLT会となりました。

実際の「Hacker's GATE Beer Bash!!!!」の様子

ここからは、実際の「Hacker's GATE Beer Bash!!!!」の当日の様子について、写真で紹介していきます!

f:id:miyanishi-yuki:20180724104059j:plain f:id:miyanishi-yuki:20180724031649j:plain LT発表中の様子です。 皆さん、お酒やおつまみを片手に、真剣にLTに聞き入っています。

ウィルゲートからは、新卒1年目で新規メディア立ち上げのPMを務めた吉田さんが発表しました(写真1枚目)

tech.willgate.co.jp

PM業務の中でも、デザイナーとエンジニアの連携の部分に焦点を当てた発表でした。 発表スライドはこちらです。

speakerdeck.com

f:id:miyanishi-yuki:20180724031825j:plain f:id:miyanishi-yuki:20180724031811j:plain 質疑応答の時間も様々な質問が飛び交いました。

f:id:miyanishi-yuki:20180724031808j:plain f:id:miyanishi-yuki:20180724031703j:plain f:id:miyanishi-yuki:20180724031826j:plain お待ちかねの交流会!いろんなところで笑いが起こり、活発な交流の場になっていました。

f:id:miyanishi-yuki:20180724031824j:plain 締めの様子です。この後もしばらく話が盛り上がり、2度目の締めまでたっぷりお話ししました。

最後に

今後もHacker's GATE運営では、社外への発信を目的としてLT会や交流会を開催していく予定です。 次回の開催時のご参加を、心よりお待ちしております!!

インフラ配属の新卒エンジニアが入社後3ヶ月でやってきたこと 【新規技術・ホスティング先移行編】

インフラ配属の新卒エンジニアが入社後3ヶ月でやってきたこと 【IaC編】の後編です!

前編はこちら↓

tech.willgate.co.jp

新規技術の検証・導入

1. Capistrano から AWS CodeDeploy へ

ウィルゲートではこれまで、アプリケーションのデプロイには Capistrano を利用していました。 しかし、せっかくなら AWSSaaS を利用してデプロイ作業を自動化させようということで、 AWS CodeDeploy を利用することにしました。

アプリケーションのソースコードを対象のブランチにマージした際、 CI ツールが動き AWS S3 へ ZIP ファイルとして配置します。 また、 S3 へ配置したと同時に CodeDeploy を実行させることで、 CodeDeploy は S3 から最新のソースコードを取得しデプロイを実行します。

これまで利用していた Capistrano は In Place デプロイを行うため、多少のダウンタイムや新旧が混在する時間が生まれてしまいます。 しかし、 CodeDeploy を利用することで Blue/Green デプロイが可能となり、ダウンタイムゼロのデプロイ環境を構築することができました。

f:id:tkhttty:20180619135143p:plain
デプロイフロー

2.( Packer + Terraform )+ Ansible

CodeDeployを利用して Blue/Green デプロイを行う際、上図のように既存のオートスケーリンググループ とは別のオートスケーリンググループ を作成します。

しかし、AWS が提供しているオートスケーリングは、スケールアウトした際にあらかじめ起動設定として指定した AMI を元にインスタンスを生成するため、 nginx や users などの設定がされていないまっさらなインスタンスが出来上がります。

そのため、スケールアウトし web アプリケーションが乗っかっていても web サーバが入っていないという事態に陥ってしまうことがあります。

そこで、 「Packer」 を活用して上記の問題を解決することにしました。

f:id:tkhttty:20180619134426p:plain
スケールアウト時のインスタンスの状態

■Packer とは

  • HashiCorp 社が提供している OSS
  • EC2 インスタンスを作る際のマスターイメージ( AMI )を作る
  • ビルド時に Ansible を流すことも可能
  • 構成ファイルは JSON ひとつだけ

上記に書いた通り、 Packer は AMI を作成する際に Ansible を流すことができるため、一通りの設定がすでに完了したイメージを作成することができます。

Packer で作成した AMI を起動設定としたオートスケーリンググループ を Terraform から作成することにより、スケールアウトしても一通りの設定が済んだ状態でインスタンスが作成されるようになりました。

3. AWS Lambda の導入・連携

前述した CodeDeploy を利用するにあたり、デプロイの進行状況が AWS の管理画面を確認しないと分からないという問題がありました。

そこで、 AWS Lambda を活用し、デプロイのステータスが変わるごとに Slack へ通知するようにしました。

AWS Lambda とは

サーバーをプロビジョニングしたり管理しなくてもコードを実行できるコンピューティングサービス。 AWS SNS や SQS 、 S3 のイベントをトリガーとして Python などのスクリプトを実行することができる。

f:id:tkhttty:20180619135111p:plain
Lambdaを活用したデプロイ通知のフロー

結果、 CodeDeploy の進行状況が都度 Slack に対して通知がくるので、一目でデプロイ状況が確認できるようになりました。

f:id:tkhttty:20180619135215p:plain
デプロイのステータス通知

標準開発環境のホスティング先移行

最後に余談ですが、ウィルゲートの開発室では標準開発環境と呼ばれる1人1つの自由に使うことができる開発環境を提供しています。

これまでは、契約している専用サーバで開発環境を提供しており、Windows の vSphere クライアントから仮想環境の構築やコピーを行っていました。 しかし、専用サーバのリソースの問題や環境のコピーに時間がかかるなどの問題から、標準開発環境を専用サーバから全て AWS EC2 へ移行を行いました。

個々人のインスタンスは全て Terraform で構成・管理されており、誰かのインスタンスをコピーして欲しいという要望に対しても、迅速に対応することが可能となっております。

おわりに

まだ入社して間もない私ですが、様々なことにチャレンジさせて頂ける環境があることに対し、とてもやり甲斐を感じています。

これからも新しい技術に触れていき、より組織に貢献できるよう頑張っていきます!

インフラ配属の新卒エンジニアが入社後3ヶ月でやってきたこと 【IaC編】

こんにちは。 2018 年度新卒入社で開発室インフラチームに配属された高畑です。 今回は、入社して 3 ヶ月が経過した今までにやってきたことを振り返っていきたいと思います。

Infrastructure as Code(IaC)の活用

現在、ウィルゲートで運用しているメディアサービスは、「Terraform」と、「Ansible」を利用して各種サーバの構成を行なっています。

■Infrastructure as Code(IaC)とは

インフラの構成管理をコード化し、 Git 管理やプルリク(レビュー)、 CI などソフトウェア開発のプラクティスを適用すること。

■Terraform とは

  • HashiCorp 社が提供している OSS
  • 管理画面触らなくてもコード 1 つで AWS EC2 などの SaaS 環境が構築できる
  • 冪等性がある
  • tf ファイルで構成を記述

■Ansible とは

  • RedHat 社が提供している OSS
  • サーバのミドルウェアをコードで管理・構築ができる
  • 冪等性がある
  • 構成ファイルは yml
  • Dynamic Inventory がアツい

手作業でインフラ環境を構築するとなると、台数が少ない分には問題ないのですが多くなっていくにつれて各種サーバのパラメータなどの構成管理が難しくなっていきます。

また、インフラの構築手順を手順書に纏めたとしても、手作業による設定・作業ミスが起こるリスクがあり、結果障害へと繋がっていきます。

そこで、インフラの構成をコード化し自動で設定できるようにすることで、手作業による作業ミスがなくなり、また誰が実行しても同じ状態のインフラ環境が構築できるようになるため、より安全に構成を変更できるようになります。

私自身、 IaC に関してはウィルゲートに入社してから触るようになったのですが、 web メディアの運用保守や運用改善を行う際にサーバの構成が書かれた Ansible のコードを修正してレビューをしてもらうのはとても新鮮でした。

f:id:tkhttty:20180619170935p:plain
コードレビューの様子

インフラセクションでの取り組み

TerraformやAnsibleなどの技術についてどのように活用したのか、またどのように使用すると良いのかをインフラチーム内でナレッジの共有を定期的に行っています。

また、後編で紹介する新規技術についても、技術検証した結果や実際に使用してみた事例などを共有して、他のプロジェクトに活かせないかを議論しています。

f:id:tkhttty:20180619175816j:plain
インフラセクションの様子

後編はこちら↓

tech.willgate.co.jp

CakePHP1系からCakePHP3系へ移行した直後に起きたサービス障害と学び

f:id:toropoko002:20180717200616j:plain

はじめに

18年度新卒の垣花です。

僕は現在サグーワークス開発チームに所属しています。

インターン時代に過去に発生した障害の事後対応を行ったことがありました。

内定者がチャレンジ!遠方インターンで事業と会社を知る - WILLGATE tech blog

今回はその障害の内容とそこからの学びを紹介しようと思います。

障害の背景

サグーワークスではCakePHP3系へ移行した直後に、ユーザの生年月日を登録できるページを作成し、以下のように保存処理を書いていました。

<?php 
    # CakePHP3
    $birthday = '1969-01-01';
    $userTable = TableRegistry::get('Users');
    $user = $userTable->get($userId);
    $user->birthday = date("Y-m-d H:m:s", strtotime($birthday);
    $userTable->save($users);
?>

この処理を通して1969年が誕生日のユーザが保存されると、2069年が誕生日として保存されてしまっていました。

障害の原因を調査後に下記のように修正することで無事1969年生まれとして誕生日が保存されます。

<?php 
    # CakePHP3
    $birthday = new Time('1969-01-01');

    $userTable = TableRegistry::get('Users');
    $user = $userTable->get($userId);
    $user->birthday = $birthday->i18nFroamt('yyyy-MM-dd HH:mm:ss');
    $userTable->save($users);
?>

なぜ障害が発生したのか

2069年として保存されてしまう原因を調査した結果、下記のことがわかりました。 問題点について順番に説明していきます。

  • CakePHP1系とCakePHP3系のfind結果の違い
    • datetime型がCakePHP1系ではstringが、CakePHP3系ではFrozenTimeClassのオブジェクトが返ってくる
  • strtotimeの仕様
    • 年を下2桁で指定した場合に2069年など、未来の年月日のunixtimeが出力されてしまう。
  • FrozenTimeClassの仕様
    • マジックメソッドである_toString()が下2桁の年を返す

CakePHP1系とCakePHP3系のfindの違い

まずはfind結果を見比べてみます。

今回は関係ありませんが、CakePHP1系は結果が配列で返ってくるのに対し、CakePHP3系ではEntityClassのオブジェクトが返っていますね。

見るべきポイントは"birthday"がどのように返ってくるのかです。

CakePHP1系ではstringで、CakePHP3系ではFrozenTimeClassのオブジェクトが返っています。

<?php
    # Cakephp1
    $this->loadModel('User');
    $example = $this->Users->find('first', ['fields' => ['birthday']]);
    var_dump($example);
?>
    /* 出力結果
    * array (size=1)
    * 'User' =>
    *   array (size=1)
    *     'birthday' => string '1969-01-01 18:00:00' (length=19)
    * /
<?php 
    # Cakephp3
    $Users = TableRegistry::get('Users');
    $example  = $Users->find('all')->select('birthday')->first();
    var_dump($example);
?>

    /* 出力結果
    * object(App\Model\Entity\User)[1346]
    *   public 'birthday' =>
    *     object(Cake\I18n\FrozenTime)[1335]
    *       public 'time' => string '1969-01-01T18:00:00+09:00' (length=25)
    *       public 'timezone' => string 'Asia/Tokyo' (length=10)
    *       public 'fixedNowTime' => boolean false
    *       ...
    * /

CakePHP3系の場合、FrozenTimeClassはDateFormatTraitを読み込んでいるので、i18nFormatが使えるためデータのフォーマットが簡単ですね。

<?php 
    # CakePHP3
    $Users = TableRegistry::get('Users');
    $example  = $Users->find('all')->select('birthday')->first();
    var_dump($example['birthday']->i18nFormat('yyyy-MM-dd HH:mm:ss'));
?>

    /* 出力結果
    * string '1969-01-01 18:00:00' (length=19)
    */

一方CakePHP1系の場合、string型の日付だと以下のようにフォーマットしなければいけません。

<?php
    # CakePHP1
    $this->loadModel('User');
    $example = $this->Users->find('first', ['fields' => ['birthday']]);
    var_dump(date("Y-m-d H:m:s", strtotime($example['User']['birthday'])));
?>

    /* 出力結果
    * string '1969-01-01 18:00:00' (length=19)
    */

strtotimeの仕様

PHP Manualのstrtotimeのページをみていると以下の注意事項が書かれています。

年を 2 桁の数値で指定した場合、その値が 00-69 なら 2000-2069 に、 70-99 なら 1970-1999 にそれぞれ変換されます。

PHP: strtotime - Manualより引用

今回の障害の対象となったユーザの誕生日が1969年です。

00-69の間は2000-2069年としてunixtimeに変換される為、下2桁だけ見ると00-69の間に入っていました。

次はFrozenTimeの設計をみていきます。

FrozenTimeClassの仕様

FrozenTimeにはDateFormatTraitが読み込まれているので、DateFormatTraitを確認してみます。

cakephp/FrozenTime.php at 3.2.10 · cakephp/cakephp · GitHub

マジックメソッドの__toStringが存在していました。返り値はi18nFormat()の返り値です。

<?php
    # DateFormatTrait.php
    public function __toString()
    {
        return $this->i18nFormat();
    }
?>

cakephp/DateFormatTrait.php at 3.2.10 · cakephp/cakephp · GitHub

i18nFormat()では$formatを引数として渡さない場合、$ _toStringFormatを日時の形式として使っています。

<?php 
    # DateFormatTrait.php
    public function i18nFormat($format = null, $timezone = null, $locale = null)
    {
        $time = $this;
        if ($timezone) {
            // Handle the immutable and mutable object cases.
            $time = clone $this;
            $time = $time->timezone($timezone);
        }
        $format = $format !== null ? $format : static::$_toStringFormat;
        $locale = $locale ?: static::$defaultLocale;
        return $this->_formatObject($time, $format, $locale);
    }
?>

cakephp/DateFormatTrait.php at 3.2.10 · cakephp/cakephp · GitHub

DateFormatTraitを読み込んでいたFrozenTimeClassに戻ってみると$_toStringFormatのデフォルトが設定されています。

# FrozenTimeClass
protected static $_toStringFormat = [IntlDateFormatter::SHORT, IntlDateFormatter::SHORT];

cakephp/FrozenTime.php at 3.2.10 · cakephp/cakephp · GitHub

IntlDateFormatterClassの定数としてSHORTが呼び出されているのでみてみると、12/13/52のような日時形式が定義されています。

年が下2桁で表現されていたようです。

<?php
    # IntlDateFormatterClass
    /**
    * Most abbreviated style, only essential data (12/13/52 or 3:30pm)
    * @link http://php.net/manual/en/intl.intldateformatter-constants.php
    */
    const SHORT = 3;
?>

3つの問題点のまとめと問題点を踏まえた解決策

以前CakePHP1系からCakePHP3系へ移行に関しての取り組みをブログ記事で紹介しましたが、サグーワークスでは長期間CakePHP1系で開発をしてきていたことで移行後すぐはまだCakePHP1系の方法で日付をフォーマットしていました。

PHPフレームワークのバージョンを上げるための取り組み - WILLGATE tech blog

そのため、CakePHP1系のfind結果を想定してCakePHP3系のFrozenTimeオブジェクトを日付フォーマットしたため以下のようなことが起こっていました。

$user['birthday']はFrozenTimeClassのオブジェクトのため、文字列出力しようとすると"1969/01/01"が"01/01/69"としてフォーマットされてしまいます。

その後、strtotime()の引数として"01/01/69"が渡り、2069年として解釈されstring型でunixtimeの"3124256400"が返ります。

そのunixtimeをdate()で見やすいようにフォーマットすると、2069年生まれのユーザが誕生してしまいます。

これらを踏まえると以下の点に気をつけることで未来に生まれるユーザの発生を防げます。

  • 年を下2桁の形式で取り扱わないこと
  • CakePHP3系ならTimeClassで定義されている関数を用いること(find結果のカラムがdatetime型であればFrozenTimeClass)

最初に紹介したコードの解決策として以下のように修正しました。

<?php
    # CakePHP3
    $birthday = new Time('1969-01-01');

    $userTable = TableRegistry::get('Users');
    $user = $userTable->get($userId);
    $user->birthday = $birthday->i18nFroamt('yyyy-MM-dd HH:mm:ss');
    $userTable->save($users);
?>

終わりに

CakePHP1系からCakePHP3系のfind結果が同じだろうとこれまで通りの実装してしまうことはあっても、strtotimeの仕様やFrozenTimeClassの設計を把握していれば防げた問題だろうと思います。

納期の問題などで言語やフレームワークの全ての仕様を把握することは膨大で難しいですが、今回のような障害を防ぐためにも利用する関数の仕様だけでも把握することが大切です。