npm scriptが動かない!?Windows環境で引っかかった落とし穴

トラストバンクのフロントエンドエンジニア、田口です。
ずいぶん暖かくなってきましたね。でも花粉が舞っているので毎日家にこもってエルデンリングをやってます。神秘マンです。
さて今回は既存のnpm scriptが動かなくなったときの話になります。かなり初歩的な内容ではありますが、環境によっては気づきにくい落とし穴かもしれません。

前提

ふるさとチョイスの一部コンテンツではejsを使用しており、そのコンテンツのhtmlをビルドするためにnpm scriptでgulp(と、そのプラグイン)を使っています。
htmlをビルドする際、jsonで作成しているコンテンツデータを取り込み、ejsに流し込んで最終的なhtmlにしています。
該当のnpm scriptは自分を含めた何人かの手が入っており、利用した人も複数いますが、これまでで異常は発生していませんでした。

唯一のWindowsユーザーで発生したエラー

弊社のエンジニアの多くはMacユーザーなのですが、Windowsユーザーにejsの変更作業をお願いしたところ、ejsのビルドに失敗しました。
そのWindowsユーザーはWSLを使用しておらず、Git bashを使っていました。

ログを出力してもらいながら調査していたところ、以下の箇所でエラーになっていました。

data.page = file.path.split('src/ejs/')[1].replace('.ejs', '.html').replace('index.html', '');

内容としてはパスを文字列として置換をかけているような処理です。
何がおかしいのか。file.path には文字列のファイルのパスが入っていますが、それをMacPOSIX)とWindowsで比較すると、

'/your/directory/something.html' // Mac
'C:\\your\\directory\\something.html' //Windows

このように、ディレクトリの区切り文字が異なっていました。

解決策

色々試してみましたが、お手軽なPath.sep*1を使用する形に変更しました。

data.page = file.path.split('src' + path.sep + 'ejs' + path.sep)[1].replace('.ejs', '.html').replace('index.html', '');

その他のパスを文字列として扱っている箇所も同じように変更して、エラーは発生しなくなりました。よかった!

やはりWSLか…

今回ハマった箇所としてはPOSIXWindowsの違いだったのですが、Node.jsだけでなく、様々な面でWSLの使用をお勧めすべきなのだと改めて感じました。
Node.jsに関して言えば、Windowsで引っかかる点は幾つもあります。*2
Windowsユーザーを考慮してコードを書くのも一つの手ですが、やはりWSLを使用する方向で開発組織として統一してしまうのがベストなのだと思います。

ということで今回はあっさりとここまでです。お疲れ様でした。

いつもの

トラストバンクではnpm scriptによってフロントエンドの開発効率をグングン向上できるような、開発環境整備にも強いエンジニアを募集しております! www.wantedly.com

冬も終わりなのでワーケーションしてみた話🏕

コロナ禍&基本リモートワークなこともあって、今年の冬はほとんど家に引きこもっていたSREのbutadoraです。

弊社では今年からフレックス勤務とワーケーションが可能となり、 3月に入って気候も暖かくなってきたので早速試してきました🙋

計画編

最初は単純にどこか旅行先を決めて宿からお仕事スタイルで考えていましたが、
昨年からキャンプデビューして冬キャンを夢見ていた私は近場のキャンプ場を探し始めました*1

Google Mapで埼玉県内をウロウロしていたところ、狭山市にある智光山公園キャンプ場を発見しました。
ちょうど今年の3月にリニューアルオープン予定で、Wi-Fi設備の設置といったワーケーション機能を強化したということでこちらに決めました。

今回は冬キャンもワーケーションも初めてということで、電源付きオートサイトを借りることにしました。
これで万が一Macのバッテリーが切れても電源は確保できますし、自分のキャンプ装備で寒さに耐えられなかったときに車に逃げ込むことができます💪

また、キャンプ場はチェックインが11:00からとうこともあって、 午前半休で午後からワーケーションすることにしました。

ざっくりとしたスケジュールはこんな感じです。

1日目

  • 9:00 カーシェア貸出、買い出し
  • 11:00 チェックイン、テント設営開始
  • 13:00 出勤
  • 17:00 退勤&焚き火開始
  • 22:00 消灯

2日目

  • 6:00 起床
  • 8:00 チェックアウト
  • 9:00 カーシェア返却
  • 10:00 出勤

当日の様子

キャンプ地

(キャンプ場のページに掲載されてフリー素材と化したのは内緒)

Wi-Fi設備

ちなみにCiscoのAPだった

焚き火

ご飯

ワーケーション感想

  • meet, zoomのノイズキャンセリングが強力
    • ヘリコプターや航空機の音や設営の音がしたが、通話相手には届いていなかった
    • でもカラスの声はしっかり届いていたらしい
  • 半日(4時間)程度の勤務であれば、ビデオ通話しながらでもバッテリーは持つ
    • ディスプレイの輝度が常にMAXでバッテリーの消耗が激しい
  • キーボードを打つ手がかじかんだ
    • 3月に入って暖かくなっていたものの、冬に屋外でコーディングは厳しさがありそう
  • 保温ボトルを持っていくべきだった
    • 暖かい飲みものも一瞬で冷めていくし、自宅で用意して持参すべきだった
  • なんだかんだでワーケーション最高!
    • ずっと家にこもりっぱなしだったのもあっていいリフレッシュになった
    • オープン翌日&平日だったこともあってか、お客さんも少なく快適に過ごせた*2

