Azure Managed Redis: client libraries, caching, and data operations
Back to the AI-200 path
AI-200Chapter 15

Microsoft AI-200 Certification Study

Azure Managed Redis: client libraries, caching, and data operations

Design low-latency cache patterns, select the right tier and client, connect securely, manipulate Redis data structures, control expiration, and invalidate stale AI application data.

Suggested study time: 105 minutes • Intermediate • Complete original rewrite with a concise version of every topic, assessment review, and guided Python lab

Neon Microsoft Certified AI-200 shield with Azure Managed Redis, secure clients, cache patterns, TTL, and data operations

1. Why an AI application needs an intentional cache

Consider an e-commerce assistant that must combine a customer profile, catalog facts, conversation history, session state, and expensive model results for thousands of simultaneous chats. Querying the system of record for every turn adds latency and load. puts frequently reused data in memory so the application can respond in sub-millisecond cache time while the durable database remains the source of truth.

  • Explain the managed service, its tiers, and common caching strategies.
  • Select a language client and a clustering-compatible connection mode.
  • Store, retrieve, batch, expire, and remove data with redis-py.
  • Apply cache-aside, explicit invalidation, pooling, retry, security, monitoring, and recovery practices.
  • Build and verify a small Python console application against a disposable instance.
An AI application checks Azure Managed Redis before databases and model services, with TTL, invalidation, security, and monitoring around the cache.
A cache shortens the hot path; it does not replace durable data or model services.

Topic summary

Use Redis for bounded, repeatable, latency-sensitive data, while keeping ownership and durability in the appropriate backing system.

2. Understand the managed Redis architecture

is a Microsoft-operated, Azure-hosted in-memory store built on Redis Enterprise. It keeps Redis protocol compatibility while adding managed deployment, parallel shards, high availability, active-active geo-replication, security controls, and Azure monitoring. Applications can connect from Azure or elsewhere, subject to the configured network path.

Multiple Redis server processes, or shards, run across nodes. Primary and replica shards are distributed so compute is used efficiently, while a proxy on each node manages connections and self-healing. Part of the available memory is reserved for replication and failover work, so provision from measured usable capacity rather than assuming the entire advertised size stores application values.

Active-active geo-replication links instances in different regions. Each region can accept reads and writes and changes converge with eventual consistency. The application still needs health-aware traffic routing and must not assume an immediate cross-region synchronization time.

Topic summary

The service combines Redis-compatible access with managed shards, replicas, proxies, failover, and optional multi-region active-active operation.

3. Choose the caching pattern from the data lifecycle

Three common uses of .
PatternWhat belongs in RedisFreshness approach
Data cacheDatabase rows, catalog records, computed model responsesCache-aside plus TTL and update invalidation
Content cacheHeaders, footers, navigation, templates, banners, shared UI fragmentsLonger TTL or publish-time invalidation
Session storeCart, preferences, authentication context, conversation stateSliding or fixed session expiration

In cache-aside, the application looks in Redis first. A hit returns immediately. A miss reads the database, writes a reusable representation with an expiration, and returns it. Because a database is usually larger than the cache, lazy population is more practical than loading everything. When the source changes, delete or replace every derived cache key that could now be stale.

Static page fragments reduce rendering work and the number of web servers required for the same traffic. ASP.NET can use a Redis output-cache provider, and other frameworks implement the same idea through their cache abstractions. Clustering distributes large content sets across nodes.

For sessions, keep only an opaque session identifier in the browser cookie and keep the larger state in Redis. This avoids sending the entire session on every HTTP request and response. Framework integrations exist for ASP.NET, ASP.NET Core, Node.js, Python, and Java; replication and expiration support availability and cleanup.

Topic summary

Data, shared content, and sessions have different owners and expiration needs, but all benefit when small hot values avoid repeated backend work.

4. Select a tier from memory, throughput, and availability evidence

tier profiles.
TierMemory-to-vCPU postureTypical fit
Memory OptimizedApproximately 8:1Large working sets with moderate throughput; development can start on smaller SKUs
Balanced (Memory + Compute)Approximately 4:1General-purpose production workloads
Compute OptimizedApproximately 2:1Maximum throughput and CPU-intensive command rates
Flash OptimizedRAM plus NVMe flashVery large, colder datasets that can trade some latency for lower memory cost

The tier sets the performance ceiling, usable memory, availability features, and monthly cost. Test with realistic value sizes, command mix, concurrency, network path, and high-availability settings before committing. As of this edition, Flash Optimized and some very large in-memory SKUs are preview features; verify current availability and limits in the target region.

An eviction policy determines what happens when memory fills, but eviction is not a substitute for a capacity plan. TTLs, maximum value size, expected key count, replication overhead, and a safe memory margin should be part of the model.

Topic summary

