Guide to Implementing Caching for Optimized Typesense Search Performance
Caching is a vital strategy to optimize the performance and reduce the load on your Typesense search engine. By caching frequently requested search results, you can minimize response times and improve the overall user experience. Here’s a comprehensive guide on implementing caching for Typesense:
Why Cache?
- Reduced Latency: Caching reduces the need to perform expensive operations repeatedly, thereby decreasing response times.
- Load Reduction: Reduces the number of requests hitting your Typesense server, making it more scalable and resilient.
- Improved User Experience: Faster responses lead to a smoother and more responsive application.
Implementing Caching in Typesense
1. Choose a Caching Method and Store
You can use libraries such as node-cache
for in-memory caching in a Node.js application, or use more robust solutions like Redis for distributed caching.
a. In-Memory Caching with node-cache
Install node-cache:
npm install node-cache
Initialize and configure the cache:
const NodeCache = require('node-cache');
const searchCache = new NodeCache({ stdTTL: 300, checkperiod: 320 }); // 5 minutes TTL
b. Distributed Caching with Redis
Install Redis client for Node.js:
npm install redis
Initialize and configure the Redis client:
const redis = require('redis');
const client = redis.createClient();
2. Cache Search Results
You can wrap your search logic to check the cache before hitting Typesense and cache the results afterward.
a. Using node-cache
const performSearch = async (query, queryBy) => {
const cacheKey = `${query}:${queryBy}`;
const cachedResult = searchCache.get(cacheKey);
if (cachedResult) {
console.log('Serving from cache');
return cachedResult;
}
const result = await client.collections('books').documents().search({
q: query,
query_by: queryBy
});
if (result) {
searchCache.set(cacheKey, result);
}
return result;
};
b. Using Redis
const performSearch = async (query, queryBy) => {
const cacheKey = `${query}:${queryBy}`;
// Check Redis cache
const cachedData = await new Promise((resolve, reject) => {
client.get(cacheKey, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
if (cachedData) {
console.log('Serving from Redis cache');
return JSON.parse(cachedData);
}
// Perform search query
const result = await client.collections('books').documents().search({
q: query,
query_by: queryBy
});
// Store in Redis cache
if (result) {
client.set(cacheKey, JSON.stringify(result), 'EX', 300); // Expire in 5 minutes
}
return result;
};
3. Cache Invalidations
To ensure the cache doesn't serve stale data:
- TTL (Time To Live): Set an appropriate TTL for cache entries.
- Manual Invalidation: Invalidate the cache when data changes significantly.
- Cache Busting: Add versioning or timestamps to cache keys if the underlying data changes frequently.
4. Monitoring and Metrics
Monitor the hit/miss ratio of your cache to understand its effectiveness:
- Hit Rate: Percentage of requests served from the cache.
- Miss Rate: Percentage of requests served by making a new call to Typesense.
For node-cache
console.log(searchCache.getStats());
For Redis
Use Redis commands to monitor performance:
redis-cli info stats
Example Application Structure
Initialization (Setup Typesense and Redis)
// typesenseClient.js
const Typesense = require('typesense');
const typesenseClient = new Typesense.Client({
nodes: [{ host: 'localhost', port: '8108', protocol: 'http' }],
apiKey: 'xyz',
connectionTimeoutSeconds: 2
});
module.exports = typesenseClient;
// redisClient.js
const redis = require('redis');
const redisClient = redis.createClient();
redisClient.on('error', (err) => {
console.error('Redis error: ', err);
});
module.exports = redisClient;
Performing Search
// searchService.js
const typesenseClient = require('./typesenseClient');
const redisClient = require('./redisClient');
const performSearch = async (query, queryBy) => {
const cacheKey = `${query}:${queryBy}`;
const cachedData = await new Promise((resolve, reject) => {
redisClient.get(cacheKey, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
if (cachedData) {
console.log('Serving from Redis cache');
return JSON.parse(cachedData);
}
const result = await typesenseClient.collections('books').documents().search({
q: query,
query_by: queryBy
});
if (result) {
redisClient.set(cacheKey, JSON.stringify(result), 'EX', 300); // Expire in 5 minutes
}
return result;
};
module.exports = performSearch;
By implementing caching using either node-cache
for simple use cases or Redis for more complex scenarios, you can dramatically improve the performance of your Typesense-powered search in a Node.js application. This approach ensures quick response times and a smooth user experience.