Hello!
I want to cache query results in a distributed cache (Redis), so I need to serialize/deserialize fetched entities very fast.
However, it turned that SQLAlchemy entities are very heavy for pickle to dump/load. Regular classes and dicts with the same structure takes significantly less amount of time to reconstruct from bytes.
It's even slower than fetching and mapping the entities from a database.
Here are benchmark results, loading 10000 simple entities:
class Entity(Base):
__tablename__ = 'test_entity'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
field1 = sa.Column(sa.String)
field2 = sa.Column(sa.Integer)
Name (time in ms) Median StdDev Dump bytes
--------------------------------------------------------------------------------------------------------
test_unpickle_dicts 2.3764 (1.0) 0.0797 (1.09) 337806
test_unpickle_dataclasses 9.2546 (3.89) 0.0734 (1.0) 437861
test_unpickle_classes 9.4188 (3.96) 0.1230 (1.68) 437862
test_load_from_database 91.3482 (38.44) 0.6686 (9.10)
test_unpickle_unsaved_entities 108.6726 (45.73) 0.5154 (7.02) 1448169
test_unpickle_persisted_entities 166.4531 (70.04) 0.3787 (5.16) 1697849
---------------------------------------------------------------------------------------------------------
Environment:
OS: Ubuntu 16.04
Python: CPython 3.6.3
SQLAlchemy: 1.3.0
Database: in-memory SQLite, PostgreSQL (all gives similar results)
The full benchmark code is in a single attachment file.
To run:
pytest test_ser.py --benchmark-columns=median,stddev --benchmark-disable-gc
Requirements:
pip install sqlalchemy==1.3.0 pytest==3.6.0 pytest-benchmark==3.1.1 dataclasses==0.6
Questions:
1) What strategy for caching I should consider while using SQLAlchemy? Currently, the only option I see is to have a duplicated declaration of entities in a form of simple classes and use it when I don't need modification. Needles to say, it's a lot of code duplication.
2) Is it possible to have a read-only loader in SQLAlchemy? I can imagine mapping loaded data to an entity class but without instrumentation overhead which is not needed to read the data.
If continue the idea further, I can imagine a situation when I load the entity for modification, and it has a read-only relationship to another entity. So that I don't pay for things I don't use.
Probably I'm making some crazy things and all wrong, feel free to point me out. My ultimate goal is to manage caching with SQLAlchemy and understand SQLAlchemy more in this direction.
Thank you in advance!