Choose the tier from measured memory and throughput, then validate availability, preview status, eviction behavior, and cost for the deployment region.

5. Match the client library to language and clustering policy

Common community Redis clients.
LanguageLibrary
C# / .NETStackExchange.Redis
JavaLettuce or Jedis
Node.jsnode_redis or ioredis
Pythonredis-py

Client libraries translate their APIs into Redis commands and are maintained by their own communities, not by the service team. Use an actively maintained, current release and revisit upgrades regularly because connection handling, cluster support, reliability, and performance continue to improve.

Every client can connect to the Enterprise clustering policy because the service proxy presents a compatible endpoint. With the OSS policy, the client must understand Redis Cluster topology and hash slots. In Python that means redis.cluster.RedisCluster rather than the ordinary redis.Redis class. This choice is part of the connection design, not a late configuration detail.

Topic summary

Pick a current community client for the application language and confirm that its connection class supports the instance clustering policy.

6. Design multi-key operations for cluster slots and blocked commands

A clustered cache partitions keys into hash slots. Under OSS clustering, every key in one multi-key command must land in the same slot or the server returns CROSSSLOT. A shared hash tag—such as user:{42}:profile and user:{42}:cart—can deliberately co-locate related keys, but concentrating too much traffic in one slot creates a hot shard.

Cross-slot behavior to remember.
ConfigurationCommands allowed across slots
Enterprise clusteringDEL, MSET, MGET, EXISTS, UNLINK, TOUCH
Active-active databasesMGET, EXISTS, TOUCH; multi-key writes must remain in one slot
OSS clusteringMulti-key commands require keys in the same hash slot

Because Microsoft controls service topology, Enterprise clustering blocks CLUSTER INFO, CLUSTER HELP, CLUSTER KEYSLOT, CLUSTER NODES, and CLUSTER SLOTS. Active geo-replication blocks FLUSHALL and FLUSHDB. Application code should not depend on administrative commands that the managed platform owns.

Topic summary

Hash-slot placement governs multi-key behavior, and managed-service command restrictions protect topology and replicated data.

7. Apply development practices before adding more capacity

  • Prefer more keys with smaller values; split oversized objects and avoid returning multi-megabyte payloads.
  • Use pipelining to combine network round trips when operations are independent.
  • Use SCAN for incremental iteration; KEYS can block the server and should not run in production paths.
  • Place the application and Redis instance in the same Azure region when possible.
  • Connect by hostname, never by a public IP that can change during scale or maintenance.
  • Keep TLS enabled; supports TLS 1.2 and 1.3 and requires encrypted transport by default.
  • For exceptional bandwidth demand, benchmark a larger client host or several connection objects distributed round-robin.

Pipelining is not the same as a transaction: it reduces round trips, while atomicity depends on the specific Redis command or an explicit transaction. Measure the command latency distribution and server load so an optimization does not merely move the bottleneck to the client network.

Topic summary

Small values, pipelining, nonblocking iteration, local placement, hostnames, and TLS usually improve reliability before scaling the service.

8. Connect securely with redis-py and

and Enterprise caches use TLS port 10000; uses 6380. Mixing those defaults is a common connection failure. decode_responses=True converts returned bytes into text. Leave it false for images, serialized Python objects, or any payload whose bytes must remain unchanged.

import redis

# Access-key example; prefer Microsoft Entra ID in production
client = redis.Redis(
    host="<cache-name>.<region>.redis.azure.net",
    port=10000,
    ssl=True,
    password="<access-key>",
    decode_responses=True,
)

# Use decode_responses=False for images or other binary payloads.

Access keys work, but is the preferred passwordless model for production. enables Microsoft Entra authentication by default on new caches. Grant the user, service principal, or managed identity only the required data permissions and let the credential provider refresh expiring tokens.

import redis
from azure.identity import DefaultAzureCredential
from redis_entraid.cred_provider import create_from_default_azure_credential

provider = create_from_default_azure_credential(
    ("https://redis.azure.com/.default",),
)

client = redis.Redis(
    host="<cache-name>.<region>.redis.azure.net",
    port=10000,
    ssl=True,
    credential_provider=provider,
    decode_responses=True,
)

# With OSS clustering, use redis.cluster.RedisCluster instead.

Microsoft Entra authentication requires TLS. The token scope is https://redis.azure.com/.default. If access keys are disabled later, all current connections are terminated, so plan the cutover and reconnect behavior.

Topic summary

Use the correct TLS port and cluster-aware client, prefer , and handle token refresh and reconnects as normal production behavior.

9. Choose a Redis data structure that matches the operation

Core structures in this module.
StructureUseful commandsGood fit
StringSET, GET, MSET, MGETText, serialized results, feature flags, binary values
HashHSET, HGET, HGETALLProfiles, product fields, compact structured objects
ListLPUSH, RPUSH, LPOP, RPOP, LRANGEFIFO queues, LIFO stacks, recent-item feeds
Numeric stringINCR, DECR, INCRBY, DECRBYAtomic counters, rate limits, distributed totals