さいごに

昨年トラストバンクにJoinして、エンジニアが働きやすい環境づくりをSREとして推進する一方、 今回のワーケーションのような制度づくりが進められており、日を追うごとに働きやすさ向上を実感しています。

こんな働き方もできる弊社ではSREを絶賛募集中です。
興味がある方はぜひ一度お話しましょう!

www.wantedly.com

*1:某キャンプアニメに触発されて始めた口です

*2:見える範囲だと私を含めて4組だけ

Laravelでクリーンアーキテクチャ (ディレクトリ構造編)

トラストバンクふるさとチョイス事業本部CTO室に所属の海塩(うしお)と申します。
主に新規プロジェクトの立ち上げやアーキテクチャ設計を行っております。

前置き

PHP・Laravelにおけるクリーンアーキテクチャ実践の一例をご紹介します。
クリーンアーキテクチャ自体の説明はしておりませんのでご了承ください。
すでになんらかの形でドメイン駆動やクリーンアーキテクチャを実践している方に刺されば幸いです。
全体ボリュームがかなり大きいので、今回はディレクトリ構造のお話です。
今後は、

などを予定しています。

ディレクトリ全体像

こんな感じになります。

├── app
│   ├── Console
│   ├── Contexts
│   │   └── {コンテキスト名}
│   │       ├── Domain
│   │       │   ├── Entity
│   │       │   │   └── {エンティティ名}
│   │       │   ├── Exception
│   │       │   ├── Notification
│   │       │   └── Persistence
│   │       ├── Infrastructure
│   │       │   ├── Http
│   │       │   │   ├── Controller
│   │       │   │   └── Request
│   │       │   ├── Notification
│   │       │   │   └── templates
│   │       │   ├── Persistence
│   │       │   └── Presenter
│   │       │       └── templates
│   │       └── UseCase
│   │           └── {リソース名}
│   │               └── {アクション名}
│   ├── Exceptions
│   ├── Http
│   ├── Models
│   ├── Policies
│   ├── Providers
│   └── View
├── bootstrap
├── config
├── database
├── public
├── resources
├── routes
│   └── contexts
│       └── {コンテキスト名}
├── storage
├── tests
└── vendor

なるべくLaravel標準に従うことで、

  • プロジェクト参画者のキャッチアップがしやすくなる
  • Laravelのバージョンアップに低コストで追従できる

といったメリットが得られます。

app/Contexts

コンテキストを表現するディレクトリになります。
コンテキスト毎にレイヤー構成を持ち、コンテキストをまたいだ参照は一切無いようにします。

app/Contexts/{コンテキスト名}/Domain

クリーンアーキテクチャの「Enterprise Business Rules」と「Interface Adapters」にあたります。 ビジネスロジックがここに集まるようにします。
Laravelに関連するコード(グローバルメソッドやファサードも含む)は置いてはいけません。

app/Contexts/{コンテキスト名}/Domain/Entity

エンティティを配置します。
エンティティに紐づく値オブジェクトはサブディレクトリに配置します。

app/Contexts/{コンテキスト名}/Domain/Exception

コンテキストのビジネスロジックにより発生する例外を配置します。

app/Contexts/{コンテキスト名}/Domain/Notification

ビジネスロジックにおいて、何らかの通知を行う場合のインターフェースを配置します。
UserStoredNotificationみたいな名前で、handleメソッドのみを持つインターフェースであることが多いです。

app/Contexts/{コンテキスト名}/Domain/Persistence

エンティティの永続化、および復元に関連するインターフェースを配置します。
以下のセットをリソース単位で用意することが多いです。

  • UserRepository:単一エンティティの永続化と復元を行うインターフェース
  • UserListRepository:エンティティの一覧の復元を行うインターフェース
  • UserRepositoryRecord:エンティティとリポジトリとの間で使用するDTOインターフェース

app/Contexts/{コンテキスト名}/Infrastructure

クリーンアーキテクチャの「Frameworks and Drivers」にあたります。
ビジネスロジックの実現手段をここに配置します。

app/Contexts/{コンテキスト名}/Infrastructure/Http

Web経由でのコントローラを配置します。
LaravelのControllerやFormRequestを活用します。
本家クリーンアーキテクチャと大きく動きが異なります。
コントローラはユースケースを呼んで終わりではなく、戻りを受け取ります。
さらにユースケースからの戻りをプレゼンターへ渡してUIを生成し、Webのレスポンスとして返却します。

app/Contexts/{コンテキスト名}/Infrastructure/Notification

Domain/Notificationのインターフェースの実装を配置します。
メール通知であればLaravelのMailableやSendGrid APIを活用します。

app/Contexts/{コンテキスト名}/Infrastructure/Persistence

Domain/Persistenceのインターフェースの実装を配置します。
データベースへの永続化であればLaravelのEloquent Modelを活用します。

app/Contexts/{コンテキスト名}/Infrastructure/Presenter

Web経由でUIを伝えるための実装を配置します。
クリーンアーキテクチャの「Interface Adapters」にあたりますが、上述の通り構成はだいぶ異なります。
同じレイヤのコントローラから呼ばれて戻り値としてUIを返すことになるため、インターフェースと実装は分離しません。

