IT系フリーランスの日記

大まかにIT系と呼ばれる仕事を自営業として営んでいる者の日記です

認証サーバーの負荷テスト再び

あの後、認証サーバーの負荷テストがなぜリクエストの頻度が落ちた後もエラー率の高いまま推移し、回復しなかったのかサーバーのログを見てみました。
すると、認証サーバーに登録されたアカウント番号が既にトークンを発行しており、そのトークンを認証サーバーが所持している場合は既存のトークンを削除した上で、
新たなトークンを発行するようなシステムだったのですが、アクセスが集中することでトークンの削除がうまくいかなくなると、それ以降の処理が全てエラーになってしまっていることが発覚しました。
具体的に、Djangoトークン発行の箇所だけを抜き出してコードを以下に示します(バグがあったものです)。


@staticmethod
def create(user: UserAccount):
# ユーザの既存のトークンを取得
if UserToken.objects.filter(account_no=user.account_no).exists():
# トークンが既に存在している場合は削除する
UserToken.objects.get(account_no=user.account_no).delete()
# トークン生成(メールアドレス + パスワード + システム日付のハッシュ値とする)
dt = timezone.now()
original_str = str(user.account_no) + dt.strftime('%Y%m%d%H%M%S%f')
hash_str = hashlib.sha1(original_str.encode('utf-8')).hexdigest()
# トークンをデータベースに追加
token = UserToken.objects.create(
account_no=user.account_no,
token=hash_str,
access_datetime=dt)
return token

この、

if UserToken.objects.filter(account_no=user.account_no).exists():
# トークンが既に存在している場合は削除する
UserToken.objects.get(account_no=user.account_no).delete()

コードの部分だけで、重複したトークンを保存しないようにしていたわけですが、トラフィックが増えてきて負荷が掛かると、ある時点で削除に失敗してしまい、
次からは削除しようにも、複数のデータが存在する事を示すエラーを出力して500 Internal Server Errorとなっていました。

この部分を次のように置き換えることで、トラフィックが増えて負荷が上がっても、エラーを起こすことなく最後まで負荷テストを終えることが出来るようになりました。

# ユーザの既存のトークンを取得
if UserToken.objects.filter(account_no=self.account_no).exists():
# トークンが既に存在している場合は削除する
UserToken.objects.filter(account_no=self.account_no).delete()
# トークン生成(メールアドレス + パスワード + システム日付のハッシュ値とする)
dt = timezone.now()
original_str = str(self.account_no) + dt.strftime('%Y%m%d%H%M%S%f')
hash_str = hashlib.sha1(original_str.encode('utf-8')).hexdigest() # utf-8エンコードしないとエラーになる
# トークンをデータベースに追加
token = UserToken.objects.create(
account_no=str(self.account_no),
token=hash_str,
access_datetime=dt)
try:
user_account_update = UserAccount.objects.get(account_no=self.account_no)
user_account_update.update_access_datetime()
except():
print("トークンを発行したユーザーの最終アクセス日時をアップデートできませんでした。")
return token

負荷テストの結果は以下の通りです。
f:id:tyusuke88:20191116214032p:plain

フレームワークを使わずにPythonでニューラルネットワークを作る。

以前、Udemyのゼロから作るニューラルネットワークという講座の最終課題として、PythonとNumpy、Pandasを使用して、
2013年のワシントンD.C.におけるバイクレンタル数のデータをニューラルネットワークに学習させて、予測するモデルを
作成するという課題を行ったときの事を書きます。

作成したコードをGoogle Colabで公開しています。
自作ニューラルネットワーク

作成当初、入力数値を標準化するという考えがなく入力データをそのまま使用していたため、
シグモイド関数がサチュレートしてしまい、うまく計算できないという現象に出くわして、右往左往していました。

結局いろいろ調べて、入力に用いる値は標準化する事や、One-Hot Encodingするべき値などについて理解して、ニューラルネットワークの自作に成功しました。

今回作成したものは、CNNなどの畳み込み処理を行っていないことや、入力層と隠れ層、出力層の3層のみでディープニューラルネットワークではない事から、
LeRU関数ではなく、古典的なシグモイド関数を使用しました。

以下のスクリーンショットが、教師データ以外のデータから、その日に貸し出されるだろうと予測されたレンタルバイクの台数と、
実際の貸し出されたレンタルバイクの台数を比較したものです。
f:id:tyusuke88:20191112152132p:plain

適合はまずまずのようです。

最後に、学習を進めていくうちに、訓練データでの精度は上がり続けるにもかかわらず、訓練データに使用していない検証用データでの精度が
落ち始めて、過学習が進んでいく過程を含めた全体のエラー率(学習モデルの誤差)をグラフにしたものです。

f:id:tyusuke88:20191112153222p:plain
訓練の過程

Djangoで作ったライセンス認証サーバーの負荷試験をしてみた。

先日DjangoのRestFrameworkを活用して作成した、ライセンス認証サーバーがどれくらいの負荷に耐えられるのか自分の中で自信がなかったため、
負荷試験を行う方法を探していたのですが、どれも設定が難解だったり、GETリクエストの負荷試験の方法は分かっても、
POSTリクエストを使用した負荷試験の方法は分からなかったりあまり使い勝手がいいものが見つからない中、これであればというものが見つかりました。
app.loadimpact.com

