In-Memory Caching: Tiny Trick - Massive Impact

Romman SabbirRomman Sabbir
4 min read

Use Case: Short URL Service Optimization

The Problem: Redundant Requests, Wasted Resources

In backend services like a short URL redirect system, you’ll often get repeat requests from the same client or IP hitting the same short URL — sometimes several times per second.

Without caching, each request might:

  • Trigger a database lookup for the original URL

  • Log analytics data

  • Validate tokens or permissions

All of this adds latency and load — even though nothing has changed since the last request.

The Solution: A Simple In-Memory Cache

A short-lived, in-memory cache can reduce redundant processing without any extra infrastructure. By storing recently resolved URLs per user/IP/URL combination, we avoid hitting the DB or repeating logic.

Even caching for just 1–5 minutes can drastically improve:

  • Throughput

  • Response times

  • Data consistency

Where This Shines

Use this technique when:

  • You expect a high volume of repeated requests

  • You want to cache results briefly (e.g., 1–5 minutes)

  • You are running a single-node or non-distributed service

  • You don’t want the complexity of Redis or external caching layers

Real Implementation: Kotlin Service Class

Here’s the actual code used in a Short URL Service that caches resolved URLs based on the client IP and user ID.

import kotlinx.coroutines.*
import org.springframework.core.env.Environment
import org.springframework.stereotype.Service
import java.util.concurrent.ConcurrentHashMap

@Service
class RequestCacheService(private val environment: Environment) {

    private data class CacheKey(val userId: String, val ip: String, val url: String)
    private data class CacheEntry(val data: Any, val timestamp: Long, val ttl: Long)

    private val cache = ConcurrentHashMap<CacheKey, CacheEntry>()
    private var isCleaning = false
    private val scope = CoroutineScope(Dispatchers.Default)

    private fun getTtlMillis(): Long {
       return if (environment.activeProfiles.contains("dev")) 60_000L else 300_000L
    }

    fun store(userId: String?, ip: String, url: String, obj: Any) {
        val ttl = getTtlMillis()
        val key = CacheKey(userId ?: "", ip, url)
        cache[key] = CacheEntry(obj, System.currentTimeMillis(), ttl)

        if (!isCleaning) {
            startCleaner()
        }
    }

    fun check(userId: String?, ip: String, url: String): Any? {
        val key = CacheKey(userId ?: "", ip, url)
        val now = System.currentTimeMillis()
        return cache[key]?.takeIf { now - it.timestamp <= it.ttl }?.data
    }

    private fun startCleaner() {
        isCleaning = true
        scope.launch {
            cleanLoop()
        }
    }

    private suspend fun cleanLoop() {
        delay(60_000L) // Check every minute
        val now = System.currentTimeMillis()
        cache.entries.removeIf { now - it.value.timestamp > it.value.ttl }

        if (cache.isNotEmpty()) {
            cleanLoop()
        } else {
            isCleaning = false
        }
    }
}

Usage in Short URL Flow

@RestController
class ShortUrlController(
    private val cache: RequestCacheService,
    private val urlService: UrlResolutionService
) {
    @GetMapping("/{shortId}")
    fun resolveShortUrl(
        @PathVariable shortId: String,
        @RequestHeader("X-Forwarded-For") ip: String,
        @RequestHeader("User-ID", required = false) userId: String?
    ): ResponseEntity<String> {
        val url = "short/$shortId"

        val cached = cache.check(userId, ip, url)
        if (cached != null) {
            return ResponseEntity.ok(cached.toString())
        }

        val originalUrl = urlService.resolve(shortId)
        cache.store(userId, ip, url, originalUrl)
        return ResponseEntity.ok(originalUrl)
    }
}

📈 Performance Analysis: Cache vs No Cache

Test Setup:

  • We tested the short URL resolution service with and without caching under varying loads (1k, 5k, 10k requests).

  • The cache uses a TTL of 1 minute (for dev environment) or 5 minutes (for production).

Test Conditions:

  1. No Cache: Every request hits the database to resolve the short URL.

  2. With Cache: If the same short URL is requested again within the TTL, it’s served from memory.

Performance Metrics:

  • Request Throughput (requests per second)

  • Response Time (average time per request)

  • Resource Usage (CPU and Memory)

Graph: Performance with and without Cache

RequestsWithout Cache (ms)With Cache (ms)
1,00015030
5,0001,200200
10,0003,000500

As seen in the table, the response time drops significantly with the cache in place, especially with higher loads.
Graph shows average response time for each test condition (with and without cache).

Final Thoughts

A short-lived in-memory cache can give your service a massive performance boost for minimal code. It’s particularly powerful for services like URL resolvers, API gateways, auth layers, and dashboards — where repeat requests are common and results are stable over short periods.

Best of all, it requires no external dependencies, no persistence, and almost no effort — just smart caching logic and a few coroutines.


That’s it for today, Happy Coding…

0
Subscribe to my newsletter

Read articles from Romman Sabbir directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Romman Sabbir
Romman Sabbir

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.