booklista tech blog

booklista のエンジニアリングに関する情報を公開しています。

DatadogのRUM Session Replay費用削減について

アイキャッチ

こんにちは。プロダクト開発部でクラウドインフラエンジニアとして業務を行っている高澤です。

今回は、Datadogの料金削減の一環として行った、「RUM Session Replay の費用削減」についてお伝えします。

費用削減の効果を先に書くと、RUM Session Replayの料金がほぼ0ドルになりました。

この記事で伝えたいこと

Datadog の概要

Datadogとは、Datadog社が提供する監視・モニタリングのSaaSサービスです。

各リソースやクラウド(AWS,GCP,Azureなど)からメトリクス・ログなどを取り込み・連携し、モニタリングできるサービスです。

その他できることは多岐に渡ります。

Datadog の具体的な使用例

Datadogを利用し、現在以下をメインで行っています。

  • AWSのメトリクス連携をし、監視・通知
  • リソース監視・通知
  • ログを取り込み、可視化・検知
  • ダッシュボードを使用し、リソース状況やKPIを把握
  • SLO監視
  • Syntheticsテストの実施・監視・通知
  • アラート・オンコールの集約

Datadog費用削減に至る背景と見直しポイントについて

Datadogは上記機能を提供してくれて大変便利なサービスなのですが、データの取り込み量が増えるに従い、料金が増えていきます。

料金の削減が課題となり、プラン見直しも含め、Datadog営業担当の方と相談する機会を設けていただきました。

相談の結果、個々のサービスについて、最適なプラン変更などを実施していただき料金を抑える目処は付きました。

しかし「On-Demand RUM Session Replay」という請求項目については、「利用する・しない」を設定するものではありませんでした。

データを送っているとその分課金される種類の機能であり、呼び出し側でデータ送信の設定をしないといけない、と判明しました。

RUM Session Replayという機能について

この「RUM Session Replay」は、Datadogの請求から確認できる項目名です。

「RUM Session Replay」の具体的な機能としてはどのようなものでしょう、ということをまず確認しました。

を確認し、ポイントは以下です。

Datadogのセッションリプレイは、レビュー、分析、トラブルシューティングのために、ユーザーのWebブラウジング体験をキャプチャして視覚的に再生することができます。DatadogのエラートラッキングやAPMトレースによってフラグが立てられたユーザーセッションを再生することで、UXの問題をより迅速に特定し、修正することができます。

つまり、ユーザーのブラウズ操作(ユーザーセッション)をリプレイし、エラー発生や遅延を再現させ、問題解決に役立てる機能のようです。

RUM Session Replayの費用削減設定について

機能の利用有無についてチーム内確認

上記の機能説明にて「〜のようです。」という表現で書いている通り、このデータは意図して送信していたわけではなく、デフォルト設定のまま運用した結果、Datadogに送信されていたデータでした。

念の為、チーム内にて「RUM Session Replay」のデータを使った機能を利用している人がいないかを確認し、特にいないことが確認できたため、データ送信を止めることになりました。

対応方法についての確認

セッションリプレイ機能を無効にするドキュメントを確認しました。

上記ドキュメントで記述されている対応方法としては、以下です。

セッションの記録を停止するには、startSessionReplayRecording() を削除し、sessionReplaySampleRate を 0 に設定します。 これにより、リプレイを含む Browser RUM & セッションリプレイプランのデータ収集が停止します。

上記対応を実施していきます。

対応時の細かな問題

現在この機能のために使用している @datadog/browser-rum パッケージのバージョンは 3.11.0でした。

該当バージョンの場合、取りうる設定値の中に sessionReplaySampleRate という値がありませんでした。

そのため、以下パッケージのドキュメントを確認しました。

sessionReplaySampleRate の以前の値が、 replaySampleRate であったと読み取れます。

replaySampleRate については非推奨の値ですが、今回の対応ではパッケージのアップデートは見送ることとし、replaySampleRateを0としてみます。

設定及びデプロイ・設定効果確認

replaySampleRate を0に修正してデプロイします。

その結果を見るためには、Datadogにログインします。

左側メニューから「 UX Monitoring → Sessions & Replay 」を辿り、Real User Monitoring画面へ遷移します。

以下スクリーンショットのように Session Replay available にチェックを入れ、結果を見ます。

スクリーンショット

こちらで確認した結果、データが検索で出なくなっていることを確認できたため、対応完了となりました。

費用削減の結果

はじめに書いた内容の繰り返しになりますが、対応の結果、RUM Session Replayの料金がほぼ0ドルになりました。

まとめ

Datadogの料金削減ポイント・注意点を合わせてまとめると以下となります。

デフォルトのままだと意図していないデータが自動で送られ、料金高騰に繋がりますので、随時見直していきましょう。

  • 対応方法についてはドキュメントに記載されていることが多いので確認する
  • どうしてもわからない場合はDatadogサポートへ問い合わせる

機能のローンチ直後は無料であっても、有料になることが散見されますので、料金削減の観点からはなるべく明示的にデータを送らない設定にすることが有効です。

  • まず利用してみてから見極める
  • Datadogからのお知らせはよく確認し、料金改定・有料化のお知らせの場合は注意し対応を検討する

以上です。

YouTube Data API の Quota 上限アップ申請で注意すべき 3 つのこと

アイキャッチ

株式会社ブックリスタ プロダクト開発部の酒井です。

去年の秋頃、推し活アプリ「Oshibana」の新機能として「YouTube ウィジェット」を開発しました。
YouTube ウィジェットでは、ユーザーが登録中の YouTube チャンネル一覧から選択したチャンネルの最新動画および配信情報をウィジェット上に表示できます。
自分が推している YouTuber やゲーム実況者、芸能人、アイドルなどの新着動画や Live 配信を見逃さずに視聴できるので、非常に便利です。
ぜひとも使ってみてください。

YouTubeウィジェット YouTubeウィジェット



概要

ユーザーが登録中の YouTube チャンネル一覧は 「YouTube Data API」 の「Subscriptions: list」を利用して取得しています。
しかし、YouTube Data API では 1 日の API 最大使用量(Quota)が決まっています。
Quota は単純に API を呼び出した回数ではなく、呼び出し回数にコストをかけた数値になります。
API ごとにかかるコストが決まっており、例えば YouTube チャンネルの情報を取得する「channels:list」は 1 コストかかり、YouTube 動画を検索する「search:list」は 100 コストかかります。
デフォルトは 10000 であるため、その日中に「search:list」が 100 回呼ばれたら、次の 101 回目以降はエラーとなり、データが取得できなくなります。
API 実行は全ユーザーのリクエストが合計してカウントされるため、ユーザーが多ければ例え 1 コストだったとしても Quota が 10000 では心許ない数値となります。
※特に Oshibana では iOS のウィジェット上から定期的に API を実行するので、すぐに上限を超えてしまいます。

公式ドキュメント

YouTube Data API の概要 - クォータの使用量
https://developers.google.com/youtube/v3/getting-started?hl=ja#quota

YouTube Data API (v3) - Quota Calculator
https://developers.google.com/youtube/v3/determine_quota_cost

上限を上げるためには、YouTube に申請し、アプリが問題無いものであるか審査を受けて承認される必要があります。
申請内容やアプリ自体に問題があると申請が却下(リジェクト)されてしまい、以降はメールで直接 YouTube とやり取りすることになります。
相手はアメリカの企業であるため、返信メールの文面は全て英語で記述する必要があります。
基本的には Google 翻訳や DeepL 翻訳で大体伝わる内容になると思いますが、翻訳ミスにより内容が正しく伝わらない可能性もあるので、念のため文面に問題がないかある程度は確認しておいた方が良いです。

