Beyond SQLite: Supercharging SQLAlchemy with a Pure In-Memory Dialect

Posted on Sat 10 May 2025 in Software Architecture

Introduction

SQLAlchemy has long been the go-to ORM for Python developers. Over the years, it has evolved significantly, introducing major performance and architectural improvements from versions 1.3 to 2.0. In this post, we explore how these changes affect in-memory workloads and introduce a new, purpose-built pure-Python in-memory dialect that can dramatically accelerate your SQLAlchemy-based backtests and simulations.

Experiment Setup

To evaluate performance, we use a realistic scenario: a simple trend-following strategy on intraday futures data.

Backtest Details:

  • Event-driven architecture (no vectorization)
  • Time range: 2025-01-01 to 2025-03-01
  • Instrument: ES (E-mini S&P 500) RTH, 1-minute bars (~15,000 bars)
  • Entry logic:
    • Go long after 30 consecutive closes above VWAP
    • Go short after 30 consecutive closes below VWAP
  • Exit logic: static stop-loss and take-profit

Hardware: Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz with 16GB RAM

Note: I intentionally run benchmarks on weaker hardware to expose performance weaknesses and bottlenecks more clearly. Caching is also disabled to stress-test the backtester’s database access pattern (database is queried for open orders/positions on every bar).

Backtesting with SQLAlchemy 1.3 + In‑Memory SQLite

Version 1.3 represents the pre-2.0 SQLAlchemy world, relying heavily on explicit Core/ORM separation and without statement caching.

As expected, performance was the slowest in this configuration.

Backtest runtime: 79.2 seconds

Backtesting with SQLAlchemy 1.4 + In‑Memory SQLite

SQLAlchemy 1.4 was a transitional release, introducing many of the architectural concepts of 2.0. Statement caching and streamlined ORM execution show significant performance benefits here.

Backtest runtime: 19.8 seconds

Backtesting with SQLAlchemy 2.0 + In‑Memory SQLite

Version 2.0 completes the modernization of SQLAlchemy. Unified ORM/Core APIs, native asyncio support, and an overall cleaner execution model reduce boilerplate and runtime overhead even in sync codebases.

Backtest runtime: 17.9 seconds

Introducing sqlalchemy‑memory

Now let's push beyond SQLite.

sqlalchemy-memory is a pure-Python dialect for SQLAlchemy that implements the full ORM and Core interface in memory.

Why I Built It

In production systems, models like Order, Position, and Trade are stored in a persistent database. But during backtesting, hitting a SQL engine (even SQLite in-memory) adds overhead you don't want. Rewriting your code to use custom classes or lists during backtests is brittle, and maintaining two versions of the logic is error-prone.

sqlalchemy-memory bridges that gap. It allows you to:

  • Use your real SQLAlchemy models and queries

  • Avoid any SQL driver overhead

  • Run backtests significantly faster

Backtest runtime: 6.4 seconds

Conclusion

Here are the results side-by-side:

Backtest Type Duration
SQLAlchemy 1.3 + in-memory sqlite 79.2s
SQLAlchemy 1.4 + in-memory sqlite 19.8s
SQLAlchemy 2.0 + in-memory sqlite 17.9s
SQLAlchemy 2.0 + memory dialect 6.4s

While SQLAlchemy 2.0 has brought impressive speedups, removing SQL parsing and I/O entirely through a custom memory dialect provides a dramatic boost for performance-critical simulations. You can view the official benchmark here.

Happy coding !