TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

587

こんにちはエンジニアリングマネージャーをしております植田です。4月18日に グロースプラン の提供が開始されました。今回この開発プロジェクトにて「リスク管理」に取り組んでみたのでそのお話をします。 Index リスク管理に取り組んだ背景 そもそもリスク管理とは 具体的なリスク管理の進め方 リスク管理はどのようなプロジェクトで実行すべきか 実際にどのように取り組んだか 取り組んで良かったことと、今後発展させたいこと リスク管理に取り組んだ背景 まず今回なぜリスク管理に取り組んだかをお話します。BASEでは現在大小様々な開発プロジェクトが同時進行していますが、日に日にその複雑性は増しています。年月を追うごとに積み重なる仕様、日に日に拡大していくリポジトリ(ソースコード群)…と、複雑性・難易度は増す一方です。その中で、開発プロジェクトもいわゆる「不確実性が高い」と言われることが当たり前の状況になっています。不確実性とはつまりリスクのことです。この不確実性・リスクに立ち向かうにはリスク管理を行っていくことが有効な手段であると自身の経験から考えました。冒頭に触れたグロースプランプロジェクトは 「ミッションクリティカルなプロジェクト」 であったこと、 「複数のチームからメンバーが集まり横断的に開発するタスクフォース型のプロジェクト」 であったこと、という点でリスク管理をするにはもってこいのプロジェクトであると考え、関係者に呼びかけリスク管理に取り組んでみることとなりました。 なお、私自身はPMBOKをベースとしてリスク管理を学びましたので、もしご興味がある方はPMBOKの書籍等もあわせてご覧いただけると嬉しいです。 そもそもリスク管理とは リスク管理とは、プロジェクトにおける潜在的なリスクを洗い出し、リスクが顕在化しないように先手を打つプロジェクト管理手法の1つです。また、仮にリスクが顕在化してしまった時のためにあらかじめ対処を決めておくことで問題の影響を最小限に抑える行為です。リスク管理を行うことで、問題を防ぐことができ、また問題に発展した場合も即座の対応が先回りしてできるため、言わば転ばぬ先の杖として機能することができます。プロジェクトマネジメントの格言として 「リスクを制する者はプロジェクトを制す」 といった言葉があるほどリスク管理は重要なプロセスですのでぜひご参考いただきたいです。 具体的なリスク管理の進め方 具体的なリスク管理表を見て頂いたほうが理解しやすいので下記のサンプル表をご覧ください。 ※サンプルのためグロースプランプロジェクトの内容とは関連ありません このようなリスク管理簿を作成し運用していきます。サンプルのためシンプル化していますが、実際には、起票者、起票日、備考欄など管理上必要だと思う項目は適宜追加ください。 続いて実際の進め方、サイクルについて解説します。 こちらの図のように1週間程度のサイクルで「リスクの特定→対策の立案→対策の実行→モニタリング」を繰り返すのが基本です。 ①リスクの特定と優先順位付け プロジェクトのキックオフが開始されたあとはできるだけ早期にリスク特定のための会議を開きましょう。リスクはプロジェクトが開始された時点から潜在的に存在するものでプロジェクトの脅威となるものです。1日でも早くリスクを特定し対策を実行することでプロジェクトの成功確度が高まります。1〜2時間ほど関係者にてリストアップを行いましょう。 リスクが洗い出せたあとは、リスクに対して優先順位を決めていきます。仮にリスクの洗い出しを行って20件のリスクがリストアップされたとしても、愚直にリストの上から順番にすべて対処をするのは効率的ではありません。発生確率と影響度合いを決定し、それらをかけ合わせた上で優先度を決定し、優先度が高いものから対処を検討していきましょう。なお、発生確率と影響度合いは3段階で決定しますが、こちらは当事者で相談して決定すればある程度感覚値であっても問題ありません。優先度が決まればよいので、この数値に厳密性は必要ありません。 ②リスクへの対応戦略(対策の立案) リスクには「リスクが顕在化しないようにする事前対策」を実行しモニタリングすることが有効な手段です。リスクへの対応戦略は以下の4種類に大別されます。 種類 説明 回避 リスクを避けたり、リスクの発生原因を取り除いたり、リスクの影響を避ける為にプロジェクトの計画を変更すること 転嫁 リスクの影響を責任とともに第三者へ移転すること 軽減 リスクの発生確率および影響度をプロジェクトが許容できる程度まで低減すること 受容 事前対策を講じないと決めること、リスクの除去が困難な場合や、万が一リスクが顕在化しても大きな影響を及ぼさないときに採択される これらをリスクごとの基本方針としプロジェクトメンバーと共に検討し、対応策を決定していきます。優先度の高いリスクへの対策に関しては第三者や上位層へレビューしてもらうことでより有効な対応策にブラッシュアップしていくこともできます。リスク管理を繰り返していくことで、対策を考えるコツも掴めてきますので、当事者でどんどんアイデアを出し合ってよりよい対策を立案していけるといいですね。 ③対策の実行 リスクのリストアップ、優先順位付け、対策が決まれば、優先順位の高いリスクから1つずつ対策を実行していきましょう。担当者(担当チーム)、対応期日を決めきちんと対応していくことでプロジェクトが実を結びます。 ④モニタリング 実行プランができあがり対策が順次開始されたあとは ”モニタリング” が重要となってきます。新たなリスクが生まれていないか、既存リスクが問題となって顕在化してこないか、リスクが顕在化した場合はすぐに課題の対策を実行する、といった動き出しが必要です。なお、プロジェクトが終了する時期にリスクをすべて解消・解決している必要はありません。優先度が低いもの、リリースに特段の影響を及ぼさないものは無理に解消する必要はありませんので、現実とも向き合いながらモニタリングしていきましょう。 リスク管理はどのようなプロジェクトで実行すべきか リスク管理が有効だからといって、動いているすべてのプロジェクトで実行する必要はないでしょう。ではどんなプロジェクトで活用すればよいかですが、例えば以下のような項目にあてはまるプロジェクトではリスク管理の適用を検討してみると良いと考えていますのでご参考ください。 期間的:3ヶ月以上かかるようなプロジェクト 技術的:技術的に難しい、新しい技術を導入するなど 人的:リソースが不安定、新規の顔ぶれ、ステークホルダーが多いなど その他:イレギュラー要素があるなど 実際にどのように取り組んだか 今回はトライアルでグロースプランプロジェクトにて取り組んでみたので、まずは関係するマネージャー数名で始めてみました。以下は具体的な経過です。 1回目(1時間):初回はまず想定されるリスクをみんなでリストアップし、結果20件ほどのリスクが洗い出されました。この日は発生確率と影響度合まで相談し優先度を決定しました。 2回目(1時間):翌週の2回目は優先度9〜6までのものに対してリスクの対策を検討しました。時間があればすべての対策を考えてもよいのですが限られた時間を有効活用し優先度の高いものにフォーカスして対策を決定しました。 3回目(1時間):さらに翌週の3回目は優先度9〜6までのリスクの対策の進捗確認と、優先度6未満の対策を検討しました。 4回目以降は基本的にリスクの対策の進捗確認がメインとなり、プロジェクトがリリースされるまで毎週30分程度集まりました。 大事なことはリスクの対策が進むに従い、それぞれのリスクのステータスを未着手→対応中→モニタリング→クローズ、と変更していくことだと思っています。こうすることで、プロジェクトにおけるリスク管理が進捗していることがステータスの遷移と共に把握できます。 今回は1週間に1度の頻度でリスク管理定例を開催しましたが、開催頻度は現場で相談の上決定いただければよいかと思います。また、今回のプロジェクトは3ヶ月程度の期間でしたので、リスク管理定例も合計10回程度開かれたのが実績となります。 取り組んで良かったことと、今後発展させたいこと 取り組んで良かったこと 20件程度のリスクがリストアップされ、リスク顕在化の抑制が出来たり、リスクが顕在化したときにあらかじめ想定した通りの対処が実行されたこと リスク管理が実施されていなければ、問題が発生してから慌てて解決に動いていたであろう事象が複数あったこと ステークホルダーに対してプロジェクトにおけるリスクの件数を定量的にレポートできたこと 今回初めて取り組んだマネージャーからリスク管理終盤に「やってよかったですね」と率直な感想をもらえたこと 今後発展させたいこと 開発チームメンバーとともにリスク管理に取り組みたい 今回はトライアルだったこともありまずマネージャーのみでリスク管理に取り組みましたが、開発プロジェクトのリスクを一番知っているのは開発に向き合っているメンバーだと思いますので、ぜひ今後はマネージャーとメンバーが一緒になってリスク管理をやっていけたらと思っています。 リスク管理の横展開 今後少しずつ社内で横展開を行いリスク管理をしているプロジェクトを増やしていきたいと考えています。 以上がBASEでリスク管理に取り組んだお話です。ぜひ皆様もリスク管理に挑戦いただけると嬉しいです。
はじめに 基盤チームでバックエンドエンジニアをやっている松田( @tadamatu )です。 以前にCTO川口が当ブログ内で公開した以下の記事があります。 devblog.thebase.in 新規接続の限界 BASE のアクセス量の伸びは凄まじくこの構成でも接続エラーが発生するようになってしまいました。 ピーク時に秒間 2 万もの新規接続が primary インスタンスへ行われているといった状態です。 この記事が公開されたのが約2年前で、 当時100万程度 だったショップ数は 170万を超え 、我々はまだまだ伸ばしたいと考えています。 これは、ショップ数の伸びとともに、指数関数的に増えていくユーザのアクセスを捌く必要があることを意味します。 ブログ公開当時、我々はさまざまな検討の末、以下のような対策を取りました。 残された手段は primary のインスタンスに対しての接続数を如何にして減らすか、ということのみです。 ここで出来ることアプリケーション側の接続をいかに reader インスタンスに行うか、ということです。 これはクエリを reader に向けるというよくある負荷対策ではなく、新規接続自体を reader に最初から向ける 対応後もメディア露出などによりアクセスが集中すると、たびたびMySQLへの新規接続がボトルネックとなり、接続しづらい状態に陥っていました。 そこでBASEでは、 RDS Proxy が2020/06にGAされた こともあり、検証を開始し、導入するまでに至りました。 aws.amazon.com 本記事では、そこで得た 知見 、 PHPとの相性はどうなのか 、 検証した方法 、 導入後どの程度効果があったのか 、 導入後発生した障害 、など、RDS Proxy導入における メリット・デメリット を公開させていただこうと思います。 RDS Proxy導入方法など基本的なことは他の記事に譲ります。この記事では 実際の導入を通して得たTips をできるだけ網羅しましたので、 RDS Proxyが気になっている方 や 導入を検討している方 のお役に立てれば嬉しいです。 RDS Proxyとは まず、 RDS Proxy を導入することで公式に記載されているメリットを簡単に書いておきます。 aws.amazon.com  (1) 接続プーリングによるパフォーマンスの向上  (2) フルマネージド  (3) フェイルオーバー発生時の時間短縮などによるアプリケーションの可用性向上  (4) IAM 認証を利用しセキュリティレベルの向上(オプション) などいくつかありますが、もちろん我々が一番得たかったメリットは (1) になります。 導入前の検証 さて、検証を始めていくわけですが、ポイントとなったものを記載していきます。 アプリケーション的な障壁はほぼなかった まず手始めに実際にDEV環境にRDS Proxyを構築してみて、アプリケーションからのDB接続をRDS Proxy経由に切り替えて検証をしてみます。 これに関してはQAチームに協力をしていただき、一通りリグレッションテストを通すことで検証、動作担保を行いました。 結果、あっさりリグレッションテストをパス! つまり、RDS Proxyの切り替えに関してコードの変更はほぼなしで可能で、 コード変更するのは接続先の切り替えくらいというライトな感覚で切り替えが可能 ということが分かりました。 (後述しますが、BASEではRDS Proxyの切り替えで、全くアプリのコードは変更しませんでした) 費用面について見積もり RDS Proxyは RDSインスタンスのvCPUの総数 に応じて費用がかかります。 aws.amazon.com BASEでは、過去に障害が発生していた経緯もあり、安定運用のため、少し力技でAuroraのインスタンスを増やし、インスタンスサイズも大きくしたものを利用していました。 そのままシフトしてRDS Proxy採用した場合で見積もると、 年間300万円程度の上乗せ費用が発生する ことが分かりました。 決して小さな額ではないですが、BASEでは将来の接続安定性が得れるのであれば導入しようということで、検証を続けました。 しかし導入後、力技分のインスタンスを減らし、インスタンスサイズも落とす、という調整ができたことで、結果的にRDSの削減分も含めると 年間960万円もの削減 ができる計算になります。 これは非常に大きなメリットでした。 ピン留めが心配 RDS Proxyを導入するにあたって、一番気になっていたのは ピン留め という現象です。 ピン留めを回避する より抜粋 RDS Proxy は、他のセッションに不適切なセッション状態の変化を検出すると、クライアント接続を特定の DB 接続に自動的にピン留めします。ピン留めにより、接続の再利用の有効性が低下します。 ピン留めは CloudWatch の DatabaseConnectionsCurrentlySessionPinned メトリクスによって確認ができます。 検証の結果、BASEのアプリケーションからの接続では ピン留めが全く発生しませんでした 。 BASEではCakePHPをフレームワークとして利用していますが、PHPは一般的に(ミドルウェアなどで接続プーリングを実装していない限り)リクエストのたびに接続・切断を繰り返すため、ピン留めの影響を全く受けなかったのではないかと推測しています。 ちなみに、この悪魔の証明とも言えそうなメトリクスに表示されないピン留めをどのように検証したかというと、まずRDS Proxy経由でmysqlクライアントにより接続し、 SET ステートメントや LOCK TABLE 操作をすることにより無理やりピン留めを発生させ、メトリクスとしては検知できることを確認します。 その後、QAのリグレッションテストを通したときにピン留めメトリクスが検出されないことを確認することで発生していないと結論づけました。 (実際、本番リリース後もこのメトリクスはほぼ検知されませんでした) 負荷試験について BASEでは、負荷試験用のDEV環境があります。 今回は、その環境で動かすアプリケーションからのDB接続を、  (A) RDS直接接続した場合(現状の環境想定)  (B) RDS Proxy経由し接続した場合 で比較し、検証しました。 検証する軸としては以下の2軸  (1) インスタンスタイプによる調整  (2) スクリプトによりアクセス数を調整 まず (A) の限界値 を見極めます。 (1) を本番と同じものに固定し、 (2) のアクセス数を上げていき、 エラーが出る限界値 をだしました。 次に、 (B)の限界値 を各軸で上下させることで出していきます。 (2) を固定にして、 (1) を下げていきます。 結果、 インスタンスタイプを1/4 サイズに落としてもエラーが出ないという好成績 に。 これ以上インスタンスタイプを落とし続けても意味がないため、次に (1) インスタンスタイプを 1/4 の状態で固定し、 (2) のアクセス数を増やし限界値を見極めます。 こちらも結果として、 5倍以上のアクセス数にしてもDBのエラーが出ません でした。なんと、ボトルネックがRedisやApacheなど他に移ってしまい、限界値が計測できなかったくらい効果がありました。 接続切断を繰り返すというオーバーヘッドがそれほど大きかったのだろうという結論にいたりました。 PHPと相性がとても良かったという結果だと思います。 カスタムエンドポイント問題 RDS Proxyを導入すると、 DBへの接続はRDS Proxyがcluster単位でコントロール をするようになります。 BASEでは、DBレプリケーションをclusterに依存し、Writer/Reader構造を利用して運用をしていました。 少し特殊な部分として、調査や分析などでサービスへの影響を与えないよう、Readerのうちの1つを分析用として利用し、他のDBをカスタムエンドポイントによりDB接続をコントロールしていました。(下記イメージのAS-ISの部分) RDS Proxyを導入すると、すべてのDBがコントロール対象となってしまい、サービスに影響を与えず分析できるDBが確保できないという状態になるため少し問題です。 ここで私たちが取った解決策としては、 Binlogによるレプリケーションした別clusterを自分たちで構築 しました。(下記イメージのTO-BE) Binlogによるレプリケーション構築 余談ですが、実は我々がRDS Proxyの導入をする上で一番手を焼いたのが、この別clusterをつくるという部分でした。 スナップショットをとり、そのピンポイントのタイミングのポジションをとり、別clusterでBinlogのレプリケーションを開始する。この操作がダウンタイムなしでは難しく、メンテナンスコントロールなど必要でした。 ただこれは、BASEの独特な環境の問題なのでRDS Proxy導入で必須ではありません。 MySQLユーザのアクセスコントロール RDS Proxyを導入する場合、 RDS Proxyに対してMySQLへのアクセス情報(ユーザ/パスワード)をアタッチしておく必要があり ます。 これは、すべてのアクセスをRDS Proxy経由にした場合、MySQLに登録してあるユーザをすべてアタッチしておく必要があるということです。 ユーザを増やした場合、都度追加する必要があります。権限管理のためにユーザも増やすこともよくありますし、管理的にも煩雑でよくありません。 そこで私たちは小さくルールづけをして、コントロールすることにしました。以下のようなものです。 - SREがコントロールするユーザに関しては、RDS Proxyを経由してアクセスをする - 基本的にはサービスからのアクセス - それ以外のユーザに関してはRDS Proxyを経由せずアクセスする 前者のユーザを増やした場合のみ、アタッチが必要となります。たとえアタッチが漏れたとしても動作検証時に発見できます。 一般ユーザ追加などの頻度の高い操作ではアタッチが不要です。DB操作開発者が増えてユーザ作成したが、DBにアクセスできない、なんでだぁ、といった混乱が防げます。 いざリリース 接続エンドポイントの切り替え BASEでは、アプリケーションからのRDSへの接続は clusterのエンドポイント を利用せず、Route53経由することで行なっています。 リリースはこれを RDS Proxyのエンドポイント に Route53上で切り替えることで実施 しました。 実行はアクセスの少ない深夜帯に、ダウンタイムなし(ユーザサイドから見たメンテナンス状態にせず)で切り替え完了。 切り替え後、モニタリングでも逆に心配になるくらい何も問題なく、レイテンシーが少し発生しているなーくらいで完了しました。 リバート 実際はしなかったのですが、リバートについてもRoute53上でエンドポイントを戻すだけというライトなものでしたので、今回は思い切った実施ができました。 心の安心は大切ですね。 (余談)切り替え後、接続が増え続け不安に 切り替え直後、DBへの接続が24時間のあいだ一方的に増え続けモニタリングしていて不安になりましたが、原因は以下の通りでした。 MaxIdleConnectionsPercent より抜粋 RDS プロキシは、データベース接続が使用されなくなった 24 時間後にその接続を閉じます。プロキシは、アイドル状態の最大接続数の設定値に関係なく、このアクションを実行します。 以下は、 CloudWatch の DatabaseConnections メトリクスなのですが、切り替え直後は24時間で接続がガクンと落ち、その後すこしづつ平準化されていく動きになっていることがわかると思います。 RDS Proxy切り替え後のDatabaseConnectionsメトリクス 導入後の測定・効果 ConnectionAttemptsが無風に これまでの障害で接続サージが発生していた時は、 新規接続を試す数である ConnectionAttempts というメトリクスが異常な値を示して いました。 RDS Proxyを導入する前までは、それなりの数値を示していたのですが、 導入後は接続プールでコントロールされるため1桁 になりました。 これも1つの安定接続を得たという証拠となります。 (グラフではゼロに見えますが実際は少しあります。それだけ減ったということです。) RDS Proxy切り替え時のConnectionAttemptsメトリクス 接続が安定 前述しましたが、 DatabaseConnections が非常に安定 しました(下図)。 導入前に比べて若干増加してるのは、RDS Proxyがアイドル状態になったコネクションを開いたまま保持するためと考えられます。 アイドル状態のコネクション保持最大値については、 MaxIdleConnectionsPercent という値で設定できますので環境に合わせて調整すると良いと思います。 RDS Proxy切り替え後のDatabaseConnectionsメトリクス 下図は、最大接続数のグラフなのですが、多くても 1,200 程度にしかなっていません。 RDSに設定してある max_connections が 12,000 なので、現状は かなりの余裕がある結果 となっています。 RDS Proxy切り替え後の現在の最大接続数 Readerインスタンスが減らせた 上記の接続安定の結果を受け、接続に余裕をもてると分かったことで、 Readerインスタンスを1つ削除 できました。 削除した後も CPU使用量が少し増え 、 各DBのネットワークスループット量が増えた くらいで、想定通りの結果となりました。 メモリ使用量は増えませんでした。 下図は削除前後の CPU使用量 (CPUUtilizationメトリクス) なのですが、削除後は 30-35% で遷移しており、これまで接続安定化のために無駄にサイズを上げてましたが、むしろ最適化できたのではないかと考えています。 Readerインスタンス削除時のCPUUtilizationメトリクス 削除して以降、問題は発生しておらず、 費用削減に貢献 しました。 Writerインスタンスのサイズを下げることができた さらに接続安定の結果を受け、 Writerインスタンスを半分にする ことができました。 サイズを下げた後もReaderと同様で CPU使用量が少し増え たくらいで、問題は発生しておらず、 費用削減に貢献 しました。 ここで特筆すべきは、Writerインスタンスのサイズを下げたときの フェールオーバーの動き です。 サイズの変更は、まずcluster内に「切り替え先となるDB」を事前に作成し、フェールオーバーさせることで切り替え、切り替えた後に「切り替え元のDB」を削除したのですが、ここでもRDS Proxyのおもしろい動きを見てとることができました。 下図は、フェールオーバーさせたときの RDS Proxyのメトリクス です。 切り替えた瞬間、 ClientConnections が急激に増え 、クライアントからの接続をRDS Proxyが待機させていることが見て取れます。 それと同時に DatabaseConnections が急激に増えている ことが分かります。これはおそらく待機させた接続をいっきに捌くためだと考えられます。 Writerインスタンス変更時のRDS Proxyのメトリクス そして下図が QueryResponseLatency メトリクス です。 これを見ても最大で4秒ほど待機させていることが分かります。 つまりユーザから見ると、「あれ?ちょっと遅いな」程度で済んでいるということです。 Writerインスタンス変更時のQueryResponseLatencyメトリクス もちろんエラーが全くなかった訳ではありません。Sentryにエラー 2006 MySQL server has gone away が出力されていましたが、かなり少なく通常のフェールオーバーと比べるとかなり小さな影響で済んだと言えると思います。 そして下図が Database側から見た DatabaseConnections メトリクス(1日以上経過したもの) です。 前述しましたが、フェールオーバー直後リクエストを捌くため一気に増え、またアイドル状態で24時間経過したものから解放されていくというのが見て取れるメトリクスになっています。 24時間後は接続も必要分だけとなり、安定しています。 Writerインスタンス変更時のDatabaseConnectionsメトリクス RDS Proxyの特徴の一つである「(3) フェイルオーバー発生時の時間短縮などによるアプリケーションの可用性向上」を確認することができました。 レイテンシーが発生する AWS公式 では応答時間に 平均5ミリ秒/クエリ のネットワークレイテンシーが発生すると記載があります。 BASEでも例外なく、導入したことで APIのレスポンスタイムが 0.05〜0.1秒 程度増加 しました。 BASEではRDS Proxy により多段になったことによる、仕方のないコストとして、将来のアクセス安定性を取り許容することにしました。 RDS Proxy導入後のレイテンシー モニタリングの統合について BASEでは監視サービスとして Mackerel や New Relic を利用し、モニタリングしているのですが、RDS Proxyのメトリクスをそれらに 統合できませんでした。 ( 統合することはできます(下に追記あり) ) RDS Proxyは、まだまだ利用者が多いと言えず監視サービス側で適用されていないのだと思います。 今後、RDS Proxyの利用者が増えてくれば対応されてくる可能性もあるかもしれません。 以下が、各サービスのメトリクスになりますので、状況はこちらで確認ください。 Mackerel - AWSインテグレーション - RDS NewRelic - Amazon RDS monitoring integration 2023/03/10 追記 上記ページのメトリクスは旧来からのAPI Pollingの対応表であり、新規の対応をしていないためだそうです。 CloudWatch Metric Streams での対応をすることで、統合することが可能です。 (New Relic の担当者様からご連絡いただきました。ありがたい。) 導入後に発生した障害 ここは検証で発見できなかったBASEで発生した障害(失敗談)になります。 つまり環境に依存した内容も含まれますが、参考になればということで書いておきます。 長時間接続問題 BASEサービスには常時動作しているWorkerがあります。 こちらもPHPで実装されており、基本は プロセスが生きている間はDBへ接続されっぱなしになっているのですが、これが影響しエラーが発生 しました。 RDS Proxy の導入前は、 MySQLの設定値 wait_timeout = 28800 (8時間・デフォルト値) により長時間接続ができていた のですが、RDS Proxy の導入後は、 前段のRDS Proxyサイドでタイムアウトにより切断されてしまう ことが原因でした。 RDS Proxy には、 アイドルクライアントの接続タイムアウト(IdleClientTimeout) という設定があり、これが 30分 になっていたため 、タイムアウトがこれまでよりも短時間で頻発するようになっていました。 今回はこれをMySQLの時間と同じ 8時間 に設定する ことで解決しました。 デフォルト値が 30分 ですので、DB接続を長時間維持する部分がある場合は注意が必要です。 大量のレコードが発生するものは極端にレイテンシーが発生する RDS Proxy導入後、あるページが特定ユーザで表示できない現象が発生しました。 原因調査した結果、 SQLクエリの結果が大量レコードを返す ことが分かり、RDS Proxy導入後、そのクエリだけが異常に遅くなっていることが分かりました。(数秒程度だったレスポンスが、数分かかるようになっていた) 転送レコード量が適切になるよう修正する ことでこれを解決しました。 公式に記述はないのですが、多段になったことで転送が量に応じて時間がかかるようになったのではないかと想像しています。 実際、DBクライアントなどで、大量レコードを返す同じクエリを、RDS直接続と、RDS Proxy経由接続した場合で比較すると明らかにレスポンスタイムが変わって来ます。 大量レコードを返す処理がある場合には注意ポイントだと思います。 クエリ数の調査 障害ではないですが、前述した通りクエリ数に応じてレイテンシーが発生することが分かっていたので、後追いで1リクエストのクエリ数の調査を実施しました。 調査の結果、N+1問題がちらほら発生していることが確認できたので、ひどいものに関しては即時対応を実施しました。 ここはRDS Proxy関係なくケアしていきたいところです。 RDS Proxy 自体の設定変更は実行中も導入ユーザへの影響はない 上記に記載したような、いくつかのトラブルが発生し、RDS Proxyの設定を変更したい場合がありました。 アイドルクライアントの接続タイムアウト(IdleClientTimeout) の調整や、RDS Proxyの ログ記録を有効 にしたい場合などです。 AWSサイドにも確認をしましたが、これらの変更はダウンタイムなしで切り替わるため、気軽に変更しても大丈夫のようです。 まとめ BASE が RDS Proxy を導入することで得たメリット・デメリットをまとめると以下のようになります。 メリット MySQLへの接続が超安定 ボトルネックが別に移ったと言っても過言ではないほどの安定接続を獲得できた 費用的なメリットあるかも 本来、RDS Proxyにはコストがかかるが、RDSのインスタンスを減らすことができたため、実質コストダウンになった PHPとの相性が非常によかった 毎アクセスで接続切断を繰り返すような言語やアプリケーション( PHP 、 サーバーレスアプリケーション など)とは相性が良い可能性あり デメリット (今後の課題や許容した副作用) 若干のレイテンシーが発生する BASEではRDS Proxyを挟む分、仕方のないコストと考えた プロジェクトによっては許容できない可能性もあるので注意が必要 現状は監視サービスでモニタリング統合ができない BASEではRDS Proxyを導入することで、安定した接続ができるようになり、今後増えていくであろうアクセスを迎え入れる準備もでき、非常に大きなメリットがあったと考えています。 この記事が、皆様のRDS Proxy導入検討の参考になれば嬉しいです。 以上、BASEでのRDS Proxy導入レポートでした。 最後に BASEでは、このように提供するサービスを利用してくださる購入者やショップオーナーの皆様のことを第一に考えて、様々な角度からサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。 open.talentio.com
2022/4/28(木)にオンラインで開催された「 \非公式/ Go Conference 2022 Spring スポンサー企業4社 アフタートーク 」にBASEからも2名が登壇しました。 andpad.connpass.com イベントについて 今回登壇させていただいたイベントは先日開催された「 Go Conference 2022 Spring 」にスポンサーとして協賛していた4社で行われたイベントです。主催のANDPADさまお誘いありがとうございました。 gocon.jp 当日は弊社含めて次の4社(敬称略)のGopherの発表とパネルディスカッションが行われました。 株式会社Showcase Gig BASE株式会社 株式会社LayerX 株式会社ANDPAD (順番は当日の発表順) 当日のTwitterハッシュタグは #gocon2022_4sponsor でした。 BASEとGo Conference 2022 Spring 先日行われたGo Conference 2022 SpringでBASEが行なったスポンサー内容、メンバーによる登壇内容については次の記事をご参照ください。 devblog.thebase.in 登壇内容について アフタートークではBASE BANKチームの清水( @budougumi0617 )と永野( @glassmonekey )が登壇しました。 当日はYouTubeで配信され、録画は次のとおりです。 www.youtube.com ANDPADさまの登壇内容については次の記事を参照ください。 \非公式/ Go Conference 2022 Spring スポンサー企業4社 アフタートーク LT内容の解説 〜ExcelとShift-JISとの闘争編〜 - ANDPAD Tech Blog New Relicを使った Observabilityの実現方法と活用例 by @budougumi0617 BASE株式会社BASE BANKチームにて、 テックリードをしている清水( @budougumi0617 )です。 アフタートークでは私たちBASE BANKチームでNew RelicをどのようにGoアプリケーションに導入しているかを発表させていただきました。 Goはアノテーションプログラミングなどが出来ず、明示的にSaaSのSDKの呼び出しをコードの中で実装する必要があります。そのため何も考えずにNew Relicの計装コード(計測用のコード)を適用していくと、次の課題が挙げられます。 新規機能・新規エンドポイントを開発するたびにNew Relicの計装コードが必要になる ビジネスロジックのレビュー以外にNew Relicの対応が漏れていないかレビューする必要がある 発表ではこれらの課題について我々がどのようにアプローチをしているのかサンプルコードと一緒に紹介させていただきました。New Relicを使っていない方にもひとつの設計パターンとして参考にしていただけるかと思います。 Goで始めるTDD by @glassmonkey おなじくBASE株式会社BASE BANKチームにて、 Engineering Program Manager をしていると永野( @glassmonekey )です。 今回のアフタートークではGoでTDDをしつつ小さく作っていくプロセスに関して話をさせていただきました。 TDDを”どう”やるのかという話はよく聞くと思いますが、TDDを"なぜ"やるのかの一例をお伝えできたなら幸いです。 今回は Go Conference 2022 Spring で発表の題材にした姓名分割ツールの glassmonkey/seimei を題材にしました。 github.com 他の方が作ったコードながらも、試しながら作ることで少しずつ理解を重ねつつコードを書くことができました。そのおかげか2~3日ぐらいの短期間ながらも正確に実装することができました。 まさに @t_wada さんが仰ってた 質とスピードの話 を体験することができました。 今回の場合だと質を 移植元のツール(rskmoi/namedivider-python) が質を担保してくれていたというのも作業としては進めやすくはありました。 github.com また、発表後に @serima さんが言ってた点はまさにこれで、昨今のDevOpsの文脈で言われるデプロイ頻度が重要と言われる点もそうですが、開発者個人にもフィードバックループの回数は品質において重要な要素だと再認識しました。 Goはそういった意味でもコンパイル言語ながらもコンパイル時間が短く最高です。 今日の @glassmonekey さんの発表を聞いてこれを思い出したんだった! いかに「小さく早く」失敗に気付ける仕組みをつくるか〜みたいな話ですね。OGP にもなっているこの図が秀逸! https://t.co/E1nXHVYsn1 — serima | LayerX (@serima) 2022年4月28日 最後に改めて元ツールの作者である @rskmoi さんには感謝を述べたいと思います。世界で2番目にアルゴリズムへの理解が深まったと自負してるので、今後フィードバックは適宜していきたいなと思ってはいます。 昨日の #gocon で、私が個人開発してるPythonのOSSをGoに移植したLTがありました。私の実装を読んで完璧に移植して頂けたこと、Goの特性を生かしてCLIとしての性能を格段に上げて頂けたことなど嬉しいことばかりです🙌 https://t.co/buFlswv6iV — Rei Sakamoto (@rskmoi) 2022年4月24日 パネルディスカッションについて 各個人の発表後は4社合同でパネルディスカッションが行われ、当日は次のような話題が挙がりました。 Go Conference 2022 Springで気になった発表 データベースパッケージの選定事情 アフタートークの発表内容について CSVを扱う際の苦労話 普段どのように登壇内容を決めているのか 他社の方々と普段なかなかお話しすることができない「これハマるよね」という話ができたり、スライド発表とは違った知見共有の時間になったと思います。 宣伝 BASE BANKチームでは Go, Python, PHPを中心に、フロントからインフラまでを一気通貫で開発しています。 また開発だけでなく、機能をグロース・分析・サポートまで担当します。 そんな開発スタイルに興味あるぞって方は永野( @glassmonekey )にDMを送っていただくか、 下記のリンクから気軽にご連絡ください。 open.talentio.com 「転職活動はしていないけど、Goの日々の開発の困りごとってどうやって解決しているの?」のような雑談がされたい方は @budougumi0617 のMeetyでお話しましょう。 meety.net
この度は、4/9(土)~4/11(日)に開催された PHPerKaigi 2022 にメンバーが登壇したり、プラチナスポンサーおよびスポンサーブーススポンサーとして協賛しました。 今回は、アンカンファレンスの様子やアンカンファレンスで登壇したメンバーからコメントをお届けします! PHPerKaigi 2022 とは 2022/04/09(土) ~ 2022/04/11(月) の 3 日間にわたって PHPerKaigi 2022 が開催されました。今年はオンラインとオフラインのハイブリット開催になります。 BASE はこれまでにも開催されている PHPerKaigi への登壇並びにスポンサードをコミュニティ貢献活動として行って参りました。 アンカンファレンスの様子 企業ブース横のコーナーでは、アンカンファレンスと称し、イベント中に募集したセッションが繰り広げられました。また、今回はオンラインオフラインのハイブリット開催だったため、オンラインで実施されたアンカンファレンスもありました。 【現地アンカンファレンスのお知らせ】 パブリックビューイング会場には「現地アンカンファレンス」エリアがあります。 オンライン(Track C)のアンカンファレンスとは違い、現地に居る人がその場で気軽にトークを開始できます。 現地に居る方はぜひチェック&ご利用ください! #phperkaigi pic.twitter.com/BqmMgWYI2M — PHPerKaigi 2025 @3/21-3/23 (@phperkaigi) 2022年4月10日 BASEからは、以下の発表を行いました。 BASE x メルカリ スピーカー アフタートーク by kawashima オブジェクト指向UIという考え方をエンジニアがチームに導入した話 by wakano 「PHP 8 で作る JSON パーサ」 (by @shin1x1 さん)を実装してみる by kushibiki 再演:リーダブルコミットのすゝめ by 02 無限LT - オンラインわいわい by cureseven はじまりました! #phperkaigi pic.twitter.com/E2CKMbsXh9 — Mercari_Dev (@mercaridevjp) 2022年4月10日 BASE x メルカリ スピーカー アフタートーク は、メルカリの方にお誘いいただき実現しました。両社モジュラーモノリスに挑戦しており、似たような悩みを持っていそうなので対談しませんかとお声がけいただき、開催に至りました。 kawashimaと登壇してくださった安達様をはじめ、メルカリのみなさまありがとうございました。 トークの中では、 モノリスとマイクロサービスの中間の選択肢ができたのは良いことだと思う 綺麗に境界を状態にするのは困難なので、関係ある場所まで誰でも触れる状態を作っておくことが大事 モジュラーモノリス化を進めるにあたり、組織改変も一緒に進める必要があると感じている といった話がありました。 オブジェクト指向UIという考え方をエンジニアがチームに導入した話 by wakano 「PHP 8 で作る JSON パーサ」 (by @shin1x1 さん)を実装してみる by kushibiki 再演:リーダブルコミットのすゝめ by 02 無限LT - オンラインわいわい by cureseven 登壇者のコメント 若野 ( @sam8helloworld ) BASEのお金周りのサービス開発をしている @sam8helloworld です。PHPerKaigi 2022では現地アンカンファレンスにて「オブジェクト指向UIという考え方をエンジニアがチームに導入した話」という発表を行いました。 自分自身このイベントは初参加であること、一緒に参加する同僚はほぼ全員リアルでは初めましてであること、発表内容が開発の文脈からは離れたものであることなどが理由で当日は朝からかなり緊張していました(笑) また自分の取った枠の開始時間はお昼のパブリックビューイングのセッションと被っていて、時間になっても誰も人が周りにいませんでした。緊張してたのも相まって「人いないしアンカンファレンスやめようかな?」と思ったりもしてました。 ただ、PHPerKaigiは凄く温かいものでだんだんと人が集まってきて気づいたら何人もの人がスライドを映したスクリーンを見てくれていました。この光景にとても嬉しくなり気づいたら挨拶して発表を始めてました。 発表中は参加者の皆さんが相槌を打ってくれてたり、メモしてくれているのが見えるので嬉しくなり、何とかしてこの人たちに自分の体験談を役に立ててもらいたいと思いました。なので用意してた原稿には書いてなかったことも含めて沢山話してしまいました。 当初20分ちょっとで終わる内容で作ったスライドだったのですが、気づいたら40分近く話してて終わる頃には声が少し枯れちゃってたのもいい思い出です。 発表後は前日までの疲れや緊張は吹き飛び、あまり話かけられなかった会場の参加者や一緒に参加してた同僚とたくさん話すことができました。これはカンファレンスハイ、とでも言うのでしょうか?私にとって緊張する久しぶりのオフラインカンファレンスは、アンカンファレンスのトークというリハビリを通して凄く楽しいカンファレンスになっていました。 今は来年のPHPerKaigi 2023が楽しみで仕方がないです! 櫛引 ( @Panda_Program ) @Panda_Program です。PHPerKaigi 2022 では 「PHP 8 で作る JSON パーサ」 (by @shin1x1 さん)を実装してみる というタイトルで発表をしました。 普段業務では TypeScript で Vue や React を書いているフロントエンドエンジニアです。ただ、元々キャリアを PHP のサーバーサイドエンジニアから始めた(本当の最初は WordPress の Plugin 開発)ため、PHPerKaigi の存在は知っていました。 社内でアンカンファレンスの枠が空いているからどうかとお声かけ貰い、とても嬉しかったので参加することにしました。 今回はアンカンファレンスということで双方向的なやりとりができることを前提にネタ探しをする間に、以前 PHP カンファレンスで新原さん(@shinx1x)が PHP で JSON パーサを実装するという発表をされていたこと、自分が PHP を書いていた頃は 7.4 が最新だったため PHP 8 の機能を学びたいという動機でこの題材を選びました。 本番はライブコーディングですが、流石にぶっつけ本番で上手くはいかないだろうと、準備をしました。準備内容は、前日と前々日に1回ずつ通しでスライドを見ながら実装することです。 準備ではコードを書くこと、パーサの仕組みに興味があったこと、また PHP の新機能を使えること(特に match 式が魅力的)から、心理的には発表用の資料作成より楽でした(笑)。 コードは GitHub に上げています。 オンラインでのアンカンファレンスであったことや本番当日の直前、自分の発表と同じ時間帯に、現地で BASE × メルカリ の最強アンカンファレンスの開催が突発的に決まったこともあり、Zoom の視聴者数は10名いるかいないか程度でした。 ただ、その中でも参加者の方が動画で顔出しして自分の解説にうなづいてくださっていたり、テストが落ちた時のデバッグをコメントで手伝ってくださったりと、オンラインでしたが交流ができてよかったです! 今回は引っ越し直後でバタバタしていたこと、ライブコーディングをやり切ることを目標としていたのでオフラインよりは緊張しないオンラインを選びました。 しかし、発表中に自分のエンジンがかかり、発表後もすぐには熱が冷めなかったため、ライブコーディングに失敗しても、オフラインで参加するのがよかったかなと思いました。オンラインだと「カンファレンスの廊下」(立ち話)もできないですしね。 また参加する機会があれば、次はオフライン参加して見たいと思います! 当日のライブコーディングの様子。現地メンバーが BASE のブースでも流してくれていました 最後に PHPerKaigiアンカンファレンスは、オフラインならではの交流がたくさん生まれました。カンファレンスと違い、ラフな空気で質問もたくさん飛び交っている様子でした。 そんな空気感の中で、発表を通じてたくさんの人と交流できたことを嬉しく思っています! これからもBASEはエンジニアコミュニティとのつながりを大事にしていきます。 私たちはサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。 https://open.talentio.com/r/1/c/binc/homes/4380
スポンサーブースでの集合写真 この度、4/9(土)~4/11(日)に開催された PHPerKaigi 2022 にプラチナスポンサーおよびスポンサーブーススポンサーとして協賛しました。 今回は、スポンサーブースの様子やPHPerトークンの答え合わせなどをお届けします! PHPerKaigi 2022 とは 2022/04/09(土) ~ 2022/04/11(月) の 3 日間にわたって PHPerKaigi 2022 が開催されました。今年はオンラインとオフラインのハイブリット開催になります。 BASE はこれまでにも開催されている PHPerKaigi への登壇並びにスポンサードをコミュニティ貢献活動として行って参りました。今回はプラチナスポンサーおよびスポンサーブーススポンサーとして当カンファレンスに協賛しています。 プラチナスポンサーの一覧(2段目1番左にBASEアイコン) スポンサーブーススポンサーの一覧(1段目1番右にBASEアイコン) スポンサーブース出展 elePHPantぬいぐるみがたくさん並んだブースでみなさんをお出迎え パブリックビューイング会場の練馬区立区民・産業プラザ Coconeriホールでは、オフラインで参加されている皆さまと交流する場として、ブースを出展しました。 オフィスにたくさんいたelePHPantたちを総動員して、ブースを彩ってもらいました。 BASEからは合計6人のメンバーが登壇し、メインセッションやLT、オフライン・オンラインアンカンファレンスなどの様々な場所で発表を行っていたのですが、登壇後、トークの内容について質問や相談などのコミュニケーションを行う場としてたくさんの方にお立ち寄りいただき、大変賑わっておりました。 お立ち寄りいただいた皆さまありがとうございました! 実際に出展したスポンサーブースの写真 ブースコンテンツのBASE Appsクイズ また、ブースでは遊びに来てくださった皆さまによりBASEのことを知っていただくために、「BASE Appsクイズ」というものをご用意して遊んでいただいていました! ネットショップ作成サービス「BASE」には、どんな規模のショップでもショップ運営に必須な「基本機能」の他に 「BASE Apps」というプラグイン方式でショップ運営に必要な機能を必要になったタイミングで追加できる仕組み があり、今回はその「BASE Apps」のアイコンから機能の内容と名前を推測して答えていただくというクイズを作成しました。 BASE Appsクイズトップページ ショップオーナーさんとの出会い たくさんの方がブースに立ち寄っていただいたのですが、その中でも実際にBASEをご利用いただいている方々に出会い、サービスについての生の声をいただけて非常にありがたい機会にもなりました。 お隣の企業ブースにて参加されていた方が手芸作品などの販売に使っていただいていたり、ご家族が手作りのアクセサリーの販売にご利用くださっている方がいらっしゃったり、はたまた「こういう使い方ってできるかな?」と相談しに来てくださる方もいらっしゃり、BASEというサービスが色々な方に利用いただいていることを改めて気付かされて「これからも良いサービス作っていくぞ!!💪 」とモチベーションが高まるきっかけをいただきました。 また、お隣の企業ブースにいらっしゃった方はライブエンブロイダリングでPHPerKaigi 2022ロゴを刺繍し、ミニクッションを作られていて、「こうやってBASEショップで販売されている商品はつくられているのか…!」と、思わずまじまじと見てしまいました(笑) くるりとぬい終わったら、ひっくり返して綿を詰めて詰めて詰めて... #phperkaigi ロゴのミニクッションが出来ました! pic.twitter.com/UX6WTXfX7p — 枯白菜ちゃん/まりゐ (@marii_mikuriya) 2022年4月11日 PHPerトークンの解説 PHPerチャレンジ 今回のイベントではオンライン、オフラインどちらの参加者もゲーム感覚で楽しめる#から始まるトークンを探しポイントを競うPHPerチャレンジも併せて3日間のイベントの間みんなでランキングを競っていました! 全150トークンをパンフレットや記事に散らばっているのでみんなで色々と探して楽しんでおりました〜!隠れているトークンを探すのは楽しいですね! ランキングの結果は こちら !!! BASEからのトークンをお見せします! 「#今BASEに入社してやることあるの?」 パンフレットのBASEのコーナーのタイトルにありました! パンフレットのBASEのコーナー 「#BASEから4人登壇」 BASE Bookの記事の中に入っていました! BASE Bookの記事の写真 basebook.binc.jp 「#登壇応援中」 Youtube(People & Culture in BASE)の概要欄、ハッシュタグの中にありました! Youtubeの概要欄の写真 【前編】登壇するエンジニアを増やしたい。登壇支援活動をするエンジニアに話を聞きました - YouTube 最後に オフラインでのスポンサーブースの参加は久々なこともありオフラインならではのコミュニケーションも久しぶりにできたのかなと感じました。 BASEをもっと身直に感じていただけることができオーナーさんもとも実際にお話しができたりこれからショップを使ってみたい方のご相談に乗ることができました。 たくさんの参加者の方々や発表にふれることができ、とても充実した時間を過ごさせていただきました。 それも実行委員長である長谷川さんをはじめ、実行委員会の皆様のおかげです。心より感謝申し上げます。 来年もまた皆様にお会いできることを楽しみにしております。
はじめに こんにちは。Product Dev Division でエンジニアリングマネージャーをしている @tac_tanden です。 Docker Desktop 有料化の移行期間が終わって約 3 ヶ月が経ちましたが、皆さまいかがお過ごしでしょうか? 旬の時期は過ぎている気もしますが、BASE 社内で行った Docker Desktop の有料化移行をする中で得た知識や知見を改めてまとめたので、テックブログで公開させていただくことになりました。 有料アカウント購入の運用フローや社内の予算案の作成を担当した当時の自分が、最初から知っていればあんなに苦労しなかったのに!というのを主にまとめています。 なので、想定される読者の方としては以下の通りです。 Docker Desktop の有料プランを購入予定だが、どういう基準で選んでよいのかわからない 近い将来、有料プランの購入条件に該当しそうなので、予め準備をしておきたい 新入社員の方の Docker Desktop の有料プランへの追加運用フローをどうやっているのか気になる それでは、よろしくお願いします! Docker Desktopの利用が有料になる基準はどこから?  Docker のプランごとの価格表が掲載されているページの一番下に以下のように書かれています。 Docker Desktop is free to use, as part of the Docker Personal subscription, for individuals, non-commercial open source developers, students and educators, and small businesses of less than 250 employees AND less than $10 million in revenue. Pricing & Subscriptions 特にスモールビジネスの部分ですが "small businesses of less than 250 employees AND less than $10 million in revenue"とあり、従業員数が 250 人未満かつ年間売上 1000 万ドル未満の企業のみ無料で利用可能と書かれています。 例えば BASE 株式会社は従業員数こそ 250 名以下ですが、年間売上高 1000 万ドル以下には該当せず、無料利用の対象外ということがわかります。 無料対象の企業 従業員数 250 人未満 年間売上 1000 万ドル未満 有料プランは全部で3種類 支払い方法により値段が変わってきます。 年払いをする場合は以下の値段になります。 また、月払いをする場合は以下の値段になります。 Pro と Team だと 1 ヶ月あたりの金額が 2 ドル違います(年間 24 ドル)。さらに、Business だと 1 ヶ月あたり 21 ドル(年間 252 ドル)となり一気に値段が上がります。GitHub Enterprise プランが年間 252 ドルと同じ金額なので、それを意識した値段設定になっているのかもしれません。"Buy Now"の上に目立つように"Contact Sales"のボタンがあるので、問い合わせをしてボリュームディスカウントやサポートなどで値段を調整する前提になっているようにも見えますね。 Team プランでは、年払いの場合は "Start with minimum 5 users for $25."とあり、最低 5 ユーザ分購入が必要ですが、最初の 5 ユーザまでは合わせて月額 25 ドルで利用できることがわかります。 月額 25 ドルなので年間 300 ドルとなり、通常だと 5 ユーザで月額 35 ドルなので年間 120 ドル安く購入できる計算です。 また、月払いの場合の同様の割引があり、最初の 5 ユーザはまとめて月々35 ドルで利用が可能です。 購入数 月払い 年払い 最初の 5 ユーザ合計 35 ドル/月 25 ドル/月 6 ユーザ目以降(1 ユーザあたり) 9 ドル/月 7 ドル/月 ※プランの価格は 2022/04/28 現在の価格になります。最新の情報は Pricing & Subscriptions を御覧ください。 プランごとの機能の差 BASE 社内でプランを検討したときに比較したポイントをいくつかピックアップします。 Docker Desktopの利用 BASE での一番の目的が Docker Desktop の利用でした(代替方法がいくつかありますが、それらとの比較は割愛します)。 すべてのプランに含まれているので、プラン間の比較で特に考慮すべき点はありませんでした。 有料アカウント管理 Pro プランの説明には "Includes pro tools for individual developers who want to accelerate their productivity." とあり、基本的には個人向けのプランで、購入も個人毎になってしまいます。対して、Team, Business は組織向け(会社など)のプランで、購入も「Organization」という単位で行われます。Docker Hub の Organization 内にユーザを招待して、有料アカウント枠(シート)に追加できます(GitHub の Organization とほぼ同じ役割ですね)。 会社の開発組織として購入する場合は、一括で管理できたほうが良いと思うので、基本的に Team プラン以上が選択肢になりそうです。 Audit Log Team 以上のプランで、Organization 内の変更ログを確認できるようになります。 Purchase via invoice 請求書による購入は Business プランのみ対応しているようです。会社として必須なケースがあるかもしれません。 Single Sign-On (SSO) BASE 社内で検討する際に、一番重要なポイントだったのが SSO でした。SSO は Business プランしか対応していません。SSO は会社として必須になっている場合もあるのではないでしょうか。 弊社の場合、社内のアカウントの管理も担当しているチームと確認し、導入するサービスが SSO に対応していることは現時点で必須にしていないというガイドラインのもと、SSO を理由に Business プランを選択しませんでした。SSO 以外にも細かい部分も含めて議論した結果、まずは Team プランを導入することに決まりました。 ※プラン内容は 2022/04/28 現在のものになります。最新の情報は Pricing & Subscriptions を御覧ください。 有料プランの予算を立てる上で考慮したいポイント ここでは「Team」プランを導入するときに、年間の予算を立てる上で調査してわかったポイントをまとめていきます。 年払い/月払い まず、年払い or 月払いで 1 ユーザあたり年間 24 ドル差があります。そして、最初の 5 ユーザまでは合算で 25 ドル(月払いの場合は 35 ドル)で利用できるので、計算する場合は少しややこしくなります(年間 120 ドルの差ですが)。 自分は、「最初 5 ユーザまで」のところを読み違い、5 ユーザ毎にディスカウントされると思い予算の計算をしてしまいましたが、120 ドルのディスカウントになるのは最初の 5 ユーザのみで、以降のユーザに対してはディスカウントはないのでご注意ください。 また、 Add seats to your subscription のページを確認したところ、年払いと月払いのユーザを混在させることはできませんでした。 When you add a monthly or annual subscription, it includes seats for that subscription only. 有料アカウントを増減するときにかかる料金 次に有料アカウント(シート)数の増減の部分を考えていきます。 こちらも Add seats to your subscription を確認すると、追加購入は次回の支払い期日までの日割り按分で支払うことが書かれています。 When you add seats to your subscription in the middle of your billing cycle, you are charged a prorated amount for the additional seats. そして、チームの有料アカウントは「シート(席)」という概念で、有料席が空いたら別のユーザを紐付けられる仕組みになっています。なので、もし退職者が出てしまった場合でも、使わなくなった有料アカウントを別のユーザに付け替えることが可能です。 逆に解約時ですが、 Remove seats from your subscription には、解約は次回の支払い時にその分を減額すると記載されていました。 You can manage your Docker Hub subscription anytime by removing seats from your subscription. If you remove seats in the middle of the billing cycle, changes are applied immediately and reflect in the next billing cycle. 上記の要素に加えて、会社の採用状況と今後の人員計画、過去の退職者/利用者の実績から 1 年間に支払う料金を比較して、年払いか月払いを検討するとよさそうです。 有料アカウントに新入社員の方を追加する運用方法 最後に BASE での、有料アカウントの管理方法を共有させていただこうと思います。とは言っても特別なことはしておらず、Google form とスプレッドシートを使ってアカウントの追加や付け替えの管理を行っています。 新しく入社された方に、オンボーディング期間に Google form から利用中の Docker ID を投稿していただくようにようにしています。Google form では回答がそのままスプレッドシートに反映されるように設定できますし、form の管理者は新しい回答がされた際にメールで通知が飛ぶようにすることもできるので、見逃してしまう心配も少ないです。 新しく入社された方がオンボーディング期間に忘れずに申請しなければいけない、という点はまだ改善の余地はありますが、現状は以上のような運用になっています。 ご参考になれば幸いです。 おわりに Docker Desktop 有料化対応を担当していた当時の自分が、最初から知っていれば苦労しなかったのになあ、というポイントを主にまとめてみました。少しでも参考になれば幸いです。 最後まで読んでいただき、ありがとうございました。 BASE ではユーザ第一にサービスを共に作っていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください! https://open.talentio.com/r/1/c/binc/homes/4380
こんにちは。BASE株式会社 BASE BANKチームの前川、清水( @budougumi0617 )です。 3月上旬にNew RelicからCircleCI integrationが公開されました。 BASE BANKチームで使用してみたので、設定方法などをまとめてお送りいたします。 TL;DR 3月上旬にNew RelicのCircleCI Integrationが公開され、New Relic上でjobやワークフローの実行時間、ジョブヘルス、実行数などが取得できるようになった New Relic OneとCircleCIを連携し、ダッシュボードを作成する方法をご紹介する jobの状況だけでなく、workflowの状況もダッシュボードに表示する方法をご紹介する 背景 2022年3月に New Relic Instant Observability(New Relic I/O) の新たな連携先パートナーとしてCircleCI, Netlify, Jiraなどが発表されました。 docs.newrelic.com BASE BANKチームでは、自分たちの開発組織としてのパフォーマンスがどれくらいなのかを判断するために、ソフトウェア開発チームのパフォーマンスを示す 4 つの指標のうち手始めにデプロイ頻度を計測しています。 devblog.thebase.in これまではDeployment Markerを用いてNew Relic One上でデプロイ頻度を計測していましたが、この度CircleCI Integrationが登場したので、これを使ってデプロイ頻度などの情報をダッシュボードに表示してみました。 CircleCI Integration このIntegrationではCircleCI Webhookから取得できる情報をNew Relic Oneに連携することができます。 newrelic.com circleci.com CI上で実行するジョブやワークフロー、パイプラインのIDや名前、ステータス、実行開始時間、終了時間だけでなく、CircleCIと連携しているVCS(gitなど)のコミット時間や内容、authorなども取得することができます。 そのため、例えば 単位時間あたりのトータル実行ジョブ数、ワークフロー数 ジョブヘルス 直近失敗したジョブ プロジェクトあたりのジョブ実行数 コミットからリリースまでの所要時間 など、取得できるパラメータの使い方によって様々なデータを計測、表示することができます。 設定 ダッシュボードを作成する サンプルのダッシュボードはNew Relic I/OのCircleCIページの「Install quickstart」から簡単に作成できます。 New Relic上の Install quickstart ボタン Quick Installを完了すると、2022/04/06時点では次のようなダッシュボードが作成されます。 New Relic上に作成されたCircleCIのダッシュボード このクイックインストールではダッシュボードは作成されますがデータ連携の設定は行われません。 作成されたダッシュボードにCircleCIからCI/CDのデータを送信するにはCircleCI上での設定が必要になります。 CircleCIからNew Relicへデータを送信する Quick Installの手順には記載されていません 1 が、CircleCI上からNew Relicに送信するにはAPIキーを利用して後述のNew RelicのエンドポイントのURLをCircleCIのWebhookに登録する必要になります。 New RelicのAPI Keyを取得する 今回の連携で利用するNew RelicのAPIはLog APIですので、API Keyはライセンスキーになります。 docs.newrelic.com ライセンスキーは次のドキュメントに記載されている手順で確認できます。ライセンスキーによって連携するアカウントが区別されるので、アカウントを間違えないようにしてください。 docs.newrelic.com ライセンスキーが表示されない場合は権限不足なので組織のNew Relic管理者に問い合わせてください。 CircleCI上でWebhookを登録する CircleCIからNew Relicにワークフローの実行結果を表示するには、New Relic上でデータを表示したいプロジェクトごとにNew RelicのWebhookを登録する必要があります。 circleci.com まず、New Relicでパイプライン情報を確認したいプロジェクトの「Project Settings」を開きます。 GitHubリポジトリと連携したCircleCIプロジェクトの場合、「Project Settings」は以下のURLです. https://app.circleci.com/settings/project/github/${ORG_NAME}/${REPO_NAME} 「Project Settings」の中にある「Webhooks」をクリックすると「Add Webhook」ボタンがあるのでそれをクリックします。 CircleCIのWebhook設定画面 新しいWebhookを追加する画面になるので確認したAPI Keyを使って次のように設定します。 設定名 内容 Webhook name 任意の判別しやすい名前 Receiver URL https://log-api.newrelic.com/log/v1?Api-Key=${NEW_RELIC_API_KEY} Secret token 空欄のまま Certificate verification チェックを入れる Workflow Completed チェックを入れる Job Completed チェックを入れる これでCircleCIが実行されるたびにダッシュボードに情報が流れるようになりました。 複数のプロジェクトのパイプラインの実行結果をNew Relicで利用したい場合はこの操作をプロジェクトごとに繰り返します。 より価値のある情報を表示する NewRelic上でCircleCIのワークフローの実行情報を取得できるようになりました。 しかし、クイックインストールで作成したダッシュボードのサンプルグラフは我々の運用方法ではあまり意味のないものでした。 なぜかというと、複数プロジェクトのワークフローの実行結果を一つのグラフにすると、ジョブの数ではデプロイの実体がわからなくなるためです。我々のプロジェクトはプロジェクトごとにデプロイパイプラインが異なり、デプロイするまでに実行されるジョブの数が異なります。あるプロジェクトAは1回のデプロイでジョブを10個実行し、あるプロジェクトBが1回のデプロイでジョブを5個しか実行しない場合、ジョブの実行総数を可視化しても意味のある情報にはなりません。 ジョブの実行総数が100だった場合、すべてプロジェクトA起因ならば10回デプロイしたことが想定されますが、すべてプロジェクトB起因だった場合は20回デプロイしていたことになります。 そこで、ワークフロー単位のグラフを改めてつくることにしました。 CircleCI からどんな情報が取得できるのか? CircleCi からは現在以下の 2 種類のイベントを取得できます。 workflow-completed job-completed Webhook - CircleCI New Relic 上では type というパラメータで表されており、以下のクエリで 2 種類のイベントが取得できていることを確認できます。 FROM Log SELECT uniques( type ) since 30 days ago NRQLリファレンス | New Relic Documentation それぞれのイベントで取得できる情報を、 New Relic 上ではそれぞれ以下のクエリで確認することができます。 FROM Log SELECT keyset() WHERE type = ' workflow-completed ' since 30 days ago FROM Log SELECT keyset() WHERE type = ' job-completed ' since 30 days ago NRQLリファレンス | New Relic Documentation 情報の一部を確認すると、workflow-completed イベントではワークフローに関係する情報を取得でき、job-completed  イベントではジョブに関係する情報と関連するワークフローの情報の一部が取得できていることがわかります。 また、ワークフローのステータスに関しては workflow-completed でのみ取得できるということが確認できます。 ワークフロー単位での情報の表示について ワークフローに関する情報は workflow-completed と job-completed 両方のイベントに含まれています。 また、New Relic 上の実際のデータを確認すると、workflow-completed イベントに関しては殆どが workflow.id でユニークでしたが一部重複がありました。 よって今回ワークフロー単位での情報の表示については、以下の方針に基づいて可視化を行いました。 uniqueCount 関数を利用してワークフローに関する属性のユニーク値の数を集計する workflow-completed イベントの情報を集計する NRQLリファレンス | New Relic Documentation まずはワークフローの数を表示してみましょう。 FROM Log SELECT uniqueCount(workflow.id) WHERE type = ' workflow-completed ' since 30 days ago ワークフロー数 同様に、日毎のワークフロー数の推移をプロジェクト別に可視化するクエリは以下のようになります。 FROM Log SELECT uniqueCount(workflow.id) WHERE type = ' workflow-completed ' FACET project.name TIMESERIES 1 days since 30 days ago 日毎のワークフロー数 次に、ワークフローのステータスについて可視化してみましょう。 workflow-completed イベントについて、 workflow.status の値でグループ化しカウントして表示するクエリは以下のようになります。 FROM Log SELECT uniqueCount(workflow.id) WHERE type = ' workflow-completed ' FACET workflow.status since 30 days ago ワークフローステータスの集計 最後に、今週のデプロイ総数を先週と比較してみましょう。 今回は成功ステータスである特定のワークフロー名のワークフローの数をカウントすることでデプロイの数を集計してみます。 FROM Log SELECT uniqueCount(workflow.id) WHERE workflow.name = ' test-build-deploy ' AND workflow.status = ' success ' SINCE this week COMPARE WITH 1 week ago 今週のデプロイ数と先週との比較 同様に、日毎のデプロイ数の推移をプロジェクト別に可視化するクエリは以下のようになります。 FROM Log SELECT uniqueCount(workflow.id) WHERE workflow.name = ' test-build-deploy ' AND workflow.status = ' success ' TIMESERIES 1 day FACET project.name SINCE 30 days ago プロジェクトごとのデプロイ数の推移 おわりに 今回、新しく登場したNew Relic OneのCircleCI Integrationの導入方法についてご紹介しました。 設定さえすれば最低限の情報はすぐに出力できるようになるため、読者の皆様が所属する開発組織のパフォーマンス計測の一助となれば幸いです。 New Relicを活用したサービス品質の向上や、開発組織のパフォーマンス改善に興味のある方はぜひカジュアルにお話しましょう! open.talentio.com どこかに書いてあるのかもしれませんが、我々の調査ではドキュメントが見つからずテクニカルサポートに問い合わせました。 ↩
2022/4/23(土)にオンラインで開催されるGo Conference 2022 Spring Onlineにシルバースポンサーとして協賛し、2名のメンバーが登壇します。 Go Conferenceとは https://gocon.jp/2022spring/ Go Conference 2022 Spring Online Go Conferenceは一般社団法人Gophers Japanが主催し半年に1回行われるプログラミング言語Goに関するカンファレンスです。 前回 に引き続き、オンライン開催です。 今回、弊社は前回に引き続きシルバースポンサーとして協賛します。 ※ The Go gopher was designed by Renee French . Illustrations by tottie . BASEとGo プロダクトの大半のサーバーサイドがPHPで実装されているBASEですが、BASE BANKチームが開発・運用している資金調達サービスである「YELL BANK」、ショップの売上金をVisa加盟店の決済で利用できる「BASEカード」はGoの分散サービスとして実装されています。 thebase.in https://cp.thebase.in/basecard cp.thebase.in 「YELL BANK」のリリースは2018年12月であり、Goを使ったプロダクト運用実績は3年以上になります。 登壇内容について testingパッケージを使ったWebアプリケーションテスト(単体テストからE2Eテストまで) by @budougumi0617 gocon.jp BASE株式会社BASE BANKチームにて、 テックリードをしている清水( @budougumi0617 )です。「testingパッケージを使ったWebアプリケーションテスト(単体テストからE2Eテストまで)」というタイトルでプロポーザルを提出し採択されました。 ソフトウェアとテストは切っても切れない関係です。 いっぽう、Webアプリケーション開発においてはDBなどのミドルウェア・外部API・永続化情報の状態など様々な依存関係が存在します。 本セッションではDBや外部APIに依存するコードの単体テストからテスト中にWebサーバやDBを起動するシミュレーションテストまで、 私がtestingパッケージを使って行なっている様々なレベルのテストについて紹介します。 前職の経験も合わせると業務でGoのWebアプリケーション開発に携わり始めて5年になります。 その中で行なった試行錯誤して書いたテストのアプローチを紹介します。 プロダクトによってテストで担保したい品質、仕様は異なります。私の発表を視聴していただいた方のテストに対する「手札」がひとつでも増えれば幸いです。 Python製の姓名分割ライブラリをGoに移植した話 by @glassmonkey gocon.jp 一般的にわかち書きでは無い日本語で姓名から「姓+名」の分割を行うことは困難です。 しかし、Python製の姓名分割ライブラリ( https://github.com/rskmoi/namedivider-python )を用いるとある程度精度良く分割は可能です。 そこでシングルバイナリで扱えるGoのメリットを活かして、Python製の姓名分割ライブラリをGoに移植した話をします。 その際移植で工夫した点や気をつけた点をお話します。 今回、他の方が書いたコードを他言語に移植することを初めてチャレンジしました。 今回のケースではLL言語故のruntime時の不安定さをGoに移植することで解消することができました。 ただ、一部numpyの実装を独自に行う必要があったなど、メリットばかりでは無いことも学びとしてありました。 別言語の実装をGoやさらに他言語へ移植する際の意思決定の参考になれば嬉しく思います。 オフィス・アワーについて 今回は我々BASEはシルバースポンサーとして協賛させていただくため、イベント当日はRemo上でオフィスアワーを開いて参加者のみなさんをお待ちしております。 オフィスアワー中の弊社ブースでは弊社が定期開催しているGophers Code Reading Partyを開催予定です。 同Partyはコードリーディングに限らず、その場で集まったメンバーが最近気になったGoのトピックを話す集まりで、社外ゲストも含めカンファレンスの廊下やオフィスの休憩スペースのような雰囲気で雑談する集まりです。 普段使っているGoのOSSのコードリーディング ちょっと挙動がわからなかった標準パッケージのコード 直近話題になったGo関連のトピック・ブログ記事について BASEでGoをどうやって使っているか etc... publicリポジトリのissueで毎回メモを取っています。 当日用はこちらのissueを使う予定なので事前にトピックをコメントしていただくのも大歓迎です。 github.com 普段の雰囲気やトピックについては過去の回のメモ(既存issue)を御覧ください。 https://github.com/basebank/gophers-code-reading-party/issues 宣伝 ANDPADさん主催のGoConスポンサー企業合同アフタートークイベントにも参加予定です。 andpad.connpass.com 株式会社Showcase Gigさん、株式会社LayerXさん、株式会社アンドパッドさんと4社合同で行ないます。 弊社からは @budougumi0617 , @glassmonekey が次の内容の発表とパネルディスカッションに参加する予定です。 New Relic Oneを使ったObservabilityの実現方法と活用例 by @budougumi0617 Goで始めるTDD by @glassmonekey 宣伝その2 BASE BANKチームでは Go, Python, PHPを中心に、フロントからインフラまでを一気通貫で開発しています。 また開発だけでなく、機能をグロース・分析・サポートまで担当します。 そんな開発スタイルに興味あるぞって方は永野( @glassmonekey )にDMを送っていただくか、 下記のリンクから気軽にご連絡ください。 open.talentio.com 「転職活動はしていないけど、Goの日々の開発の困りごとってどうやって解決しているの?」のような雑談がされたい方は @budougumi0617 のMeetyでお話しましょう。 meety.net 最後に、Go Conference 2022 Spring Onlineに参加するには次のイベントページより参加登録をお願いします。 gocon.connpass.com それでは4月23日にお会いしましょう!
メンバーが登壇している様子 この度は、4/9(土)~4/11(日)に開催された PHPerKaigi 2022 に4名のメンバーが登壇しました。 今回は、登壇者 4 名からコメントと、他のセッションの感想などをお届けします! PHPerKaigi 2022 とは 2022/04/09(土) ~ 2022/04/11(月) の 3 日間にわたって PHPerKaigi 2022 が開催されました。今年はオンラインとオフラインのハイブリット開催になります。 BASE はこれまでにも開催されている PHPerKaigi への登壇並びにスポンサードをコミュニティ貢献活動として行って参りました。 登壇者のコメント 川島 ( @nazonohito51 ) TechLeadの川島( @nazonohito51 )です。 今回はBASEがサービスとしても組織としても成長していく中で生産性を維持するためのアーキテクチャ戦略についての発表をさせていただきました。社内でこの戦略が打ち出されたのはかれこれ2年ほど前になるのですが、明確な形で社外に公表されたのは今回が初になります。 この戦略はアーキテクチャの本からチーム・組織・文化などの本から「学習する組織」といったテクノロジー系とは言えない本まであちこち読んだ末に考え出されました。実態としてはクリーンアーキテクチャやマイクロサービスなどのアーキテクチャパターンというより、進化的アーキテクチャ+DevOpsといった趣旨の内容であると言ったほうが近いと思います。中長期的な期間で考えればアーキテクチャに固定的な解は存在せず、システムを取り巻く環境の変化の中で常にバランスを取り続ける変化する動体である必要があります。そして変化の方向は、その時の目先の問題だけを反応的に局所最適で解決するのではなく、常に何かしらの目的を達成するような構造へ向かうような指向性が求められます。そして弊社における目的とは資料の前半で触れられていたようなものでした。 各所の反応を見る限り「モジュラモノリス」という単語に惹かれた方が多そうな印象ですが、趣旨としては中長期的にアーキテクチャに対してどんな姿勢で行くかの考えを整理したものがメインコンテンツになります。モジュラモノリスはその中の中心ではありますが一部に過ぎず、「モジュラモノリス」という単語そのものに「組織の生産性」という期待を寄せているならば何か見落としがあると思います。この資料で終始徹底したのは技術的な方法論からは入らない、という点で、事業と技術の整合性をどう取るのかについて一番エネルギーが使われています。 このアーキテクチャ戦略は未だに手探りの部分がほとんどの状況ですが、これから社内で少しずつ進めていく予定です。 Discordチャンネルに送られた質問について Discordに送られてきた質問はおそらく他の多くの方も持たれる疑問だと思われるのでこちらにも記述します。 モジュラモノリスの時点ではDB分割をしていない状態なのか? そうなります。理由はDB分割の境界はドメインが根拠であるべき、と考えているためです。ドメイン基準で分割したいけど境界線が分からない->DB設計の手戻りはコストが高い->アプリケーションの手戻りはDBよりも低コストなのでまずはアプリケーション(=モジュール)境界を安定させてからそれをDB設計に反映する、という戦略を立てているため、モジュラモノリス開始時点ではあえてDB分割していません。 漸進的にマイクロサービスへ向かっていくかどうかでモジュールをまたいだトランザクション境界について考え方が変わらないか? マイクロサービス化するならモジュールをまたいだトランザクションを許してはならないし(マイクロサービス化する時点でトランザクション分かれる)、マイクロサービス化しないならそういうトランザクションを許可する、という考え方にならないか、という質問でした。 結論としてはご指摘のとおりになります。マイクロサービス化する場合、CAP定理にもある通りCAPのいずれかが大きく損なわれます。スライド資料中にも赤文字で記述していますが、モノリスと分散システムにおけるデータ整合性に対する戦略は根本的に異なります。モジュラモノリス時点でやれていたことはマイクロサービス化しても全てが同じようにできるわけでは決してありません。トランザクション境界に対する明確な戦略は打ち出せてはいないのですが、少なくともモジュラモノリス時点で同一トランザクションで処理することもできれば別トランザクションに分けることもできるという選択肢を用意しています。もちろんイベントドリブンな結果整合性の処理を実現することはモジュラモノリスにおいても出来ます。モジュール境界線が明確ならはじめからトランザクションを分けたり、結果整合性の処理にしてしまうことが後のマイクロサービス化するときに有利になります。が、境界線が明確でないなら無意味に更新処理が複雑化したり、更新が反映されていない参照が発生する可能性をもたらしてしまったり、あるいは「モジュール境界線自体が後から見直しやすい」というモジュラモノリスのメリットを一部手放すことになる可能性もあります。トランザクション境界に対しては現状画一的な判断はできず、都度判断することになることになると考えています。 あと実は、発表中には触れませんが、BASEで実際にマイクロサービス化する箇所は極めて限定的になるのではないかと考えている背景もあります。少なくとも全モジュールがマイクロサービスになって動いているような未来はほとんどありえないだろうと考えています。 モジュラモノリスというパターンについて モジュラモノリスというパターン自体は「こう作れ」という明確な指示があるわけではないので、自社が「モジュール」という構造を通して何を実現したいかによってその姿は変わってくると思います。弊社はクリーンアーキテクチャをベースにしましたが、これは一例に過ぎません。「マイクロサービスアーキテクチャによって何を達成したいのか明確に把握していない場合には、マイクロサービスアーキテクチャは悪いアイディアである」という言葉はモジュラモノリスにおいてもそのまま当てはまるかと思います。 スライド資料だけを見て動画を見てない方には誤解を生みかねないのでこちらの記事でも触れさせていただきますが、モジュラモノリスは決して銀の弾丸ではありません。マイクロサービスとは別の形をした諸刃の剣です。振り方を誤ればきちんと怪我をしますのでご注意を。 永野 ( @glassmonekey ) BASE BANKチームでEngineering Program Managerをしている永野( @glassmonekey )です。 今回は個人開発や副業で扱ってるGraphQLに関して、普段業務で扱ってるPHPを通してどうなのかをトークしました。今回事前収録が個人的にも初めてで運営の皆様にはご迷惑をおかけしました。 弊社ではGraphQLに取り組んでいるわけではなかったのですが、改めて導入すべきかどうかを漠然と考えていたので、発表資料を作る過程を通して良い思考実験になりました。皆様も迷ったら登壇駆動はおすすめです。 発表後には、GraphQLの導入に迷ってたがかなり参考になったといった感想をいただく機会もありかなりの励みになりました。特にオフラインだったので直接感想を言い合えるという体験は最高でした。 これも運営の皆様の調整あってこそだったと思うので、改めてありがとうございました。 大津 ( @cocoeyes02 ) Product Dev Division / Service Dev Section に所属している02( @cocoeyes02 )こと大津です。 今回はコミットメッセージ軽量規約「Conventional Commits」の説明と関連ツールを使ってみた様子をトークしました。 PHP カンファレンス沖縄 2021でトークした リーダブルコミットのすゝめ でも少しだけ「Conventional Commits」について触れましたが、今回はガッツリ環境を用意して試すところまでやった他、 「Conventional Commits」の公式ドキュメントサイトにIssueやPRを出すところまでやってみました (とはいえ反応薄くてちょっと悲しい・・・) 今回使用した ramsey/conventional-commits ですが、導入の提案 Issue を laravel/framework や CakePHP など PHP フレームワークのリポジトリで出してみようかなと思っています! 導入そのものよりも、PHP OSS コミュニティ界隈の人がコミットメッセージについてどう思うか議論するというのが目的です。 分かりやすいコミットメッセージのメリットをもっと多くの人が享受できるよう、今回の発表以外でも動いていきたいと思います! 炭田 ( @tanden ) Product Dev Division / Service Dev Section に所属しているtanden( @tanden )です。 今回のLTでは「Webサービスのバウンスメール処理の事始め」ということで、そもそもバウンスメールとは何なのか、AWS SESをつかってバウンスメールをサービスにどのようにフィードバックするのかを簡単に発表させていただきました。 LTの発表は事前収録ではなかったので、ココネリホールの会場での発表でしたが、オンラインでの気軽さや視聴のしやすさはもちろんあるのですが、オフラインでの発表の雰囲気はやはりいいな、素敵だなと改めて思いました(その分緊張もすごいのですが)。 素敵な雰囲気の会場を作ってくださった、運営の皆さまに改めて感謝申し上げます。ありがとうございました。 個人的な心残りは、LTで笑いを全くとらない真面目な発表になってしまったことです。次回はもっとフランクなLTに挑戦してみたいと思います!(笑) 他のセッションについて 若菜 ( @ wakanaction ) Product Dev Division / Service Dev Section に所属しているwkです。 Day1とDay2の少しだけ、オンラインで視聴参加しました。 いくつかかいつまんで感想書きます 👨‍🍳 予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント @t_wada さん ついにt_wadaさんのセッションをリアルタイムで聞くことができ、感激...! PHPerのみならず、そして初学者から上級者まで広い範囲の方に刺さりそうな内容だった 中でも、以下を用いて堅牢な設計を考えていく運びが大変ためになった 型宣言 列挙型 モデリング 普遍性と等価性 完全性 責務の配置 メソッドに渡る値を型宣言によって絞る、さらに扱う内容が限られている値は列挙型で絞る、といった具合 度々、 プログラマが知るべき97のこと から引用されていたが、中でも以下を重点に置いていた。自分の業務でもぜひ参考にしたい考え方だった。 いいインターフェースの条件とは、正しく使用する方が 操作ミスをするより簡単 誤った使い方をすることが困難 「不安や疑念はテストに書いておく」 業務ロジックに限らず、FWの挙動、組み込み処理の動きなどもテストに残しておくことで、PHPの思わぬ落とし穴に気付けたり、一方を直したら一方が壊れた、なんてことに気づきやすくなっていて、精神衛生としてもすごく良かった。 総論:設計やコードレビューの際に常に念頭に置いておきたいような、即業務に活かしていける内容だった。型厳格な方向に進んでくれたPHPの恩恵に感謝しながら頑張ります コミットメッセージ規約「Conventional Commits」を導入してみよう! @02 さん Commitメッセージ、わかりやすく書きたい気持ちはあるものの、実際どうしたら良いかいまいちわかっていなかった 日本語で丁寧に書いてみたり、英語で統一して書いてみたり色々試した Commitメッセージに関する規約があるのは初めて知った フォーマットが決まっており、「Prefix (feat, fix, など) 、タイトル、本文、フッター、破壊的変更」のような内容でcommit メッセージを書く 多少規約が厳しめに感じたが、それくらいの方が規約を用いる意味があるか。または続けやすい形で一部取り入れるのも良いのかも。 また、コミットメッセージをわかりやすく書いていきたいと考えた時、副次的に以下のような考えにも至った。 コミットメッセージをわかりやすくしたい ↓ コミットに含む内容をわかりやすくしないと、わかりやすいメッセージは作れない ↓ 適切な範囲でコミットを切る意識が育てられる! 総論:大規模開発において、Commitメッセージが残す情報は重要であるため、試しにでも実施してみようと思った。 普段使っているSourceTreeでは複数行にわたるCommitメッセージが書きやすいため、試しやすいと思った。 shiiyan( @shiiyannn ) Product Dev Division / Service Dev Section に所属しているshiiyanです。 PHPerKaigi2022のDay1とDay2をオンラインで参加させていただきました。 印象に残ったいくつかの発表に感想を書きます。 day1 - MongoDB に溜まった約1.6億レコード、データ量1TBのあらゆるサイトの記事データを BigQuery で高速検索できるようにした話 植江田さん 大規模データ処理関して、最近業務上でも課題がありました。こちらの発表は課題解決のヒントになれるかと思い、PHPerKaigiの中に特に興味を持ちました。 MongoDBに保存されたデータについて以下のことが紹介されました。 様々なサイトから記事をクロールしクリップする クロールした記事データをMongoDBで保存 1日で10万件のレコードが保存される 1日10万レコードならば、1ヶ月で300万レコードとなり、数年経てば約1.6億ととんでもない規模になっていくことがわかりました。 データ移転中の課題について以下のことが紹介されました。 動的スキーマから静的スキーマへの移行課題 存在しないカラムがあるとエラーとなる カラムの順番が変わるとエラーになる 処理時間とサーバーストレージ容量の課題 移行処理が完了までに20時間以上が必要 移移行処理が完了までに6000以上のcsvファイルが必要 課題に対しての解決法について以下のことが紹介されました。 存在しないカラムにnullを入れる PHPの連想配列でカラムの順番を固定した ストリームコピー( stream_copy_to_stream )を利用すれば2倍高速した 動的スキーマを採用したデータストアでは、カラムが利用中で増えても、カラムの順番が変わってもエラーなく使い続けます。マイグレーションが不要でスキーマの変更やメンテナンスがしやすい一方で、静的スキーマのデータストアに移行する時に、スキーマの整備という手順が発生するという知見を得られました。 また、ファイルを開いて一行ずつコピーする以外に、ストリームコピーというやり方を今回で新しく学びました。高速化というメリットがあるので、今後PHPでファイルに書き出す処理を実装する時に活用できると思いました。 day2 - コミットメッセージ規約「Conventional Commits」を導入してみよう!02さん 今まではコミットメッセージを割と雑で書いていました。そのせいで、PRのコンフリクト対応時やgit rebase時に苦労した経験がありました。 『コミットは他人が見るものだから、他人が書き手の意図を理解できないと❌』というのが発表の内容にありました。まさに、その通りだと思いました。コミットはpushするだけのものではなく、将来の自分や他人がその意図を理解できないと意味がないと理解しました。 Conventional Commitsというコミットメッセージの軽量規約も紹介されました。 featやdocsなどのコミットメッセージのプリフィックス前から知っているものの、コミットメッセージ規約を勉強するのは今回が初めてでした。 発表に利用された サンプルリポジトリ のコミット履歴を眺めると、規約に沿って書いたコミットメッセージがあるとソースコードを見なくても、何をやったのかを想像できることが実感できました。 発表の後、choreというプリフィックスはいつ利用されるかを調べました。 chore (updating grunt tasks etc; no production code change) 私の理解では、featやdocsなど明確な目的があるプリフィックス以外、本番コードに影響しないその他的な変更があった時に使います。 また、破壊的変更(Breaking changes)があるときに必ずフッター部分で明言することも覚えました。 今後はぜひ規約に合ったコミットメッセージを書いて、コミットメッセージが役立つようにしたいと決めました。 最後に 今回計 4 名のメンバーが登壇する機会をいただき、 PHP コミュニティの盛り上がりに貢献することができ大変有意義な時間となりました。 また自身の発表以外にも、多くのスピーカーの発表を通して各々が新たな知見や気づきを持って帰れたと考えております。 業務でお忙しいにも関わらず、スタッフの方々には多くの時間をカンファレンス準備へ割いていただいたかと思います。この場を借りて心より御礼申し上げます。 今回はトーク編の記事となっております。他にもスポンサー編、アンカンファレンス編、スタッフ編の記事を投稿する予定です。 それでは、来年もまた皆様にお会いできることを楽しみにしております!
はじめに こんにちは。バックエンドエンジニアの小笠原です。 今回は、2022年2月18日から2022年3月4日にかけて発生していたこちらの障害に対し私達開発チームが実施した、session.cookieで定義しているCookieのkey名を変更するという影響範囲の大きい対応について、実施に至るまでの経緯や対応過程についてご紹介したいと思います。 ショップオーナー向けに掲載していたお知らせの内容 背景 全ては iOS14.5から端末識別子の取得に同意が必要になったことから始まった ことの発端は、iOS14.5以降からIDFA(端末ごとに持つ固有識別子)の取得に端末所有者の許可が必要になったことでした。 この変更は、端末所有者側から見ると情報の活用範囲を自身で管理できることでよりプライバシーに配慮されるようになった良い変更と言えるでしょう。 一方で、広告出稿側から見た場合は拒否をしたユーザーの広告トラッキングが出来なくなることで広告の効果測定が大幅に制限される、という問題が発生してしまいます。 この問題に対して、Facebookピクセルという広告効果測定ツールを提供しているMeta社(旧Facebook社)は、広告効果測定の仕様を変更して合算イベント測定による集計を行うことでIDFAの取得を拒否したユーザーについても広告の効果測定ができるように対策を行いました。 BASEにおいてもInstagram広告Appがこの影響を受けるので、何らかの対応を行う必要に迫られました。 合算イベント測定に対応する際の詳細については本記事の主題ではないのでここでは省略させていただきますが、結論としてeTLD+1なドメインを認証することで合算イベント測定を使用可能になるということがわかったため、当時開発チームはショップ開設時に選択することができるドメイン群をPublic Suffix List(PSL)に登録するという対応を行っていました。 Public Suffix List(PSL)とは Public Suffix List(PSL)とは、jpやcomなどのTop Level Domain(TLD)と、co.jpやmeguro.tokyo.jpのような実質的にTLDのように振る舞うことが期待されるeffective Top Level Domain (eTLD)を管理しているリストのことで、GitHub上で管理・運営されています。 このリストに対して必要な情報を添えてPull Requestを送ることで、誰でも任意のドメインの追加を申請することが可能です。 https://github.com/publicsuffix/list つまり、PSLに任意のドメインを登録することでそのドメインをeTLDとして扱うようにすることができ、これによってショップのURLがeTLD+1と認識されるため、ショップ単位でドメイン認証を行うことで合算イベント測定を使用可能になる、ということです。 この対応のため、開発チームはショップ開設時に選択することができる以下のドメイン群をPSLに登録する申請を行いました。 base.ec official.ec buyshop.jp fashionstore.jp handcrafted.jp kawaiishop.jp supersale.jp theshop.jp shopselect.net base.shop リポジトリのPull Request履歴を確認すると、登録申請をしたのは2021年9月14日で、マージされたのは2021年12月5日だということがわかります。 https://github.com/publicsuffix/list/pull/1420 PSLに登録されたドメインにはCookieを保存できない ところで、 PSLに登録したドメインにはCookieを保存することができなくなってしまいます。 仮にjpのようなTLDに対してCookieを保存可能にしてしまうと、そのドメインを使用している全てのWebサイトでそのCookieを共有できることになってしまいます。TLDは不特定多数の利用者が様々な目的でサブドメインを取得して運用していることが多く、このような広範囲に対してCookieを参照可能な状態にしてしまうことはセキュリティリスクが高いため推奨されるものではありません。 そのため、TLDにはCookieを保存できないルールになっています。そして、TLDと同様の振る舞いをするeTLDに対しても同じことが言えるため、TLDと同様にeTLDに対してもCookieを保存できません。 つまり、PSLにドメインを登録するということは、そのドメインに対してCookieを保存できなくなる、ということを意味します。 PSLへドメインを登録したことによってどのような影響が出てしまったのか BASEのショップでも例に漏れずCookieを利用しており、例えば「シークレットECショップへのログイン情報」「カートへ商品を追加する際の商品情報」などはCookieの THEBASE というkey名に保存して管理していました。そして、これらのCookieはショップ毎に割り当てられているサブドメインに対してではなく、前項で紹介したPSLに登録したドメインに対してCookieを保存する処理になっていました。 つまり、これらの情報をCookieに保存できなくなったことで「シークレットECにログインできない」「カートへ商品を入れてもカートの中が空のまま」といった不具合が発生していたことが今回の障害の裏側で発生していた事象でした。 なぜPSLにドメインを登録してから数ヶ月経過してから問題が顕在化し始めたのか PSLにドメインを登録したのは2021年12月5日ですが、この障害を開発チームが認識したのは2022年2月19日の段階でした。 なぜおよそ2ヶ月ほど経過するまでこの不具合に気がつくことができなかったのかというと、それはブラウザが最新のPSLを取り込んだタイミングが関係していたようでした。 実は、各ブラウザは常に最新のPSLを参照しているわけではなく、任意のアップデートのタイミングでその時点の最新のPSLのスナップショットをビルドに含めて参照しています。 さらに、以下の表のように必ずしもアップデート時に最新のPSLへと更新しているわけではなく、その更新周期には規則性がないこともわかりました。 ブラウザ名 PSLの更新周期 FireFox Firefox96(2022/01/12リリース)時点ではBASEの登録したドメインは含まれておらず、Firefox97(2022/02/08)には含まれていた Chrome chrome97(2022/01/04リリース)時点で 2021/10/27 のPSLを取り込んで以後、更新されていない 上記の通り、直近のFirefox97のリリースによってこの障害に遭遇する購入者が徐々に増えてきたのではないか、と推測されました。 障害への対応内容 base.shopなどのeTLDに対してCookieが保存できないという問題に対して、今回はショップのURLに該当するサブドメインに対してCookieを保存するように変更するという方法を採用しました。 これは、ショップ毎にサブドメインを割り当てているBASEの仕組みを考えると、基本的にはショップの中でsessionが保持できれば購入者の買い物体験は阻害されないであろう、という判断によるものです。 実現方法として、チームでは以下2点の選択肢が挙がりました。 Cookieのdomain属性でサブドメインを指定する Cookieのdomain属性を指定しない domain属性を指定しなかった場合は一番狭い範囲に対してCookieが保存されるため、挙動としては「サブドメインに対してCookieを発行するように変更する」という点でどちらの対応を実施しても同じ意味となります。 今回はセキュリティの入門書として有名である『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』にdomain属性を指定しない状態が最もCookieの送信範囲が狭く安全な状態であるという言及があったことから、後者のdomain属性を指定しないように修正する方針に決まりました。 問題点 さて、前置きが長くなってしまいましたが、ここからが本記事の本題となります。 対応方針が決まったところで検証環境で動作確認をしていると「対応後のソースコードでもシークレットECにログインできない」という障害が稀に発生することがありました。 修正前と修正後の動作確認結果の比較 この現象は eTLD+1をドメイン属性にもつ THEBASE のCookieと、eTLDをドメイン属性にもつ THEBASE のCookieが同時に送信されているケースで発生していることがわかりました。 これは、今まで不具合が発生していたショップ(Cookieが保存できていなかったショップ)では修正後のCookieのみが保存されているために不具合が解消された一方で、今まで正常にログインできていたショップ(Cookieが保存できていたショップ)で新たに不具合が発生するようになった、ということです。 そもそもなぜ同じkeyのCookieが二種類できてしまうのか THEBASE のCookieは有効期限をセッションに設定していたため、この現象に遭遇した場合はブラウザを再起動すれば古いCookieが削除されて問題を解消することができると予想されました。 ところが、実際にブラウザを閉じてショップを開きなおしても、本来であれば消えるはずの前回アクセスした際のCookieが残ったままになってしまっていることが発生していました。 実は、この問題はブラウザが「前回開いたサイトを復元する」機能を実現するために、ブラウザを閉じた後も有効期限がセッションになっているCookieを保持し続ける挙動をすることが原因で発生しているらしいことがわかりました。 さらに、同名のCookieが存在する場合のCookieの取扱もブラウザによって異なっていることが私たちを混乱させました。 ブラウザ Cookieの並び順 同名のCookieが複数ある時シークレットECにログインできるか FireFox 古いCookieが新しいCookieよりも先に並ぶ できない Chrome 古いCookieが新しいCookieよりも先に並ぶ できない Safari 新しいCookieが古いCookieよりも先に並ぶ できる このように、ブラウザによって挙動が異なっており、いつCookieが削除されるのかがブラウザ依存であるという状態であることから、Cookieのdomain指定方法を変更するだけでは障害から復旧できないことがわかってしまいました。 解決案の模索 この問題に対して、私達のチームでは2つの案について検討しました。一つ目の案はこの現象を許容したままで対応をリリースすること、そして二つ目の案はsession.cookieで定義しているCookieのkey名を変更した上で対応をリリースすること、でした。 この2つの案の比較検討と障害の影響範囲の把握のため、開発メンバーで協力してソースコード上でsession.cookieの定義を使用している全ての参照箇所を洗い出しました。 以下の表は、この2つの案に対してそれぞれ比較検討した内容を表にまとめたものです。 案1:Cookieの重複を許容する 案2:Cookieのkey名を変更する 影響範囲 FireFox97とSafari以外のブラウザを使用している購入者 全ての購入者 メリット すぐにリリースできる 完全に不具合が発生しなくなる デメリット Cookieが二重で登録されてしまった購入者には、ブラウザキャッシュを削除してもらう必要がある セッションがリセットされるので、再ログイン等が必要になる 工数 なし リグレッションテストが膨大 影響期間 ブラウザの旧Cookieが消えるまで(つまりいつ収束するか不明) デプロイのタイミングのみ どちらの案でも発生するデメリットとして、デプロイを跨いで購入者が操作した際に以下の影響が出るという問題がありました。 改善リリースデプロイ前に抽選・定期便・コミュニティ限定商品をカートへ追加して未購入状態の場合、改善リリースデプロイ後にはカート内の商品が全て消えてしまう コミュニティ会員ページへログイン済みの状態でも、再度ログインが必要になってしまう シークレットECがかかったショップページへアクセスをしている状態でも、再度PW入力が必要になってしまう デプロイをまたいで購入をしたユーザーの場合、決済が走っているもののCookieを持ち越せないために購入完了画面が表示されないことで、購入に失敗したと誤解をして重複購入してしまう 修正対応リリース前後で問題が発生するケース そして我々はsession.cookieを修正してCookieのKey名を変えた 最終的に、Cookieのkey名を変更する案2の方がより購入者に優しいだろう、ということで決まりました。 一時的な不便を全購入者に要求してしまうことにはなるのですが、購入者に要求する操作としては再ログインやカートへの再度の商品追加など、通常のWebブラウジングの操作の範囲内で対処できるものとなっています。 逆に、案1の場合はブラウザのキャッシュを削除するという通常のWebブラウジングでは行わない操作を購入者に要求してしまう上に、BASE以外のサイトのキャッシュも削除してしまうことになります。もちろん特定のサイトのみのCookieを削除することもブラウザの機能としては可能ですが、その操作はさらに難易度の高いものです。 また、案1については重複したCookieが削除されるタイミングがブラウザ依存であるため、インシデントの収束タイミングを把握できないという問題点がある以上避けるべきだろう、という意見もありました。 そうして、上記のような理由から安全かつ完全な形で障害から復旧させる方法である、Cookieのkey名を変更してからリリースする、という方法を実施する決断を行いました。 リリースに当たっては、万全を期すために調査で判明したsession.cookieの定義を使用している処理を全て網羅するテストケースを作成すると共に、QAチームが使用しているリグレッションテスト項目を共有してもらい、購入者の一般的な操作を全て動作確認することでより安全性を高めました。 これによってさらに障害の復旧までに時間を要することにはなりますが、より安全かつ完全に対応するためには必要な作業だというのが開発チームの共通認識でした。 おわりに 今回、障害の発生を認識してから収束するまでの間に2週間という時間がかかってしまった点と、対応の副作用によって購入者様の皆様にご迷惑をおかけしてしまったことは大変申し訳なかったと感じています。 サービスを提供していく上で、障害を起こさないように気をつけることは重要なことです。そして、万が一障害が発生してしまった際には、如何に素早く影響を最小限に留めて適切な対処で障害を解決することができるか、という点もまた重要なことです。 BASEでは、このようにBASEが提供するサービスを利用してくださる購入者やショップオーナーの皆様のことを第一に考えてサービスを共に発展させていく仲間を募集しております。 カジュアル面談も実施しておりますので、ぜひお気軽にお問い合わせください。 https://open.talentio.com/r/1/c/binc/homes/4380
フロントエンドエンジニアの @rry です。 自分は BASE の Sales Promotion というチームで主に新規機能開発を行っています。このチームでは主にオーナーさんの使う管理画面に新しく機能追加をしています。 そこで、管理画面で使っている API Client と型を、 OpenAPI Generator を使って自動生成するようにしてみたのでそのお話を書きたいと思います。 そもそも OpenAPI とは? https://www.openapis.org/ OpenAPI とは、RESTful Web サービスを記述、生成、使用、および視覚化するための仕様です。 ※ 以前は OpenAPI ではなく仕様自体も Swagger と呼ばれていましたが、現在は仕様自体については OpneAPI と呼ばれており、Swagger というのは OpenAPI を使ったツール群のことをさすようになりました。まぎらわしいので Swagger ではなく主に OpenAPI と呼びます(ツール群のほうも「OpenAPI のツール」と呼んでいきます) BASE では YAML ファイルで記述しています。 OpenAPI とそのツール群を使うことでなにができる? API の仕様(スキーマ)を定義 定義の一元管理ができる API ドキュメントを生成 ドキュメントのメンテナンスが楽 API モックサーバーを立てられる API が出来上がっていなくても先にフロントエンドの開発ができる API Client を自動生成 API Client のコードをフロントエンドで書かずにすむ! API リクエスト・レスポンスの型を自動生成 スキーマから生成した型を使うことでより型安全になる 周辺ツールでいろいろできる バックエンドの実装と定義したスキーマが乖離した場合に自動テストが落ちるようにしたりもできる バックエンドの実装が乖離しないようにできる OpenAPI のようなスキーマを中心にした開発のことを、「スキーマ駆動開発」といいます。 OpenAPI を使ったスキーマ駆動開発をすることでなにがうれしいの?どういう問題を解決するの?というところは、以下のスライドが参考になるのでそちらをどうぞ。 BASE 既存システムへの OpenAPI 導入の背景について BASE では最近カートの大規模リプレイスを行いました。 BASE Tech Talk #1 〜Next.jsを使ったカート大規模リプレイスPJの裏側〜 - connpass 新しいカートのアーキテクチャでは、既に OpenAPI が導入されておりスキーマ駆動開発を行っていました。自分もフロントエンドの開発で OpenAPI から生成した API Client を利用したりしていました。 しかし BASE のオーナーさんの使う管理画面など、カート以外のシステムでは既存の API 定義は OpenAPI ではなく API Blueprint を利用していました。 API Blueprint を使った開発では API の仕様(スキーマ)を定義 API ドキュメントを生成 API モックサーバーを立てられる これらのことはできますが、API Client や型を自動生成することはできず、毎回手動で API Client と型を定義していました。 手動で定義したり API の仕様が変わったときにそれらの追従をすることが大変だと思い、カート開発のときと同様の開発体験を得たかった自分は「今回の PJ から OpenAPI を使ったスキーマ駆動開発をしよう!API Client と型を自動生成していこう!」と呼びかけ、そのための仕組みを導入することにしました。 API Client って何?自動生成ってどういうこと? API にリクエストを送るためのコードを API Client と呼んでいます。 // このような感じのコード export class FooApiClient extends APIClient { async getBar () { return this .request < APIResponseWith < Bar >>( { url: ` ${ BASE_PATH } /foo/bar` , } ) } } BASE では今まで上記のような API Client を手動で書いていたのですが、これからは OpenAPI から自動生成する API Client を利用していくことにしました。 以下は実際にどのようにして API Client と型を自動生成しているのかについて詳しく説明していきます。 ① API Client を自動生成する仕組みの概要 OpenAPI からどのようにして API Client を生成しているかをまとめました。 OpenAPI の個別のファイル群を編集 一つの大きな merged.yaml という OpenAPI ファイルを swagger-merger を使って生成 merged.yaml を元に openapi-generator-cli を使って API Client やスキーマの型を生成 その他便利関数と一緒に GitHub Packages を使って npm パッケージとして配信 openapi-generator-cli の typescript-fetch を使って fetch API の API Client を生成しています。 API Client を生成する流れはこのような感じですが、他にも merged.yaml を元に Docker を利用して色々しています。ReDoc / SwaggerUI を立てて API ドキュメントを読んだり、API Sprout を使って API モックサーバーを立てたりもしています。 どのような開発体験になるか まずバックエンドエンジニアが API を作る前に、OpenAPI の YAML ファイルだけを追加した PR を出して API のスキーマについてフロントエンドエンジニアと共にレビューします。PR がマージされると GitHub Actions が自動で API Client の npm パッケージを配信してくれます。 フロントエンドは配信されたパッケージを利用して、スキーマに沿った API へのリクエスト・レスポンスを実現することができます。 また、便利関数として API モックサーバーへのリクエストもできるようにしています。 これにより API の開発を待たずしてフロントエンド側の実装を進めていくことができます。 このようにして自動生成した API Client は以下のような形で使うことができます。 import { apiConfig , FooApi , FooBarResponse } from 'api-client' const client = new FooApi ( apiConfig ) const result: FooBarResponse = await client.getBar () ② OpenAPI の個別のファイル群について OpenAPI の個別のファイル群は、API Client を生成しやすいようにいくつかの命名規則に沿って作られています。 ディレクトリ構成は以下のとおりです。 ├── README.md ├── docker-compose.yaml ├── src ├── _components.yaml - components 定義 ├── _paths.yaml - paths 定義 ├── components - 共通 components 定義 │ └── error_response.yaml ├── merged.yaml - Docker から参照するためのファイル。特にいじらない ├── openapi.yaml - ベースファイル └── services - サービスディレクトリ └── <service_name> ├── components - service の components 定義 │ ├── xxx_request.yaml │ └── xxx_response.yaml ├── definitions - service の definitions 定義 │ └── user.yaml ├── examples - examples 定義 │ ├── <paths_name> │ │ ├── default.yaml │ │ └── xxx.yaml │ └── 400_example.yaml └── <paths>.yaml src/openapi.yaml の tags に name を定義 src/_paths.yaml に path を定義 src/services/ 配下に yaml ファイルを作成 yaml ファイルの内容が src/merged.yaml に反映される というのがザックリとした編集方法です。 OpenAPI は $ref というキーワードを使って外部ファイルを参照可能なため、 path API エンドポイント リクエスト・レスポンスの schema example これらを services 配下にまとめて、細かくファイル分割をして管理しやすい形にしています。 ③ API Client やスキーマの型を生成するにあたっての命名規則 ファイル名やその他命名など細かい規則をいくつか設けていますが、その中でも API Client の生成に影響するものをまとめました。 フォルダの命名規則 src/services/* 配下 各 API のスキーマを置く場所 API の URL と同じ構成にする この際 path に api が入っている場合は api を抜く 例) /apps/api/foo/bar なら、 src/services/apps/foo/bar.yaml となる 生成される API Client はフラットな階層に一律出力されるため、そもそもの命名としてユニーク性が必要です。そのため URL の構成に従って命名しています。 tags と operationId tags フォルダの path の services から親となるリソースまでの path をつなげる 例) src/services/apps/foo/bar.yaml なら appsFoo になる tags は services の各まとまりごとに同じものを用いる 例) /services/apps/foo/bar.yaml と /services/apps/foo/baz.yaml は同じ tags appsFoo を使う operationId リクエストメソッド + リソース名 例) /services/apps/foo/bar.yaml の GET リクエストだったら getBar となる API Client が生成されるとき、tags が API クラス名で operationId がメソッド名となります。そのため上記の命名規則で生成されるクラスは以下のような形になります。 export class AppsFooApi extends runtime.BaseAPI { async getBar ( requestParameters: GetBarRequest = {} , initOverrides?: RequestInit ) : Promise < AppsFooBarResponse > { // ... } } requestBody と responses のスキーマと examples これらは別ファイルに models として切り出すようにしています。 get : tags : - appsFoo operationId : getBar responses : '200' : description : OK content : application/json : schema : $ref : ./components/bar_response.yaml examples : default : $ref : ./examples/default.yaml '500' : description : Internal Server Error content : application/json : schema : $ref : ../../../components/error_response.yaml examples : barInternalError : $ref : ./examples/bar_internal_error.yaml # ... # ./components/bar_response.yaml title : appsFooBarResponse type : object properties : status : type : number bar : type : string nullable : true # ./examples/default.yaml value : status : 200 bar : null models として切り出して個別に title を定義することで、レスポンスの型の命名が自動的に InlineResponseXXX のようになってしまうのを防ぎます。 また、examples も default のように名前を定義することで、API モックサーバーへリクエストするときに意図したレスポンスを返してもらうことができるようにしています。 import { mockApiConfig , AppsFooApi } from 'api-client' const client = new AppsFooApi ( mockApiConfig ( { status : 500 } )) const result = await client.getBar ( {} ) // 500 エラーが返ってくる // example の value は OpenAPI 定義の examples の key を指定 const client = new AppsFooApi ( mockApiConfig ( { example: 'default' } )) const result = await client.getBar ( {} ) // default で設定した example の値が返ってくる ④ その他 API Client を利用する上で用意した便利関数 非同期関数の catch や try / catch でエラーが起きたときのハンドリングを行う便利関数も用意しています。 apiErrorType を使って起こったエラーを3つのパターンに整形する APIError: API から返ってきたエラーレスポンス Error: それ以外の何かしらのエラー null: 401エラーの場合は一律でエラーハンドリングしており何もしないため null を返す isAPIError を使ってエラーが APIError なのか Error なのかを判別する const client = new AppsFooApi ( apiConfig ) const result = await client.getBar ( {} ) . catch (async ( e ) => { const error = await apiErrorType ( e ) // 401 のときは自動的に FlashMessage が出るようにしているため早期リターン if ( ! error ) return if ( isAPIError ( error )) { // APIError で 404 など返ってきたときのエラーハンドリング } else { // そうでないただのエラーが返ってきたときのエラーハンドリング console .log ( error.message ) } } ) また、エラーハンドリングについての考え方は今まで実装者の判断に委ねていた部分がありましたが、エラーハンドリングのやり方についても別記事でまとめて認識合わせをしたりしました。 Web サービスを開発するときのエラーハンドリングについて ユーザーに表示するエラーメッセージを管理するのはフロントエンド?バックエンド? 注意したいのがここで返ってくる title や detail などは、ユーザーに表示するための文言ではなくあくまで開発者に何のエラーか教えてあげるための文言だということです。 エラーを返しているのは API であって、API を操作するのはフロントエンドのコード(つまりフロントエンド開発者)なので、開発者がわかるエラーメッセージで十分です。 ユーザーに表示するエラー文言については、デザイナーと相談して決めることがほとんどかと思います。ここは細かい調整が行いやすいフロントエンドで管理するのが良いでしょう。ユーザーに表示する領域はフロントエンドの領域です。 ⑤ GitHub Actions を使った GitHub Packages の配信 src/merged.yaml または api-client/package.json に変更があった PR が main ブランチにマージされた場合は GitHub Actions で GitHub Packages を配信するようにしています。 - uses : docker://openapitools/openapi-generator-cli with : args : generate -g typescript-fetch -i src/merged.yaml -o api-client/src/generated --additional-properties=modelPropertyNaming=camelCase,supportsES6= true ,withInterfaces= true ,typescriptThreePlus= true - run : | cd api-client yarn install --frozen-lockfile yarn build - env : GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} run : | cd api-client npm config set //npm.pkg.github.com/:_authToken=$GITHUB_TOKEN npm publish しかし、main にマージした際に何らかの理由で npm publish が失敗したらどうしましょう?🤔 そんなときのために、CI で publish できるかもチェックしています。 can-npm-publish を利用して npm publish ができない場合は CI が落ちて気づけるように GitHub Actions を設定しています。便利ですね。 - env : GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }} run : | cd api-client npm config set //npm.pkg.github.com/:_authToken=$GITHUB_TOKEN yarn run can-npm-publish --verbose 以上、①〜⑤まで API Client と型の自動生成をする上でのポイントをあげました。 このような形で現在は自分のやっている PJ 以外でも OpenAPI から生成した API Client と型が利用されるようになってきています。 使っていく上でのメリットとこれからの課題 OpenAPI を利用したスキーマ駆動開発をやってみて感じたメリットは以下の通りです。 API の実装を待たずしてフロントエンド開発ができる API モックサーバーを使うことで、外部連携などが必要な複雑な API であったとしてもフロントエンドの動作確認や開発を楽に行うことができる API Client やスキーマの型をいちいち手動で書く必要がなくなる 仕様と実装の乖離がなくなる このように、開発を楽に&速くすることができました! 🙌 とはいえ、全体を通した課題はまだまだ感じます。 フロントエンドの開発にとってメリットを感じられることは大きいのですが、バックエンドの開発にとってはどうでしょうか。 バックエンドの実装と定義したスキーマが乖離した場合に自動テストが落ちるようにしたりなどの設定がまだできていない バックエンドは仕様と実装の乖離が起こっても検知できないので、API に変更がある場合はコミュニケーションでなんとかする必要がある といったように、現状はフロントエンドの開発ではスキーマ駆動開発の良さを享受できるけどバックエンドの開発ではフロントエンドほどの恩恵は受けられていない状況です。 とはいえバックエンドの開発でも、API の実装がフロントエンドの実装のブロッカーにならずにすむというというのはバックエンド開発者の精神衛生上とても良いことだと、うれしいフィードバックを受けたりもしました。 今後バックエンドの状態が変わり次第 OpenAPI のツールを入れるなりして、さらにより良いスキーマ駆動開発を推進していければいいなと思っています。 おわりに OpenAPI を利用したスキーマ駆動開発で得られるメリットはとても大きいものです。 ぜひ API Client と型を自動生成して各 PJ で活用してみてください!
BASEの機械学習チームで論文読み会を実施してみました こんにちは。BASEのDataStrategy(DS)チームでエンジニアをしている竹内です。 DSチームではBASEにおける様々なデータ分析業務をはじめ、機械学習技術を利用した検索、推薦機能のサポート、商品のチェックや不正決済の防止などに取り組んでいます。 先日、チーム内で最新の機械学習技術についての知見を相互に深めるための試みとして、各々興味のある機械学習系の論文を持ち寄って紹介し合う、いわゆる論文読み会というものを実施してみました。 この記事では、その会で私が発表した内容の一部を紹介したいと思います。 ※ 中身は論文読み会用から本記事用に一部修正を加えています。 A ConvNet for the 2020s 紹介する論文について タイトル: A ConvNet for the 2020s 著者: Zhuang Liu, Hanzi Mao, Chao-Yuan Wu, Christoph Feichtenhofer, Trevor Darrell, Saining Xie Facebook AI Research (FAIR), UC Berkeley CVPR 2022 arXivリンク: https://arxiv.org/abs/2201.03545 公式実装: https://github.com/facebookresearch/ConvNeXt ※ 挿入している図(画像)と英文は特に言及がない限り本論文からの引用になります。 TL;DR 直近の画像処理NNのアーキテクチャにおいては、Transformerをベースにしたものがトップクラスの性能を発揮(Swin-T) TransformerのキーとなるモジュールはMulti-Head Self-Attention(MSA)だが、実際にはそれ以外にも従来のConvNetに取り入れられていない様々なテクニックが存在→真にConvNetを上回っているとは言えないのでは そこで従来のResNetに、Transformerに加えられているMSA以外のテクニックを可能な限り盛り込んだ(ConvNeXtと命名) ConvNeXtは従来のTransformerベースのモデルに対して、モデルサイズを抑えながら性能を上回ることができた We gradually “modernize” a standard ResNet toward the design of a vision Transformer, and discover several key components that contribute to the performance difference along the way. 画像分類タスクにおけるConvNeXtとViTの性能比較 画像系NNモデルアーキテクチャの流れ ConvNeXtに到達するまでのターニングポイント的なアーキテクチャをざっくりと振り返ってみる。(リンクはarXiv) AlexNet(2012) ConvNetの始祖的な存在 ImageNetコンテストで圧勝 →以降ConvNetの層を深くするのがトレンドに →層を深くすると以下の二つの問題が浮上 Back Propagation時の勾配消失・勾配爆発の問題 精度の飽和、学習時のエラーの上昇(Degradation)の問題 ResNet(2015) 勾配消失・勾配爆発問題はBatch Normalizationが有効 Degradationに対するアプローチとして、層間のショートカット接続の重要性に注目 ショートカットを含んだブロックを含むアーキテクチャを提唱 ResNetのショートカット構造(He, Kaiming, et al. "Deep residual learning for image recognition."より) ResNeXt(2016) ResNetのブロックを並列に並べて集計する仕組みを提唱 パラメータ数と性能のトレードオフを改善 並列に並べる数Cardinalityをハイパーパラメータとして導入 少ないパラメータ数、小さいモデルサイズ、シンプルな形でResNetの性能を上回る ResNeXtの仕組み(Xie, Saining, et al. "Aggregated residual transformations for deep neural networks."より) EfficientNet(2019) ネットワークの深さ、広さ、解像度(画像サイズ)の3つをパラメータとして最適化 扱いやすく画像系の機械学習コンペ等ではよく見るアーキテクチャの一つ VisionTransformer(ViT)(2020) 自然言語処理系NNにおいてはデファクトスタンダードとなったTransformerを画像処理に応用 画像を16x16のパッチに分け、それぞれのEmbeddingを単語に見立てる 画像分類において少ない計算コストでトップ性能を発揮 画像処理における最近の大きなブレークスルーの一つ ViTで用いられる画像のパッチ化(Dosovitskiy, Alexey, et al. "An image is worth 16x16 words: Transformers for image recognition at scale."より) Swin Transformer(Swin-T)(2021) ViTを物体検知やセマンティックセグメンテーションなど、ピクセル単位の解像度が要求される他のタスクでも効果を発揮できるように改良 パッチ化の処理を階層化+1マスずつズラす処理(Shifted Window)を導入することで画像サイズの2乗であったViTの計算量を線形まで落とした 画像処理系のトップカンファであるICCV'21のBest Paper ConvNeXtの論文では、Swin-Tで採用されているWindowをズラす処理がConvと類似しているため、重要な要素であると考えられることが言及されている For example, the “sliding window” strategy (e.g. attention within local windows) was reintroduced to Transformers, allowing them to behave more similarly to ConvNets. ... Swin Transformer’s success and rapid adoption also revealed one thing: the essence of convolution is not becoming irrelevant; rather, it remains much desired and has never faded. Swin-Tで用いられる階層的なパッチ化(Liu, Ze, et al. "Swin transformer: Hierarchical vision transformer using shifted windows."より) ConvNeXt(2022) 本記事で紹介している論文 ResNetがConvNeXtになるまでに加えられた改良 Chapter2以降ではベースとなるResNetに加えられた手法と、それによる精度の改善幅について順に説明されている。 2.1 学習手法 ネットワークのアーキテクチャを弄る前に学習手法をTransformerに倣って改善していく。 エポック数を90→300に AdamW optimizer(2019) L2正則化とWeight DecayがAdamでは同一視することができないことを示し、AdamのWeight Decayに修正を加えた データ拡張 Mixup(2018) 2種類のデータとラベルをベータ分布からランダム生成された を使って以下のように混ぜ合わせる データ: ラベル: Cutmix(2019) 画像の一部を切り取り、別のラベルの画像を挿入 RandAugment(2020) グリッドサーチで最適な拡張度合いを見つける RandomErasing(2017) 画像内にランダムな矩形を追加する Cutmix(Yun, Sangdoo, et al. "Cutmix: Regularization strategy to train strong classifiers with localizable features."より) Random Erasing(Zhong, Zhun, et al. "Random erasing data augmentation."より) 正則化 Stochastic Depth(2016) ランダムでResブロックをスキップのみにする(後ろの層になるほどその確率が高くなる) Label Smoothing(2016) 正解ラベルと不正解ラベルの値を1, 0ではなく0.9, 0.1などとする これらの追加により性能は76.1%→78.8%に改善 2.2 マクロデザイン ここからは、ResNetのマクロな構造をTransformerに近づけていく。 ステージごとの計算比率の変更 複数のResブロックからなる1かたまりはステージと名付けられており、ConvNeXtには合計で4つのステージが存在する。 それぞれのステージのブロック数はResNet-50では(3, 4, 6, 3)であったが、これをSwin-Tに合わせて(3, 3, 9, 3)に変更した。 78.8%→79.4%に改善 stemで画像をパッチ化するように変更 入力された画像に対して一番最初に処理を行う部分はstemと名付けられており、ViTなどでは画像のパッチ化を行う部分に相当する。 従来のResNetでは、まず入力の画像を適切な特徴量のサイズにするためにカーネルサイズ7×7ストライド2のConv+Max Poolingを使用することで4倍のダウンサンプルを行なっていた。 一方でViTでは画像を16×16のパッチにする処理を行なっているが、これはカーネルサイズ16×16で重複なし(ストライド16)のConvに相当する。 Swin-Tではより小さい4×4のパッチを作成しているため、これに倣ってstemでカーネルサイズ4×4でストライド4のConvを使用する。 79.4%→79.5%に改善 2.3 ResNeXt化 ResNeXtで取り入れられている、1つのConvを複数に分岐させてあとからまとめることでパラメタ数を削減する手法を適用した。 モデルのキャパシティの減少を抑えつつパラメタ数を効率的に削減できるため、モデルサイズを維持したまま性能を大幅に引き上げることができる。 今回はチャンネル数分の分岐を作成するDepthwise Convを使用する。これはTransformerにおけるAttention層のMulti-Head化に相当すると言及されている。 We note that depthwise convolution is similar to the weighted sum operation in self-attention 79.5%→80.5%に改善 2.4 Inverted Bottleneck Transformerでは入力の次元より隠れ層MLPの次元の方が4倍大きくなるInverted Bottleneckというデザインを採用している。 このアイデアはMobileNetV2(2018)ですでに利用されており、その後の改良型ConvNetでもしばしば用いられている。 下図の(a)がResNeXtの1ブロックで用いられる通常のBottleneckで、チャンネルサイズを384→96に落としてからDepthwise Convで96→96に畳み込み、最後にチャンネル数を384に戻している。 (b)がInverted Bottleneckで、チャンネル数を逆に96→384に増やしてから384→96に戻している。 これによってDepthwise Convの計算量は増えるものの、入力部分がダウンサンプルされていることによってResブロックのショートカットの1×1Convの計算量が減るため、全体の計算量は減ることになる。 (a)がResNeXt, (b)がInverted Bottleneck, (c)がDepthwise Convを移動させたもの 80.5%→80.6%に改善(ResNet-200では81.9%→82.6%に改善) 2.5 Large Kernel Sizes 従来のConvNetでは3×3など小さいカーネルサイズ使用するのが主流であったものの、Swin-TのWindowのサイズは小さくとも7×7である点を考慮すると、カーネルサイズは大きい方が有効だと思われる。 これを実現するために以下の二つの手順を踏んでいる。 Depthwise Convの移動 より大きなカーネルサイズを利用するために、Depthwise ConvをResブロックの最初にもってくる。(上の図の(b)→(c)に対応) これはTransformerのMulti-Head AttentionがMLPの前に配置されていることに対応する。 (一時的に)80.6%→79.9%に悪化 カーネルサイズの増加 Depthwise Convを移動させた後、そのカーネルサイズを3から5, 7, 9, 11と増やしていくと計算量は大体保たれたまま性能が改善され、7で大体性能が飽和する。 サイズの大きいResNet-200でも同じ7で飽和することが確認されている。 79.9%→80.6に改善 この時点でViTで採用されているデザインの大部分を実現できていることになる。 2.6ミクロデザイン 大枠のアーキテクチャは完成したため、ここからはレイヤーレベルで改善していく。 ReLUをGELUで置き換える 活性化関数として使用されているReLUをBERTやGPT-2、ViTでも使われている以下の GELU(2016) に置き換える。 GELUと他の活性化関数との比較(Hendrycks, Dan, and Kevin Gimpel. "Gaussian error linear units (gelus)."より) 80.6%→80.6%で性能据え置き 活性化関数を減らす Transformerの1ブロック(入力のKey/Query/ValueをEmbeddingしてMLPに入れる部分)には活性化関数が1回しか使用されていない一方で、ResNetは1ブロックにConv層の数だけ活性化関数が存在する。 これを1×1のConv2つの間のみに絞ることで活性化関数の数を合わせる。 80.6%→81.3%に改善 Normalization層を減らす これもTransformerに合わせて1×1conv層の前にのみBatch Normalization層を置く。 81.3%→81.4%に改善 BatchNormをLayerNormに変更 これもTransformerで使用されている手法ではあるが、単純なResNetのBNをLNに置き換えるだけでは性能が下がることが確認されている。ここまでの改造を全て加えた上でLNに置き換えると性能の改善が見られる。 81.4%→81.5%に改善 ダウンサンプル層を切り離す ResNetでは各ステージの最初のResブロックでストライド2のConvによってダウンサンプリングを行なっているが、Swin-Tではこのような処理は各ステージの間で行われている。 これに倣ってConvNeXtでもステージの間にダウンサンプル層とNorm層を追加することで学習を安定化させた。(Norm層なしだと学習が発散した。) 81.5%→82.0%に改善 ResNetに加えた全ての変更点とそれによる性能および計算量の改善 最終的なモデルのアーキテクチャ 性能 画像分類タスクにおける性能比較 ImageNetにおいてConvNeXtは同程度のモデルサイズのSwinTransformerを上回る性能を発揮している。 物体検出タスクにおける性能比較 COCOデータセットにおける物体検出においてもConvNeXtは同程度のモデルサイズのSwinTransformerを上回る性能を発揮している。 感想など ここ最近の画像処理系の流れを振り返るのにちょうど良い論文で、公式のpytorchによる実装と合わせて内容が非常にわかりやすく良い論文 強いて言えば既存の技術の応用という面が強いため、新しいアイデアや知見、理論的な深掘り(「なぜAttentionよりConvolutionの方が上手くいくのか」など))の面では若干物足りない気もする 昔読んだ深層強化学習系の Rainbow: Combining Improvements in Deep Reinforcement Learning という論文になんとなく立ち位置が似ているなと感じた ベースとなるDeep Q-Networkという手法に7つの改善手法を加えたときのパフォーマンスの改善について研究した論文 自分でベースモデルのアーキテクチャや学習手法に改善を加える際の流れとしても参考になる おわりに 今回のような論文読み会はDSチームとしては初の試みでしたが、新しい知見を取り入れ視野を広げる良い機会に感じたので1Qに1回ぐらいのペースで継続していけたらと思っています。 今後もDSチームでは新しい技術についても積極的に検討し、検証を重ねることで更なるプロダクトの改善、サービスの向上に取り組んでいきます。
初めまして。フロントエンドエンジニアの近藤 @kon_engineer と申します。 本記事では、2022年1月24日(月)にリリースされた、商品在庫絞り込み機能の振り返りと、サービス全体の状況を可視化できるNew Relicというプラットフォームを活用したAPIの観測について紹介します。 今回の事例では、New Relicで観測可能なAPIのレスポンス速度や各クエリパラメータのリクエスト状況などを分析して、効果測定や今後の施策に活かす取り組みを行いました。New Relicの詳しい説明や、BASEがNew Relicを導入した経緯は こちらの記事 をご参照頂けたらと思います。 商品在庫絞り込み機能とは 商品管理画面で商品の在庫数を指定して検索できる機能です。BASEでは商品管理画面の一覧から、在庫切れの商品や在庫が少なくなっている商品を簡単に見つけることができないという課題がありました。特に商品数が50を超える場合は、在庫状況をページングして確認する必要がありました。そこで、在庫数で商品を絞り込めるように改修して、商品管理の利便性の向上を図りました。 Twitter上の告知は以下です。 ╭━━━╮​  NEW✨ ╰━v━╯ BASE( ᐛ )⛺️ 🛒「商品管理」🛒 がアップデート! 💡在庫がない商品がパッと見つけられる💡 管理画面の「商品管理>絞り込み」から 🛒在庫なし 📝在庫が一定数以下 の商品の絞り込みが可能に◎ ぜひご活用ください! pic.twitter.com/7PtLZCf7GW — BASE(ベイス)🔎新機能登場! (@BASEec) 2022年1月25日 New Relicによる監視 New Relicによる監視はBASE全体として推進しているものの、本PJのメンバーは本格的な導入経験はありませんでした。今回はプロジェクトとしてトライしてみようということで、メンバーそれぞれが仕組みを学びながら運用していきました。 開発前 検索利用状況 商品在庫検索機能は、既存の商品検索APIにクエリパラメータを追加することで実装しました。実装前に既存の商品検索APIのレスポンス速度や、使用されている頻度を把握することで既存の問題や、追加で行った方が良い改善がないか、開発前に観測することにしました。 まずは検索API全体の呼び出された回数を測定して、次に各パラメータが指定された回数を測定することで、どのパラメータが使われて、どのパラメータが使われていないか分かるようにしました。 得られた結果として、全体の検索回数に対して、キーワードを指定して検索された回数が約87%あることが測定できたため、キーワードで主に検索されていることが分かりました。また、商品タイプ別の検索は、他の検索条件よりも極端に使用されていないことが呼びされている回数から分かったため、何らかの対応を検討する価値があると把握できました。 さらに、キーワード検索以外は、絞り込みボタンを押して検索モーダルを開かなければ検索できないUIのため、キーワード以外の各検索回数の結果から、検索モーダル経由で一定数の検索リクエストが呼ばれていることが分かりました。今回は検索モーダルに在庫数の検索機能を追加するため、そもそも検索モーダルがほとんど開かれていない、という状況ではないことを把握することは重要でした。 負荷状況 レスポンス速度に改善の余地はないか、また今回の改修によってレスポンスが悪くならないか把握するために、現状の応答速度を事前に把握しました。特に大きな問題は見つからなかったため、この速度を維持することを目標としました。 リリース前後 規模別 事前にショップを規模別に分類して、規模別でどのような効果があったのか観測できるように準備しました。今回の機能は、商品数が増えて在庫管理が大変な大規模ショップにより使ってもらいたいという思いがあったので、規模別に観測することで効果を細かく把握する狙いがありました。 リリース後の検索回数 また、在庫数を検索された回数がどれくらいなのか、リクエストURLを監視してリリース後すぐに分かるようにしました。機能が順調に使われていることが観測できて、PJメンバーで喜ぶ場面もありました。 どのような値で検索されているか把握する 当初は観測していなかったのですが、リリース後に在庫数0で検索されているケースがとても多いことが判明しました。在庫が少ない商品を検索するために使われることは予想していたのですが、それにしても0で検索されることが多い、という印象でした。そのため、どのような数値で検索されているのか、後からNew Relicのダッシュボードに追加して観測しました。結果として8割以上が在庫数0で検索されていたことが分かり、数字から事実として在庫切れ商品を把握したいニーズを把握することができました。これにより、在庫切れ商品を素早く通知するような、在庫切れにアプローチした施策が今後も有効だろうということが分かりました。 負荷状況 事前に十分に検証はしたものの、リリース後もレスポンスが遅くなっていないか、エラーコードが出ていないかなどの検証をしました。特にエラーも発生しておらず、レスポンスも悪化していないことを観測することができました。 終わりに PJが始まった時、今まで効果測定を適切に行えていないというチームメンバーの課題感がありました。そのため、ただリリースして終わるのではなく、機能を使ってもらっているのか、どのような使われ方をしているのかを把握して、反省と次の施策に繋げる取り組みを行いました。 今回の施策は、規模として大きな改修ではありませんでしたが、改修したAPIをNew Relicで観測することで、しっかりと機能が使われていることを把握できて、施策として一定の効果があったことが分かりました。また、当初チームが持っていた仮説として、ショップオーナーの方々は、在庫切れの商品を少ない時間で簡単に把握したい、というものがありました。仮説が間違っていなかったことが改修したAPIの利用状況からも分かり、今後も在庫切れの商品がすぐに分かるような施策が有効であると、自信を深めることができました。 何より、作ったものがしっかりと使われていることを観測できることで、チーム全体の士気も上がったと思います。今後も継続的にこのような取り組みを行うことで、施策の有効性や妥当性を意識して開発していきたいと考えています。
この記事は BASE Advent Calendar 2021 の25日目の記事です。 はじめに メリークリスマス!!! 執行役員 VP of Productの神宮司( @7jin16 )です。 2021年に取り組んだ顧客フィードバックを製品開発に活かすためにおこなったことを書きます。 なぜ始めたか これまでも顧客からのフィードバックを活かしていなかったわけではないのですが、より多く、より広くフィードバックを集める、全社で閲覧・利用可能な形で集積すること製品企画・開発の質が向上すると考えて取り組むことにしました。 なにをしたか "より多く、より広くフィードバックを集め、全社で閲覧・利用可能な形にする" を理想として掲げてそれを達成するための方法を考えました。 1. フィードバック量を増やすために 今までは製品内から直接フィードバックを送信する機能はなかったため、製品のほぼすべてのページのフッター付近にフィードバック送信フォームへのリンクを設置しました。リンクを設置したところリリースから日が浅い頃は1日数百件のフィードバックが送られてきました。 2. 製品開発に役立てるために フィードバックをただ集めるだけでは、製品開発に最大限役立てることはできません。おかげさまで「BASE」でネットショップを運営しているショップは160万ショップを超えていて売上規模や扱っている商材もショップによってさまざまです。同じ機能へのフィードバックでも注文数の多さや扱う商品数によってショップが抱える課題はまったく異なってくるため送信元ショップの注文数や商品数、業種などの情報は必須です。 フォームとしてはひとつですが送信元のショップと注文数、商品数、業種などのショップの情報をフィードバックと紐づけています。 フィードバックを送信すると社内管理画面に送られます。 3. 全社で閲覧・利用可能な形にする 以前から「BASE」にはお問合せや口頭で多くの顧客からフィードバックが寄せられていましたが、SlackやGoogleスプレッドシートに保存されていて集積場所が点在しているため一覧性や検索性が低く利用しやすい状態ではありませんでした。 利用しにくい状態だとフィードバックを集めても社内のメンバーも活用しにくいため集積場所を統一することから始めました。 顧客が「BASE」にフィードバックを伝える方法は カスタマーサポートのお問合せフォーム(Zendesk) 顧客と話している社内メンバーに口頭で伝える 製品に設置されているフォームから送信 の3パターンあり、どこから伝えられても社内管理画面に集積されていくようにしました。 SaaSを選べなかった理由 製品フィードバックを複数チャネルで集めて管理するSaaSは国内外問わず増えていてます。しかし、「BASE」では前述した以下の要件を落とすことができず短期的にはSaaSで達成することができないため自社開発をすることにしました。 同じ機能へのフィードバックでも注文数の多さや扱う商品数によってショップが抱える課題はまったく異なってくるため送信元ショップの注文数や商品数、業種などの情報は必須です。 実際にプロダクト改善に活かせたのか? 6月にフィードバックフォームをリリースしてから6,000件以上のフィードバックをいただいています。フィードバックをいただいたからといってそのまま機能として実装されることはなく、フィードバックを送信してくれた顧客が抱えている本当の課題を探り、課題のコアに辿り着くことが重要だと思っています。 フィードバックフォームをリリースしてから既に50件以上の機能改善につながっており、今後も発展させていきたいです。 オーナー様/お客様の声から生まれた改善レポートまとめ 「BASE」では一緒にプロダクトを改善していくメンバーを募集してます!幅広い職種で募集中ですので少しでもご興味がございましたら 採用ページ からカジュアル面談を申し込んでいただけると嬉しいです!
はじめに CTOの川口 ( id:dmnlk ) です。 これはBASE Advent Calendar25日目の記事です。僕は立候補してないのに勝手に日程が組み込まれてました。 BASE株式会社では積極的にエンジニア採用を行っております。 その中でよく質問を頂くのは「BASEに今から入社した場合にやることはあるのか?」というものです。 確かにサービスは成長し上場もしている企業で自分がやることはあるんだろうか、というのは僕も同様に疑問を持つだろうなとは思います。 ですので今回は話せる範囲ではありますが、今BASEで必要とされていることを書いていこうと思います。 フレームワーク移行 来年以降、BASEシステムとして取り組むものとして非常に大きいウェイトを占めるのは間違いなくこれになるでしょう。 現在BASEの大部分が利用している言語及びフレームワークはCakePHPですが2系を利用しておりEOLを迎えているだけでなく開発生産性などが数世代遅れているということを否定出来ません。(CakePHP2というフレームワーク自体が悪かったという話ではありません) これらを徐々に別のフレームワークや言語のシステムに変更していくというのが主なミッションです。 その中でモノリスからマイクロサービスという形にしていくという選択肢もあるでしょう。個人的にはRDBMSのトランザクションの恩恵を捨てる選択肢を取ることに恐怖はありますが。 BASEシステムのコード量は非常に多くデータのパターンも多岐に渡るため、コードの歴史の解読、調査、修正にテストなど技術理解だけでない幅広いスキルが必要になると思います。 使っていただいているユーザーさんの不便を起こさないよう、安全にシステムをマイグレーションしていくという仕事は胆力のいるミッションだとは思いますが我こそはという方は是非お待ちしています。 open.talentio.com セキュリティ BASEでは多くのユーザー様のデータをお預かりしており、社内やアプリケーションのセキュリティを強化していくことは重要です。 今まで専任のセキュリティエンジニアは採用しておらず、元々セキュリティに一定の造詣があるメンバーによって脆弱性診断の対応等を行っておりました。 しかしこれでは組織として非常に弱く、より踏み込んだセキュリティ対策を行えない状態ではありましたので今回募集をさせて頂いております。 特にアプリケーション側のセキュリティエンジニアに関しては、内部統制や各種認証取得などの整備などとは別のチームとして動いていただきます。 BASEで利用しているAWSのセキュリティ製品の適用によるセキュリティ向上、脆弱性診断の結果トリアージ、不正リクエストの検知対応など様々あります。 脆弱性診断の結果トリアージは、場合によっては修正からデプロイまで行っていただく予定でありPHPである必要はありませんがウェブアプリケーションの開発経験を要求しています。 大量のリクエストや多くの種類のユーザーさんが飛び交うアプリケーション上で起きうるたくさんのセキュリティリスクを低減し、安心して使っていただける環境を作るチームとして立ち上げていきたい方をお待ちしています。 open.talentio.com DX改善 Digital Transformationではなく、Developer Experienceです。 開発者体験が悪い状態での開発は進められますが、複利的に効いてきます。 それを改善するために、デプロイ速度の向上やローカル及び開発環境の整備、CI/CDパイプラインの効率化などやることは非常に多くあります。 先日のイベントで軽く話しましたがこのあたりは常時タスクがあるというわけではなく専属エンジニアが付けづらくCTO職などがやることが多くなってしまうのが現実だったりします。 開発者の現状に寄り添いながらよりよい開発者体験を作っていきたい方を募集しています。 EMの育成や新規メンバーのオンボーディング強化 組織が大きくなると必ず非常に重要になってくるのがマネージャーの存在です。 普段のタスク管理などはそこまで問題となりませんが、メンバーの成長を考えながらチームとしての成果を最大化を考える必要があります。 恥ずかしながらCTOの自分はそのあたりの知見が薄く得意でもないので、EMを育成という観点で何が有効になっていくかがあまりわかっていません、 大規模組織でのマネジメント経験や育成を行っていた方は是非BASEに来てその知見を活かして欲しいと思います。 合わせて、続々と入ってくる新メンバーに大規模になってきたBASEシステムのオンボーディング体験を向上させていくといったことも一緒に考えていただければと思います。 どれだけ優秀なエンジニアでも入社してからの成果を最大化してもらうためにオンボーディングは必須であり、課題は大きくなっているのでぜひとも取り組みたい箇所です。 まだ予定はありませんが新卒採用も見据えてやっていきたいですね。 パフォーマンス改善 多くのショップ様が使ってくれる現状においてパフォーマンス改善は日々のタスクとして取り組まないといけない課題です。 データ量が多くなったショップ様の管理画面パフォーマンス、ショップページのレンダリング高速化など改善すべきページはたくさんあります。 現代的なCDN技術などを利用し高速化を図っていきたいと思っています。 New Relicを積極的に活用しているので監視を超えて可観測性を高めてより高速なネットショップ作成サービスにしていきたいですので、是非パフォーマンスジャンキーの方を募集しています。 最近ではこのようなアウトプットもあります。 devblog.thebase.in おわりに これらの取り組みだけでなく、事業の課題についてのサービス開発、Pay ID開発、さらなるUXの向上、アクセシビリティ、データ基盤整備、機械学習による不正決済対策、レコメンド開発などなどたくさんあります。というかまだまだある!メチャクチャある!助けてください! 是非興味ある方は下記から応募して頂いたり、僕のTwitterにDM、Meetyなどどんな手段でもいいのでご連絡ください。 では、皆様良いお年を!!! open.talentio.com meety.net
この記事はBASE Advent Calendar 24日目の記事です。 BASEテックブログ編集長の松原( @simezi9 )です。 12月もいよいよ大詰め、クリスマス・イブということでそろそろ年内の仕事を納められた方もいるのではないでしょうか。 BASEのアドベントカレンダーも今年で 4回目 となりました。 今年も全部で32本(一日で複数記事の日も作ったため)の記事を12/1~12/25の期間で投稿してきました。 本記事ではブログ編集長としてアドベントカレンダー運営に際して行ったことと、その振り返りをしたいと思います。 これは来年またBASEのアドベントカレンダーが無事に開催されること、 あるいは世の中のだれかがアドベントカレンダーを運営する際になにか1つでも参考になることがあればと思って書き残す備忘録でもあります。 タイムライン 編集部としてのアドベントカレンダーの準備のための流れは以下のようになりました 日時 イベント 11/4 アドベントカレンダーのレギュレーションを社内に公開 & 執筆者の募集を開始 11/18 カレンダーの日程が埋まる 11/25 アドベントカレンダーの各記事に使うアイキャッチ画像のテンプレ用意完了 11/30 告知ページ 公開 12/1 記事公開開始 レギュレーションについて まず11月頭にアドベントカレンダーの趣旨と参加方法を書いたドキュメントを社内ナレッジベースにポストしました。 このタイミングで公開したのは主に 記事の内容と公開手段について 記事作成から実際の公開に至るまでのフロー 参加日を表明できるカレンダー の3つでした。それぞれを掘り下げてみます。 1. 記事の内容について 記事の内容はBASEや開発に関係があることであれば何でも可、としテックブログには技術記事のみ掲載可能とし、記事の内容次第では note や個人ブログを使っても大丈夫ということを明確にしました。 当たり前の内容に思えるかもしれませんが、これについては過去の経緯があります。 過去BASEのアドベントカレンダーではテーマをフリーにしたところ、真面目なものからゆるいものまで幅広いテーマの記事が集まったことがありました。 とてもにぎやかではあったのですが、それらの記事をすべてこのテックブログで公開したことでブログの記事の軸がぶれてしまう結果になってしまったことがあります。 これは既存のブログ読者の方にとってもあまり好ましくない状態であろうと考え、昨年はテックブログでの公開にふさわしい技術記事のみで構成したアドベントカレンダーとしました。 しかしながら、社内の雰囲気をお伝えするという意味で多様な記事がアドベントカレンダーに掲載されることは基本的に良いことであると私自身は考えていました。 過去に発生した問題は結局のところ、公開場所にふさわしくない記事をアドベントカレンダーだから掲載したということで起きているだけなので、 note や個人ブログでアドベントカレンダー公開も可能であるということを最初から明言すればよいと思いそれを盛り込むことで、技術記事でなくてもよいというのを今年改めて決定できた、という流れです。 (余談ですが BASEはnote株式会社と資本業務提携 を結んでいます。) 残念ながら今年はnoteでの記事公開はなかったというオチがついてしまったのですが、とりあえず公開する記事の方向整理ができたという点で来年以降に期待したいと思っています。 2. 記事作成から公開に至るまでのフロー アドベントカレンダー期間は通常の状況を遥かに超える記事のレビュー依頼が来るため兼務で行っているブログ編集部の負荷をへらす必要があります。 そこで記事の公開までに必要な以下の手順を公開しドキュメントとしました。 下書きを書く場所とレビュー依頼の出し方 レビューについては完了までの時間の目安も同時に書く アイキャッチ画像の作り方 これについてはデザイナーの @nomjic が誰でもコピーしてテキストを編集するだけでアイキャッチが作れるステキFigmaを用意してくれました テックブログに投稿する場合の手順 これによって一度アドベントカレンダーが始まってしまえば編集部員の負荷はほぼほぼ記事のレビューだけとなりました。 3. 参加表明のためのカレンダー ブログ編集部としてカレンダーページを公開するタイミングで恐れていたのは以下の2点でした 空白だらけのカレンダー TBDがズラッとならぶカレンダー これらを避けるために11月頭から積極的に動いていました。 まず空白だらけのカレンダーを避けるために執筆者の確保に走りました。 アドベントカレンダーは世にたくさんあり、テーマ次第では空席が増えてしまうこともありますが、企業のアドベントカレンダーでそのような状態で公開されることはあまり印象が良くないと考えていました。 過去数年の経験から記事が不足することはまずないと分かっていたものの、各マネージャー陣にお願いしてメンバーへの働きかけをしてみたりslackの#generalで宣伝をしてみたりと先手を打って動いていきました。 ここで考えておかないといけないことは日程かぶりの問題です。 アドベントカレンダーは一人1日1記事が文化となっていて、カレンダーが埋まったら2個目のカレンダーを作るというスタイルで運営されることが多いです。 実際にBASEでも2019などは1日2記事で2個のカレンダーを走りました。 このスタイルは運営が大変なところが多く、1個目のカレンダーは埋まっているけど2個目のカレンダーがスカスカということも起こりかねず負荷が高いです。 そう考えていくと、そもそもカレンダー個あたり一日1記事という縛りがおかしくて、 一日n記事でいいじゃん という単純なことを思いつきます。 ただ適当に好きな日で、とやると前半が空っぽになって後半に集中しすぎるという可能性もあったので、 「1日あたりまず2人までは先着順で参加表明でよく、全体的に埋まってきたら2人の制約を緩和する」 というスタイルにしました。 これである程度カレンダー全体に記事を散らすことを可能にしつつ、日程がかぶっても問題にならないスケーラビリティを確保しました。 また、参加表明をしてもらったタイミングで記事で扱う予定のトピック、テーマだけは必ず表明してもらい(変更可)、TBD/未定で埋まることを防ぎました。TBDがカレンダーに並ぶとどうしても行き当たりばったりな感じが出てしまいますし、どういう雰囲気のアドベントカレンダーが開催されるかが伝わらないため、興味を持ってもらうこともできないだろうと考えたためです。 振り返り ここまでは実際にアドベントカレンダーを運営する上で行ってきたことを書きました。 ここからはテックブログ編集長の思いを書いてみようと思います。 アドベントカレンダーの意義 技術広報に積極的な企業にとって自社のアドベントカレンダーを開催することはもはや当たり前のようになっています。 ただ忘れてはいけないことがあります。それは 「アドベントカレンダー期間は大量のテック記事が公開されるレッドオーシャンである」 ということです。 もちろん読者側のアンテナが高くなる面もあります。ただ、それを遥かに上回る量で記事の供給量が増える期間です。 読者が技術記事に割く時間の総量はそこまで増えたりしません。その時間を世の様々な記事と取り合うことになります。 純粋に自社の技術を世に広めて自社の魅力をアピールするのであれば、常日頃からの定期的なアウトプットをまず確立すべきで、アドベントカレンダーの激しい競争で燃え尽きてしまうのは勿体ないと思っています。 まずは自社から定期的に記事を出せるような運営が行えるようになってからアドベントカレンダーでお祭りに参加するという流れのほうがよいのではないかという気がしています。 ただ逆に、レッドオーシャンというのを利用して勢いで普段アウトプットを出すことに不安を感じるようなメンバーの背中を押して記事を書いてみてもらう絶好の機会であるとも言えるかもしれません。 対外的なアピールとしてアドベントカレンダーを使うのではなく、メンバーに記事を書く経験値を積む機会の価値を強調することで協力してもらう、という考えで運営するほうが得るものが多いこともあるのかもしれません。 記事のレビューについて 少しアドベントカレンダーの振り返りとは話がずれるのですが、記事のレビューについて考えてることを書きます。 記事のレビューは気を使う作業です。我々自身も別に出版業界で編集者をやっていたわけでもなく文章の素人です。 ただし、企業のテックブログを運営する中で無責任なことはできません。記事のレビューは必要です。そのなかでフィードバックを行うことは少なからず疲れる作業です。 ただ自分は基本的にあまり多くをFBしないようにしています。観点としては ポリコレや倫理に反する内容はないか 業務上公開できない内容はないか 記事として読みやすくなるためにできる構造的な工夫はないか です。最初の2つは言わずもがなで必ずチェックする部分ですしこれは比較的明瞭に判断できる部分です。 それに対して、最後の文章の工夫については感覚的な領域も多く非常に難しくなります。 自分は基本的に「文章の順番を入れ替える」「図を追加する」「得られた成果をわかりやすくする表現を追加する」の3つのFBをします。 いい文章とは引き算によって産まれるとよく言われますが、文章を削ぎ落として引き締めて素晴らしいものに仕上げるというのは文筆の素人には荷が重いことです。人の文章に対して、「何かを足したほうが良い」とは言いやすいですが「この文章はいらない」「この表現は正しくない」というようなことは言いづらいからです。 全然関係のない個人的な余談なのですが、この記事のレビューという行為をするときによく思い出すことがあります。 それはキリコというラッパーの「ありがとう。名無しの2チャンネラー諸君」という曲のことです。 この曲自体はキリコが当時の2ちゃんねるにあった自分のスレッドに対して「全レス」を返す形でラップするという怪作です。(しかも14分弱ある) かなりクセの強い曲で私自身は好きでも他人におすすめするタイプの曲ではないのですが、この曲の最終盤に登場する 自分の世界をいじれるのは自分だけであるべきだ。赤ペンで訂正されて良い気分がしないのはみな同じだろう という一節をよく覚えています。 つまり何が言いたいのかというと、ブログの記事というものも個人の内側から生まれた自己表現であり、それを最大限尊重することが素人編集としてもっとも重視するべきことなのではないか、ということです。 細かい表現への違和感の表明などはやろうと思えばきっといくらでもやれますが、それはおそらくテックブログに求められることでもなければ記事を書いてくれたメンバーの求めている部分でもないのではないか、そう考えて情報の構造に対してなにか加えられることはないか、それだけを考えてレビューをするようにしています。 最後に 最後は少しとりとめがなくなってしまいましたが、最後にこの記事を読んで来年のアドベントカレンダーに役立てていただける方が一人でも居れば幸いだと思っています。 明日の最終日は弊社が誇るCTOの @dmnlk が素晴らしい作品をドロップしてくれる予定です。乞うご期待ください 今年も執筆に協力していただいて素晴らしい記事を提供してくれた弊社の皆様にこの場を借りてお礼を言わせていただければと思います。(この記事で偉そうなことをいいながらレビュー依頼を見落としていたりと大変ご迷惑おかけしました)
この記事は BASE Advent Calendar 2021 23日目の記事です。 こんにちは。 UXライターの藤井です。 ふだんは、BASEプロダクト全般におけるテキストの品質を向上させる、UXライティングを担当しています。「テキストコミュニケーションをデザインする」をキーワードに、テキスト版デザインシステムとして「運用ガイドライン」「用語リスト」を作成、タッチポイントごとに担当を分け、最終レビュー担当者をそれぞれアサインし、運用しています。あらゆるタッチポイントにおいて、日々テキストコミュニケーションの最適化を図っています。 トンマナ≠UXライティング この取り組みのなかで見えてきたのが、こんな2つの課題でした。 トンマナ(トーン&マナー)をひたすら磨く作業が、現状のテキストデザインの大半を占めていること 運用ガイドラインがあっても、品質の担保は、最終的にはどうしても属人的にならざるを得ないこと つまり、たくさんのショップオーナーの皆様が利用するプラットフォームにプロダクトが育っていく課程において、表面的にはトンマナが統一されていることで、一定のテキスト品質は担保されている一方で、UXライティングの本来の役割である「テキストを設計することによって、プロダクトとユーザーのコミュニケーションをデザインすること」、つまり「体験をデザインすること」という本質に、あまり時間を割けていないのでは、ということに気づいたのです。 品質の担保を前提に、本質的な「体験のデザイン」へ この次なるテーマと向き合うにあたり、必要になると考えたのが、「テキスト入力支援ツール」の導入でした。いくら洗練され磨き上げられた「運用ガイドライン」や「用語リスト」があっても、それはあくまで必要なときに紐解く「逆引き辞典」のようなもの。漢字の閉じ開きや記号、正式名称のBASEルールは、そもそも暗記するような類いのものではありません(さすがに何年もトンマナを整えていると、ほとんど身体に染み付いてはいますが)。あらかじめ定義されたルールに則していないものを指摘し、自動的に修正が加えられたら、あらゆるチームから「プロダクトに反映させたい」と提案されるテキストの品質は、すくなくとも「トンマナ」という文脈においては、その時点で担保されます(もちろん、完璧にではないとは思いますが)。これにより、「体験をデザインすること」ーーテキストのターゲット/目的/ゴールを定義し、情報の構成を設計し、マイクロコピーをライティングする、というプロセスに、さらにていねいに取り組むことができるのではないか、と。 入力支援ツールの導入検討、ガイドラインのアップデート そのために取るべきアクションは、2つあると考えています。 テキスト入力支援ツールの選定と導入 正誤の参照先となる「運用ガイドライン」「用語リスト」のDB化 まずは、現状のプロダクトやレビューフローとマッチするツールのメリット/デメリットの調査と検討。また、導入にあたって、エンジニアチームにどのくらいの稼働が発生するのか。 「textlint」や「文賢」をはじめとして、すでにけっこうな数のサービスがリリースされていて、インターフェースやできることがそれなりに違うので、導入に向けてあれこれ調査しつつ、現在先行して作業を進めているのが、もう1つのアクション「運用ガイドライン」「用語リスト」のDB化です。 BASEがUXライティングというアプローチを取りはじめてからおよそ3年、昨年もご紹介しましたが、ユーザー体験全体を通して語られる、ブランドの理念を反映した言葉遣い(ボイス)、ユーザー体験の各部分における言葉遣いの変化(トーン)のたたき台も形作られ、それなりに育ってきてはいるのですが、「運用ガイドライン」は目的や対象範囲などがまだきちんと定義され、明文化されていませんでした。また、「用語リスト」もレビュワー目線で構成されており、お世辞にも「誰が見てもすぐに活用できる」ところまでは整えられていない状態でした。 テキストライティングの方針を定める そこで、まずはテキストガイドラインにおける「テキストライティングの方針」、目的を定めました。 ショップオーナーの皆様だけではなく、これからショップを開設しようと検討している方、BASEのプラットフォームを利用したショップでお買い物される購入者、購入を検討している方をふくめ、BASEのタッチポイントに触れる可能性のあるすべての方を対象に、伴走者としてどんなテキストコミュニケーションであるべきなのか、という観点・視点がベースとなっています。 テキストライティング方針の対象範囲の明文化 また、現段階における「対象とする範囲」「対象としない範囲」も定めています。 2021年の段階において、プロダクトおよびプロダクトに付随するDBC(管理画面のお知らせカード)、メール、自社メディア(BASE活用方法などの記載された記事)などを対象範囲とし、メディアの属性によって受け取り手がキャッチできる情報や、印象に差分が発生しやすいSNS、広告などは、対象からあえて外しています。あくまでも、ユーザーの行動を促す短い文言「マイクロコピー」および「マイクロコピー」を支える屋台骨としての最低限のテキスト、にフォーカスしています。 正誤が検知できる、DB化されたガイドライン そして、来たるべきDB化を念頭に、項目自体をあらためて精査、正誤表にすることにより、正以外を検知できるような作りにしました。 テキストガイドラインとして「漢字・ひらがな」「カタカナ」「記号」「数字・単位・日付」、また用語集として「サービス正式名称」「BASE固有の用語」「用語」、マイクロコピーは用途別に「ボタン・リンク」「エラー・バリデーション」「フラッシュメッセージ・トーストメッセージ」「警告メッセージ」「ダイアログ」と、使いやすく分類し、検索性を高めました。 定形メッセージのテンプレ化による、メッセージ・デリバリーの高速化 さらに、よくある定形のメッセージに関しては、使い勝手を最大化するため、テンプレートも用意しました。 ウェビナー告知やApp新機能紹介など、ある程度フォーマット化できるものをテンプレート化することで、毎回イチからテキストを起こさなくてもよいように、新しい情報をターゲットに最速でアナウンスできるようになりました。 これから そんなわけで、昨年同様、すべては現在における仮説でしかないことを前提に、「運用ガイドライン」「用語リスト」のアップデートとさらなる活用策を模索してきた2021年。すべては、BASEというプラットフォームを通して、ショップオーナー様の皆様やお客様の叶えたい願いを実現するために。2022年のBASEにも、どうぞご期待ください。
BASE Advent Calendar 2021 はじめに コスト考慮型学習とは Cost-Sensitive Learningの手法 コスト行列 閾値の調整による誤分類コストの反映 実際のデータセットを用いた例 まとめ 参考文献 はじめに この記事はBASE Advent Calendar 23日目の記事です。 こんにちは、DataStrategyチームの竹内です。 BASEではより良いサービスを提供するために色々なところで機械学習モデルが活用されています。 BASEに限らず、インターネット上のあらゆるサービスに機械学習の技術が活用されるようになって久しい昨今ですが、こうした実際のサービスやビジネス領域に近いところで活用される機械学習モデルにおいては、計算コストやメンテナンスコスト、解釈性やバイアス、データセットシフトなど色々と考えなければいけない特有の要素が存在します。 今回はその中の1つとも言える誤分類コストの非対称性の問題について考え、それに対するアプローチとしてコスト考慮型学習(Cost-Sensitive Learning)について扱っていきたいと思います。 コスト考慮型学習とは コスト考慮型学習とは、データマイニングにおける誤分類時のコスト(誤分類コストに限らず、計算コストなどの他の要素を考慮する場合もあります。)を考慮した学習手法のことです。 機械学習などによる分類モデルを現実の問題に適用する場合、どのデータをどのクラスに誤分類してしまうかで生じるコストが異なる、いわゆる誤分類コストの非対称性の問題に直面することがあります。 1 よく挙げられる例で言えば、がんのような重大な病気を診断する場合、本当に罹患している人に対して健康であると誤診してしまった際の影響は極めて致命的になり得る一方で、健康な人に対して罹患していると誤診してしまった際は追加の検査費用分のコストで済むことになります。 医療診断における非対称な誤分類コスト 健康な人 罹患している人 陰性と診断 - 治療の遅れ、信頼の失墜(コスト大) 陽性と診断 追加の検査費用(コスト小) 早期治療 医療診断の場合ではどの患者についても概ね同様なコスト行列が適用できますが、中にはサンプルごとに誤分類コストが異なる場合もあります。 例えばカードローン審査のような例では、利用客によって申請金額が異なるため誤分類コストが変わってきます。 カードローン審査におけるサンプルごとに異なる誤分類コスト 返済できる人 返済できない人 審査を通す 金利や手数料分の利益 金額分の損失 審査を通さない 適格な申請数の減少 不適格な申請数の減少 このようなサンプルごとに誤分類コストが異なるタスクにおいてモデルの作成や改良を行う際、サンプル数ベースの正答率や再現率、AUCの改善が必ずしも金額ベースの改善につながらない可能性があることに注意する必要があります。 このように現実の問題を分類タスクとして捉え、予測モデルの作成や改良を行う場合、適切な誤分類コストに基づいた性能評価が求められる場合があります。 適切な誤分類コストの設定には機械学習や統計一般の知識だけではなく、その分野特有の知識、いわゆるドメイン知識を十分に活用することが求められます。 Cost-Sensitive Learningの手法 ここからは上の例のような非対称な誤分類コストに対する具体的なアプローチについて扱っていきます。 誤分類コストを考慮したモデルを構成する手法は大きく分けて3つ存在します。 通常の学習を行なったモデルの出力に対する検出閾値を、サンプルの誤分類コストに応じて変更する 学習データセットのクラス比率、あるいは重みを誤分類コストに応じて変更した上で通常の学習を行う 誤差関数などモデルの学習手法そのものに誤分類コストを組み込む 今回は3つアプローチの中から、1つ目の閾値を変更する手法について説明していきます。 コスト行列 手法の説明に入る前に、コスト行列について整理しておきます。 クラス数Mの多クラス分類において、モデルが と予測したデータの真のクラスが であった時の誤分類コストを と表すことにします。 例えば二値分類の場合、クラス1を陽性、クラス0を陰性とすると、 は「陽性と予測したが本当は陰性だった」ため偽陽性、逆に は「陰性と予測したが本当は陽性だった」ため偽陰性のコストを表していることになります。 この時、 を要素としてもつM×Mの行列をコスト行列と呼びます。 例えば二値分類の場合は以下のような2×2の行列となります。 真のクラスが0 真のクラスが1 予測したクラスが0 予測したクラスが1 や には正しく分類した時の利益(負の値)が入りますが、実際はこの部分を0として誤分類コストの方に機会損失として織り込むことができます。(後ほど説明します。) ものすごく大雑把な例ですが、先程のカードローン審査の例で申請者 の申請金額を 円とし、金利を1%とした上で、rejectした場合の申請数の減少による効果を1人あたり大雑把に20000円と見積もる(返済できる人の申請数が減る場合は損失とし、返済できない人の申請数が減る場合は利益とします。)と、以下のようなコスト行列を考えることができます。 返済できる人 返済できない人 審査を通す 審査を通さない 20000 -20000 閾値の調整による誤分類コストの反映 コスト行列を定義したところで、閾値を調整する手法の具体的な説明に入っていきます。 大まかには「間違えたらまずい(誤分類コストが相対的に大きい)」クラスについては、その予測確率がたとえ50%を下回っていたとしても、予測結果としてそのクラスを出力するという手法になります。 最適な検出閾値はコスト行列から具体的に以下のように計算することができます。 データ がクラス に属する確率が であった時、コストの期待値が小さい方に分類することを考えます。 式で表すと、 (クラス と予測した時のコストの期待値) (クラス と予測した時のコストの期待値 が成り立つ時、つまり が成り立つ時にクラス =陽性と判別すれば良いことになります。 陽性である事後確率 を とおくと、 から が得られます。 ここで「間違えて分類した時のコストは常に正しく分類できた時のコストよりも大きい」つまり すべての となる に対して、 が成り立つことを仮定します。 この時上の式から とすると、 が得られます。 つまり正しい事後確率 が得られた時、検出閾値を として がそれ以上であれば陽性、そうでなければ陰性とすることで誤分類コストの期待値を最小化することができます。 また の式からコスト行列を 真のクラスが0 真のクラスが1 予測したクラスが0 0 予測したクラスが1 0 とおいたものも同じ結果が得られることがわかります。 これは先程説明した通り、正しく分類できた時に得られる利益を誤分類した時の機会損失として扱うことに相当します。 また、真のクラスにかかわらず同じ予測に対して常に発生する同じ大きさのコストは相殺することがわかります。 実際のデータセットを用いた例 実際のデータセットで閾値の調整によるコストの変化をみてみたいと思います。 今回はUCIのMushroom Classificationのデータセットを検証用に使わせていただくことにします。 https://www.kaggle.com/uciml/mushroom-classification このデータセットは、様々なキノコの傘の形や色などの特徴量と共に、そのキノコが食用なのかそうでないかのラベルが与えられたデータセットとなります。 今回はこれを用いて、キノコのいくつかの特徴量からそれが食用なのかそうでないかを予測する単純な二値分類のタスクを考えます。 分類器としては単純な決定木を使うことにします。 食用でないキノコのクラスを1、食用であるキノコのクラスを0とすると、サンプルサイズは以下のようになります。 クラス0: 4208 クラス1: 3916 必要なライブラリのimportや前処理など from sklearn.preprocessing import LabelEncoder from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn import metrics import numpy as np import pandas as pd import seaborn as sns from matplotlib import pyplot as plt def plot_confusion_matrix (y_test, y_pred, y_prob, cost_matrix): cm = metrics.confusion_matrix(y_test, y_pred) tn, fp, fn, tp = cm.flatten() accuracy = (tp + tn) / (tn + fp + fn + tp) precision = tp / (tp + fp) recall = tp / (tp + fn) f_score = 2 * recall * precision/(recall + precision) fpr, tpr, thresholds = metrics.roc_curve(y_test, y_prob) roc_auc = metrics.auc(fpr, tpr) total_cost = np.sum(cost_matrix * cm.T) print (f "accuracy: {accuracy*100:.4f}%" ) print (f "precision: {precision*100:.4f}%" ) print (f "recall: {recall*100:.4f}%" ) print (f "f-score: {f_score:.4f}" ) print (f "AUC: {roc_auc:.4f}" ) print (f "total cost: {total_cost}" ) df_cm = pd.DataFrame(cm.T, range ( 2 ), range ( 2 )) sns.set(font_scale= 1.4 ) sns.heatmap(df_cm, annot= True ,annot_kws={ "size" : 16 }, fmt= "" ) plt.xlabel( "true label" ) plt.ylabel( "prediction" ) plt.show() df = pd.read_csv( "mushrooms.csv" ) le = LabelEncoder() for k, v in df.dtypes.items(): df[k] = le.fit_transform(df[k]) # 全特徴量を使用すると完璧に分類できてしまうぐらいタスクが簡単なので、今回は実験用に使用する特徴量を制限します df = df[df.columns[: 4 ]] df 混同行列と各種指標 今回の場合、食用でないキノコを誤って食用だと判別して食べてしまった時の被害と、食用のキノコを誤って食用でないと判別して食べ損なってしまった時の被害では前者の方がより重大であると考え、以下のようなコスト行列を設定することにします。 真のクラスが0 真のクラスが1 予測したクラスが0 0 10 予測したクラスが1 1 0 このコスト行列のもとで決定木による分類を行い、まずは閾値を0.5に設定して混同行列やrecision、recallなどの各種指標とともにコストの総計を計算してみます。 cost_matrix = np.array([ 0 , 10 , 1 , 0 ]).reshape(( 2 , 2 )) x_train, x_test, y_train, y_test = train_test_split(df.drop(columns=[ "class" ]), df[ "class" ], test_size= 0.2 , random_state= 0 ) dtc = DecisionTreeClassifier(max_depth= 5 , random_state= 0 ) model = dtc.fit(x_train, y_train) y_prob = model.predict_proba(x_test)[:, 1 ] y_pred = (y_prob >= 0.5 ).astype( int ) plot_confusion_matrix(y_test, y_pred, y_prob, cost_matrix) 混同行列と各種指標 コスト行列と混同行列の要素積を取ることで得られたコストの総計(total cost)は2201となりました。 次に閾値を先程の に変えて同じように分類を行ってみます。学習する過程ではコスト行列は使用しないため、再学習せずに同じモデルを使用することができます。 threshold = (cost_matrix[ 1 , 0 ] - cost_matrix[ 0 , 0 ]) / (cost_matrix[ 1 , 0 ] - cost_matrix[ 0 , 0 ] + cost_matrix[ 0 , 1 ] - cost_matrix[ 1 , 1 ]) y_pred = (y_prob >= threshold).astype( int ) plot_confusion_matrix(y_test, y_pred, y_prob, cost_matrix) 混同行列と各種指標 結果としてprecisionが下がる代わりにrecallが上がることでtotal costを2201から690まで下げることができました。 一応閾値に対するtotal costをplotしてみると、 がtotal costを最小化する閾値であることが確認できます。 x = np.linspace( 0 , 0.5 , 5000 ) y = [] for i in x: y_pred = (y_prob >= i).astype( int ) cm = metrics.confusion_matrix(y_test, y_pred) total_cost = np.sum(cost_matrix * cm.T) y.append(total_cost) fig, ax= plt.subplots( 1 , 1 , figsize=( 10 , 8 )) ax.plot(x, y) ax.vlines(threshold, ymin= 0 , ymax= max (y), color= "orange" ) ax.legend([ "total cost" , "p*" ], loc= 'upper center' ) ax.set_xlabel( "threshold" ) ax.set_ylabel( "total cost" ) plt.show() 検出閾値と誤分類コスト まとめ 誤分類コストの非対称性の問題と、それに対するアプローチの1つであるコスト考慮型学習(Cost-Sensitive Learning)について紹介させていただきました。 BASEで活用されている機械学習モデルの一部にも、こうしたコスト考慮型学習のアプローチが用いられています。 実際には正確なコスト行列を設定することが難しい場合もありますが、それでも誤分類コストの非対称性については常に意識する必要があります。 例えばローン審査において機械学習モデルを人手によるチェックのための一時フィルター的な役割で使用する場合には、あらかじめ許容できる件数ベースの偽陽性率の上限を決めた上で、金額ベースの再現率が最も高くなるような閾値を設定する、といった方法が有効かもしれません。 その場合はモデルを開発するエンジニアだけではなく、二次チェックを行うオペレーターともうまくコミュニケーションを取りながら達成すべき目標を明確にしていくステップが必要不可欠となります。 また、実際に運用していく上では誤分類時のコストだけではなく、冒頭で触れた通り計算コストやメンテナンスコスト、解釈性の問題など色々な要素が影響してきます。それらを踏まえた上でドメイン知識を活用し、短期的な利益だけではなく、長期的な利益を見据えて最適なモデルを選択しチューニングしていくことが、ビジネスでの機械学習モデルの活用を求められるデータサイエンティストの役割であると考えます。 参考文献 Elkan, Charles. (2001). The Foundations of Cost-Sensitive Learning. Proceedings of the Seventeenth International Conference on Artificial Intelligence: 4-10 August 2001; Seattle. 1. Ling, Charles & Sheng, Victor. (2010). Cost-Sensitive Learning and the Class Imbalance Problem. Encyclopedia of Machine Learning. 特に現実の応用例を考える際、誤分類コストの非対称性の問題は、必ずと言っていいほど不均衡データの問題と一緒になって現れますが、個人的にはこの二つの問題は分けて考える方が良いかと思っています。というのも不均衡データには不均衡データ特有の、分類器の識別境界にかかるバイアスなど( https://scikit-learn.org/stable/auto_examples/svm/plot_separating_hyperplane_unbalanced.html や https://ieeexplore.ieee.org/document/6137280 などで説明されている)タスクのドメイン(医療診断で使うのか、ローン審査で使うのかなど)とは独立した問題が存在し、誤分類コストが対称であっても生じる可能性がある一方で、誤分類コストの非対称性の問題はタスクのドメインに依存した問題であり、不均衡データでなくても起こり得るからです。ただし、不均衡データの問題を解決するために非対称な誤分類コストを設定したり、誤分類コストの非対称性を考慮するためにunder-samplingやover-samplingなどによって敢えて不均衡な事前分布を設定する(この記事では詳しく紹介できませんでしたが Cost-Sensitive Learningの手法 の2つ目に相当します。)アプローチが取られることはあります。今回の記事ではトピックを絞るため、あまりデータの不均衡性の問題は取り上げていません。(今後機会があれば、2つ目以降の手法と共に記事にしようかと思います。) ↩
この記事は BASE Advent Calendar 2021 の22日目の記事です。 こんにちは!BASEでエンジニアをやっている大津( @cocoeyes02 )です。今回はiikanji-conference-toudanチームの取り組みについてご紹介します! iikanji-conference-toudanチームとは? iikanji-conference-toudanチームとは、BASEから技術イベント・カンファレンスへ登壇する人たちを応援するチームです。登壇を有志による属人的なものにするのではなく、文化として当たり前になっている状態を目指して活動しております。 もともと登壇自体は前から推奨されていましたが、「BASE全体で外部への技術的なブランディングをしっかりやってきたい」という話があり、 iikanji-conference-toudanチームが生まれました! ちなみにiikanji-conference-toudanチーム以外でも、 ブログ や 自社イベント 、 Youtube など精力的に活動していますのでそちらもどうぞ! これまでどんなことしてきたの? チームで応援できそうなことを調査 応援と一口に行っても、様々な応援の形があるかと思います。そこで登壇する人を増やすために我々に何ができるのかをブレストし、まとめました。 ブレストの内容をグルーピングしたり抽象化した様子 特に、登壇のモチベーションのところを深掘りしたいという話になり、実際に登壇に対してどう思っているのかアンケートを取りました。 すると圧倒的に「登壇するネタがない」と思っている人が多かったため、「ブログや社内ドキュメントの内容をベースに登壇を進めたら、登壇してくれるのではないか?」という仮説を立て、登壇を勧めました。 アンケート結果の一部 実際この方法で何人か登壇した人を見たので、登壇を勧めるときの1つの方法として確立しつつあります。 一例として、 @tawamura さんの ElasticsearchとKibela APIを使ってSlackでのCSお問い合わせ対応業務を改善した話 がその1つになります。よければブログや登壇動画も是非ご覧ください! ちなみに、このPHPカンファレンス2021ではBASEから計5人が登壇しました!その時のイベントレポートは以下の記事になります。こちらも是非ご覧ください! devblog.thebase.in 登壇前のトークへのフィードバック より良い登壇ができるように、登壇前にzoom等でトークを聞いてもっと良くなる点などがあればフィードバックします。 例えばトークを聞きながら、以下のようなことについてフィードバックしています。 スライド全体が頭に入ってきやすい構成になっているか スライド等の資料で分かりにくいところは無いか 図や動画を使った方が良いところは無いか 時間オーバーしているのであれば、どこを削るべきか 喋りのスピードが早くて聞いている側が負担にならないか など 技術イベント・カンファレンスでの登壇は、大抵スライド等の資料を使いながらトークする形になります。なので、スライド等の資料のフィードバックが中心になります。 技術イベント・カンファレンスのスポンサー業務 技術イベント・カンファレンスでスポンサーとして名乗り出るのにはさまざまな理由がありますが、登壇している人のモチベーションアップやトークを聞いてもらうきっかけとしてスポンサー業務もやっています! また、カンファレンスによってはスポンサーセッション枠が設けられているスポンサープランもありますので、そちらも積極的に狙っていきます。 例えばPHPカンファレンス2021では、 @tanden さんを中心として「 BASE のお問い合わせ対応の裏側!」という座談会形式のスポンサーツアーの企画・開催をしました。その時の様子は以下の記事に書いてあります! devblog.thebase.in アーカイブ動画をみんなで見る会を開催 近年の技術イベント・カンファレンスでは、各トークのアーカイブ動画がアップロードされていることも珍しくありません。 登壇だけでなく、技術イベントの参加自体もっとわいわいしていきたい。そんな思いから各トークのアーカイブ動画をみんなで見る会の開催もしました! PHPカンファレンス2021のアーカイブ動画を見る会をSlackで募った様子 これからはどんなことをしていきたい? 色んな分野技術イベント・カンファレンスでの登壇を応援していきたい 現在登壇人数で言うと、PHPやGoのカンファレンスでの登壇が多い状況です。ですが、技術イベント・カンファレンスはPHPやGo以外にもたくさんありますので、色んな分野での登壇を応援していきたいと思っています! どの分野の技術イベント・カンファレンスでも日々登壇するチャンスを狙える体制づくりとして、例えば現状技術イベント用のカレンダーを用意して、色々な技術イベント・カンファレンスの日程を追えるようなものを作成しています! 登壇者だけでなく、技術イベントの参加自体ももっとわいわいして行きたい 登壇する人だけにフォーカスするのではなく、技術イベント自体もわいわいして登壇している人のモチベーションアップ&登壇へのハードルを下げるのも狙っていきたいと思います! 技術イベント・カンファレンスを楽しむための施策として、例えば定期的に技術イベントのアーカイブ動画を見る会を開催したりし始めています! 最後に もともとBASEでは、会社全体でOSS(コミュニティ)を応援する文化があり、この記事を書いた直近ではThe PHP Foundationに寄付を行いました。 devblog.thebase.in 我々のチームでは、技術イベント・カンファレンスに登壇して盛り上げていくという形で、OSS(コミュニティ)に貢献できればと思っております。引き続き、色々なアプローチで技術イベント・カンファレンスでの登壇を応援していきます! OSS(コミュニティ)に貢献したい!盛り上げていきたい!と思う方は、ぜひBASEへどうぞ! open.talentio.com 明日は @FUJIIMichiro さんの「UXライティング関連」と、 @shotakeuchi さんの「分類コストを考慮した機械学習モデルの考え方」です!お楽しみに!
この記事は BASE Advent Calendar 2021 の22日目の記事です。 はじめに はじめまして、Owners Success Frontend Shop Frontチームの坂口です。 普段はフロントエンドエンジニアとしてVue.jsを使った開発をメインに行なっているのですが、チームでプロジェクトマネージャーやデザイナーが手軽に動作を確認できるレビュー環境がほしいという話があり、AWS App RunnerとGitHub Actionsを連携して構築をしたのでその話をしたいと思います。 レビュー環境とは レビュー環境というのはGitHubのプルリクエストやブランチごとに動作確認ができる環境で以下のような動きをするものを今回は構築することにしました。 プルリクエストが作成されるとレビュー環境立ち上げ (作成) プルリクエストに紐付いたブランチが更新されるとレビュー環境更新 (更新) プルリクエストがclose、またはマージされるとレビュー環境削除 (削除) イメージとしてはHerokuの Review Apps やNetlifyの Deploy Previews などに近いかもしれません。 使用技術 AWS App RunnerとGitHub Actionsを利用して以下のような構成にしました。 AWS App Runner AWS App Runnerは今年5月に発表されたコンテナベースのフルマネージドサービスで、インフラの知識があまりなくても簡単にアプリケーションをデプロイすることができます。 https://aws.amazon.com/jp/apprunner/ 今回構築するにあたってAWS LambdaやAWS ECSなど選択肢はありましたが、簡単にデプロイできるということで採用しました。 準備 事前にGitHub Actionsで利用するAWSのIAMユーザーを作成して、リポジトリのsecretsにアクセスキーとシークレットアクセスキーを用意しておきます。 必要な権限は こちら の公式記事で書いてあるものになります。 ワークフロー内容 ワークフローは先ほど上げた作成、更新、削除に加えて、一時停止、復帰を追加した以下5パターンを用意しました。 プルリクエストにラベルがつくとレビュー環境立ち上げ (作成) ラベルが付いたプルリクエストへ新たにpushされると更新 (更新) プルリクエストがclose、またはマージされるとレビュー環境削除 (削除) 毎日21時になると稼働中のレビュー環境を一時停止(一時停止) プルリクエストの /resume とコメントすると復帰(復帰) プルリクエストにラベルがつくとレビュー環境立ち上げ name : review-app-deploy on : pull_request : types : - labeled jobs : deploy-review-app : name : Deploy Review App runs-on : ubuntu-latest environment : development if : ${{ github.event.label.name == 'ラベル名' }} env : BRANCH_NAME : ${{ github.head_ref }} steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ${{ secrets.AWS_REGION }} - name : Login to Amazon ECR id : login-ecr uses : aws-actions/amazon-ecr-login@v1 - name : Build, tag, and push image to Amazon ECR id : build-image env : ECR_REGISTRY : ${{ secrets.ECR_REGISTRY }} ECR_REPOSITORY : ${{ secrets.ECR_REPOSITORY }} run : | IMAGE_TAG=`echo $BRANCH_NAME | sed -e "s/[^a-zA-Z0-9_-]/-/g" ` SERVICE_NAME=`echo review-app_$IMAGE_TAG | cut -c 1-40` docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG echo "::set-output name=service::$SERVICE_NAME" echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - name : Deploy to App Runner id : deploy-service uses : awslabs/amazon-app-runner-deploy@main with : service : ${{ steps.build-image.outputs.service }} image : ${{ steps.build-image.outputs.image }} access-role-arn : ${{ secrets.APPRUNNER_ROLE_ARN }} runtime : NODEJS_16 region : ${{ secrets.AWS_REGION }} cpu : 1 memory : 2 port : 80 wait-for-service-stability : false - name : Comment Review App URL run : | SERVICE_URL=`aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='${{ steps.build-image.outputs.service }}']" | jq -r '.[]["ServiceUrl"]' ` gh pr comment $PR_NUMBER --body "Review App URL: https://$SERVICE_URL" 大まかな流れは以下になります。 Dockerイメージビルド Amazon ECRへDockerイメージプッシュ AWS App Runnerのサービス作成 今回、プルリクエストのブランチ名をDockerイメージのタグとAWS App Runnerのサービス名に利用しているのですが、AWS App Runnerのサービス名の 制限 に合わせて文字列を置換しています。 またあとから識別しやすいようにサービス名に review-app_ プレフィックスを付与しています。 IMAGE_TAG = ` echo $BRANCH_NAME | sed -e " s/[^a-zA-Z0-9_-]/-/g " ` SERVICE_NAME = ` echo review-app_ $IMAGE_TAG | cut -c 1-40` サービス作成では、AWS公式GitHub Actionsである aws-actions/configure-aws-credentials を利用していて、CPU、メモリは最低限にしています。 wait-for-service-stability を true にするとサービス作成完了まで待ってくれるのですが、完了まで5分ほどかかってしまいGitHub Actionsの枠を使い切ってしまうことを懸念して false にしています。 さらにAWS App RunnerのURLをプルリクエストのコメントに通知するようにしています。(※サービス作成完了後ではないので、利用可能までに5分程度待機が必要です。) SERVICE_URL = `aws apprunner list-services --query " ServiceSummaryList[?ServiceName==' ${ { steps.build-image.outputs.service } }'] " | jq -r ' .[]["ServiceUrl"] ' ` gh pr comment $PR_NUMBER --body " Review App URL: https:// $SERVICE_URL " 以下のように特定のラベル(今回は deploy review app )を付与するとワークフローが走り、サービスURLをコメントしてくれるようになります。 ラベルが付いたプルリクエストへ新たにpushされると更新 name : review-app-update on : pull_request : types : - synchronize jobs : update-review-app : name : Update Review App runs-on : ubuntu-latest environment : development if : ${{ contains(github.event.pull_request.labels.*.name, 'ラベル名' ) }} env : BRANCH_NAME : ${{ github.head_ref }} steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ${{ secrets.AWS_REGION }} - name : Check Service Exist id : check-service-exist run : | IMAGE_TAG=`echo $BRANCH_NAME | sed -e "s/[^a-zA-Z0-9_-]/-/g" ` SERVICE_NAME=`echo "review-app_$IMAGE_TAG" | cut -c 1-40` SERVICE_ARN=`aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='$SERVICE_NAME'&&Status=='RUNNING']" | jq -r '.[]["ServiceArn"]' ` echo "::set-output name=image-tag::$IMAGE_TAG" echo "::set-output name=service-arn::$SERVICE_ARN" - name : Login to Amazon ECR id : login-ecr if : ${{ steps.check-service-exist.outputs.service-arn }} uses : aws-actions/amazon-ecr-login@v1 - name : Build, tag, and push image to Amazon ECR id : build-image if : ${{ steps.check-service-exist.outputs.service-arn }} env : ECR_REGISTRY : ${{ secrets.ECR_REGISTRY }} ECR_REPOSITORY : ${{ secrets.ECR_REPOSITORY }} run : | docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:${{ steps.check-service-exist.outputs.image-tag }} . docker push $ECR_REGISTRY/$ECR_REPOSITORY:${{ steps.check-service-exist.outputs.image-tag }} - name : Update App Runner Service id : update-service if : ${{ steps.check-service-exist.outputs.service-arn }} run : | aws apprunner start-deployment --service-arn ${{ steps.check-service-exist.outputs.service-arn }} 大まかな流れは作成とほぼ同じになりますが、注意点として aws-actions/configure-aws-credentials 内部でサービスが存在する場合は更新コマンドを実行しているのですが、更新コマンドはサービスの設定を更新してくれるのみでソースを更新してはくれないため、そのまま利用せず、 start-deployment を実行する必要があります。 aws apprunner start-deployment --service-arn ${ { steps.check-service-exist.outputs.service-arn } } また細かいですが、 jq では -r オプションをつけて結果の文字列からダブルクウォートを削除して扱いやすくしています。 SERVICE_ARN = `aws apprunner list-services --query " ServiceSummaryList[?ServiceName==' $SERVICE_NAME '&&Status=='RUNNING'] " | jq -r ' .[]["ServiceArn"] ' ` 毎日21時になると稼働中のレビュー環境を一時停止 name : review-apps-pause on : schedule : - cron : '0 12 * * MON-FRI' jobs : pause-review-app : name : Pause Review App runs-on : ubuntu-latest environment : development steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ${{ secrets.AWS_REGION }} - name : Pause App Runner Services id : pause-services run : | SERVICE_ARN_LIST=`aws apprunner list-services --query "ServiceSummaryList[?Status=='RUNNING']" | jq -r '.[] | select(.ServiceName | test("^review-app_")) | .ServiceArn' ` if [ -n "$SERVICE_ARN_LIST" ] ; then echo $SERVICE_ARN_LIST | while read SERVICE_ARN ; do aws apprunner pause-service --service-arn $SERVICE_ARN done fi AWS App Runnerはサービスが起動している間課金されてしまうため、深夜の誰も利用しない時間では一時停止するようにしています。タイムゾーンはUTCなので注意してください。 on: schedule: - cron: ' 0 12 * * MON-FRI ' 一時停止するサービスはAWS CLIの --query オプションで稼働中のものを抽出した後、jqでサービス名が review-app_ プレフィックスのものを絞り込んでいます。 SERVICE_ARN_LIST = `aws apprunner list-services --query " ServiceSummaryList[?Status=='RUNNING'] " | jq -r ' .[] | select(.ServiceName | test("^review-app_")) | .ServiceArn ' ` プルリクエストに /resume とコメントすると復帰 name : review-app-resume on : issue_comment : types : [ created, edited ] jobs : resume-review-app : name : Resume Review App runs-on : ubuntu-latest environment : development if : ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/resume' ) }} env : GH_TOKEN : ${{ secrets.GITHUB_TOKEN }} steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ${{ secrets.AWS_REGION }} - name : Check Service Exist id : check-service-exist run : | BRANCH_NAME=`gh pr view ${{ github.event.issue.number }} --json headRefName --jq '.headRefName' ` SERVICE_NAME=`echo "review-app_$BRANCH_NAME" | cut -c 1-40 | sed -e "s/[^a-zA-Z0-9_-]/-/g" ` SERVICE_ARN=`aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='$SERVICE_NAME'&&Status=='PAUSED']" | jq -r '.[]["ServiceArn"]' ` echo "::set-output name=service-arn::$SERVICE_ARN" - name : Resume App Runner Service id : resume-service if : ${{ steps.check-service-exist.outputs.service-arn }} run : | resume-service --service-arn ${{ steps.check-service-exist.outputs.service-arn }} issue_comment をトリガーにしており、作成や更新のように github.head_ref でブランチ名が取得できないため、コメントされたプルリクエストからブランチを特定するための処理を挟んでいます。 BRANCH_NAME = `gh pr view ${ { github.event.issue.number } } --json headRefName --jq ' .headRefName ' ` 以下のようにコメントすることでワークフローが走り、一時停止中のAWS App Runnerサービスが復帰してくれます。 プルリクエストがclose、またはマージされるとレビュー環境削除 name : review-app-delete on : pull_request : types : - closed jobs : delete-review-app : name : Delete Review App runs-on : ubuntu-latest environment : development if : ${{ contains(github.event.pull_request.labels.*.name, 'ラベル名' ) }} env : BRANCH_NAME : ${{ github.head_ref }} steps : - name : Checkout uses : actions/checkout@v2 - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v1 with : aws-access-key-id : ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key : ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region : ${{ secrets.AWS_REGION }} - name : Delete App Runner Service id : delete-service run : | SERVICE_NAME=`echo "review-app_$BRANCH_NAME" | cut -c 1-40 | sed -e "s/[^a-zA-Z0-9_-]/-/g" ` SERVICE_ARN=`aws apprunner list-services --query "ServiceSummaryList[?ServiceName=='$SERVICE_NAME']" | jq -r '.[]["ServiceArn"]' ` if [ -n "$SERVICE_ARN" ] ; then aws apprunner delete-service --service-arn $SERVICE_ARN echo "::set-output name=tag::$SERVICE_NAME" fi - name : Delete ECR Image id : delete-ecr-image if : success() env : ECR_REPOSITORY : ${{ secrets.ECR_REPOSITORY }} run : | TAG=`echo "$BRANCH_NAME" | sed -e "s/[^a-zA-Z0-9_-]/-/g" ` aws ecr batch-delete-image --repository-name $ECR_REPOSITORY --image-ids imageTag=$TAG 削除したいサービスの存在確認をして、サービスとDockerイメージを削除します。 SERVICE_NAME = ` echo " review-app_ $BRANCH_NAME " | cut -c 1-40 | sed -e " s/[^a-zA-Z0-9_-]/-/g " ` SERVICE_ARN = `aws apprunner list-services --query " ServiceSummaryList[?ServiceName==' $SERVICE_NAME '] " | jq -r ' .[]["ServiceArn"] ' ` if [ -n " $SERVICE_ARN " ] ; then aws apprunner delete-service --service-arn $SERVICE_ARN echo " ::set-output name=tag:: $SERVICE_NAME " fi おわりに 今回は公式でGitHub Actionsが用意されているのもあって比較的簡単に連携・レビュー環境の構築ができました。まだサービス作成完了時の通知など改善の余地はたくさんありますが、手軽に構築ができるので、ぜひ一度検討してみてはいかがでしょうか。