Google Cloud Memorystore: Managed Redis and Memcached for Low-Latency Caching
Database queries that take 50 ms are fine when they run once. They become a problem when they run 10,000 times per second for the same data. Caching moves frequently accessed, rarely changing data into memory so those reads return in under a millisecond instead of requiring a round-trip to the database.
Cloud Memorystore manages Redis and Memcached on GCP โ patching, replication, failover, and monitoring are handled by Google. You connect your application to an endpoint and use it exactly as you would a self-managed cache.
Redis vs Memcached: Choosing the Right Engine
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Redis โ Memcached โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ Rich data structures โ Simple key-value only โโ (strings, hashes, lists, โ โโ sets, sorted sets, streams) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ Optional persistence โ In-memory only (data lost on restart) โโ (RDB snapshots, AOF log) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ Replication (primary + โ Sharded across nodes, no replication โโ read replicas) โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ Pub/sub, Lua scripting, โ Multi-threading (better raw throughput โโ transactions (MULTI/EXEC) โ on multi-core machines) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโคโ Best for: session store, โ Best for: simple object caching, โโ leaderboards, rate limiting, โ CDN metadata, database query caching โโ job queues, pub/sub โ where data richness is not needed โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโFor most new applications, Redis is the better default. Its data structure richness covers more use cases, persistence options protect against data loss, and replication provides high availability.
Memorystore for Redis: Tiers and Capacity
Memorystore Redis has two tiers:
Basic tier: Single node, no replication. Lower cost. Suitable for development, test, and non-critical caches where losing cache data on a node failure is acceptable (the application falls back to the database).
Standard tier: Primary node plus at least one read replica in a different zone. Automatic failover โ if the primary fails, a replica is promoted with typically under 1 minute of downtime.
# Create a 5 GB standard-tier Redis instancegcloud redis instances create prod-cache \ --size=5 \ --region=us-central1 \ --zone=us-central1-a \ --redis-version=redis_7_0 \ --tier=standard \ --redis-config=maxmemory-policy=allkeys-lruThe maxmemory-policy controls what happens when Redis reaches its memory limit. allkeys-lru evicts the least recently used keys โ the standard choice for a cache. For session stores where losing data is worse than running out of memory, noeviction causes writes to fail rather than silently evicting data.
VPC-Native Access: No Public Endpoint
Memorystore instances do not have public IP addresses. They connect exclusively through VPC peering to your GCP projectโs network. This is a security feature โ your cache is not accessible from the internet.
Application (Cloud Run, GKE, GCE) โ (private VPC connection) โผMemorystore Redis endpoint: 10.0.0.3:6379 (internal IP, reachable only from within VPC)This means your application must be running inside GCP (or connected via VPN/Cloud Interconnect) to reach Memorystore. Serverless services like Cloud Run and Cloud Functions require a VPC connector configured to access the VPC subnet where Memorystore lives.
# Create a VPC connector for serverless access to Memorystoregcloud compute networks vpc-access connectors create cache-connector \ --region=us-central1 \ --subnet=my-subnet \ --subnet-project=my-project \ --min-instances=2 \ --max-instances=10
# Deploy Cloud Run service with VPC connectorgcloud run deploy my-service \ --image=gcr.io/my-project/my-app \ --vpc-connector=cache-connector \ --region=us-central1Common Caching Patterns
Cache-aside (lazy loading)
The application checks the cache first. If the data is not there (cache miss), it reads from the database and writes the result to the cache. On the next request, the data is found in cache.
import redisimport jsonimport psycopg2
cache = redis.Redis(host="10.0.0.3", port=6379)
def get_product(product_id: str) -> dict: cache_key = f"product:{product_id}"
# Try cache first cached = cache.get(cache_key) if cached: return json.loads(cached)
# Cache miss โ fetch from database conn = get_db_connection() cursor = conn.cursor() cursor.execute( "SELECT id, name, price, stock FROM products WHERE id = %s", (product_id,) ) row = cursor.fetchone() if not row: return None
product = {"id": row[0], "name": row[1], "price": row[2], "stock": row[3]}
# Store in cache with 5-minute TTL cache.setex(cache_key, 300, json.dumps(product)) return productWrite-through caching
Writes go to both the database and the cache simultaneously, keeping cache always fresh.
def update_product_price(product_id: str, new_price: float): # Update database cursor.execute( "UPDATE products SET price = %s WHERE id = %s", (new_price, product_id) ) conn.commit()
# Update cache cache_key = f"product:{product_id}" cached = cache.get(cache_key) if cached: product = json.loads(cached) product["price"] = new_price cache.setex(cache_key, 300, json.dumps(product))Redis Data Structures for Advanced Patterns
Session storage with hashes
# Store user session data as a Redis hashsession_id = "sess_abc123"cache.hset(f"session:{session_id}", mapping={ "user_id": "usr_001", "username": "alice", "role": "admin", "last_seen": "2025-03-15T10:30:00Z",})cache.expire(f"session:{session_id}", 3600) # 1 hour TTL
# Read specific session fieldrole = cache.hget(f"session:{session_id}", "role")Rate limiting with INCR and TTL
def check_rate_limit(user_id: str, limit: int = 100) -> bool: key = f"rate:{user_id}:{int(time.time() // 60)}" # per minute current = cache.incr(key) if current == 1: cache.expire(key, 60) # expire after 1 minute return current <= limitSorted sets for leaderboards
# Add or update a scorecache.zadd("game:leaderboard", {"player_alice": 9500})cache.zadd("game:leaderboard", {"player_bob": 11200})cache.zadd("game:leaderboard", {"player_carol": 8750})
# Get top 10 playerstop_10 = cache.zrevrange("game:leaderboard", 0, 9, withscores=True)for rank, (player, score) in enumerate(top_10, 1): print(f"#{rank}: {player.decode()} โ {int(score)}")
# Get a specific player's rank (0-indexed from top)rank = cache.zrevrank("game:leaderboard", "player_alice")Redis Persistence Options
By default, Memorystore Redis instances have persistence enabled through RDB snapshots. You can configure the snapshot frequency.
For workloads where losing even a few minutes of cache data is costly (distributed locks, rate limiting counters), Append Only File (AOF) persistence logs every write operation:
# Enable AOF persistence when creating an instancegcloud redis instances create session-cache \ --size=2 \ --region=us-central1 \ --tier=standard \ --redis-config=appendonly=yes \ --redis-config=appendfsync=everysecappendfsync=everysec flushes the AOF log to disk once per second โ a balance between durability and performance. appendfsync=always flushes every write, guaranteeing no data loss at the cost of throughput.
Monitoring Key Metrics
Memorystore exposes metrics via Cloud Monitoring. The most critical metrics:
redis.googleapis.com/stats/memory/usage_ratio โ % of maxmemory used. Alert above 80%. โ If consistently above 90%, increase instance size or enable eviction.
redis.googleapis.com/stats/keyspace_hitsredis.googleapis.com/stats/keyspace_misses โ Hit rate = hits / (hits + misses). Below 90% suggests cache is undersized or TTLs are too short.
redis.googleapis.com/stats/connected_clients โ Watch for connection exhaustion. Use a connection pool.
redis.googleapis.com/stats/evicted_keys โ Non-zero means maxmemory was reached and keys were evicted. Investigate whether instance is undersized.Summary
Memorystore eliminates the operational overhead of running Redis or Memcached while keeping their APIs identical to self-managed versions. Redis is the better choice for most applications given its richer data structures, optional persistence, and replication support. Standard tier provides high availability with automatic failover. VPC-native connectivity means all access is private. Common patterns โ cache-aside, session storage, rate limiting, leaderboards โ all work through standard Redis commands. The main operational discipline is watching the memory usage ratio and cache hit rate to size the instance correctly and set sensible TTLs.