当たり前のことでも小さなことからコツコツ書いていくスタイル。
今回はユーザーの扱いをどのようにしたら良いのか、という序盤で考えることをサラリと流します。
ユーザーセッションとは
一応ここでの用語として。ユーザーセッションとは、ログイン開始からログアウト完了までの、ユーザーの識別が可能な状態のことを指すとします。ただし、負荷試験においては試験時間終了時にわざわざログアウトする必要もないので、ログアウトのことは考えないことにします。ユーザー情報がなくともアクセスできる公開ページを持つサービスもありますが、たいていはそれは部分的であり、大半の処理はユーザー識別情報を必要とすることが一般的なので、ユーザーセッションについて考えることは必須です。
では、負荷試験ではどのようにユーザーを取り扱うのがよいのか、を考えていきます。
セッション開始のタイミング
Locust での例になりますが、このように実行したとき、ユーザ数を 10 として試験を実行します。
1 |
locust -f test.py --users 10 ... |
コード的には、開始と同時に on_start() が呼ばれ、そこで認証処理を仕込みます。
1 2 3 4 5 6 7 8 9 10 |
class LocustBehavior(TaskSet): def on_start(self): # ログイン・リクエストを送って res = self.client.post("/login", {"user":"gedow", "password": "father"}) self.setSessions(res) def setSessions(self, res): # 任意のレスポンス・ヘッダなどからセッション維持用のデータを保持する # ただし、Set-Cookie は自動的に覚えてくれるので不要 |
ユーザーごとの処理は独立しているので、実際には連番を数えて認証情報に使ったり、リストデータから順に取り出して認証します。
認証後は、一時的な認証データを毎回送信することで、ユーザー識別とするのは通常のサービスと同様です。
認証
昔はメールアドレス+パスワードのような形式でログインするサービスが多かったですが、昨今はSNSやプラットフォームのような第三者を経由したものが多く、自前で大元の認証データを扱わないサービスすらあります。負荷試験の認証において、現実と同じ処理を行って認証することは可能ですが、バカ正直にやるとなると、外部サービスにアカウントを大量に保持したり、試験のたびに外部へ大量リクエストを送ることになります。それはマナー以前に違反行為であるため、その方法は取らないことが確定します。
すなわち、自社管理のサーバー範囲で完結する負荷試験とすることになります。
試験用認証
ユーザー識別さえできればいいとするならば、1つ試験用のログイン処理を作ることになります。例えばこういう流れClient | Server | ||
ユーザー認証データを送信 | GET /stress/login?user_id=123 | → | ユーザーデータ照合 不在ユーザーは初期化 |
識別用データを返信 | セッションに保持 | ← | X-STRESS-USER-ID: 123 |
ヘッダに付与して送信 | GET /mypage X-STRESS-USER-ID: 123 | → | 認証済みとして処理 |
パラメータとして送信も可 | GET /mypage?user_id=123 | → | 同上 |
IDの送受信をヘッダでやるかパラメータでやるかは、やりやすい方でという感じですが、ヘッダにするとアクセスログとしては余計なものがつかないし、パラメータだとログに残るけど逆にテスト中はわかりやすくて良いかもしれない、くらいの違いはあります。
あと識別データに何を採用するかですが、IDじゃなくアカウント名にしたり、返すセッション用データをランダム文字列にしたり、とか考えられますが、あえて複雑にする意味もここにはほぼ無いので、最もシンプルな識別データを扱えばよいと思います。
ダミーAPI
上記手法の場合、新しいリクエストパスの作成と、通常にはないリクエストを送ることになります。それはたいした不便ではないですが、全体のリクエスト構成を変えずに自サーバーで完結する方法があります。設定等に外部APIのホストを記述しているはずですが、これをテスト用サーバーのホストに変更、または名前解決を同名で上書き設定することで、外部にリクエストが飛ばないようにします。その代わりに、外部APIと同等のリクエストを受け入れるダミーAPIを置いておく必要があります。
こうすると認証だけでなく、一般的な処理で使うAPI処理もカットすることなく、内部だけでテストデータをやり取りすることができます。こちらの方がハードルは高めですが、要件次第では必要になることもあるでしょう。
ユーザーリストと初期化
ユーザーの認証データは、1からの連番でもいいかもしれないし、任意のリストが必要なこともあります。これについて、既存ユーザーと不在ユーザーについて考えておく必要があります。負荷試験は場合によっては何度も似た試験を繰り返すことがあり、テストデータも何度も作り直されることがあります。その際に、不在ユーザーの認証でエラーが起きる仕組みだと、ユーザーリストやテストデータを作り直す必要があり、効率がよくありません。
これについての方法としては2つ考えられます。
テストデータからユーザーリストを抽出
試験前に、現在存在するユーザーをテストデータからリスト化し、それをClientが使用する方法です。これは全ユーザーである場合や、特定の条件を満たしたユーザーのみで行いたい場合など比較的、最終局面で役立ちます。
1つ弱みがあるとするならば、10000ユーザー抽出した場合、同時接続数も10000までしか調整できないので、予めテストデータ作成時点で数の調整をすることになります。
自動採番を認証データとする
コーディング次第で、ユーザーの処理は 1 から連番を作ることができるので、順に user_id = 1, 2, 3 と送信して認証することができます。この場合、テストデータ側にそのIDがないとエラーになるのですが、作り変えられる可能性のあるテストデータに対してそれは旨くありません。
サーバー側で不在IDの認証リクエストがきた場合、まずは自動的にユーザーをそのIDで作成することです。そして、ユーザー作成後の初期データ編集やチュートリアルの類が済んだ状態に、データを進行させます。
こうすることで、例えばID 1~10000 の同時接続をいつ始めても、存在すれば認証され、無ければ新規作成されるので、テストデータの状態に関わらず1万ユーザー分の試験を常に正常試行することができます。
重複ユーザー
自動的な連番ユーザーの場合、ユーザーが重複することはありませんが、ユーザーリストを順に選択する形式の場合、同時接続ユーザー数はそれを超えることはできません。リストに100ユーザーあり、同時接続数を 110 にすると、10接続分のユーザーの扱いはコーディング次第になります。リストが足りない分は最初からループして選択するか、エラーとしてしまうかです。
この場合、対応としてはリストを110用意するか、同時接続数を100に下げるか、で調整しなくてはいけません。
同じユーザーを複数セッション作成してしまうと、現実では普通ありえない処理となり、主にデータベースのトランザクションでロックが長めにかかったり、予期せぬエラーになるからです。
ただ、あえて意図してそういう負荷をかけることはありえます。ユーザーの悪意あるハックとして同ユーザーが複数セッションを作成し、可能な限り同時にアクセスした場合どうなるのか、というようなバグ探しに利用できます。
本番との区別
テスト用としての特殊な簡易認証処理は、特定の条件を満たした時にしか使用できない状態にあるべきです。万が一にも本番サーバーで稼働してはいけない処理だからです。例えば環境変数でその機能のON/OFFを区別するのは1つの方法ですし、滅多に使わない処理としてそもそも本番のコードには含まないよう管理し、試験のときだけマージするのも1つの手です。
簡易認証は開発時にはあると便利だったりするので、なにも負荷試験に限った話ではありません。開発も負荷試験も便利にしつつも、安全に気を配ることは忘れないようにしましょう。
もっと突き詰めると、ライト/ヘビーユーザーを演出したくなったり色々出てくるかもしれませんが、それはまた別の話。
まずはこのように、他社に迷惑をかけず、安全で効率的に、という方向性で設計するとよいかと存じまする:-)