전혀 다른 두 버그를 같은 날 잡았다. 하나는 알림이 이상하게 뜨는 문제, 하나는 업로드 시각 자체가 틀어지는 문제다.
metric_query 기반(수식) 알람이 뜨면 함수명이 "알 수 없음"으로, Namespace가 undefined로 찍혔다. 원인은 세 가지가 겹쳐 있었다.
수식 알람은 Dimensions가 Trigger 최상위가 아니라 Trigger.Metrics[].MetricStat.Metric.Dimensions에 중첩돼 있다.
if (!functionNameFromDimension && alarm.Trigger.Metrics) {
for (const m of alarm.Trigger.Metrics) {
const dim = m.MetricStat?.Metric.Dimensions?.find((d) => (d.name ?? d.Name) === 'FunctionName');
if (dim) {
functionNameFromDimension = dim.value ?? dim.Value;
break;
}
}
}
Namespace도 마찬가지로 최상위가 아니라 Metrics[] 안에 있어서, 폴백 순서를 만들었다.
const namespace =
alarm.Trigger.Namespace ??
alarm.Trigger.Metrics?.find((m) => m.MetricStat)?.MetricStat?.Metric.Namespace ??
'AWS/Lambda';
dlq-notifier, cloudwatch-notifier 워커는 함수명을 역산할 때도 문제가 있었다. Terraform 맵 키는 언더스코어(dlq_notifier)인데 알람 이름은 대시(dlq-notifier) 기준이라 매칭이 안 됐다. 역산 자체도 -worker-가 없는 두 워커를 놓쳐서, 아예 명시적 매핑 테이블로 바꿨다.
const FUNCTION_NAMES: Record<string, string> = {
'script': 'shorts-script-worker-prod-handler',
...
'dlq-notifier': 'shorts-dlq-notifier-prod-handler',
'cloudwatch-notifier': 'shorts-cloudwatch-notifier-prod-handler',
};
const workerKey = alarm.AlarmName
.replace(/^prod-/, '')
.replace(/-error-rate$/, '')
.replace(/_/g, '-');
const functionName = functionNameFromDimension ?? FUNCTION_NAMES[workerKey] ?? `shorts-${workerKey}-prod-handler`;
패턴으로 추측하는 방식은 결국 예외 케이스가 계속 나온다. 워커 개수가 뻔한데 그냥 맵으로 못박는 게 나았다.
이건 더 심각했다. 채널 스케줄러에서 "매일 오전 9시"로 저장한 스케줄이 실제로는 UTC 9시, 즉 KST로 밤 18시에 돌고 있었다.
// 표준 5필드 cron(KST) → EventBridge 6필드 cron(UTC) 변환
// EventBridge는 UTC 기준이므로 KST 시간에서 9시간을 뺀다.
function toEventBridgeCron(cron: string): string {
...
let ebHour = hour;
let adjustedDow = dow;
if (hour !== '*') {
const kstHour = parseInt(hour, 10);
const utcHour = (kstHour - KST_OFFSET + 24) % 24;
ebHour = String(utcHour);
// KST 시간이 09:00 미만이면 UTC로 변환 시 전날이 됨 → 주간 스케줄 요일 하루 앞으로 조정
if (kstHour < KST_OFFSET && dom === '*' && dow !== '*') {
adjustedDow = dow.replace(/\b([0-6])\b/g, (_, n) => String((parseInt(n, 10) - 1 + 7) % 7));
}
}
...
}
DB에는 KST 기준 cron이 그대로 저장돼 있어서 문제없었다. EventBridge 규칙을 만드는 시점에 변환을 아예 안 하고 있었던 거다. hour='*'인 매시간 스케줄은 변환할 게 없어서 그대로 뒀고, 요일 스케줄은 KST 자정~오전 9시 사이 시간대일 때 UTC로 넘어가면서 전날로 밀리는 것까지 같이 보정했다.
기존에 잘못 생성된 규칙은 채널 스케줄을 다시 저장해야 고쳐진다. 코드만 고친다고 이미 만들어진 EventBridge 규칙까지 자동으로 갱신되진 않는다.