Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
16 Sep 2025 · 6 min read ·Article 78 / 125
Go

78 Caching Query and Result Resolver

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

78 Caching Query and Result Resolver: Efficiency, Best Practices, and Real-World Implementation

By: [Your Name], Senior Software Engineer


Caching is one of the most effective strategies for improving system performance, especially in today’s increasingly real-time and high-traffic world. In this article, I’ll go into detail about query caching and result resolver caching: how they differ, when and how to implement them, and I’ll share best practices based on my experience across several scale-up projects. I’ll also include code examples, real-world simulations, and visualizations of the process using mermaid flow diagrams.


Understanding the Basics: Query Caching vs Result Resolver

Before diving into code and simulations, it’s crucial to understand the difference between the two:

1. Query Caching

Query caching stores the result of executing a specific query against a data source (usually a database). So when an identical query is run again, the cached result is returned without needing to access the database.

2. Result Resolver Caching

Result resolver caching is typically used at the business logic layer or within a data-fetching framework (such as GraphQL or DataLoader). Resolver caching optimizes data fetching across components or fields, avoiding repeated calls to the data source for the same entity.


Benefits of Both Caching Approaches

Query CachingResult Resolver Caching
AdvantageReduces DB load, fast response timeReduces duplicate fetches for the same data
ScaleOften used at the DB/server levelFramework/Business Logic Layer
DrawbackProne to stale data when a table changesRequires managing dependencies between fields

Case Illustration: An Article Recommendation System

Imagine an article recommendation system with a query like:

sql
1SELECT * FROM articles WHERE published = true ORDER BY recommended_score DESC LIMIT 10;
Or in GraphQL:
graphql
 1{
 2  articles(recommended: true) {
 3    id
 4    title
 5    author {
 6      id
 7      name
 8    }
 9  }
10}

Here:

  • Query caching stores the result of the entire article list.
  • Result resolver caching comes into play when the author field is called repeatedly.

Simulation Without Caching: The Author Bottleneck

Suppose we fetch 10 recommended articles. Each article fetches its author data. Without a resolver cache, that means 10 queries to the authors table.

Illustrative pseudo-code (without cache):

javascript
1const articles = await db.query('SELECT * FROM articles WHERE ...');
2const results = [];
3for (const article of articles) {
4  const author = await db.query('SELECT * FROM authors WHERE id = ?', [article.author_id]);
5  results.push({ ...article, author });
6}
7// Total = 1 (articles) + 10 (authors) = 11 queries


Simulation With Result Resolver Caching

With result resolver caching (for example, using DataLoader ), the system collects all unique IDs and then fetches the authors just once.

Pseudo-code with a resolver cache:

javascript
1const articles = await db.query('SELECT * FROM articles WHERE ...');
2const authorIds = articles.map(a => a.author_id);
3const authors = await loadAuthorsByIds(authorIds); // batch
4const results = articles.map(article => ({
5  ...article,
6  author: authors[article.author_id],
7}));
8// Total queries: 1 (articles) + 1 (all unique authors)


Simulation of Query Caching at the Database

What if this heavy recommended-articles query is called frequently (for example, on every homepage visit)? Enable query caching.

Query Caching flow diagram (Mermaid):

MERMAID
flowchart LR
  A[Client Request] --> B[Check Cache Layer]
  B -->|Cache Hit| C[Return Cached Result]
  B -->|Cache Miss| D[Process to DB]
  D --> E[Store in Cache]
  E --> C


Implementation: Query Caching (Node.js + Redis)

Let’s say the stack uses Express, PostgreSQL, and Redis as the cache.

A simple caching middleware:

javascript
 1const redis = require('redis').createClient();
 2
 3async function cacheMiddleware(req, res, next) {
 4  const cacheKey = "homepage:articles";
 5  const cachedResult = await redis.get(cacheKey);
 6  if (cachedResult) {
 7    return res.json(JSON.parse(cachedResult));
 8  }
 9  // Store on res.locals so the downstream handler can use it
10  res.locals.cacheKey = cacheKey;
11  next();
12}
13
14// Main handler
15app.get('/api/homepage', cacheMiddleware, async (req, res) => {
16  const articles = await db.query("SELECT ..."); // Heavy query
17  await redis.set(res.locals.cacheKey, JSON.stringify(articles), 'EX', 60); // TTL 1 minute
18  return res.json(articles);
19});

Advantages:

  • Only one heavy query per 1 minute / cache TTL.
  • Drastically reduces the load on the database.

Implementation: Result Resolver Caching (Node.js + DataLoader)

DataLoader is extremely popular for GraphQL and REST mediation.

javascript
 1const DataLoader = require('dataloader');
 2
 3const authorLoader = new DataLoader(async (authorIds) => {
 4  const rows = await db.query('SELECT * FROM authors WHERE id = ANY($1)', [authorIds]);
 5  // Build a map of id => author
 6  const map = new Map(rows.map(row => [row.id, row]));
 7  return authorIds.map(id => map.get(id));
 8});
 9
10// Inside the resolver/handler
11const articleIds = [...]; // result of the article query
12const authors = await authorLoader.loadMany(articleIds);

Best Scenario:

  1. A request comes in and fetches all article records.
  2. DataLoader collects all unique author_id values.
  3. Only 1 query is made to authors.
  4. The result is cached (per request — if you want it global, you can add a Redis layer on top).

Table: Query Performance Comparison

SchemeNumber of QueriesLatency (ms, simulated)Notes
No Caching11500Fetches author N times
Query Caching Only150Everything cached
Resolver Caching Only21201x articles, 1x authors
Both150Entire path cached

Case Study: Cache Invalidation

The hardest part of caching is invalidation. When a new article is added, you need to make sure neither the query cache nor the resolver cache holds stale data.

A simple approach:

  • For the query cache, use a short TTL (e.g., 1 minute).
  • For the result resolver, invalidate/clear the cache when the subject data (e.g., the author) changes.

Tips from the field:

  • For a short-lived resolver cache (per-request), no manual invalidation is needed.
  • For a larger query cache, always balance performance against data freshness.

Conclusion

Query and result resolver caching are the secret weapons in modern data-driven application development. By applying these two layers of caching, you can cut latency from hundreds of milliseconds down to tens of milliseconds — even under 10ms for repeated requests — without overloading the database.

Best-practice checklist:

  • Enable query caching for heavy queries that are called frequently.
  • Implement resolver caching for fields or entities that recur often in batches.
  • Always set up cache invalidation or a reasonable TTL.
  • Monitor cache hit-rate and latency in real time.

A carefully designed caching structure will bring your system close to 99% reliability and make it 10x more resource-efficient than brute-force querying.


Want to learn more?
Check out the source code and further discussion on Github , or DM me on Twitter for a consultation on caching architecture in your system.


Thanks for reading!


References


Happy caching, engineers! 🚀

Related Articles

💬 Comments