app/Contexts/{コンテキスト名}/Infrastructure/Presenter/templates

コンテキスト固有のBladeテンプレートを配置します。
layoutや共通パーツなどはLaravel標準のresources/viewsに置きます。 \Illuminate\Support\Facades\View::addNamespaceによって動的にディレクトリを設定するため、configやLaravelコードはいじりません。

app/Contexts/{コンテキスト名}/UseCase

クリーンアーキテクチャの「Application Business Rules」にあたります。
Domainコードを活用し、ビジネスロジックを組み立てます。
ここにもLaravelに関連するコード(グローバルメソッドやファサードも含む)は置いてはいけません。

終わりに

いかがでしたでしょうか。
クリーンアーキテクチャの実践には正解がなく、日々改善を重ねています。
トラストバンクでは、堅牢で変更に強いプログラムを書きたいエンジニアの方を募集しています!

www.wantedly.com

クライアントでのエラー監視要員としてSentryを導入した話

どうもはじめまして、トラストバンクでフロントエンドエンジニアをしている田口です。 早速ですが少し前にそのふるさとチョイスにSentryを導入したので、今回はそれについて書こうと思います。

解決したかったこと

ふるさとチョイスはありがたいことにとても多くのユーザーがいます。そしてそれに比例するように、クライアント環境もたくさんあります。
特にふるさとチョイス、というより弊社のサービスはその性質上、他社さんのサービスと比べて恐らくですが、特殊な環境であったり、IEなどの古めな環境で使われることが多いです。
それゆえ、ユーザーから不具合に関する問い合わせを受けても、こちらで持っている環境ではほとんど再現せず、原因調査と改善に至らないこともあります。
まぁIEゆえに発生するような不具合は再現しやすいほうなのですが……。

このような環境依存の不具合に関する問い合わせは、サービス全体としては特殊な例ですが、それが発生したユーザーにとってはその不具合が全てですから、解決しないとなるとサービスから離れてしまい、ユーザーにとっても我々にとっても不幸な結果になります。
こうした不幸を少しでも減らすために、原因調査の手助けとしてSentryを導入することとしました。

Sentryへの登録と仮導入

Sentryについての説明は省略しますが、まずは登録をします。
登録は英語ですが、特に複雑なことはありません。必要項目を入力して進めていくと、すぐにStart upを開始できるようになります。
導入に使用する言語が選択可能で、JSだけでなくJSフレームワークであるReactやVue、Node.js、もしくは別の言語を選択することもできます。

f:id:gatag:20220225190915p:plain
クライアントサイドだけでなくサーバーサイドも

Qiitaにもさらっと書いたのですが、ふるさとチョイスはJSフレームワークをまだ導入していないので、普通のJSを選択しました。
言語を選択すると、あとはもうチュートリアルの手順に従ってコードを仕込むだけです。めちゃ簡単……

仮導入で気になったこと

仮導入していて2つほど気になったことがありました。

dsn って公開していいの?

Sentry SDKのコードを実装するにあたり、dsnがベタ書きなのが気になりました。dsnというのはお察しの通り(?)、Data Source Nameの略語です。
Sentryのドキュメントにもdsnの説明がありますが、公開された状態でも問題無いと書かれています*1ので、結局特に何もせずベタ書きにしています。

コード量が増えた

ふるさとチョイスはES2015↑のコードをWebpack + Babelに噛ませてビルドしています。
で、実際にSentry SDKを実装したところ、ビルドされたJSファイルがまあまあ膨れ上がりました。
これに関してはパフォーマンスを確認した上で許容としていますが、個人的にはなんとかしたいと思っています。なんとかならんのか

チューニング

チュートリアルの内容だけでもエラーを検知してSentry画面上で確認することはできたのですが、実際に使っていくうえではチューニングの必要性が見えてきました。

whiteListUrlsの設定

whiteListUlrsは監視するファイルを正規表現で範囲を絞り込むための設定なのですが、最初はこれをドメインレベルまでの設定にしていました。
これだけでもサードパーティのJS(マーケティングツール関連など)にて発生したエラーを監視してSentryに送るようなことは無くなりました。しかし、送られてくるエラーのほとんどが内容的にサードパーティのものであり、こちらで実装したものではありませんでした。

f:id:gatag:20220228105217p:plain
GTMのJSでエラーが起きているのをキャッチしてしまった
なんでかな〜と思っていたところ、辿っているうちに発生箇所がdocumentの中だということに気づき、document内に書かれてしまうとドメインレベルでのホワイトリスト設定は通過してしまうことが分かりました。

document内に書いてあるJSが一切無いというわけではないのですが、それらのほぼ全てがタグマネージャやアナリティクスに関するものだったため、ホワイトリストとしてはJSのディレクトリに絞りました。

tracesSampleRateの設定

これはSentryのドキュメントをちゃんと見ていなかっただけなのですが、普通にユーザーが来訪する本番環境に適用するには、tracesSampleRateをチュートリアルで書かれている1.0から下げるべきだったのです。
それに気付かずに本番環境に導入したことで、仮導入したページは一部だったにも関わらずかなりの勢いでトランザクションが消費されてしまいました。

f:id:gatag:20220228105713p:plain
来訪の少ない一部のページだけに入れたはずなのに42%も食われた
本格導入するにあたっては1.0のままではすぐにトランザクションを使い切ってしまうので、これを下げます。

