Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Database Query Caching

Use database query caching when a repository result is expensive enough to reuse and the application can describe its identity and invalidation model.

The database layer remains visible. HydraCache does not parse SQL, infer table dependencies, install triggers, or replace SQLx, Diesel, SeaORM, or a repository layer.

    let local = HydraCache::local().build();
    let queries = DbCache::new(local, "db");

    let user = queries
        .entity::<User>("user", 42)
        .collection_tag("users")
        .ttl(Duration::from_secs(60))
        .fetch_with(|| async {
            // Replace this with SQLx, Diesel, SeaORM, or repository code.
            Ok::<_, std::io::Error>(User {
                id: 42,
                name: "Ada".to_owned(),
            })
        })
        .await?;

    assert_eq!(user.name, "Ada");

Before enabling a cached query, answer:

  1. What exact result does this key identify?
  2. Which write paths invalidate the tags?
  3. What TTL or stale behavior is acceptable if invalidation is delayed?
  4. How will hits, misses, loader calls, and invalidations be observed?

For SQLx, Diesel, and SeaORM integrations, keep the adapter helper small. Drop to fetch_with when a transaction, macro-shaped query, or repository method needs more control.

Policy Macro

Use query_cache_policy! when a query has stable metadata and repeated builder calls would obscure the key/tag contract.

    let user_id = 42_i64;
    let policy = query_cache_policy!(
        preset = read_mostly,
        name = "load-user",
        entity = User,
        id = user_id,
        refresh_ahead_secs = 10,
        stale_while_revalidate_secs = 300,
    );

    assert_eq!(policy.name(), Some("load-user"));
    assert_eq!(policy.key_value(), Some("user:42"));
    assert!(policy.refresh_policy_value().is_some());

    let search = query_cache_policy!(
        name = "search-users",
        key_segments = ["tenant", 7_u64, "q", "ada:lovelace", "page", 1_u32],
        tag_segments = [["tenant", 7_u64], ["users"]],
        ttl_secs = 30,
    );

    assert_eq!(search.key_value(), Some("tenant:7:q:ada%3Alovelace:page:1"));
    assert_eq!(
        search.tags_value(),
        &["tenant:7".to_owned(), "users".to_owned()]
    );

The macro is still explicit: it names the key source, tags, TTL or preset, and optional refresh behavior. It does not inspect SQL or infer invalidation.