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')
        # utf-8でエンコード
        hash_str = hashlib.sha1(original_str.encode('utf-8')).hexdigest() 
        # トークンをデータベースに追加
        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
</span>

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