GAE/py + SQLAlchemyで "2062, Cloud SQL socket open failed"


エラー遭遇

Google AppEngine Standard Environmentにデプロイしている、
pythonのサーバからCloud SQLへSQLAlchemyを用いて接続していましたが、
単発での接続では問題がなかったのに、連続稼働していると、以下のようなエラーが起きました。

OperationalError: (_mysql_exceptions.OperationalError) (2062, 'Cloud SQL socket open failed with error: Transport endpoint is not connected')

SQLAlchemyのエラーはSQLAlchemyのエラー回避備忘録などが大変参考になりましたが、該当するエラーはありませんでした。
というか、Cloud SQLと言っているのでGCP上でのエラーと思われます。

発生箇所はConnectionを貼るところでした。
taskを分割して別スレッドで連続して作業していると、突然コネクションが貼れなくなるようです。

こんな感じでした。

解決方法

検索するとすぐに出てくるのですが、とても簡単な話で、SQLサーバへの接続がスレッドごとにきちんとcloseされていないためでした。

コメントが違うエラーでしたが中身は同じで、12個以上のコネクションを同時に張ってはいけないようです。
FAQにもちゃんと記載がありました。

スタンダード環境で動作する各 App Engine インスタンスは、Google Cloud SQL インスタンスに対する同時接続数が最大 12 個に制限されます。

SQLAlchemyでの切断

SQLAlchemyで、query等にSessionを使っていました。
sessionオブジェクトにもcloseがあるのですが、これは厳密にはConnectionと対応していないようで、
sessionをcloseしてるにもかかわらず、相変わらずエラーが起きていました。

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

engine = create_engine(url, encoding='utf8', echo=False)
Session = sessionmaker(bind=engine)
session = Session()
...
# ちゃんとcloseしていてもエラーが起きる
session.close()

SQLAlchemyではConnectionのPoolが使用されていて、connectionを再利用するため?にpool内で接続が維持されるようです。

上のスレッドで紹介されていましたが、単発で使って、別スレッド等でコネクションを共有されないような場合ではpoolをdisableにすれば、毎回connectionがcloseされるようです。
disableにする最も簡単な方法はengineを作るときにNullPoolを指定することです。

from sqlalchemy.pool import NullPool
# poolclassにNullPoolを指定するとpoolがdisableになる
engine = create_engine(url, poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
...
# エラーが起きなくなった!
session.close()

他にも方法があるかと思いますが、今回はこのような対処をしました。
SQLAlchemyを使っていれば常識なのかもしれませんね、、以上です。