In this lesson, you will learn how to implement two distinct layers of ORM caching to eliminate redundant database round-trips and minimize query overhead.
When working with ORMs, the cost of "hydrating" objects from database rows is non-trivial. To optimize this, we categorize caching into two primary scopes: the First-Level (L1) and Second-Level (L2) cache.
First-Level (L1) Cache: The Session Scope
The L1 cache is mandatory and typically enabled by default in most ORMs. It acts as an identity map within the lifecycle of a single database transaction or "Session." When you request an entity by its ID, the ORM checks this internal map first. If the entity has already been fetched during the current transaction, the ORM returns the existing object instance instead of issuing a new SELECT statement.
# Conceptual L1 caching
session = Session()
user1 = session.get(User, 1) # SQL executed
user2 = session.get(User, 1) # No SQL; returns memory reference of user1
Second-Level (L2) Cache: The Global Scope
The L2 cache is optional and sits above the session. It persists data across multiple sessions, threads, or even different application instances. When a session queries an entity, it looks in the L1 cache; if it misses, it checks the L2 cache (often backed by Redis or Memcached). Only if both caches miss does the ORM fire a query to the database. Once the data is retrieved, it is stored in the L2 cache for future sessions to access.
Balancing Consistency and Performance
While L2 caching significantly reduces database load, it introduces a "stale data" risk. If an external process modifies the database directly, the L2 cache may remain unaware of the change. Therefore, you must configure eviction policies (e.g., Time-to-Live or LRU) to ensure that the cached state reflects the source of truth.
When implementing these strategies, always start with L1 to solve local object graph consistency, and only introduce L2 when your read-heavy workload triggers excessive database latency that cannot be mitigated by connection pooling or query optimization alone.
Key Takeaway: L1 caching ensures object identity within a transaction, while L2 caching provides cross-session reuse to drastically reduce redundant query execution.