そもそもSentryのトランザクションとはどういう時に使われるのか、調べてみたところどうやらSentryのトランザクションはエラー監視ではなくパフォーマンス監視に使用されているようです。*2
ふるさとチョイスはNew Relicも使用していますので、パフォーマンス監視はSentryではそれほど必要ではないはず……ということで思い切って0.001にまで下げました。

テスト環境やローカル環境時の動作の実装

これに関しては仮導入の段階で実装していました。
テスト環境やローカル環境で発生したエラーに関しては、本番環境のものとは分けて管理するか、そもそも監視しないという選択肢になると思います。
まずローカル環境に関しては全く監視しなくて良いので除外できるようにしたい……
テスト環境に関しては監視しつつも本番環境とは分けて管理したいため、何かしらの識別子が欲しいところ……
と思っていたところenvironmentというそのまんまのパラメータが用意されていました。
ということでドメインに基づいて簡単な制御を追加しておきました。

const getSentryEnv = () => {
  const domain = document.domain;
  if (/* ローカルだったら */) {
    return 'local';
  } else if (/* 本番だったら */) {
    return 'production';
  } else {
    return 'test';
  }
};
const sentryEnv = getSentryEnv();
if (sentryEnv !== 'local') {
  Sentry.init({
    // dsn等は省略
    environment: sentryEnv,
  });
}

これからやっていきたいこと

現段階ではまだ軽くチューニングをしただけですので、本格的に運用するためにはやるべきことがいくつかあります。

エラーのトリアージ方針決め

全てのエラーに全力対応するぜ!みたいな気合いや人員、時間があればいいのですが、現実そうはいかないので、エラーによっては対応しなかったり、逆に他のエラーを差し置いてでも対応する必要があるものもあると思います。

現状ではまだ一部ページだけでSentryの監視をスタートしているのですが、それでもすでに1日5件のペースでIssueが増えていますので、サービス全体を監視し始めるより前にトリアージ方針をある程度は決めなければなりません。
サンプルとなるデータも集まり始めているので、それを基に検討していきたいと思います。

フィルタリング

トリアージ方針とほとんど同じなのですが、Sentryの表示自体にもフィルタリングをかけると見やすくなるだろうなと思っています。

また、Unresolved、For Reviewなど、エラーごとにステータスをつけるというフィルタリングもありますので、ステータスの変更フローなどの検討もこれに含めて考えています。
SentryではReleaseという管理項目(?)もあり、SDKでパラメータを送ったりすることで、どのリリース後に起きたエラーかをタグ付けすることができます。これもまたエラーのフィルタリングで使えそうです。 ReleaseはGithub Actionsでの連携が基本になりそうなので、少しハードルは高そうですが、使用することでのメリットが大きそうなので検討していきたいところです。

Slackとの連携を直す

チャンネルを作成してSlackとSentryの連携まではできたものの、今のところ稀にメッセージが入るだけで、新しいエラーがSentryに追加されてもそれがSlackで通知されないという状態になってしまっています。 恐らく厳し目なフィルタリング設定をしてしまっていると思うので、連携設定の見直しをはかろうと思っています。

まとめ

クライアントで発生したエラーを簡単に取得できるようになった一方、まだまだ本格的に運用するには設定等の見直しが必要な状態ですので、引き続きSentryの面倒を見ていこうと思います。

トラストバンクではSentryを導入して運用してきたような、クライアントで発生するエラーにも強いエンジニアを募集しております! www.wantedly.com

それでは〜ノシ

ふるさとチョイスの推奨・動作環境を厳密化させる試み

トラストバンクふるさとチョイス事業本部CTO室リーダーの礒部です。

私が所属しているCTO室というチームは、エンジニア組織の横断的な管理、プロジェクトにおける技術的側面のサポート、新技術導入の提案・検証・実行などといった業務を行っています。

その取り組みの一つとして、ふるさとチョイスが正常に動作する環境(ブラウザ・OS)を厳密に定義しようという試みを進めているので、それについて書きたいと思います。

ふるさとチョイスのフロントエンド事情

推奨環境の前にふるさとチョイスのフロントエンド事情について簡単に触れておきたいと思います。

ふるさとチョイスのフロントエンド開発環境は、タスクランナーにgulp、モジュールバンドラーにwebpackを利用しています。

JavaScriptはES6以上の記法で記述されているため、レガシーな環境でも動作させられるようにwebpack+Babelで以下のbrowserslistの設定をもとにビルドを行っています。

"browserslist": [
    "last 1 version", // 各環境の最新バージョン
    "ie >= 11", //IE11以上
    "ios >= 10.2", // iOS10.2以上
    "android >= 4.4" // android4.4以上
]

CSSにおいてもSCSSのコンパイル時にautoprefixerでこの設定を参照しています。

browserslistには各環境の最新バージョンという広い指定から、ブラウザ・OSのバージョン指定といった細かい指定までできます。

この情報をもとに必要なベンダープリフィックスCSSに付与したり、JavaScriptを動かすのに必要なpolyfillを入れたりするので、あまり幅広い範囲を指定してしまうと、レガシーな環境で動かすための余計なコードが増えてファイル容量が圧迫したりパフォーマンスが低下するといったことも起こり得ます。