この記事では、申請を承認してもらうための注意点をできる限りまとめていきますので、これから申請を送る方たちの参考になれば幸いです。

なお、本記事の対象は iOS アプリ(ネイティブアプリ)が前提となります。
Android アプリ、Web アプリは対処法が異なる可能性がありますので、ご注意ください。



前提

この記事では、Google Cloud Platform(GCP)にて以下の作業が完了していることを前提としています。

  • プロジェクトの作成
  • YouTube Data API の有効化、API キーの取得
  • OAuth 同意画面の設定
  • Google OAuth 認証制限の解除



申請方法

申請は以下のページから行います。

YouTube API サービス - 監査と割り当て増加フォーム
https://support.google.com/youtube/contact/yt_api_form

アプリの開発組織の情報、アプリ本体(API クライアント)の情報、利用ユーザー数、API の使用方法などの基本情報を入力し、割り当てリクエストフォームの欄にて、追加割り当て量(デフォルトの 10000 から増やしたい数)、追加したい理由、追加しないとアプリで使えなくなる機能などを入力し、送信ボタンをクリックすれば申請が行われます。

申請画面は日本語でも表示できますが、内容は英語で送った方が良いです。

上限を無制限にはできないので、追加割り当て量は必要量をしっかりと計算し、申請する必要があります。
例えば Oshibana では、申請時点での API リクエストの実測値(アクティブユーザー数、画面の表示数、ウィジェット配置数など)から今後の成長予測を行い、将来の API リクエスト数を割り出し、それにコストをかけた数値を追加割り当て量として申請しています。

追加理由や上限突破で使用不可になる機能についても細かく記述しました。
追加理由については、上記の追加割り当て量の算出方法も含めてしっかりと明記した方が伝わりやすいです。



注意すべき 3 つのこと

1.デモ動画の作り方

Quota 上限アップの申請後、YouTube から「Quota の上限増加申請の理由と申請内容の根拠、および追加した Quota がどのようにアプリで利用されるのかを英語で解説した動画を準備せよ」という内容のメールが届くことがあります。
おそらく申請フォームに入力された内容だけでは判断がつかないため、実際にアプリを操作して、どのように API が呼び出されているのか、なぜ多量のリクエストが発生するのかを解説して欲しいということだと思われます。

動画は iPhone の画面録画機能を使って撮影し、YouTube へ投稿後、字幕機能を使って英語の字幕を付けました。
公開設定は「限定公開」にしており、動画の URL を返信メールで伝えています。

Oshibana のデモ動画では以下の内容を撮影しました。

  • Google OAuth 認証の手順
  • YouTube ウィジェットの作成方法およびホーム画面への配置方法
    → この時、各画面やウィジェットで実行されている API の種類や実行頻度について字幕で解説しています。
  • YouTube ウィジェットの削除方法
  • Google OAuth 認証の解除手順

特に 以下の 2 つは重要なポイントであるため、字幕でも個別に解説を入れています。

API スコープについて

API スコープとは、GCP の OAuth 同意画面で登録する「OAuth 認証の際にアプリのユーザーに許可を求める権限の範囲設定」のことです。
YouTube Data API の Subscriptions:list は「機密性の高いスコープ」に該当するため、API スコープの登録が必要となります。

「.../auth/youtube」のスコープは登録や更新等、表示以外にできることが多くなるため、「YouTube API サービス利用規約」に引っかかってリジェクトされるリスクが高くなります。
よって、API の取得結果を表示するだけなら「.../auth/youtube.readonly」で登録するのがオススメです。

GCP APIスコープ

Oshibana のデモ動画では、Google OAuth 認証の際に表示される YouTube アクセス許可ページで YouTube API が readonly のスコープであることを確認する内容を撮影しています。

アラートのメッセージが「View your videos and playlists (動画とプレイリストの表示)」となっていれば、readonly のスコープで登録されています。

ここでスコープが readonly であることをアピールすれば、YouTube からポリシー違反を指摘されるリスクが減ります。

APIスコープ APIスコープ

別件ですが、OAuth 同意画面は英語で表示されている必要があるので、表示言語を「English(United States)」に変更し、言語を切り替えている様子も撮影する必要があります。

言語変更 言語変更

API の実行方法について

YouTube API サービス利用規約 では「ユーザーの認証と認可」に関する規約があり、認証における制約やトークンの失効に関する内容が定められています。
アプリが規約を満たしていると判断されなければ、リジェクトされてしまいます。

例えば Oshibana では、「Subscriptions: list」を実行するために Google アカウントで OAuth 認証し、トークンを発行しています。
このトークンが OAuth 認証の解除処理で失効され、API が実行できなくなっていることを示す必要があります。
よって、デモ動画で Google OAuth 認証の解除手順を撮影し、トークンが失効されたことで API が実行できなくなった旨を字幕で説明しています。

ただし、API 実行に OAuth 認証を使っていない場合はその限りではありません。
Oshibana では、ホームに配置した YouTube ウィジェット上で「PlaylistItems: list」「Videos: list」「Channels: list」の API を実行していますが、これらは「非機密スコープ」の API であるため、OAuth のトークンを必要とせず API キーで実行しています。
よって、ウィジェットは OAuth 認証の解除後も API を実行し続けますが、認証不要で動く API であるためポリシーに違反してないことを字幕で説明しています。

認証の解除後も API が動いているので規約違反だと認識されないよう、API の実行箇所ごとに認証が必要なのかどうかをしっかりと解説するのがポイントです。


2.プライバシーポリシーの記載内容

以下の内容がアプリのプライバシーポリシーに記載されていないとリジェクトされます。

  • YouTube の利用規約への言及
  • アプリが YouTube API Services を使用していることの明記
  • Google のプライバシーポリシーへのリンク
  • Google のセキュリティ設定ページでアプリからのアクセス権が取り消せることについての言及

よって、Oshibana では以下のような内容をプライバシーポリシーに記載しています。