Hashes keep multiple field-value pairs under one key and are usually more memory-efficient than one key per field. Lists preserve order and support both ends. Numeric commands are atomic, so they avoid the race created by separately reading, incrementing, and writing a counter.

# Strings and batches
client.set("profile:42:name", "Ada")
name = client.get("profile:42:name")
client.mset({"feature:a": "on", "feature:b": "off"})
flags = client.mget("feature:a", "feature:b")

# Hashes model structured objects
client.hset("profile:42", mapping={"name": "Ada", "plan": "pro"})
profile = client.hgetall("profile:42")

# Lists implement queues or recent-item feeds
client.rpush("jobs:pending", "job-1001")
job = client.lpop("jobs:pending")

# Atomic numeric counters avoid read-modify-write races
count = client.incr("rate:user:42")
Redis strings, hashes, lists, and counters lead to batch, pipeline, TTL, and invalidation operations.
Model the data around the commands the application must perform, not around a generic key-value abstraction.

Topic summary

Strings, hashes, lists, and atomic counters solve different access patterns; select the structure before designing keys and TTLs.

10. Store, retrieve, batch, check, and delete data

SET and GET are the basic string pair. HSET can write a mapping and HGET or HGETALL reads one field or the full object. MSET and MGET reduce round trips across several string keys when the clustering policy permits. Multiple hashes are separate keys, so use a pipeline to issue several HGETALL or HGET calls efficiently.

pipe = client.pipeline()
pipe.hgetall("profile:42")
pipe.hgetall("profile:84")
profiles = pipe.execute()

existing = client.exists("profile:42", "profile:84", "profile:999")
removed = client.delete("profile:42", "session:expired")

EXISTS works with every data type because it tests keys, not values; with several keys it returns how many exist. DEL likewise removes complete keys and returns the number deleted. Treat a missing key as a normal cache miss rather than an application failure.

Topic summary

and pipeline independent operations, and interpret EXISTS and DEL return counts correctly across all Redis data types.

11. Control expiration with TTL at the key level

Expiration is central to automatic invalidation and memory control. SETEX stores a string plus its expiration atomically; PSETEX uses milliseconds. For a hash, list, or existing string, write the value first and then apply EXPIRE or PEXPIRE. EXPIREAT sets an absolute Unix time.

import time

# Atomic write plus expiration for a string
client.setex("session:f7c9", 3600, "user-42")
client.psetex("lock:job-1001", 5000, "worker-3")

# Any data type can receive a key-level expiration
client.expire("profile:42", 900)
client.pexpire("jobs:pending", 60_000)
client.expireat("catalog:snapshot", int(time.time()) + 7200)

ttl = client.ttl("profile:42")   # -1: no expiry; -2: key absent
ttl_ms = client.pttl("profile:42")
client.persist("profile:42")     # remove the expiry

TTL reports seconds and PTTL reports milliseconds. A result of -1 means the key exists without expiration; -2 means the key does not exist. PERSIST deliberately removes an expiration. Choose TTL from the freshness budget: about 1–5 minutes for frequently changing data, 15–60 minutes for moderate changes, 1–24 hours for stable data, and more than a day for static reference data. These are starting ranges, not service rules.

Topic summary

Expiration belongs to the key, SETEX provides an atomic string write, and TTL values must balance freshness, hit rate, and memory.

12. Invalidate stale cache entries deliberately

Time-based invalidation is simple but can serve data that changed before the TTL ended. Manual invalidation deletes or refreshes every related key immediately after a successful database update. Cache-aside combines lazy reads with a bounded TTL, while pattern-based cleanup uses SCAN to discover related keys without blocking the server.

def get_product(product_id: str, ttl: int = 600):
    key = f"product:{product_id}"
    cached = client.get(key)
    if cached is not None:
        return cached

    value = read_product_from_database(product_id)
    if value is not None:
        client.setex(key, ttl, value)
    return value

def update_product(product_id: str, value: str):
    write_product_to_database(product_id, value)
    client.delete(
        f"product:{product_id}",
        f"recommendations:{product_id}",
    )

def invalidate_user(user_id: str):
    # SCAN advances incrementally; KEYS can block production workloads.
    for key in client.scan_iter(match=f"user:{user_id}:*", count=100):
        client.delete(key)

Write the database first and invalidate second so a cache miss cannot repopulate old source data. For high-write or strict-consistency domains, consider versioned keys, events, or a write-through design. Avoid unbounded wildcard schemes; a reverse index or a predictable small set of derived keys is easier to invalidate and observe.

Topic summary

TTL limits staleness, update invalidation shortens it, and SCAN enables incremental pattern cleanup without the production risk of KEYS.

