SqlAlchemyのコネクションプーリングの効果比較
コネクションプーリングのあり・なしを比較してみた。
それぞれ、 psycopg2
と pg8000
で確認してした。
結果は、コネクションプーリングなしが 280~310倍遅いということになったが、それ以上に今回の測定では pg8000
が若干早いという結果になった。
環境
- Ubuntu 18.04 LTS ... WSL1 (Windows 10)
- libpq ... 13.2
- Python 3.8
- SQLAlchemy 1.4.6
- pg8000 1.19.1(Pure Python)
- psycopg2 2.8.6 (Nativeライブラリを使用)
測定方法
測定方法は、参考1のやり方と合わせ1多重で1000回の接続・切断を行う。
ただし、コードの書き方は厳密に合わせはしない。なるべく今風の書き方をする。
なお、SqlAlchemyではデフォルトで QueuePool
というものを使う。
コネクションプーリングを明示的に使わないようにするには NullPool
を使う。
趣味の範囲なので、複数測った平均だとかはとらず、一発取り。1000回の試行の合計なので、それだけでバラつきは減る。プログラムの調整で複数回は実行するがバラつきはおよそ±3秒の範囲。
結果(概要)
参考にしたサイトだと 270 倍だったが、同じような水準になった。
なお、PCが貧弱で、かつ WSL1 を使っているので遅い面はあるので、相対値のみ参考にしてください。
(注: 1000で割った数が1コネクションあたりの時間になります。)
pg8000 | psycopg2 | |
---|---|---|
QueuePool | 0.078 [s/1000conn] | 0.094 [s/1000conn] |
NullPool | 21.747s [s/1000conn] | 28.930 [s/1000conn] |
Ratio | 279倍 | 307倍 |
検証結果から言えること
- コネクションにかかるコストは相対的に大きい
1コネクションでみると一番遅いパターンでも 0.028 秒なので体感して遅い訳ではない - pg8000(Pure Python) より、psycopg2(ネイティブクライアント使用)のが若干遅い
検証結果に対する補足
- psycopg2 の方が遅いのは、この計測の場合 Python → C の呼び出しに関わるオーバヘッド(メモリ確保やデータ変換などの橋渡し)の影響が大きく出るのではないかと思われる
- 常駐プログラムでない場合は NullPool相当となる
- その都度処理が終わるなら当然コネクションの使いまわしができないから
検証コード
共通化することはできるのだが、まぁいいや。
pg8000 + NullPool
# -*- coding: utf-8 -*-
"""Example of postgresql+pg8000."""
import os
import time
import sqlalchemy
def main(engine):
"""Run main."""
t_0 = time.time()
for _ in range(1000):
with engine.connect():
pass
t_1 = time.time()
print(f'dt = {(t_1 - t_0):.3f}s')
if __name__ == '__main__':
engine_pg8000 = sqlalchemy.create_engine(
sqlalchemy.engine.URL.create(
'postgresql+pg8000',
host=os.environ.get('PGHOST'),
port=os.environ.get('PGPORT'),
database=os.environ.get('PGDATABASE'),
username=os.environ.get('PGUSER'),
password=os.environ.get('PGPASSWORD')
),
poolclass=sqlalchemy.pool.NullPool
)
main(engine_pg8000)
engine_pg8000.dispose()
# EOF
pg8000 + QueuePool(default)
# -*- coding: utf-8 -*-
"""Example of postgresql+pg8000."""
import os
import time
import sqlalchemy
def main(engine):
"""Run main."""
t_0 = time.time()
for _ in range(1000):
with engine.connect():
pass
t_1 = time.time()
print(f'dt = {(t_1 - t_0):.3f}s')
if __name__ == '__main__':
engine_pg8000 = sqlalchemy.create_engine(
sqlalchemy.engine.URL.create(
'postgresql+pg8000',
host=os.environ.get('PGHOST'),
port=os.environ.get('PGPORT'),
database=os.environ.get('PGDATABASE'),
username=os.environ.get('PGUSER'),
password=os.environ.get('PGPASSWORD')
),
pool_size=1,
max_overflow=1
)
main(engine_pg8000)
engine_pg8000.dispose()
# EOF
psycopg2 + NullPool
# -*- coding: utf-8 -*-
"""Example of postgresql+psycopg2."""
import os
import time
import sqlalchemy
def main(engine):
"""Run main."""
t_0 = time.time()
for _ in range(1000):
with engine.connect():
pass
t_1 = time.time()
print(f'dt = {(t_1 - t_0):.3f}s')
if __name__ == '__main__':
engine_psycopg2 = sqlalchemy.create_engine(
sqlalchemy.engine.URL.create(
'postgresql+psycopg2',
host=os.environ.get('PGHOST'),
port=os.environ.get('PGPORT'),
database=os.environ.get('PGDATABASE'),
username=os.environ.get('PGUSER'),
password=os.environ.get('PGPASSWORD')
),
poolclass=sqlalchemy.pool.NullPool
)
main(engine_psycopg2)
engine_psycopg2.dispose()
# EOF
psycopg2 + QueuePool(default)
# -*- coding: utf-8 -*-
"""Example of postgresql+psycopg2."""
import os
import time
import sqlalchemy
def main(engine):
"""Run main."""
t_0 = time.time()
for _ in range(1000):
with engine.connect():
pass
t_1 = time.time()
print(f'dt = {(t_1 - t_0):.3f}s')
if __name__ == '__main__':
engine_psycopg2 = sqlalchemy.create_engine(
sqlalchemy.engine.URL.create(
'postgresql+psycopg2',
host=os.environ.get('PGHOST'),
port=os.environ.get('PGPORT'),
database=os.environ.get('PGDATABASE'),
username=os.environ.get('PGUSER'),
password=os.environ.get('PGPASSWORD')
),
pool_size=1,
max_overflow=1
)
main(engine_psycopg2)
engine_psycopg2.dispose()
# EOF