YouTube API サービスの利用
本アプリでは、動画情報などを取得するために YouTube API サービスを利用しています。
YouTube API サービスは、Google 社のプライバシーポリシー、YouTube の利用規約に基づいて提供されています。
YouTube の利用規約、YouTube API サービス利用規約、Google プライバシーポリシーについては以下をご覧ください。
Google プライバシーポリシー( https://www.google.com/intl/ja/policies/privacy/
YouTube( https://www.youtube.com/t/terms
YouTube API サービス利用規約( https://developers.google.com/youtube/terms/api-services-terms-of-service

また、ユーザーは、Google セキュリティ設定ページ( https://security.google.com/settings/security/permissions )から本アプリケーションのアクセス権を削除することで、本アプリケーションによるユーザーの Google アカウントへの接続を無効にすることができます。
ただしこれらの場合、本アプリケーションの一部の機能が使用できなくなる、または一部のページが正しく表示されなくなる場合があることをあらかじめご了承ください。

なお、プライバシーポリシーを修正した場合、本番環境にリリースしてから YouTube に返信してください。
ユーザーがプライバシーポリシーを確認できる状態になっている必要があるらしく、プライバシーポリシーの原文をファイルで送ったり、開発環境に掲載したものを共有するなどは許容されませんでした。
社内の法務確認などで時間を要する場合は早めの対応が必要です。


3.返信メールの文面

前述の通り、申請がリジェクトされ YouTube からメールが届いた場合は、指摘を解消後、英語でメールを返信する必要があります。
指摘の内容に対して、修正結果や対応方法などが明確に伝わるよう工夫すると、やり取りが少なくなります。

例えば Oshibana では、以下のような内容でメールを返信しています。

デモ動画について

指摘内容について解説した箇所を動画の再生時間を記載するなどで伝わりやすくしました。

[Response to Policy D Violations]
The application has been modified.
The points we would like to ask you to reconfirm are explained in the video.
Please turn on subtitles to confirm.
https://www.youtube.com/watch?v=XXXXXXXXXX

  • The scope of the YouTube API used was changed to readonly. (2:51〜)
  • The application connects to the YouTube API with an OAuth token, but the acquired user data is used only for display and is not stored in the DB. (3:52〜)
  • The iOS widget also calls the YouTube API, but only the YouTube API that can be executed in a non-confidential scope. The API key is then used to access the YouTube API. (5:14〜)
  • We added a button to disconnect YouTube account. (7:00〜)
    →This is in response to your point "Add an option to disconnect your YouTube account".
  • After disconnecting your YouTube account, you can switch to another YouTube account when reassociating. (8:53〜)

プライバシーポリシーについて

プライバシーポリシーは日本語で掲載しているため、英語に翻訳した状態でキャプチャを取り、追記した部分を赤枠で囲み、修正結果をわかりやすくしました。
公開されているプライバシーポリシーが日本語でも、ここで英訳した内容を伝えることができれば問題ありません。

[Response to Policy A Violations]
We have updated our Privacy Policy regarding Policy A.
https://oshibana.fun/privacy_policy.html

The red framed area in the following screenshot is the content added in this revision.

プライバシーポリシー



やってみた感想

海外の企業を相手にメールでやり取りするのは非常に大変でしたが、YouTube の字幕の付け方を覚えられたり、YouTube API サービス利用規約の内容に詳しくなったりと、申請以外にも色々なノウハウを得ることができたと思っています。
記事では全てを記載していませんが、YouTube とは実に半年近くやり取りをしており、特に後半は YouTube から指摘されたポリシー違反がなかなか解消できず、苦労しました。
やり取りが長くなると、実は過去に返答していた内容が誤って伝わっていたり、説明不足のままやり取りが進んでしまっているケースがあるので、その時は一度最初から振り返ってみるのも良いかもしれません。

長くなりましたが、本記事が皆さんの申請作業の役に立てば幸いです。

Reader StoreのPMのお仕事

アイキャッチ

自己紹介

はじめまして。プロダクト開発部の森本です。
現在、「Reader Store(運営:株式会社ソニー・ミュージックエンタテインメント)」のPMを担当しております。

PMと言っても、会社によって業務内容が多岐にわたるのでReader StoreのPMはこんなことしていますというのをイメージしていただければと思い記事にしました。

業務内容

最近実施した新機能開発の案件に沿ってお話しします。

課題抽出

2020年~2021年にかけて有名人気コミックがシリーズ完結を迎えました。
これまでReader Storeで上記シリーズを購入していたお客様は、シリーズの完結をきっかけにストアから離れてしまう可能性があります。
BIツールを使い、実際の数値を確認し、仮説を検証していきます。実際の数値を見ても、完結シリーズを購入していたお客様の数パーセントが、シリーズ完結以降、新しい商品をストアで購入していないことがわかりました。

新しく読みたいコミックがないお客様に対し、新しい出会いを提供し、ストアを継続して利用してもらいたいと考えます。
そんななかで、選んだコミックに似た商品を表示する機能(以後、「似た商品を探す」機能)を提供したいという要望が上がったので、この機能の導入について検討を進めました。

まず、「似た商品を探す」機能を導入することでどんな効果・インパクトがあるかを試算します。
ストアには新機能と同じように、タグを用いて商品を探す機能があるため、タグ機能と同じくらいの売上が見込めるのではないかと仮定しました。

また、「似た商品を探す」機能は、作品同士の似てる要素をグラフ化するなど独自性を持たせることで、お客様に楽しんでいただく機能です。その結果、利用者の拡大や利用頻度向上にも繋がるはずだと考えました。

要件整理

「似た商品を探す」機能の要件を整理していきます。

  • シリーズを指定し、似た商品を検索
  • 似た商品結果を表示するページを新設
  • コミックジャンルに限定
  • 書誌詳細、シリーズ詳細ページに結果ページへの導線を追加

新設ページの画面構成要素については、既存のデータの持ち方や既存コードを見て実現可能なものを判断しながらラフ案(ワイヤーフレーム)を作成しました。

また、このタイミングでやらないことも明確にしておきます。
今回はスモールスタートとして、改善を進めていく前提にし、コミック以外のジャンルは見送ることや、似た商品結果の利用範囲を書誌詳細やシリーズ詳細ページからの導線に限定することなどを事前に決めておきました。

各部との調整

デザイン検討・調整

要件整理で作成したラフ案をもとに、デザイナーへデザイン作成を依頼します。
今回の案件では、似ている要素を視覚的にわかりやすくするためにどうするかをポイントにしていたので、デザイン案をいくつか作成していただきました。

似た商品を探す・デザイン検討案

上記のキャプチャは一例ですが、A案のチャートの方がB案と比べて似た商品が並んだときにぱっと見て似ている要素がわかりやすいです。
ですが、検索元商品との比較がしづらいなどの課題にも気付きました。検索元商品の要素もチャート化することにより、視覚で比較できるようにする工夫を盛り込みました。

似た商品を探す・確定デザイン このようにデザイン案を見ながら、細かく関係者と議論して調整しました。

ビジネス部門との調整

ビジネス部門に向けて、開発目的や、変更内容・決まったデザインなどを共有します。
新しい機能のリリースになるため、リリース時のユーザー周知や露出の方法なども関係者を巻き込んで相談しながら決めていきました。

開発共有

開発者へ要件を説明します。
開発者が詳細設計をしていく中で、要件では明記されていなかった細かな仕様の詳細を詰めたり、開発スケジュールの確認をしたりします。

開発サポート

進捗状況や開発を進める中で出てきた課題を確認します。
必要があれば、仕様を再調整するなど、開発がスムーズに進むよう尽力します。

効果測定

「似た商品を探す」機能の効果が期待通りに出ているかリリース後に効果測定しました。
BIツールやGAの数値を見ていくつかの気付きがありました。
「似た商品を探す」機能は、他の既存機能と比較して、CVRや離脱率などでは良好な数値を出していましたが、PV数が想定していたより良くないことがわかりました。
新機能のユーザー周知が足りていないことが原因だと考え、改善するために他チームにも協力してもらい、PUSHやメルマガ配信を実施しました。さらにTOPページの露出追加を行い、多くのお客様に知っていただこうと画策中です。
このように、効果が良くない場合は改善できる方法を考え、できることから進めていきます。

案件以外の業務内容

問い合わせ対応

運用チームやCSからの問い合わせ対応を行います。
コードやデータを見て、状況確認します。

システム対応が必要な場合は、対応の進め方を検討したり、関係部門との調整したりします。

障害対応

システム障害が発生したときには、開発メンバーの音頭をとって障害対応を進めます。

開発プロセス改善

開発が迅速に進むよう、開発ルールの整備など開発プロセス改善も進めます。

全体を通して

やりがい

新しい機能をリリースして終わりではなく、効果検証をして改善を進めていくことができます。単発ではなく、一貫して関わることができます、もっとこうしたいということを実現するチャンスが多いです。

リリースした機能に対して、社内評判が良かったり、感謝の声掛けをしてもらえるとやって良かったなと感じます。
また、ユーザーからの良い感想などを見ることでも嬉しい気持ちになります。

大変なところ

さまざまな部署やチームに説明する上で、知りたいポイントがそれぞれ違います。そのため、それぞれが理解しやすいように伝えないといけないです。資料作成など時間がかかってしまい、つらいです。

やりたいことがたくさんあり、行列待ちになっているので、できないことも出てきてしまいます。優先順位を上手く決めることややらないことを判断するのは難しいです。

まとめ

Reader StoreのPMは業務が幅広く、私自身まだまだできないことも多いですが、その分やりがいがあり、成長できる仕事です。
これからも日々の業務を頑張っていきます。

最後まで読んでいただきありがとうございました。

コミックの類似性の算出とそのシステム構成について

アイキャッチ

はじめまして。プラットフォームソリューション部の山口です。
今回は、昨年2022年末にReader Store(運営:株式会社ソニー・ミュージックエンタテインメント)にてリリースされた「似た商品を探す」の裏側について紹介します。
(現在は、コミックのみで利用できる機能です。)

機能

まずは、「似た商品を探す」機能について簡単に紹介します。
簡単には、「○□ぽい本」、「□○みたいなテイストの本」 を見つけることができる機能と考えています。
現在は、あるコミックの要素を算出して、その要素と近しい作品たちが一覧となって表示されます。

例えば、幕末を舞台にしたバトルマンガであれば、そのマンガは「幕末」「バトル」「刀剣」などの要素が強いだろうと算出します。
そして、それらの要素の傾向を持っているマンガを算出しています。

このあたりは実際に、Reader Store にて好きな、 もしくは興味のあるマンガを検索し、 「似た商品を探す」を使っていただけるとわかりやすいですので
よければ一度利用してみてください。

類似性とシステム

さて、ここからはよりシステム的な面での機能説明や裏側のことについて紹介していきます。
次のような区分にわけて説明します。

  • 要素の抽出と類似性について
  • 検索方法とシステム構成

要素の抽出と類似性について

「似た」と一口に言っても、どう似ているのか、どの程度似ているのかなど気になることがあります。
その点をシステム的にどう算出し、どう表現するのかをまずは説明します。
本を構成する要素は多分にありますが、どんな本であるかがわかる1つの要素として「あらすじ」や「説明文」があります。
今回は、「説明文」に絞って説明します。

説明文を1つとっても使われている言葉・単語や表現は多岐に渡ります。
我々はまずWord2Vecなどの機械学習アルゴリズムを利用して、この表現方法を学習し、ベクトル化することを行いました。

例えば、次のように説明文を利用して、ベクトルを算出しました。

  • マンガA ならば (0.1, 0.2, 0.3)
  • マンガB ならば (0.3, 0.1, 0.6)

この時点でも、このベクトルを利用することで、似た作品の算出はできますが、「何故似たのか」「どこが似ているのか」がわかりません。

そのため、「バトル」「刀剣」などの要素についても同様にベクトル化を行い、
本ごとにどの要素と類似しているのか(その要素を持っているのか)を算出できないかと考えました。
「バトル」「刀剣」などの要素のベクトル化は、本の説明文のベクトル化と同様の手法で算出しました。

例えば、次のようにです。

  • バトル ならば (0.0, 0.0, 0.9)
  • 刀 剣 ならば (0.3, 0.1, 0.0)

これで、本のベクトルと要素のベクトルが算出されました。
次に、「本ごとにどの要素と類似しているのか」を算出します。
この算出にはいろいろな手法があります。
例えば、ユークリッド距離やCosin類似度などです。
今回は、簡単にL1ノルムを利用して、「マンガA」と「バトル」との距離を出してみます。

バトル 刀剣
マンガA 0.9 0.6
マンガB 0.7 0.6

例えば、マンガAは「バトル」が0.9、「刀剣」が0.6とバトルが強い作品だろうと算出します。
また、マンガAとマンガBは「バトル」の要素を強くもち、「刀剣」については同程度触れている作品だろうと予測できます。

結果、マンガAの似た作品として「バトル」の要素を強くもち「刀剣」要素もあるマンガBを算出します。

実際には他の計算手法の利用やスコアのBoostingなどを行っていますが、
このようなステップで、本の要素の抽出と類似性の算出をしています。

検索方法とシステム構成

本のベクトル化やその計算方法について説明しましたが、ここでは実際のシステムに落とし込んだ構成などについて紹介します。

主だったものとして以下を利用しています。

  • AWS Lambda: 本のベクトル化などの算出に利用
  • AWS ECS: APIサーバーとして
  • OpenSearch: 書籍間の類似度の計算と絞り込みのため

今回は、運用上の観点からAWSのマネージドサービスを優先的に選択しています。
簡単に、どのような利用をしているか説明します。

簡易な構成図

本のベクトル化

まず、本のベクトルの処理は、日々多くの書籍を扱う関係上、AWS Lambdaを利用しています。
AWS Lambdaの同時実行を利用することで、数分間に数万件のベクトル化を行っています。
実際には、以下の3フェーズにわけて、処理できるようにしています。

  1. 説明文の形態素解析
  2. 説明文のベクトル化
  3. 要素との類似度の算出とDBへの挿入

また、これらの処理結果は都度S3に保存することで、
途中の処理で失敗しても、中断箇所からやり直せるようにしています。

類似作品の検索

次に、ベクトル化した本同士の類似度の計算には、OpenSearchを利用しました。
OpenSearchの持っているKNN近傍検索によって、類似度の計算をしています。
さらに、ビジネス要件にそった絞り込みに関しても、OpenSearchの検索機能を利用しています。
そのため、一度のリクエストでビジネス要件にそった類似作品の抽出を行えるようにしています。

検索API

最後に、いままでの機能を外部に提供するためのAPIについて説明します。
利用状況に応じたスケールなどの運用上の観点から、AWS ECSを利用しています。
スケールに関しては、ECS キャパシティープロバイダーとEC2 Auto ScaringGroupを利用しています。
これらを利用することで、タスクの負荷状況に応じてタスクのスケールイン/スケールアウトから、
EC2インスタンスのスケールイン/スケールアウトまでを自動で行えます。
これにより、必要なタスクの増減に伴った、インスタンスの増減に繊細な注意を払う必要がなくなりました。
おかげで、一見するとシンプルな構成にできたのではないかと考えています。

最後に

本機能は、よりお客様が本と出会える機会を増やせないかという考えを元に開発が進められました。
未熟な部分もありますが、「お、こんな本があったのか」などの本との出会いの一助となれれば幸いです。

新規プロダクト開発でOpenAPIを導入した話

アイキャッチ

簡単な自己紹介

株式会社ブックリスタでスマートフォンアプリエンジニアをしている城と申します。

現在YOMcomaというショートマンガアプリを作っています。 投稿者さん、読者さんに向けたサービスを提供予定ですので、リリースまでどうぞ楽しみにお待ちくださいませ。

OpenAPIとは

REST APIの定義方法をまとめた仕様のことで、かつてはSwaggerという名称でした。 JSONまたはYAMLでREST API定義が可能ですが、公式のサンプルはほぼYAMLで記述されており、開発現場でもYAMLで記述していることが多い印象を受けます。

参画時の状況

YOMcomaは投稿者さん向けのWEBと読者さん向けのスマートフォンアプリ(以下アプリ)があり、それぞれがREST API(以下API)でバックエンドにアクセスする構成となっています。

私は読者さん向けのアプリとバックエンドの開発を担当させていただくことになりました。 そのため本記事ではアプリ⇄バックエンドAPIの話に焦点を合わせて書かせていただきます。

アプリ開発に必要なAPIの基本設計は済んでいて、各APIの概要がドキュメントにまとめてある状態だったのでまずは詳細を定義してしまう事にしました。 OpenAPIで定義したい旨をチームメンバーに相談し「便利そうだから使ってみよう」と快諾いただけました。

YAMLによるAPI定義

サンプル

上に貼ったのは非常に簡単な例ですが、まずはYAMLを書いていきました。 私はVisual Studio Codeで記述していましたが、拡張機能のSwagger Viewerを使えば画像右側のようなプレビューを見ながら記述できるので間違いに気が付きやすかったです。 また今回はOpenAPIが初見のチームメンバーにもレビューしていただきましたが、記法は調べればすぐわかるし直感的に分かりやすいと言っていただけました。

OpenAPI仕様でYAMLを記述する方法は調べるとたくさん情報が出てくるので書き方には触れませんが、必須項目ではないけれど重要だと感じたポイントに絞って書かせていただきます。

example

パラメータや要素に例を記述できます。

後述のモックサーバーを立てる場合に返却値として活用されます。 また各要素のフォーマットを明確にしておく事で齟齬が生まれにくいので記述しておくことをおすすめします。

operationId

各APIに明示的な命名ができます。

後述のソースコード自動生成を使う場合、各APIを実行する関数名に使われるので、プロジェクトの命名規則に準じて記述することをおすすめします。 後述のドキュメント生成を使う場合、operationIdが未指定だとパーマリンクが正しく設定されなくなってしまいます。

実は色々あるYAMLの使い道

APIの詳細をYAMLで記述し、準備はととのいました。 ここからはYOMcomaでどのようにYAMLを活用していたのか紹介させていただきます。

ドキュメント生成

ReDocでYAMLファイルをドキュメントとしてHTMLに変換しました。

具体的には、コミットやプッシュ時に任意のコマンドを自動で実行できるhuskyを使用し、pre-commit時にReDocを実行しています。 これによりYAMLを改変したらドキュメントも自動更新するようにしていました。 HTMLファイルならエディタが入っていない人でも見られますし、YAMLより直感的に理解しやすいです。 API定義が見られれば作業可能なチームメンバーには、git上にあるドキュメントのパスだけ伝えることで常に最新のAPIを参照してもらえます。

huskyでpre-commit時にHTMLを出力するためのコマンド例
npm install -g redoc-cli
npm install -D husky
npx husky-init
npx husky add .husky/pre-commit 'redoc-cli bundle [yaml path] -o [output path]' 

ソースコード自動生成

YOMcomaのバックエンドはTypeScript(NestJS)で、アプリはDart(Flutter)でそれぞれ自動生成しました。

API通信部分を自動生成することで仕様と実装に乖離が生まれず、結合試験が比較的スムーズであった事もよかったです。 またAPIに修正があった時、再度ソースコード自動生成をし直す事で修正の大部分が完了する事も大きなメリットでした。

バックエンド

Himenon/openapi-typescript-code-generatorでレスポンスのスキーマ部分のみ自動生成しました。

自動生成にはほんの少し実装が必要なので、手順はリンクをご確認ください。

アプリ

OpenAPI Generatorでソースコードを自動生成しました。

Modelクラスやシリアライズ処理、APIClientクラスを自動生成し、実装工数がかなり削減できました。 ライブラリへの対応も充実していて、YOMcomaでもFlutterのHTTPクライアントとしてメジャーなライブラリDioを使ったソースコード自動生成をしています。

コマンド例
brew install openapi-generator
openapi-generator generate -i [yaml path] -g dart-dio -o ./openapi

モックサーバーを立てる

Prismでモックサーバーを立てました。

YOMcomaではバックエンド開発よりアプリ開発が先行していました。 そのためバックエンドでスタブを返すことが難しく、モックサーバーの出番となりました。 PrismはYAMLのexampleに記述した値を返してくれるので、ノーコードでモックサーバーを立てることができました。

モックサーバー起動コマンド例
npm install -D prism
npx prism mock [yaml path] -p 8080

テスト実行する

Postmanでテスト実行しました。

バックエンドのAPI開発時はPostmanにYAMLをimportしてテスト実行していました。 エンドポイントやパラメータ名を入力する手間が省けてテスト効率がアップしました。

サンプル

ハマりポイント

今回OpenAPIを使ってハマったポイントです。

allOf

type: arrayの直下にallOfや複数のpropertyを入れているケースは、OpenAPI Generatorでうまく変換されませんでした。

これはソースコードに置き換えて考えると分かりやすいのですが、List型に指定可能なのは単一のクラスです。 少なくとも私の知る言語ではList<{int id, String name}>のようには書けないので、type: arrayの下に複数の値を入れたい場合は別途Schemeを定義する必要がありました。

サンプル

さいごに

OpenAPIで定義する時にはプレビュー機能があったり、プレビューからAPIを実行できたり、他にも便利な機能がたくさんあり、きっとこれからも増えていくのではないでしょうか。 また導入コストが高くないのも大きな魅力の1つと思っています。 現在YOMcoma開発は第2フェーズに入り、チームメンバー全員がYAMLを使ってAPIを改版しており、開発サイクルが定まってきていると感じています。

この記事の内容が、これからアプリ開発をされる方の何かしらのヒントになれば幸いです。

Aurora1からAurora3へアップグレードするときのアプリケーション対応の注意点について

アイキャッチ

はじめまして。プロダクト開発部に所属しているエンジニアの姚です。
今回は「Reader Store(運営:株式会社ソニー・ミュージックエンタテインメント)」のDBをAurora1からAurora3へアップグレードしたときにアプリケーション側が対応したことを話していきます。

Aurora3導入の背景

Amazon Aurora MySQL 1 (MySQL 5.6 互換) は 2023 年 2 月 28 日にサポート終了となります。
サポート期間が終了する前にアップグレードしないといけません。
また、Aurora MySQL 2 (MySQL 5.7 互換) は 2024 年 10 月 31 日にサポート終了となります。
*2022年11月時点、最新の情報は下記公式ページにご参照ください。
Amazon Aurora メジャーバージョンが利用可能な期間

Aurora2にアップグレードしても、二年後にもう一度アップグレードすることになり、二度手間になりますので、今回はAurora1からAurora3へアップグレードすることにしました。

方針

MySQL8.0ではマイナーバージョンアップ時に後方互換を担保しないため、非推奨機能は使えなくなる可能性があります。Aurora3ではまだLTS版が出てないため、AWSのサポート期間が短かいことも予想されます。
マイナーバージョンアップ時に規模の大きな改修を行わないで済むように、アプリケーションへの影響がでる非推奨機能も調査して事前対応することとしました。

対応の流れ

アプリケーション改修

  1. 開発環境の設定変更、DBエンドポイント変更、DBドライバーのバージョンアップ
  2. 事前調査結果に基づき、MySQL8.0で廃止/変更機能、非推奨機能の改修
  3. 動作検証で検知した問題の追加改修

インフラ

  1. MySQL8.0でシステム変数のデフォルト値への対応
  2. アプリケーション改修と並行してAurora3の環境構築とデータ移行

動作検証

  1. 全機能テスト
  2. 負荷試験

リリース準備

  1. リリース手順の整備(リカバリー手順も含む)
  2. リハーサル



MySQLのバージョン変更による影響調査

基本は公式ドキュメントを参照し、MySQL5.7、MySQL8.0の廃止/変更機能+非推奨機能をリストアップして、システムに影響があるかを確認します。


アプリケーションへの影響が大きい変更

  • 廃止/変更機能
    • 予約語
    • GROUP BY暗黙/明示的のソート順変化
    • クエリーキャッシュ廃止
  • 非推奨機能
    • 文字セット(utf8)
    • システム変数explicit_defaults_for_timestamp
  • 課題
    • 一時テーブルメモリ不足のチューニング



予約語

MySQL5.6, 5.7, 8.0のキーワードーと予約語変更の公式ドキュメント: Keywords and Reserved Words

  • 予約語

    下方にキーワード表がありますが、「(R)」が付いているのが予約語です。

  • キーワードだけど非予約語

    非予約語はテーブル名などの識別子として、そのまま使っても大丈夫です。

予約語リスト

参考としてMySQL5.6から8.0の間で変更がある予約語を下記にリストアップします。

追加された予約語(31件)

CUBE
CUME_DIST
DENSE_RANK
EMPTY
EXCEPT
FIRST_VALUE
FUNCTION
GENERATED
GROUPING
GROUPS
JSON_TABLE
LAG
LAST_VALUE
LATERAL
LEAD
NONBLOCKING
NTH_VALUE
NTILE
OF
OPTIMIZER_COSTS
OVER
PERCENT_RANK
RANK
RECURSIVE
ROW
ROWS
ROW_NUMBER
STORED
SYSTEM
VIRTUAL
WINDOW

削除された予約語(1件)

ONE_SHOT

予約語の対応

DB名、テーブル名、カラム名などの識別子としてそのまま使うとダメなので、バッククォートで囲みます。

-- NG
CREATE TABLE rank (id INT PRIMARY KEY, val INT); ❌

-- バッククォートで囲めばOK
CREATE TABLE `rank` (id INT PRIMARY KEY, val INT); ✅



GROUP BY暗黙/明示的なソート順変化

ソート順を指定しないとき、GROUP BYカラムによる暗黙の昇順ソート (ASCが省略されているもの)が8.0以後なくなります。
参考:ORDER BY の最適化

変更点

  1. GROUP BYの暗黙/明示的ソート順変化
-- 発行のSQL
SELECT hoge FROM fuga GROUP BY hoge;

-- 5.7までの動作
SELECT hoge FROM fuga GROUP BY hoge ORDER BY ASC;

-- 8.0での動作
SELECT hoge FROM fuga GROUP BY hoge ORDER BY NULL;

2. GROUP BYのASCとDESCの付け方

-- 5.7まで
SELECT hoge FROM fuga GROUP BY hoge ASC;

-- 8.0
SELECT hoge FROM fuga GROUP BY hoge ORDER BY ASC;

GROUP BY仕様変更対応

  1. GROUP BYでソート順を指定していない箇所に、GROUP BYカラム順にソートするように明記
  2. GROUP BYにASCとDESCが付いているがORDER BYがない箇所にORDER BYを追加



クエリーキャッシュ廃止

クエリーキャッシュは5.7.20で非推奨に、8.0で削除されます。 過去バージョンでクエリーキャッシュが活用されていた場合に性能低下の可能性がありますので、パフォーマンスの変化に気をつける必要があります。

負荷試験を実施し、パフォーマンス劣化がありましたら、下記に限らず適切に対応します。

  • 古いデータ削除やパーティションテーブルを利用して、クエリ対象のデータを減らす
  • インメモリデータベース(Redisなど)を使ったキャッシュを導入
  • ProxySQLを使ったキャッシュの導入※
  • インフラの増強

※参考:MySQL 8.0: Retiring Support for the Query Cache
ProxySQLのキャッシュは旧来のMySQLと異なり、データ更新のタイミングでキャッシュが破棄されないので注意が必要です



文字セット(utf8)

公式ドキュメントに記載がある通りutf8文字セットが非推奨になっています。

MySQLのutf8はutf8mb3のエイリアスなので、utf8mb3 文字セットに合わせて非推奨となります。将来の MySQL リリースで削除される予定のため、かわりに utf8mb4 を使用しようした方が良さそうです。

照合順序

照合順序はデータの文字の大小関係を比較する場合の基準となるものです。

MySQL 5.6は文字セットutf8の照合順序がデフォルトutf8_general_ciとなっています。MySQL 8.0の文字セットutf8mb4のデフォルトの照合はutf8mb4_0900_ai_ciになります。

utf8mb4_0900_ai_ciだと絵文字(🍣、🍺など)が区別できません。 ソート順などを現行のまま保持したい場合は、MySQL 8.0ではutf8mb4_general_ciを明示的に指定する必要があります。

注意点
データベース、テーブル、カラムが変更対象です。文字セットを変更するときCOLLATIONを明示的に指定します。

ALTER TABLE `fuga`.`hoge`
    CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

viewは作成時の照合順序が適用されるので、テーブルの照合順序を変更した後でviewを作り直す必要があります。
なお、viewの照合順序は指定できませんが、view作成時のセッションと同じ照合順序になるためview作成前にセッションの照合順序を明示的に指定します。

-- view作成前にcollationをutf8mb4_general_ciに指定
SET @@session.collation_connection = 'utf8mb4_general_ci';

-- viewを作り直す
CREATE OR REPLACE VIEW `fuga`.`hoge_view` AS
  SELECT concat('ABC',`hoge`.`hoge_column`)
  FROM `hoge`
  WHERE ...;

文字セット移行変更方式の検討

プランA. Aurora3へアップグレード後、ALTERを発行して文字セットを変更する

  • メリット
    • 移行手順が簡単
    • データ整合性は保証できる
  • デメリット
    • 照合順序変更すると、インデックスも作り直されるので時間がかかる
    • 文字セット変換のAlter文実行中は、テーブルがロックされるのでサービス稼働中に実施できない

プランB. DMSを使ってストア稼働中に文字セットを変換する

  • メリット
    • 移行中でもデータベースは利用可能の状態になる
  • デメリット
    • データ整合性のチェックが必要となる

DMSを使った場合の具体的な対応は下記のイメージになります。

  1. 移行先のDBを作成し、utf8mb4に文字セットを変更
  2. AWS DMSでレプリケーション実施
  3. データが一致していることを確認

Amazon Database Migration Serviceを使えば、文字セットが違うDB間でもデータレプリケーションが可能です。
データ量が少ないDBはプランAで対応しやすいですが、 データ量が多いDBやサービスを長時間停止することを避けたい場合は、プランBで対応する方が良さそうです。



システム変数explicit_defaults_for_timestampをOFFにするのは非推奨

explicit_defaults_for_timestampとは

システム変数explicit_defaults_for_timestampはtimestampカラムにnull値セットの処理を有効にするかどうかを決定します。
OFFになっているとき、timestampカラムにnullをセットできますが、ONにするとnullはセットできなくなります。
詳細はMySQL 公式ページをご参照ください。

MySQL 8.0 系においてexplicit_defaults_for_timestampの設定が非推奨であり、デフォルト値 ON の動作は以下となります。

-- NG timestamp null insert
INSERT INTO hoge(..., created_tm, create_user)
values (..., null ,"aaa") ❌
-- OK 
INSERT INTO hoge(..., create_user)
values (..., "aaa") ✅

ORMを利用している場合、Entityのtimestamp項目に値を未設定のまま永続化するとtimestampにnull insertが発生する可能性があります。
なお、Aurora 1において、explicit_defaults_for_timestampを設定せず、無効化になっている事象があります。
アプリケーションがnull insertのSQLを発行しているかを気をつかないといけません。

対策として、explicit_defaults_for_timestampをOFFにすることで、timestampにnull insertのパターンも正常動作できます。
Aurora 3においてパラメータグループで explicit_defaults_for_timestamp の値は変更可能ですが、実際には反映されないので注意が必要です。

AWSサポートに問い合わせをしたところ、下記対応方法を回答いただきました。

explicit_defaults_for_timestamp を無効化する必要がある場合には、アプリケーション等でのセッション開始時に "set explicit_defaults_for_timestamp=0;" を実行いただく必要がございます。
あるいは、ドキュメントの「explicit_defaults_for_timestamp が有効になっている場合...」以下の動作を前提としてアプリケーションを修正いただくこともご検討ください。

Reader Storeにおいては、アプリケーション側でtimestampにnull insertされないように対応しました。



内部一時テーブルメモリ不足のチューニング

MySQL 8.0にアップグレード後、データ量が多いテーブルは、内部一時テーブルメモリが不足する恐れがあるので、チューニングを考慮する必要があります。

内部一時テーブルと使用するストレージエンジンについて

MySQLでは一部のクエリで内部一時テーブルを作成してます。

使用されるストレージエンジンについて、MySQL 5.6ではMemoryしかありません。
MySLQ 8.0からは従来のストレージエンジンであるMemoryに加えて、TempTableが追加され、デフォルトエンジンになります。
ストレージエンジンの仕組みは、AWSのドキュメントをご参照ください。

Aurora 3での注意点

  1. Aurora3 MySQL8.0.23のTempTableに不具合がある

    指定した一時ファイルの割り当てサイズよりデータが多かった場合に、本来は使用すべきInnoDBの内部一時テーブルが使用されず、エラーが発生します。
    参考:https://forums.percona.com/t/mysql-8-0-the-table-tmp-sql1-f519f-7-is-full/10767

  2. Aurora3 で利用可能なストレージエンジン

    パラメーターグループのinternal_tmp_mem_storage_engineでエンジンを指定できますが、リーダーインスタンスはTempTableだけが使えます。

対策

従来のMemoryを使用することにより回避できます。 リーダーインスタンスでTempTableを使用する場合は、現行のテーブルサイズから一時テーブルに使用される容量を見積もり、一時テーブルのパラメーターに適切な値を指定します。
TempTableモードで使用されるストレージ上の内部一時テーブルのサイズが不足するとSQLエラーとなってしまうため、temptable_max_mmapに余裕あるサイズを指定します。



まとめ

今回はアプリケーションエンジニアの視点から、Aurora3へのアップグレードの内容をまとめてみました。
作業するときインフラ側と密にやり取りしたり、周りの協力を得られるとスムーズに進められそうです。

MySQL8.0 へのバーション変更の影響範囲はかなり広いです。ドキュメントやコードを見るだけで全ての影響点を把握しきれてない可能性があります。
全機能テストで問題を検知したこともありますから、しっかりテストを行った方が安心です。

Apache JMeterの負荷シナリオ作りで苦労した話

アイキャッチ

はじめまして。プロダクト開発部アプリケーションエンジニアの中村と申します。
現在、「Reader Store(運営:株式会社ソニー・ミュージックエンタテインメント)」のシステム開発を主な業務として日々取り組んでいます。
今回はReader Storeの開発中に行った負荷試験の話をしていきます。

負荷試験実施の背景

Reader StoreではこれまでAmazon Aurora MySQL 1 (MySQL 5.6 互換)を使用しておりました。
しかし、2023 年 2 月 28 日にサポートが終了するため、Amazon Aurora MySQL 3 (MySQL 8.0 互換)へバージョンアップすることにしました。
バージョンアップ後もこれまでと同等の性能で動作することを確認するために負荷試験を行うことにしました。

Apache JMeterについて

Apache JMeter(以後JMeter)は、Apache Software Foundationが開発した、Webアプリケーションの機能動作及びパフォーマンス計測するためのテストツールです。
テストしたい画面のURLやリクエストパラメータやリクエスト数、スレッド数などを定義することで任意の負荷試験を行うことができます。
私はこれまで負荷試験を行ったことはありませんでしたが、本プロジェクトでは過去にJMeterを使用していたことから知見があったため、今回も使用することにしました。

公式:https://jmeter.apache.org/


また、JMeterではクラスター構成による複数台のサーバーを利用したテストを行うことができます。
1台のサーバーから負荷をかけようとするとスペックやネットワークの問題で期待している負荷をかけられない場合があります。
そのような場合に複数台のサーバーから負荷をかけることで期待通りのテストを行うことができます。

Masterサーバー1台及びSlaveサーバーを任意の台数用意し、MasterのconfigファイルにSlaveサーバーのプライベートIPアドレスを定義することでクラスター構成となります。
今回は、以前の負荷試験で利用していたWindowsのサーバーをMasterサーバーとし、SlaveとなるLinuxサーバを6台用意してクラスター構成でのテストを行いました。

なお、一部クラスター構成では実施できないテストがあるため、そういう場合はローカル環境から実行してます。 シナリオサンプル

テストシナリオの作成

作成の流れ

JMeterでテストシナリオを作成するためにまずはテスト対象の画面を決めます。
テストは全ての画面に対しては行わず、影響の大きさを鑑みて以下を基準に選定しました。

  • リクエスト数が多い画面
  • ユーザーと紐付くレコードの多いテーブルにアクセスがある画面

実際の流れは以下になります。

  1. DBのユーザに紐付く情報を保存しているテーブルのレコード数確認
  2. 各テーブルの1ユーザに紐付くレコード数を確認し、レコード数の多いテーブルを参照しているURLを確認
  3. New Relicからリクエスト数の多いURLを確認
  4. Google Analyticsから該当のURL(レコード数の多いテーブルを参照している or リクエスト数の多い)の分間あたりのリクエスト数を抽出(通常時、セール時(リクエスト増加時)でそれぞれ)
  5. Top(/)のURLを基準に、対象のURLへの導線を辿るように画面遷移のフローを決定
  6. 画面遷移フローと分間リクエスト数をもとにJMeterのシナリオを作成

なお、今回作成したシナリオは調べた分間リクエスト数を1時間実行し続けるようにします。

以下は簡単なシナリオのイメージになります。
これはTop画面を表示するだけのシナリオになります。
Thread Groupの各設定値は以下のようになります。

  • Number of Threads:テストで使用するスレッド数を指定
  • Ramp-up period:スレッド数が生成されるまでの時間(今回は60固定)を指定
  • Loop Count:シナリオの回数を指定でき、無限ループの場合はInfiniteにチェック
  • Specify Thread lifetime:DurationやStartup delayを指定する場合にチェック
  • Duration:シナリオの実行時間(今回は3600秒固定)を指定
  • Startup delay:テストを実行してからこのシナリオが動くまでの間隔を指定

画像の通りに設定した状態でシナリオを実行すると、最初の1分間に10スレッドが生成され、1時間リクエストが行われ続けるようになります。
なお、分間リクエスト数についてはConstant Throughput Timerで制御しますが、こちらについては後述で触れさせていただきます。

シナリオサンプル

シナリオ作成時のポイント(苦労したところ)

今回シナリオを作成した際に気をつけたことや苦労したことを紹介させていただきます。

ログインを一度だけ行いたい

ログインは一度行えば、セッションの有効期限が切れるかログアウトを行うまでは継続されるため、ユーザが何度も行うことはありません。
例えばマイページの画面を表示する負荷試験を行いたい場合、マイページを表示するためにログインは必要になります。
しかし、マイページ表示の前に毎回ログインさせるとログイン処理というテストの目的と沿わない負荷がかかってしまいます。

このような場合にOnce Only Controllerを使用することで実現できました。
Once Only Controller配下に定義したリクエストは、そのシナリオが繰り返し実行された場合でも最初の1度だけ行うようになります。
ログインのリクエストをOnce Only Controller配下で行うことによって、そのシナリオ内でログインを一度だけ行うようにできました。
シナリオサンプル

複数のユーザを使用してテストしたい

テストでログインするユーザを同じではなく複数ユーザで分けて使いたいということがあります。
今回、サービスを多く利用しているユーザ、平均的なユーザ、利用が少ないユーザ(=ユーザに紐づくテーブルのレコード数)を想定したユーザを用意してテストを行おうとしました。

このような場合にUser Defined VariablesUser Parametersを組み合わせることで実現できました。
まずはUser Defined Variablesには変数名(ここではIDとPASSとする)を定義します。
次にUser Parametersにはユーザごとに使用するIDとPASSの値を定義します。
最後に、ログイン処理のリクエストでパラメータの値に${ID}と指定すると、リクエストで変数を参照できるようになります。
そうすると、Thread Groupでスレッド数を2以上にした時、1スレッド目はUser ParametersのUser_1の値、2スレッド目はUser_2の値を使用するようになります。
今回、各条件のユーザのIDとパスワードをそれぞれ定義することでそれぞれのユーザを使用してテストを行うことができました。

ちなみに、User Parametersに定義したユーザ数よりもThread Groupのスレッド数の方が多い場合は、またUser_1の値から使用します。

シナリオサンプル

1ユーザで1度しか行えないテスト(例えば1日1回のログインガチャを引くなど)の負荷試験を行いたい

DBを更新する処理が行われた場合はその後繰り返しテストを行うことはできない場合もあります。
今回あったケースでは、1日1回のログインガチャを引くというテストがあり、同じユーザでは2回目以降はガチャを引けないという問題がありました。

このような場合にPost Processorを使用することで解決できました。

Post Processorをリクエストの配下に定義することで、そのリクエストが完了した後にPost Processorで定義した処理を実行できます。
今回はDBの操作したいため、JDBC PostProcessorを使用しました。
ガチャを引くというリクエストの後にガチャの結果が保存されているテーブルのデータを削除するSQLを実行することで、何度もテストを行うことができました。
実際の定義としては、対象のリクエスト配下にJDBC Connection Configuration(DBの接続情報を定義)とJDBC PostProcessorを定義して使用します。

少々話は逸れますが、クラスター構成で実行する場合は各サーバーで同じテストを行うため、上記のようなケースのテストはうまくテストできない可能性があります。
そのため、今回はそういったケースのテストだけローカルのPC上で実行するという対応をしております。

シナリオサンプル

リクエスト数の制御はConstant Throughput Timerを使用する

時間あたりのリクエスト数を制御するためにはTimerを使用する必要があります。

今回、シナリオを作成し始めた際はプラグインのThroughput Shaping Timerを使用しておりました。
こちらは標準のTimerよりも細かい制御ができる(RPSで指定できる、経過時間によってRPSの値を変更できる等)ため、高性能なTimerとなっております。
しかし、このTimerはスレッド数を増やすとリクエスト数が設定値より少なくなってしまうという問題を発見しました。
試したところ、スレッド数が4以上になると30%ほど低下していました(スレッド数と比例して低下するというわけではなかったです)。

今回は細かいリクエスト数の制御は必要ないため、標準のConstant Throughput Timerを使用することで期待通りのテストを行うことができました。
Throughput Shaping Timerでも適切なチューニングを行うことで期待したリクエスト数を実現できるかは要検証です。

クラスター構成でリクエスト数を制御するには

前述で話題に上がったConstant Throughput Timerですが、こちらはRPMでリクエスト数を指定できます。
例えば100req/分であれば100を指定します。

ただし、クラスター構成で実行する場合はサーバーの台数×RPMとなってしまうため、そのままだとサーバーの数だけリクエスト数が倍増してしまいます。
そのため、クラスター構成で実行する場合はサーバーの台数で割った数を指定する必要があります。

例えば100req/分をサーバー5台で行う場合はConstant Throughput Timerには20を指定します。

負荷試験不可の外部システムがある状態でテストするには

開発しているサービスの中では社内だけではなく社外のサービスと連携している箇所も出てきます。
負荷試験を行う場合には当然外部サービスに対しても負荷がかかってしまうため許可をいただく必要がありますが、場合によっては負荷試験がNGな外部サービスもあります。

このような場合にスタブを用意することで外部への負荷をかけずにテストを行うことができました。
今回はAWSのAPI Gatewayを利用することで簡単にスタブを用意できました。

参考:https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-mock-integration.html

失敗談

最後に、負荷試験をしていてハマったことがあったので軽くお話しさせていただきます。

今回、シナリオが全て作成できていざテストを実行してみたところ、テストの実行が途中で止まってしまう事態に遭遇しました。
必ず止まるわけではなく正常に終了することもあり、止まってしまった場合のログにエラーが出ているわけでもなかったため原因がすぐにはわかりませんでした。

最初はローカルのPCから実行していたためスペックの問題なども疑い、クラスター構成にして実行するなど試しましたが結果は変わりませんでした。
そこでログを詳しくみたところ、Slaveのサーバーのログでテストが停止する際にどのサーバーも同じシナリオで止まっていたことがわかりました。

そのシナリオの設定を確認したところ、Thread Groupの設定でテストのリクエストでエラーが返った場合の挙動の設定が他のシナリオと異なっていました。
他はContinue(テストを継続する)になっていたのに、そのシナリオだけは謝ってStop Test Now(テストを中断する)にしてしまっていました。

上記の設定の場合にはJMeterとしては正常動作となりエラーログは当然でないため、ログをしっかりと確認して停止の原因となる情報を見つけることが重要でした。
この設定ミスを発見するために数日もハマってしまったのは悔やまれますが、JMeterへの理解が深まったこととログを確認することの重要性の再認識が出来たことはせめてもの救いでした。

シナリオサンプル

まとめ

初めての負荷試験となりましたが、シナリオを作成する上でのセオリーや注意点など色々なことが学べて良い経験となりました。
ただ、JMeterは画面のボタンを押すなどの操作をしているわけではなく、あくまでその操作をした時の擬似的なリクエストを自分で作成して送信しているに過ぎません。
例えばフェデレーションログインのように外部のサービスの画面を操作したいといった場合にはどうすれば良いかなど、まだわかっていないこともあります。
上記のようなテストケースの手法についてや、JMeter以外の負荷試験ツールなどの理解も今後深めていきたいです。

この記事の内容が、これからJMeterを使用する方の何かしらの助けになれば幸いです。