現状の推奨・動作環境

現状のふるさとチョイスのbrowserslistでサポートされる環境をリストアップしたものが以下です。

Android Browser 98 (0.00% global usage)
Android Browser 4.4.3-4.4.4 (0.25% global usage)
Android Browser 4.4 (0.00% global usage)
Chrome 98 (0.02% global usage)
Edge 98 (0.00% global usage)
Firefox 97 (0.02% global usage)
IE 11 (0.60% global usage)
IE Mobile 11 (0.02% global usage)
Safari on iOS 15.2-15.3 (3.50% global usage)
Safari on iOS 15.0-15.1 (5.89% global usage)
Safari on iOS 14.5-14.8 (3.53% global usage)
Safari on iOS 14.0-14.4 (0.87% global usage)
Safari on iOS 13.4-13.7 (0.28% global usage)
Safari on iOS 13.3 (0.08% global usage)
Safari on iOS 13.2 (0.02% global usage)
Safari on iOS 13.0-13.1 (0.04% global usage)
Safari on iOS 12.2-12.5 (0.60% global usage)
Safari on iOS 12.0-12.1 (0.04% global usage)
Safari on iOS 11.3-11.4 (0.04% global usage)
Safari on iOS 11.0-11.2 (0.07% global usage)
Safari on iOS 10.3 (0.11% global usage)
Safari on iOS 10.0-10.2 (0.03% global usage)
Opera 83 (0.02% global usage)
Safari 15.2-15.3 (0.86% global usage)

browserslistではIE, iOS, Android以外は最新のバージョンとなっているので、PCは範囲が狭く、スマホは逆に範囲が広いです。

browserslistCan I Useの情報(Can I UseはStatCounter GlobalStatsのデータをもとにしている)をもとにリストを作成するため、実際のサービスのユーザー情報は加味されません。

GAのアクセスユーザー情報をもとに推奨環境を設定する

そこで実際にサイトを訪れているユーザーの情報をもとにbrowserslistを設定するためbrowserslist-ga-exportを活用してみようと考えました。

browserslist-ga-exportのGithubに書かれている手順は以下のようになります

  1. GAのユーザー設定の編集から言語をEnglish(United States)を選択する
  2. GA上でカスタムレポートを作成する

    • 種類はフラットテーブルを選択
    • ディメンションはオペレーティングシステム、OSのバージョン、ブラウザ、ブラウザのバージョン、デバイスカテゴリの並び順で設定(並び順重要)
    • 指標はページビュー数を選択

    f:id:trustbank-developers:20220222093332p:plain

  3. カスタムレポートの画面からCSVをエクスポートする

    • カスタムレポートを開き、絞り込む期間を設定
    • ブラウザで昇順にソート

    f:id:trustbank-developers:20220222093426j:plain

    • 表示件数を5000件に設定
    • 「エクスポート」からCSV形式でエクスポート
  4. 3でエクスポートしたCSVを以下のコマンドで読み込ませてJSON化する(—outputPathの指定をしなければデフォルトでbrowserslist-stats.jsonが生成される)

     browserslist-ga-export --reportPath {CSVファイル} 
    

この手順で生成されたJSONをもとにbrowserslistを出します。

npx browserslist "> 1% in my stats”

サイト利用率1%以上を対象にした場合、以下のようになりました。

and_chr 88
android 98
chrome 96
chrome 95
chrome 94
chrome 93
chrome 92
chrome 91
chrome 90
chrome 89
edge 97
edge 96
edge 95
edge 94
edge 93
edge 92
edge 91
edge 90
edge 89
firefox 80
ie 11
ios_saf 15.0-15.1
ios_saf 14.5-14.8

閾値を何%にするかは、これから調整していきますが、この方法で以前よりも推奨・動作環境を厳密化させることができると思います。

終わりに

ブラウザやOSの更新頻度はどんどん早くなってきていてユーザーが利用している環境も目まぐるしく変わっていきます。そんな中でサイトの推奨・動作環境をどのように定義していくかというのは難しい問題ではありますが、仕組みでどうにか解決していきたいですね。

P.S. 2022年6月15日にとうとうIE11がサポート終了しますね。

ふるさとチョイスのサーバー構成の変遷

トラストバンクの高橋と申します。 ふるさとチョイスのサービスが立ち上がってからもうすぐ10年。 最初は小さかったサービスが現在に至るまでのサーバー構成の変遷についてまとめてみます。 それほど革新的な内容のないごくごく平凡な構成ではありますが、 これからサービスを立ち上げる方成長途中のサービスを運用している方の参考に少しでもなれば幸いです!

なお、セキュリティ関連については、勿論、IDS/IPS、WAF、DBFWなど色々導入しておりますが、ここでは具体的な内容は伏せさせていただきます。

2012~2013年 サービス立ち上げ期

ふるさとチョイスのキャプチャー (2013年)
ふるさとチョイス (2013年)

サービス立ち上げから間もない時期のトップページです。当初は以下の様なシンプルな構成でした。

f:id:trustbank-developers:20220217235800p:plain
サーバー構成図 (2012年)

サーバーは格安のレンタルサーバー1台で、Cloudflare のフリープランのCDNを通していました。 当初はふるさとチョイス上で寄付を受け付けるという発想は無く、自治体様の寄付金の使い道やお礼の品を紹介するだけのサイトでしたので動的な要素はほぼ無く、WordPress で十分成り立っていました。

