5 min read
Time-To-Live (TTL) in Azure Cosmos DB: Automatic Data Expiration
Time-To-Live (TTL) in Azure Cosmos DB allows you to automatically delete items after a specified period. This is essential for managing session data, temporary records, audit logs with retention policies, and IoT telemetry.
Enabling TTL on a Container
# Enable TTL on container with default expiration
az cosmosdb sql container update \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--database-name mydb \
--name sessions \
--ttl 3600 # Default 1 hour in seconds
# Enable TTL without default (per-item only)
az cosmosdb sql container update \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--database-name mydb \
--name mycontainer \
--ttl -1 # No default, items must specify TTL
Using C#:
// Create container with TTL enabled
public async Task CreateContainerWithTTL()
{
var containerProperties = new ContainerProperties
{
Id = "sessions",
PartitionKeyPath = "/userId",
DefaultTimeToLive = 3600 // 1 hour default
};
await _database.CreateContainerIfNotExistsAsync(containerProperties);
}
// Create container with TTL enabled but no default
public async Task CreateContainerWithItemLevelTTL()
{
var containerProperties = new ContainerProperties
{
Id = "events",
PartitionKeyPath = "/eventType",
DefaultTimeToLive = -1 // Per-item TTL only
};
await _database.CreateContainerIfNotExistsAsync(containerProperties);
}
Setting TTL on Individual Items
// JavaScript - Creating items with TTL
const { CosmosClient } = require("@azure/cosmos");
class SessionManager {
constructor(container) {
this.container = container;
}
async createSession(userId, sessionData, durationMinutes = 60) {
const session = {
id: `session_${Date.now()}`,
userId: userId,
data: sessionData,
createdAt: new Date().toISOString(),
ttl: durationMinutes * 60 // TTL in seconds
};
const { resource } = await this.container.items.create(session);
return resource;
}
async extendSession(sessionId, userId, additionalMinutes) {
const { resource: session } = await this.container
.item(sessionId, userId)
.read();
// Extend TTL by updating the item
session.ttl = session.ttl + (additionalMinutes * 60);
session.extendedAt = new Date().toISOString();
const { resource: updated } = await this.container
.item(sessionId, userId)
.replace(session);
return updated;
}
async createPersistentSession(userId, sessionData) {
// TTL of -1 means never expire (overrides container default)
const session = {
id: `persistent_${Date.now()}`,
userId: userId,
data: sessionData,
createdAt: new Date().toISOString(),
ttl: -1
};
const { resource } = await this.container.items.create(session);
return resource;
}
}
TTL Patterns for Different Use Cases
# Python - Different TTL patterns
from azure.cosmos import CosmosClient
from datetime import datetime, timedelta
class TTLPatterns:
def __init__(self, container):
self.container = container
def create_audit_log(self, action, user_id, details, retention_days=90):
"""Audit logs with configurable retention"""
log = {
'id': f"audit_{datetime.utcnow().timestamp()}",
'partitionKey': f"{datetime.utcnow().strftime('%Y-%m')}",
'action': action,
'userId': user_id,
'details': details,
'timestamp': datetime.utcnow().isoformat(),
'ttl': retention_days * 24 * 60 * 60
}
return self.container.create_item(log)
def create_cache_entry(self, key, value, cache_seconds=300):
"""Short-lived cache entries"""
entry = {
'id': key,
'partitionKey': 'cache',
'value': value,
'cachedAt': datetime.utcnow().isoformat(),
'ttl': cache_seconds
}
return self.container.upsert_item(entry)
def create_rate_limit_window(self, client_id, limit=100, window_seconds=60):
"""Rate limiting with TTL-based windows"""
window = {
'id': f"ratelimit_{client_id}_{int(datetime.utcnow().timestamp() / window_seconds)}",
'partitionKey': client_id,
'count': 1,
'limit': limit,
'windowStart': datetime.utcnow().isoformat(),
'ttl': window_seconds * 2 # Keep slightly longer than window
}
return self.container.upsert_item(window)
def create_scheduled_task(self, task_id, execute_at, task_data):
"""Tasks that auto-delete after execution window"""
execution_time = datetime.fromisoformat(execute_at)
now = datetime.utcnow()
# Calculate TTL to expire 1 hour after scheduled execution
ttl_seconds = int((execution_time - now).total_seconds()) + 3600
task = {
'id': task_id,
'partitionKey': 'scheduled_tasks',
'executeAt': execute_at,
'taskData': task_data,
'status': 'pending',
'ttl': max(ttl_seconds, 3600) # Minimum 1 hour
}
return self.container.create_item(task)
Monitoring TTL Deletions
// C# - Monitoring TTL using Change Feed
public class TTLMonitor
{
private readonly Container _container;
public async Task MonitorDeletions(CancellationToken cancellationToken)
{
var changeFeedProcessor = _container
.GetChangeFeedProcessorBuilder<ExpandoObject>(
"ttlMonitor",
HandleChangesAsync)
.WithInstanceName("instance1")
.WithLeaseContainer(_leaseContainer)
.Build();
await changeFeedProcessor.StartAsync();
}
private async Task HandleChangesAsync(
IReadOnlyCollection<ExpandoObject> changes,
CancellationToken cancellationToken)
{
foreach (dynamic change in changes)
{
// Check for TTL deletion indicator
if (change._deleted == true && change._ttlExpired == true)
{
Console.WriteLine($"Item {change.id} expired due to TTL");
// Log or handle the expiration
await LogExpiration(change.id, change._ts);
}
}
}
}
TTL with Different Data Tiers
// TypeScript - Tiered data management with TTL
interface DataTierConfig {
hot: number; // TTL in seconds for hot data
warm: number; // TTL for warm data
cold: number; // TTL for cold data
}
class TieredDataManager {
private container: Container;
private tiers: DataTierConfig = {
hot: 3600, // 1 hour
warm: 86400 * 7, // 1 week
cold: 86400 * 90 // 90 days
};
async createDataItem(data: any, tier: 'hot' | 'warm' | 'cold') {
const item = {
id: `${tier}_${Date.now()}`,
partitionKey: data.category,
tier: tier,
data: data,
createdAt: new Date().toISOString(),
ttl: this.tiers[tier]
};
return await this.container.items.create(item);
}
async promoteToWarmer(itemId: string, partitionKey: string) {
const { resource: item } = await this.container
.item(itemId, partitionKey)
.read();
// Upgrade tier and extend TTL
if (item.tier === 'cold') {
item.tier = 'warm';
item.ttl = this.tiers.warm;
} else if (item.tier === 'warm') {
item.tier = 'hot';
item.ttl = this.tiers.hot;
}
item.promotedAt = new Date().toISOString();
return await this.container
.item(itemId, partitionKey)
.replace(item);
}
async archiveItem(itemId: string, partitionKey: string) {
const { resource: item } = await this.container
.item(itemId, partitionKey)
.read();
// Set TTL to -1 for permanent storage
item.tier = 'archived';
item.ttl = -1;
item.archivedAt = new Date().toISOString();
return await this.container
.item(itemId, partitionKey)
.replace(item);
}
}
Best Practices
- Plan TTL values carefully: Consider business requirements
- Use -1 for important data: Prevent accidental deletion
- Monitor via Change Feed: Track TTL deletions for audit
- Test expiration timing: TTL isn’t instantaneous
- Consider RU impact: Deletions consume throughput
TTL is a powerful feature for automatic data lifecycle management, reducing storage costs and ensuring compliance with data retention policies without manual intervention.