あらかじめ想定されるアクセスの内容を記述しておき、必要であれば待機時間を設けて複数の内容で負荷試験を行うものです。
今回は、前回作成したEAのライセンス認証サーバーを想定して、複数のアカウントを異なるVU(仮想ユーザーと呼ぶようです)からアクセスした状態を再現して、
返って来るレスポンスの内容が、予め指定されたものと同一かどうかで成功・失敗を判定します。
以下のスクリーンショットのテスト内容では、5VUs(5人の仮想ユーザー)で60秒間テストし、10VUsに増やして60秒間、20VUsに増やして60秒間、30VUsに増やして60秒間、
f:id:tyusuke88:20191109134014p:plain
後は、同じように、30VUs→20Vus→10VUsと60秒ごとに負荷を減らしていき、結果を見るというものです。

ライセンス認証サーバーにはAWSのt2.microインスタンスを使用しているため、そもそも30VUsには同時には耐えられないと思いますが、一応実行してみます。
f:id:tyusuke88:20191109135649p:plain
結果のグラフを見ると、設定ファイルで設定したよりは細かいステップでVUs(仮想ユーザー)を増やしているようです。
そして、仮想ユーザーがピークの30VUsに達した後に、サーバーからのレスポンスがステータス500を返すようになり、最後までそのままで終了しました。

仮想ユーザーが減少しても、サーバーエラーが戻らないのには、ロジック上の改善点がまだあるのかも知れませんが、t2.microでは、
同時に30人のユーザからアクセスがあった場合には、応答しきれずサーバーがダウンしてしまう様子が確認出来ました。

MT4のEA向け簡易ライセンス認証サーバーをDjangoで作ってみた

django rest frameworkを使用して、EAのライセンス認証サーバーを作ってみました。
構造はシンプルで、予め管理者画面で使用を許可するEAのアカウント番号を登録しておき、
エンドポイントURLにペイロードとしてJSON形式でアカウント番号を送信して、
データベースの一覧にあればステータス200を返す、無い場合はそれ以外を返すというものです。

実際にはEAのアカウント番号を用いてPOSTリクエストした際に応答としてトークンを返信しているのですが、
このトークンは実装しただけで未使用です。
トークンを利用すると、ライセンス認証サーバーから帰ってきたトークンに制限時間を設けて、時間切れになったら再度トークンを発行するといった感じで、
時間的に細かい使い方が出来るようになります。

EA→ライセンス認証サーバー→データベース

データベースの一覧にアカウント番号があった場合は、
ライセンス認証サーバー→トークン(EAへ)

データベースの一覧にアカウント番号が無かった場合は、
ライセンス認証サーバー→403エラー

といった具合に、応答を変えて自分自身が接続しているアカウント番号がライセンス認証サーバーに登録されているかどうか確認するというものです。

ただ、内容が暗号化されていない事とトークン自体が適切なものかを判別するロジックがないため、簡易版といった状態です。

今後、正規のライセンス認証サーバーから帰った来たトークンかどうかを判別できるようにしてもう少しセキュリティーを向上させようと思います。

モンティホール問題 その3 解説編 後編

モンティホール問題の解説の後編を続けます。

前回、場合の数を数えてそれらを列挙しその中から「プレイヤーが最初に選択したドア」から、「モンティが選ばなかったドア」に変更し、かつ賞品のあるドアを開けるというパターンを数えました。

f:id:tyusuke88:20161008154517p:plain

これが全ての場合の数でその数は8通り。

その内、プレーヤーがドアを変更するのが4通り。

そしてドアを変更して開いて当たりを引くのが2通りです。

ただ、これにはやはり落とし穴があります。

確率を計算するさいには、全てのあり得るパターンを挙げてかつ、その全てのパターンが「同様に確からしい」場合のみ、その中で該当するパターンの数を数えて全てのあり得るパターンの数で割る事で確率をそのまま計算できます。

ところが、最初のプレーヤーが当たりを引く確率は3/1のわけです。

であれば、最初に当たりをひいた場合の(ベイズ統計でいうところの事後確率)はそれ以降、全て1/3をかけて見積もる必要があるのです。

f:id:tyusuke88:20161008155348p:plain

この樹形図でプレーヤーが選択を変更した場合を全て左に揃えて書いてあります。

プレーヤーが選択を変えた部分に注目して下さい。

プレーヤーが選択を変えてはずれを引くのは2/12(=1/6)ですが、
プレーヤーが選択を変えて当たりを引くのは4/12(=2/6)

つまり、当選確率は倍になるというわけです。

ちなみに
プレーヤーが選択を変えずにはずれを引く確率は4/12(=2/6)
プレーヤーが選択を変えてはずれを引く確率は2/12(=1/6)

ですから、プレーヤーが選択を変えなければ当選する確率は半分に下がってしまいます。

一応、「常識的な前提」が紛れ込んでいますが、その「常識的前提」が正しいとすればこのような結論になります。

もしその「常識的前提」が間違っていれば答えは上記のようにならず、場合によっては1/2になることもあります。

以上、モンティホール問題の自分なりの説明でした。


この一連の連載は、モンティホール問題を理解する過程をなぞって確かにする目的もありますが、わかりやすく説明するという目標を達成できたかも気になります。

また、この問題は有名ですの既に知っている方もいらっしゃるかも知れません。

似たような問題に日本で有名なものには3囚人問題がありますので、その亜型ともいえます。

私は統計を専門としておりませんし、統計などに精通した方などで既にご存じの方であれば鼻で笑われてしまうかも知れません。

ただ、当時その場に居合わせたらどうか・・・

そのような想像力を働かせて経緯を追っていくことで驚きを得られることもあります。

そして忘れないように、どんなときにも驚きを感じ取る能力を・・・

また、この手の話しが苦手であり、かつここまで読んで下さった方に対しては、好奇心の豊さと忍耐強さという特質を大切に、これからも頑張って下さい。

また、上記の常識的前提について引っかかる方は、後日また時間のあるときにでも以前紹介したシミュレーターを使って確率にどのような変化が出るか、試して見たいと思います。