13. Bound connections, failures, and retries

redis-py uses connection pooling automatically. Configure a finite pool for the concurrency the application can sustain, reuse clients, and avoid opening a new TCP/TLS session for every request. Several pools or connections can distribute exceptional bandwidth demand, but more connections are not a cure for server saturation.

import random
import time
import redis

pool = redis.ConnectionPool(
    connection_class=redis.SSLConnection,
    host="<cache-name>.<region>.redis.azure.net",
    port=10000,
    max_connections=40,
    socket_connect_timeout=2,
    socket_timeout=2,
    health_check_interval=30,
    decode_responses=True,
)
client = redis.Redis(connection_pool=pool)

for attempt in range(3):
    try:
        value = client.get("model:result:42")
        break
    except (redis.ConnectionError, redis.TimeoutError):
        if attempt == 2:
            raise
        time.sleep((2 ** attempt) * 0.1 + random.random() * 0.1)

Set short connection and command timeouts, catch Redis connection and timeout errors, and allow the application to use the source of truth or return a controlled degraded response when appropriate. Retry only transient, idempotent work with exponential backoff and jitter. Do not retry an unknown write blindly, because it might already have succeeded.

Topic summary

A bounded pool, timeouts, graceful fallback, and finite jittered retries prevent a cache incident from becoming an application-wide outage.

14. Monitor, scale, and protect the cache

Watch Used Memory Percentage, CPU usage, Connected Clients, network bandwidth, cache latency, timeouts, evictions, and hit/miss ratio. The source material suggests investigating sustained utilization above roughly 75% for the four capacity metrics; production alerts should be based on the application baseline, tier limits, and response-time objective.

  • Enable high availability for production; disable it only when the reduced resilience is acceptable in development or test.
  • Use active-active geo-replication for multi-region access and resilience, while designing for eventual consistency and application-managed traffic failover.
  • Use RDB or AOF persistence when quicker same-cache recovery is needed; persistence is not a point-in-time backup.
  • Use import/export for periodic copies to a storage account, and verify current incompatibilities between persistence and active geo-replication.
  • Scale or change tiers only after performance tests show whether the constraint is memory, CPU, network, connection count, or value size.
Azure Monitor metrics feed a decision loop for tier scaling, high availability, persistence, geo-replication, and application resilience.
Observe the cache as a distributed dependency with capacity, availability, consistency, and recovery trade-offs.

Topic summary

Capacity metrics, hit ratio, latency, availability mode, persistence, backups, and geo-replication form one operational design.

15. Guided lab: perform data operations from Python

The source exercise is designed for about 30 minutes. Use a disposable resource and record the command outputs rather than treating a successful deployment as the only result.

  1. Prepare an Azure subscription, , Python 3.12 or later, the latest Azure CLI, and the redisenterprise extension installed with az extension add --name redisenterprise.
  2. Download or create a small console starter project and install redis, redis-entraid, and azure-identity as required by the chosen authentication path.
  3. Create an resource, capture its hostname and clustering policy, and grant the executing identity access.
  4. Complete the connection on TLS port 10000 and verify PING.
  5. Store a profile as a hash, retrieve individual and complete fields, and batch two reads with a pipeline.
  6. Set an expiration, inspect TTL, remove the expiration, apply it again, then delete the key and confirm TTL returns -2.
  7. Implement cache-aside plus explicit invalidation, disconnect, remove the disposable Azure resource, and retain only sanitized notes.

Topic summary

The lab proves provisioning, secure connection, hashes, pipelines, TTL semantics, deletion, and cleanup in one repeatable Python workflow.

16. Assessment review and production checklist

  1. The default encrypted connection port for is 10000; 6380 belongs to .
  2. Use SCAN instead of KEYS for production key iteration.
  3. redis-py setex() writes a string and its expiration in one atomic operation.
  4. TTL -1 means the key exists without expiration; TTL -2 means the key is absent.
  5. Enterprise and OSS clustering have different multi-key and connection requirements.
  6. with TLS is the preferred production authentication path.
  • Name keys consistently and bound value sizes.
  • Choose strings, hashes, lists, and counters from the operation pattern.
  • Set a freshness-aware TTL or explicitly document why a key persists.
  • Invalidate derived keys after the source update.
  • Use pipelines, bounded pools, timeouts, and idempotent retries.
  • Monitor memory, CPU, clients, bandwidth, latency, evictions, and hit ratio.
  • Test high availability and recovery, and remove lab resources.

Official references

  1. What is ?
  2. architecture
  3. Best practices using client libraries
  4. Development best practices
  5. Use for cache authentication
  6. Create a Python app with
  7. Caching guidance from Azure Architecture Center

Topic summary

A production-ready cache joins correct Redis commands with cluster awareness, secure identity, bounded freshness, observable capacity, and tested failure behavior.