2013~2014年 寄付受付開始、絞り込み検索導入

ふるさとチョイスのキャプチャー (2014年)
ふるさとチョイス (2014年)

サービス立ち上げ2年後のトップページです。このときには既に多くの自治体様の寄付を受け付ける仕組みができあがっていました。 他、カテゴリーなどでの絞り込み機能が追加され、ログイン・マイページ機能もできあがりました。 GCF (ガバメントクラウドファンディング)も2013年から始まり、2014年末には長野県白馬村様で最初の災害支援受付が開始されました

このときは以下のようなサーバー構成でした。

f:id:trustbank-developers:20220218003857p:plain
サーバー構成図 (2014年)

さすがにレンタルサーバは厳しくなり、クラウド利用に移行しました。

WebサーバーとDBサーバーは分離されておらずに相乗りしており、各Webサーバーは読み込み時はローカルのDBを参照し、書き込み時はマスターサーバーのDBを参照していました。 ロードバランサーなどは特に用意せず、CloudflareのDNS機能を使って各サーバーに振り分け、Webサーバー間のファイルは lsyncd で同期していました。

大変シンプルな構成ではありますが、それ故にスケールアウト対応も単純。ひたすらWebサーバーをコピーして増やし、Cloudflareで紐付けるだけでした。 今でこそふるさと納税は一般に浸透していますが、当時はまだ知る人ぞ知る仕組み、という感があり、ふるさとチョイスがテレビなどで取り上げられるとアクセスが急増し、その際は何名かで手分けしてひたすらサーバーをコピーする作業をしていました。(オートスケーリングなどの仕組みはありませんでした)

とあるテレビ番組で取り上げられた際は約100台を一時的に並列に並べました。本来、100台も必要になるようなアクセス数ではありませんでしたが、当時はまだ WordPress 上でサービスを開発しており、これが大変重かったため1台で捌けるアクセス数がかなり少なかったです。このときに1台で捌ける量を増やすため、外部のエンジニア様のご支援により Apache から nginx に移行し、nginxのページキャッシュ機能を使うことで、捌ける量が大幅にアップしました。

当時はソース管理には Subversion を使っておりました。(後に 自前GitGitHub と変遷)

2015~2017年 円熟期

ふるさとチョイスのキャプチャー (2017年)
ふるさとチョイス (2017年)

2015年2月に累計寄付件数が100万件を突破してから一気にふるさと納税の利用が拡大し、競合他社様のサービスも次々に立ち上がっていきました。2015年12月には月間1億ページビュー数を超え、 翌2016年6月には、契約自治体数が1,000自治体を超えました (全国1,788自治体中)。

そんな最中でのサーバー構成です。

f:id:trustbank-developers:20220218022411p:plain
サーバー構成図 (2016年)

より堅牢性を高めるため、データベースサーバー用にクラウドとは別に専用サーバーを購入してリモートハウジングし、クラウドサーバー用のネットワークとブリッジ接続する構成を取りました。 また、2015年まではお礼の品のフリーワード検索ができませんでしたが、 Apache Solr を使って検索サーバーを立て、検索ができる仕組みを整えました。

お礼の品の数が大幅に増加し、画像の容量が逼迫してきたので、画像専用のサーバーを別途立てました。画像のアクセスはほとんどCDNが捌いてくれるので、月間1億ページビュー数の最中でも、Core数6のサーバー2台で余裕で捌くことができました。(それでもスカスカでした)

2015年の中頃より、Cloudflareのフリープランから一気にエンタープライズプランに切り替えました。恐らく、日本の企業の中ではかなり早いプラン導入事例だったと思われます。(当時は日本語の窓口も無くお打ち合わせも英語のみでした…)

そして相変わらず WordPress を導入していましたが、これが原因でパフォーマンス上のネックがあったり、開発効率にも大きな支障があったため、徐々に FuelPHPフレームワークを使った実装に置き換えていきました。 当時は PHP界隈において、ここまで Laravel 一強時代になることを予期することができず、今思うと選択を誤ったな…、と感じております。(ただ、フレームワークの根本の考え方はそれほど変わらないです)

また、当時は NoSQL の有用性を理解しきれておらず、Key-Valueストア の代わりとして、もっぱらPHPAPC を用いていました。 ( memcached など色々試してみたものの、満足するレベルではなく、たとえサーバーごとに個別にキャッシュを持つことになったとしてもAPCの方が圧倒的に速く感じた)

ふるさと納税サイトは年末に駆け込み需要があり、大幅にアクセス・寄付が伸びますが、クラウドサーバー上で相乗りによるティーが発生しないように、1台あたりモリー224GB確保するという荒業をとることもありました。(当時は224GB確保すると相乗りされることがなかった)

2018年 ふるさとチョイス全面リニューアル

ふるさとチョイスのキャプチャー (2018年)
ふるさとチョイス (2018年)

2018年3月にふるさとチョイスを全面的にリニューアルしました。 WordPress を完全にやめ、全て FuelPHP で書き直しました。 そして、サーバーインフラ専門の会社様と相談しながらサーバー構成も大幅に見直しております。

f:id:trustbank-developers:20220218032316p:plain
サーバー構成図 (2018年)

