🐨

Azure SQL Databaseへのアクセスが稀に500エラーとなるので原因調査

2024/02/16に公開1

環境

・App Service(FastAPI製のアプリケーション)
・SQL Database

通信・認証方法

通信...プライベートエンドポイントとVPN接続からのみのアクセス
認証...ODBCドライバーを使ったAAD認証

発生したエラー

以下のエラーが稀に発生します。
タイミングとしては、最後にAPIリクエストをしてからだいぶ時間が経った後にAPIリクエストをすると発生するようです。

(pyodbc.OperationalError) ('08S01', '[08S01] [Microsoft][ODBC Driver 18 for SQL Server]TCP Provider: Error code 0x68 (104) (SQLExecDirectW)')\n[SQL: SELECT *]\n[parameters: ('',)]\n(Background on this error at: https://sqlalche.me/e/20/e3q8)

厄介なのは、最初の1,2回は下記のエラーが発生しますが、その後は処理が成功してレスポンスが返ってくることです。
認証が間違ってるのなら毎回エラーが出ますし、問題なく処理が通っているのであれば最初だけエラーが出るのが謎です。

タイムアウトがちゃんと設定されてる?

接続文字列を確認してみましたが、「Connection Timeout=60」で設定できてました。
おそらくタイムアウト以前にすぐエラーになっているようです。

同様のエラーが発生してる人がいるか調査

Microsoftのドキュメントに質問している人がいました。
ここに書かれている内容をもとに修正していきます。

https://techcommunity.microsoft.com/t5/azure-database-support-blog/lesson-learned-359-tcp-provider-error-code-0x68-104/ba-p/3834127

修正

データベースとの接続をしているファイル内に追記していきます。

  1. pyodbcの「pooling」がデフォルトでTrueなのでFlaseに変更
  2. create_engineメソッド内に「pool_recycle」を1500に設定
database.py
import pyodbc

pyodbc.pooling = False
engine = create_engine(database_url, echo=False, pool_recycle=1500)

結果

エラーが発生することがなくなりました。
無事解決したようです!

原因

そもそもプーリングって何だ?

プーリングは、複数のデータベース接続を管理するための機能で、接続を再利用することでパフォーマンスを向上させることができます。

通常、データベースに接続する際にはネットワーク接続を確立し、認証を行い、接続を初期化するなどのコストがかかります。しかし、プーリングを使用すると、一度確立した接続をプール(接続の集合)に保持し、再度接続する際にはそのプールから接続を取り出して再利用することができます。これにより、上記のコストを削減し、パフォーマンスを向上させることが可能です。

じゃあ何でここが原因だった?

SQLAlchemyとPyODBCの両方がプーリングを提供していて、これらが同時に動作すると予期しない問題を引き起こす可能性」があります。
そのため、片方のプーリング機能は無効にした方がいいとのことです。

PyODBC側のプーリングを無効にすることで、SQLAlchemyのプーリングだけが有効になり、競合によるエラーが発生しなくなります。

最後に

これまでもSQL Databaseはよく使っていましたが、このエラーが発生したのは今回が初めてでした。
認証方法によって発生するのか、リージョンによるものなのか、SQLのプランによるものなのかはこれから調査していきたいと思います。

ヘッドウォータース

Discussion