Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ public sealed class DynamoDbDistributedCache(IAmazonDynamoDB dynamoDb, string ta
{
private static readonly ActivitySource ActivitySource = new(TelemetryConstants.CacheSourceName);

private readonly IAmazonDynamoDB _dynamoDb = dynamoDb;
private readonly string _tableName = tableName;
private readonly ILogger<DynamoDbDistributedCache> _logger = logger;

// DynamoDB attribute names
private const string AttributeCacheKey = "CacheKey";
private const string AttributeValue = "Value";
Expand All @@ -34,24 +30,24 @@ public sealed class DynamoDbDistributedCache(IAmazonDynamoDB dynamoDb, string ta
var hashedKey = key.Value;
using var activity = ActivitySource.StartActivity("get cache", ActivityKind.Client);
_ = (activity?.SetTag("cache.key", hashedKey));
_ = (activity?.SetTag("cache.table", _tableName));
_ = (activity?.SetTag("cache.table", tableName));
_ = (activity?.SetTag("cache.backend", "dynamodb"));

try
{
var response = await _dynamoDb.GetItemAsync(new GetItemRequest
var response = await dynamoDb.GetItemAsync(new GetItemRequest
{
TableName = _tableName,
TableName = tableName,
Key = new Dictionary<string, AttributeValue>
{
[AttributeCacheKey] = new AttributeValue { S = hashedKey }
[AttributeCacheKey] = new() { S = hashedKey }
}
}, ct);

if (!response.IsItemSet)
{
_ = (activity?.SetTag("cache.hit", false));
_logger.LogDebug("Cache miss for key: {CacheKey}", hashedKey);
logger.LogDebug("Cache miss for key: {CacheKey}", hashedKey);
return null;
}

Expand All @@ -64,7 +60,7 @@ public sealed class DynamoDbDistributedCache(IAmazonDynamoDB dynamoDb, string ta
_ = (activity?.SetTag("cache.hit", value != null));
if (value != null)
{
_logger.LogDebug("Cache hit for key: {CacheKey}", hashedKey);
logger.LogDebug("Cache hit for key: {CacheKey}", hashedKey);
}

return value;
Expand All @@ -74,25 +70,25 @@ public sealed class DynamoDbDistributedCache(IAmazonDynamoDB dynamoDb, string ta
// Table doesn't exist - return null gracefully
// Infrastructure should create table, but don't fail hard in dev
_ = (activity?.SetTag("cache.error", "table_not_found"));
_logger.LogWarning(ex, "DynamoDB table {TableName} not found. Cache operations will fail gracefully.", _tableName);
logger.LogWarning(ex, "DynamoDB table {TableName} not found. Cache operations will fail gracefully.", tableName);
return null;
}
catch (ProvisionedThroughputExceededException ex)
{
_ = (activity?.SetTag("cache.error", "provisioned_throughput_exceeded"));
_logger.LogWarning(ex, "Provisioned throughput exceeded for DynamoDB cache table {TableName}.", _tableName);
logger.LogWarning(ex, "Provisioned throughput exceeded for DynamoDB cache table {TableName}.", tableName);
return null;
}
catch (InternalServerErrorException ex)
{
_ = (activity?.SetTag("cache.error", "internal_server_error"));
_logger.LogError(ex, "Internal server error retrieving cache key {CacheKey} from DynamoDB", hashedKey);
logger.LogError(ex, "Internal server error retrieving cache key {CacheKey} from DynamoDB", hashedKey);
return null;
}
catch (Exception ex) when (ex is not OperationCanceledException and not TaskCanceledException)
{
_ = (activity?.SetStatus(ActivityStatusCode.Error, ex.Message));
_logger.LogError(ex, "Error retrieving cache key {CacheKey} from DynamoDB", hashedKey);
logger.LogError(ex, "Error retrieving cache key {CacheKey} from DynamoDB", hashedKey);
return null; // Fail gracefully
}
// Allow cancellation exceptions to propagate to respect request lifetimes
Expand All @@ -103,7 +99,7 @@ public async Task SetAsync(CacheKey key, string value, TimeSpan ttl, Cancel ct =
var hashedKey = key.Value;
using var activity = ActivitySource.StartActivity("set cache", ActivityKind.Client);
_ = (activity?.SetTag("cache.key", hashedKey));
_ = (activity?.SetTag("cache.table", _tableName));
_ = (activity?.SetTag("cache.table", tableName));
_ = (activity?.SetTag("cache.backend", "dynamodb"));
_ = (activity?.SetTag("cache.ttl", ttl.TotalSeconds));

Expand All @@ -112,41 +108,41 @@ public async Task SetAsync(CacheKey key, string value, TimeSpan ttl, Cancel ct =
var expiresAt = DateTimeOffset.UtcNow.Add(ttl);
var ttlTimestamp = expiresAt.ToUnixTimeSeconds();

_ = await _dynamoDb.PutItemAsync(new PutItemRequest
_ = await dynamoDb.PutItemAsync(new PutItemRequest
{
TableName = _tableName,
TableName = tableName,
Item = new Dictionary<string, AttributeValue>
{
[AttributeCacheKey] = new AttributeValue { S = hashedKey },
[AttributeValue] = new AttributeValue { S = value },
[AttributeTtl] = new AttributeValue { N = ttlTimestamp.ToString(CultureInfo.InvariantCulture) }
[AttributeCacheKey] = new() { S = hashedKey },
[AttributeValue] = new() { S = value },
[AttributeTtl] = new() { N = ttlTimestamp.ToString(CultureInfo.InvariantCulture) }
}
}, ct);

_logger.LogDebug("Cache set for key: {CacheKey}, TTL: {TTL}s", hashedKey, ttl.TotalSeconds);
logger.LogDebug("Cache set for key: {CacheKey}, TTL: {TTL}s", hashedKey, ttl.TotalSeconds);
}
catch (ResourceNotFoundException ex)
{
// Table doesn't exist - fail silently in dev, log in production
// Infrastructure should create table before deployment
_ = (activity?.SetTag("cache.error", "table_not_found"));
_logger.LogWarning(ex, "DynamoDB table {TableName} not found. Unable to cache key {CacheKey}.", _tableName, hashedKey);
_ = activity?.SetTag("cache.error", "table_not_found");
logger.LogWarning(ex, "DynamoDB table {TableName} not found. Unable to cache key {CacheKey}.", tableName, hashedKey);
}
catch (ProvisionedThroughputExceededException ex)
{
_ = (activity?.SetTag("cache.error", "provisioned_throughput_exceeded"));
_logger.LogWarning(ex, "Provisioned throughput exceeded for DynamoDB cache table {TableName}. Unable to cache key {CacheKey}.", _tableName, hashedKey);
_ = activity?.SetTag("cache.error", "provisioned_throughput_exceeded");
logger.LogWarning(ex, "Provisioned throughput exceeded for DynamoDB cache table {TableName}. Unable to cache key {CacheKey}.", tableName, hashedKey);
}
catch (InternalServerErrorException ex)
{
_ = (activity?.SetTag("cache.error", "internal_server_error"));
_logger.LogError(ex, "Internal server error setting cache key {CacheKey} in DynamoDB", hashedKey);
_ = activity?.SetTag("cache.error", "internal_server_error");
logger.LogError(ex, "Internal server error setting cache key {CacheKey} in DynamoDB", hashedKey);
}
catch (Exception ex) when (ex is not OperationCanceledException and not TaskCanceledException)
{
// Allow cancellation exceptions to propagate to respect request lifetimes
_ = activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
_logger.LogError(ex, "Error setting cache key {CacheKey} in DynamoDB", hashedKey);
logger.LogError(ex, "Error setting cache key {CacheKey} in DynamoDB", hashedKey);
// Fail gracefully - don't throw
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private static void AddDistributedCache(IServiceCollection services, AppEnv appE
_ = services.AddSingleton<IDistributedCache>(sp =>
{
var dynamoDb = sp.GetRequiredService<IAmazonDynamoDB>();
var tableName = $"docs-api-cache-{appEnv.ToStringFast()}";
var tableName = $"docs-api-cache-{appEnv.ToStringFast(true)}";
var dynamoLogger = sp.GetRequiredService<ILogger<DynamoDbDistributedCache>>();
var multiLogger = sp.GetRequiredService<ILogger<MultiLayerCache>>();

Expand Down
Loading