今まで、Webサーバー上にDBなど色々なものが相乗りしていましたが、 Webサーバー (nginx)APサーバー (PHP)DBサーバー (MySQL) の3層に分け、各層間はソフトウェアロードバランサーで繋ぎました。 これにより細かいスペック調整が可能になったのと、CPUやメモリーを効率良く使えるようになり、異常発生時の切り分けもしやすくなりました。反面サーバー構成が複雑になったために、異常発生時の緊急対応が知見のある者でないと行えなくなってしまいました。 3層に分けてみて分かったこととしては、Webサーバーはほとんどスペックや台数は必要ありませんでした。APサーバーやDBサーバーはボトルネックになることがかなり多いようです。

また、自治体様が LGWAN ネットワーク上からふるさとチョイスの管理が行えるよう、 J-LIS に申請して LGWANネットワークに参加しました。 DBはインターネットとLGWANの真ん中に配置し、LGWANネットワークからは LGWAN WebサーバーFirewallLGWAN APサーバーFirewall と、2つのFirewallを挟んでつながるようにしております。

2022年~ インフラ改革

ふるさとチョイスのキャプチャー (2022年)
ふるさとチョイス (2022年)

今までトラストバンクはインフラ専任エンジニアがおらず、開発と兼務していましたが、ようやく念願のインフラに特化したエンジニアが合流し、色々な改革に着手しています。 AWSを利用した動的なスケーリングマイクロサービス化デプロイの仕組みの見直しLaravelへの切り替え、その他大小様々な対応の検討が進んでおります。

おわりに

トラストバンクにご興味をお持ちいただけましたら、是非以下の募集ページをご覧ください。様々な職種で絶賛募集中です。

www.wantedly.com

AWS EFSを使ったWordPressシステムをProduction Readyにするまでの道のり

トラストバンクふるさとチョイス事業本部SREの香西(かさい)です。

先月末に「読むふるさとチョイス」というオウンドメディアをリリースしました!

prtimes.jp

本記事ではこのオウンドメディアをEC2+EFS+RDS+ElastiCacheでAutoScalingなWordPressシステムとして構築したので、このシステムをProduction Ready*1にしていくまでの道のりをご紹介したいと思います!

要件

まず要件ですが、今回開発を外部制作会社へ発注していたこともあり、以下のような制約がありました。

デプロイをWordPressプラグインで行い、SFTP経由でのファイル更新するという要件があることで、Gitでソースを管理してCI/CDでデプロイ、ということが難しくなってしまい、プロジェクトから冗長構成でスケールできるようにしてほしいという要件も出てきました。

WordPressは基本的に冗長構成を考慮したアーキテクチャになっていないので冗長化するの大変なんですよね。。。

マスターサーバーを別にしてlsyncdで同期するとか、共有が必要なディレクトリをNFS上に置いたり、画像データをS3に置いたりと色々なやり方はあるもののベストプラクティス的なものは存在せず、デプロイでも悩むことになることも多く、WordPressをスケーラブルでイケてるシステムにするために涙ぐましい努力をしているエンジニア諸氏の屍の上に我々は立っていると言っても過言ではないのです。(過言やろ)

今回はデプロイ部分については制約があり、あまり工夫が出来ないので、どのようにデプロイ要件を満たしつつスケーラブルなインフラにしていけるかを考えていこうと思います。

システム構成

デプロイ

EFS+TransferFamilyの組み合わせで前述のデプロイはWordPressプラグインで行う&SFTP経由でファイルの更新ができるという要件をクリアしていきたいと思います。

Amazon Elastic File System (EFS)AWSが提供するフルマネージドなNFSストレージです。

WordPressプラグインでのデプロイをする&SFTP経由でもファイルを更新したいという要件があるので、EFSを使ってどのサーバーからのアクセスして、プラグイン経由でのデプロイをしても問題ないようにします。

ちなみにEFSでスループットモードをバーストモードにしている場合、クレジットが尽きるぐらいのスループットがあると完全に死ぬことになるのでご注意ください。

そして、クレジット枯渇を回避するためにプロビジョニングモードにするととてもお高いのご注意ください・・・(ツライ

AWS Transfer FamilyAWSが提供するSFTP、FTPS、FTP プロトコルを使用してS3やEFSとデータ転送できるようにするフルマネージドサービスです。

今回のSFTP経由でファイル更新の要件をこのTransferFamilyで実装していきます。

可用性&冗長性

次に、ALB+EC2+AutoScaling+RDSで冗長構成でスケールできるの要件をクリアしていきます。

また、RDS部分のスケールにはWordPressプラグインであるHyperDBを利用します。

ja.wordpress.org

これでWordPressのプログラムファイル群をすべてEFS上に置いて、各EC2サーバーからNFSマウントする形にし、スケールしてどのサーバーにアクセスしても問題がないようになりました。

構成図

全体のシステム構成は図としてはこんな感じになります。

システム構成

しかし、このままではとても遅くて使い物にならないので様々なキャッシュ機構を活用してスピードアップを図っていきます。  

キャッシュ戦略

というわけで以下キャッシュ機構を導入していきます。

  • PHP(OPcache)
  • Redis(ElastiCache)
  • CDN(Cloudflare)

PHP (OPcache)

PHP処理の高速化を実現する代表的な方法の一つにバイトコード(オペコード)をキャッシュする、所謂PHPアクセラレータを利用する方法があり、 そのPHPアクセラレータ機能を提供するのがOPcacheです。

OPcacheの導入方法は環境毎に異なるので割愛しますが、以下のようにopcache.enable => On => OnとなっていればOPcacheは有効になっています。

$ php -i | grep opcache
Additional .ini files parsed => /etc/php.d/10-opcache.ini,
opcache.blacklist_filename => /etc/php.d/opcache*.blacklist => /etc/php.d/opcache*.blacklist
opcache.consistency_checks => 0 => 0
opcache.dups_fix => Off => Off
opcache.enable => On => On
opcache.enable_cli => On => On
opcache.enable_file_override => Off => Off
opcache.error_log => no value => no value
opcache.file_cache => no value => no value
opcache.file_cache_consistency_checks => On => On
opcache.file_cache_only => Off => Off
opcache.file_update_protection => 2 => 2
opcache.force_restart_timeout => 180 => 180
opcache.huge_code_pages => Off => Off
opcache.interned_strings_buffer => 8 => 8
opcache.lockfile_path => /tmp => /tmp
opcache.log_verbosity_level => 1 => 1
opcache.max_accelerated_files => 10000 => 10000
opcache.max_file_size => 0 => 0
opcache.max_wasted_percentage => 5 => 5
opcache.memory_consumption => 128 => 128
opcache.opt_debug_level => 0 => 0
opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF
opcache.preferred_memory_model => no value => no value
opcache.preload => no value => no value
opcache.preload_user => no value => no value
opcache.protect_memory => Off => Off
opcache.restrict_api => no value => no value
opcache.revalidate_freq => 2 => 2
opcache.revalidate_path => Off => Off
opcache.save_comments => On => On
opcache.use_cwd => On => On
opcache.validate_permission => Off => Off
opcache.validate_root => Off => Off
opcache.validate_timestamps => On => On

細かいチューニングは環境毎に異なるため割愛しますが、一般的に推奨される設定について興味がある方は以下を参考にしてみてください。 まぁ、ほぼデフォルトで推奨設定になっていますがw

www.php.net

Redis(ElastiCache)

オブジェクトキャッシュとPHPセッションの保存先にRedisを利用するため、ElastiCacheでRedisを構築します。

オブジェクトキャッシュ

オブジェクトキャッシュには以下のWordPressプラグインを利用します。

ja.wordpress.org

このプラグインを利用することによりWordPressのオブジェクトキャッシュにより生成された値をキャッシュすることができ、同じオブジェクトに対して2回クエリを実行せずに、キャッシュされたオブジェクトを再利用できるので、DBの負荷を軽減し、ウェブサイトの応答時間を短縮させることができます。

設定としてはwp-config.phpに以下のようにエンドポイントを指定してあげるだけです。

define('WP_REDIS_HOST', '{Redisエンドポイント}');
define('WP_REDIS_MAXTTL', 3600);

PHPセッション

AutoScaling環境となるため、PHPセッションをRedisに保存&共有できるようにして可能性を高めたいのでphp-redisを利用します。

github.com

インストール方法については各環境毎に異なるため割愛しますが、基本的な設定としてはphp.iniに以下設定を入れるだけです。

session.save_handler = redis
session.save_path = "tcp://{Redisエンドポイント}:6379"

CDN(Cloudflare)

最後に弊社ではCDNにCloudflareを利用しているのでCloudflareのチューニングをしていきます。

キャッシュレベルはスタンダードのまま、以下の機能を有効化しました。

機能 概要
Mirage 画像の読み込みを高速化
Polish 画像のメタデータ除去&最適化によりダウンロード速度を改善させる
Rocket Loader Time to First Paint (TTFP)、Time to First Contentful Paint (TTFCP)、Time to First Meaningful Paint (TTFMP)、Document Loadのパフォーマンス向上
Auto Minify HTML/CSS/JavaScriptのサイズ縮小による転送量削減

詳細な説明は公式に委ねますが、基本的には上記設定を有効化するチューニングのみを実施しました。

Lighthouseの結果

前述のキャッシュ関連の設定を実装した状態でのLighthouseの結果は以下のようになりました。

Lighthouse結果

サーバーの台数を増やすことでスループットが伸びることも確認できたのでこれでProduction ReadyなWordPressシステムと言えるようになったのではないでしょうか!???

おわりに

今回EFSを使ったWordPressシステムを構築してみて、EFSはWordPressで冗長構成を取りたい場合には中々良い選択肢になり得るのかなと思いました。

以前は遅くて使いにくいイメージがありましたが、読み取りスループットが3倍になるアップデートもありCDNありきではありますが、Production ReadyなWordPressシステムのアーキテクチャの選択肢としては悪くないのではと現在は思っております。

aws.amazon.com

そしてまた大きなアップデートがあったのでこれはもうEFSっきゃないっしょという状況ですね。

www.itmedia.co.jp

本記事がProduction ReadyなWordPressシステム構築に挑戦しているエンジニア諸氏の参考になってくれると嬉しいです。

そんな弊社トラストバンクでは様々な職種で絶賛採用中なので気になった方は是非お気軽にご連絡くださいー!

www.wantedly.com

*1:Production Readyとは、サービスが本番トラフィックを任せられるほどの信頼性がある状態、その状態にするための考え方