🔥 Logs: 구조화 로그와 인덱싱 관점

2756자
30분

16:9 가로 표지 일러스트레이션. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 AWS 오렌지 #ff9900 리본 배너에 'CloudWatch Logs' 제목이 적혀 있다. 가운데에는 슬레이트 그레이 #1e293b LogGroup 직사각형 컨테이너가 놓여 있고, 그 안에 슬레이트 #475569 톤의 LogStream 가로 막대 세 개를 세로로 쌓아 둔다. 가운데 stream 안에는 작은 알약 캡슐 세 개를 timestamp 시계 아이콘과 { } JSON 박스로 나누어 놓는다. 오른쪽 아래 구석에는 소프트 레드 #ef4444 무한대 기호와 'Never' 라벨이 작은 알림 카드로 떠 있어, retention 기본값이 무한이라는 핵심을 가리킨다. 한 캡슐에는 에메랄드 그린 #10b981 강조점을 찍어 valid event를 표시하는 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 프로페셔널 IT 표지

"로그 한 줄을 CloudWatch에 어떻게 보내나"라고 검색했다가 PutLogEvents API 페이지에서 한 박자 멈췄다. 한 호출이 받는 한 이벤트는 {timestamp, message} 두 필드뿐이다. timestamp는 epoch milliseconds, message는 자유 텍스트. 이게 끝이다.

처음 봤을 때 나는 이게 너무 단출해서 오히려 의심이 들었다. 로그 시스템이라면 보통 레벨이나 카테고리, 적어도 source 같은 메타데이터 한두 개는 있어야 자연스러운데, AWS는 그걸 다 빼고 두 필드만 남겼다. CloudWatch란 무엇인가: AWS 모니터링의 중심 도구에서 로그가 "log group 아래 log stream에 timestamp와 자유 텍스트로 기록한다"는 한 줄로 정의해 두었는데, 그 구조를 한 단계 더 짚으면 왜 그렇게 단출한지가 드러난다. 두 필드짜리 자유 텍스트라는 형태가 검색 형식, 메트릭과의 관계, 청구서가 자라는 메커니즘을 다 결정한다.

Log Group, Stream, Event: 두 필드 위의 3단계 계층

이벤트 한 줄이 어디에 쌓이는지부터 짚는다. CloudWatch Logs는 세 단계 구조다. 가장 아래에 LogEvent ({timestamp, message} 그 자체), 그 위에 LogStream, 가장 위에 LogGroup이 있다. 이름이 비슷해서 처음엔 헷갈리는데, 역할이 분명히 다르다.

LogStream시간순으로 한 줄씩 진행하는 append-only 시퀀스다. 같은 함수 인스턴스, 같은 컨테이너, 같은 프로세스가 만든 이벤트들을 한 stream에 모은다. LogGroup은 그 stream들을 묶는 컨테이너이고, 운영 결심이 머무는 곳이다. 보존 기간(retentionInDays), 암호화 키(kmsKeyId), 로그 클래스(Standard vs Infrequent Access). 이 세 가지를 LogGroup 단위로 묶고, stream에는 그런 결심이 없다. 그래서 같은 함수의 모든 인스턴스가 만든 stream들은 한 group으로 모이고, 운영 정책은 그 group 한 군데에서 통제한다.

16:9 가로 계층 다이어그램. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 'Log Group / Log Stream / Log Event: 3-tier hierarchy' 제목. 세 개의 layer를 세로로 중첩한다. 최상위 layer는 큰 슬레이트 그레이 #1e293b 둥근 사각형 'LogGroup: retention, encryption, log class' 안에 retentionInDays, kmsKeyId, logGroupClass 세 개의 작은 속성 알약을 둔다. 중간 layer는 LogGroup 안에 슬레이트 블루 #475569 둥근 사각형 세 개를 가로로 두고 'LogStream (append-only)' 라벨과 함께 stream 이름(2026/04/27/[$LATEST]a1b2c3 등)을 적는다. 최하위 layer는 가장 왼쪽 stream 안에 가로 알약 캡슐 네 개를 세로로 쌓아 두고, 각각 작은 시계 아이콘 'timestamp'와 { message } 중괄호 박스로 나눈다. 하단 캡션에 Event = timestamp + message 라는 문구를 슬레이트 그레이로 두고, append-only 라벨에 에메랄드 그린 #10b981 강조, LogGroup 외곽선에 AWS 오렌지 #ff9900 강조를 적용한 프로페셔널 IT 다이어그램

이 구조가 손에 잡히면, PutLogEvents 한 호출이 받을 수 있는 양에도 한도가 있다는 게 당연하다. AWS 문서에는 한도가 네 개 줄줄이 적혀 있어서 내가 처음 봤을 때는 외울 게 많아 보였는데, 한 호출의 무게시점의 일관성 두 축으로 묶고 나서야 외울 게 사실상 두 축뿐이라는 걸 알았다.

무게 쪽부터 본다. 한 호출 페이로드는 1 MB를 넘지 못한다. 이때 1 MB는 단순히 message 바이트의 합이 아니라, 메시지 UTF-8 바이트에 이벤트당 26 바이트가 더해진 값이다. 이 26 바이트는 timestamp와 메타데이터를 위해 AWS가 고정으로 잡아 둔 머리값이다. 만 개의 짧은 이벤트(평균 50 바이트짜리)를 한 번에 보낸다고 직접 계산하면 메시지 합은 50만 바이트(약 488 KB)지만, 메타가 더해져 76만 바이트, 약 743 KB가 된다. 76 KB가 아니다. 나도 처음엔 "그럼 짧은 이벤트는 만 개도 여유롭겠네" 싶었는데, 계산해 보니 1 MB 한도가 의외로 가까웠다. 이벤트 수 10,000개 한도와 1 MB 한도 두 개 중 먼저 닿는 쪽이 한도다.

시점 쪽이 의외로 까다롭다. 한 배치 안의 timestamp들은 24시간 범위를 넘지 못한다. 그리고 14일 이상 오래된 timestamp는 아예 거부하고, 2시간 이상 미래의 timestamp도 거부한다. 이 두 숫자가 백필 작업의 형태 자체를 결정한다. "Lambda가 죽었던 한 시간 동안의 로그를 한 번에 다시 쏘자"는 시도는 가능하지만, "지난 달 데이터를 지금 다시 쏘자"는 시도는 14일 벽에 부딪힌다. 14일이 지난 데이터는 별도 시스템(S3 archive 등)으로 가야 한다는 신호로 나는 읽었다. CloudWatch Logs는 지금부터 최근 2주를 위한 도구라는 자기 정의가 이 두 숫자에 들어 있는 셈이다.

16:9 가로 한도 다이어그램. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 'PutLogEvents: one batch limits' 제목. 좌우 두 zone으로 나눈다. 왼쪽 zone 'Payload limits'에는 슬레이트 그레이 #475569 톤의 큰 stat 카드 두 장을 세로로 쌓아 둔다. 위쪽 카드 'Up to 1 MB per batch' 아래에 'message bytes (UTF-8) + 26 bytes per event' 부제, 아래쪽 카드 'Up to 10,000 events per batch'. 오른쪽 zone 'Timestamp window'에는 한 가로 시간축이 있고 세 색 구간으로 나뉘어 있다. 가장 왼쪽 빨간 #ef4444 구간 '> 14 days old → rejected', 가운데 에메랄드 그린 #10b981 구간 'valid range, span ≤ 24h within batch', 가장 오른쪽 앰버 #f59e0b 구간 '> 2 hours future → rejected'. 오른쪽 아래에 작은 캡션 'SequenceToken is now ignored, parallel writes allowed'를 슬레이트 텍스트로 적은 프로페셔널 IT 다이어그램

API 시그니처 자체가 한 번 더 변했다는 점도 같이 짚는다. 예전에는 SequenceToken이라는 필드가 있어서 stream에 쓰기를 직렬화했다. 한 stream에 동시에 두 호출이 들어오면 충돌이 났고, 그래서 SDK가 InvalidSequenceTokenException을 처리하는 분기를 갖고 있어야 했다. 이제 AWS는 그 필드를 더 이상 받지 않는다. 시퀀스 토큰을 무시하고, 같은 stream에 병렬 PutLogEvents를 허용한다. 옛 글이나 코드에서 그 예외 처리 분기를 보면, 그건 이제 들어오지 않는 죽은 코드다.

이벤트 한 줄의 크기 자체도 2025년 4월에 256 KB에서 1 MB로 4배 늘었다 (AWS 발표). 한 줄짜리 큰 stack trace나 큰 JSON 페이로드를 자르지 않고 그대로 보낼 수 있는 폭이 그만큼 더 크다. 한 이벤트가 1 MB이면 한 호출은 사실상 그 한 이벤트만으로 채워지지만, 잘려서 두 줄에 흩어진 stack trace를 디버깅하던 사람에게는 1년이 지난 지금도 큰 변화다.

자유 텍스트는 인덱스가 아니다: 구조화 JSON이 들어오는 이유

콘솔 화면을 잠깐 떠올려 본다. 나도 처음 CloudWatch Logs 검색창에 단어를 던졌을 때 답이 생각보다 늦게 돌아와서 의외였는데, 그 이유가 바로 페이로드 형식이다. message가 자유 텍스트라는 한 줄이 다음 결심들을 다 강제한다. CloudWatch Logs는 검색 인덱스 데이터베이스가 아니다. 메시지의 어떤 토큰이 어디에 있는지를 미리 색인해 두지 않는다는 뜻이다.

CloudWatch란 무엇인가: AWS 모니터링의 중심 도구에서 "풀텍스트 검색은 OpenSearch와 Elasticsearch가 맡는다"고 짚었던 그 구분이 이 문맥에서 의미를 갖는다. Logs Insights가 쿼리를 받기는 하지만, 그 쿼리는 스캔이지 인덱스 lookup이 아니다. 즉 매 쿼리가 해당 시간 범위의 모든 이벤트를 처음부터 끝까지 한 줄씩 읽어 내려가며 매칭한다. 그래서 AWS는 과금 모델을 스캔한 GB를 단위로 잡는다. 1주일 치 100 GB 로그를 검색하면 매번 100 GB 스캔 비용이 발생하는 구조다.

이 형태 위에서 구조화 JSON이 인덱스의 역할을 대신한다. 메시지를 그냥 "GET /api/users 200 35ms user=alice" 같은 평문으로 두지 않고 다음처럼 JSON으로 쓴다.

json
{"ts": 1761537600000, "method": "GET", "path": "/api/users", "status": 200, "latency_ms": 35, "user": "alice"}
json
{"ts": 1761537600000, "method": "GET", "path": "/api/users", "status": 200, "latency_ms": 35, "user": "alice"}

16:9 가로 비교 다이어그램. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 'Plain text vs structured JSON: same data, different operability' 제목. 가는 슬레이트 점선 분할선이 좌우 두 패널을 나눈다. 왼쪽 패널 'Plain text' 슬레이트 그레이 #475569 헤더 아래에 단일 슬레이트 텍스트 코드 박스 'GET /api/users 200 35ms user=alice'를 두고, 그 아래로 소프트 레드 #ef4444 경고 점이 붙은 카드 세 장을 세로로 쌓는다. 'parse @message: regex needed', 'filter status >= 500: regex group required', 'stats avg(latency): custom parser'. 오른쪽 패널 'Structured JSON' 에메랄드 그린 #10b981 헤더 아래에 단일 에메랄드 외곽선 코드 박스에 method GET, path /api/users, status 200, latency_ms 35, user alice 의 JSON 객체를 두고, 그 아래로 에메랄드 #10b981 체크 점이 붙은 카드 세 장을 세로로 쌓는다. 'filter status >= 500: direct field', 'stats count() by path: no parsing', 'stats avg(latency_ms): typed numeric'. 가장 아래에 두 패널을 가로지르는 슬레이트 캡션 'Same data, two operability stories'가 깔린 프로페셔널 IT 비교 다이어그램

문자열로 보면 전자가 짧고 사람 눈에는 읽기 더 편하다. 그런데 같은 데이터의 운영 가능성이 한쪽에서만 살아난다. Logs Insights의 쿼리에서 parse @message 없이 바로 filter status >= 500, stats count() by path, stats avg(latency_ms)로 들어갈 수 있는 건 후자뿐이기 때문이다. 전자는 매 쿼리마다 문자열을 직접 자르는 정규식을 한 줄씩 써야 한다.

이게 왜 비용이 되는지 한 번 계산한다. 같은 100 GB 로그에 매일 10번씩 쿼리를 발행한다고 가정한다. JSON 형태라면 필드 단위 비교만 하므로 가장 적은 데이터만 읽어도 답이 나온다. 평문 형태라면 매 쿼리마다 모든 줄을 정규식으로 파싱해야 하므로 같은 100 GB를 매번 풀 스캔한다. 한 달이면 그 차이가 ingestion과 storage 청구서 옆에 추가 쿼리 비용이라는 줄로 따로 자란다. 처음엔 작아 보이지만 운영 6개월 차에 보면 무시 못 할 항목이다.

자유 텍스트라는 페이로드 형태는 그래서 글을 읽는 도구가 아니라 글을 쓰는 쪽에서 결심한다. 운영을 시작하는 시점에 로그 포맷을 JSON으로 쓰겠다는 결심 한 줄이, 6개월 뒤 디버깅 비용을 결정한다. 그 결심을 한 단계 더 밀면 메트릭까지 한 호출로 같이 처리할 수 있는 옵션, Embedded Metric Format이 들어선다.

Embedded Metric Format: 한 호출에 메트릭과 로그를

Embedded Metric Format, 줄여서 EMF는 한 줄로 설명할 수 있다. 로그 한 줄에 메트릭을 끼워서 PutLogEvents 한 호출에 둘 다 처리하는 사양이다. 로그를 쓰는 단계에서 자동으로 메트릭을 추출하고, 사용자는 PutMetricData를 따로 부를 필요가 없다. 페이로드 형태는 이렇다.

json
{
  "_aws": {
    "Timestamp": 1761537600000,
    "CloudWatchMetrics": [{
      "Namespace": "MyApp",
      "Dimensions": [["Service", "Region"]],
      "Metrics": [
        {"Name": "Latency", "Unit": "Milliseconds"},
        {"Name": "RequestCount", "Unit": "Count"}
      ]
    }]
  },
  "Service": "users-api",
  "Region": "ap-northeast-2",
  "Latency": 35,
  "RequestCount": 1,
  "user": "alice",
  "path": "/api/users"
}
json
{
  "_aws": {
    "Timestamp": 1761537600000,
    "CloudWatchMetrics": [{
      "Namespace": "MyApp",
      "Dimensions": [["Service", "Region"]],
      "Metrics": [
        {"Name": "Latency", "Unit": "Milliseconds"},
        {"Name": "RequestCount", "Unit": "Count"}
      ]
    }]
  },
  "Service": "users-api",
  "Region": "ap-northeast-2",
  "Latency": 35,
  "RequestCount": 1,
  "user": "alice",
  "path": "/api/users"
}

16:9 가로 해부 다이어그램. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 'Embedded Metric Format: one log line, two outputs' 제목. 가운데에 가로 슬레이트 그레이 #475569 둥근 카드 'PutLogEvents: one EMF log line'을 두고, 그 안을 두 영역으로 나눈다. 왼쪽 영역은 슬레이트 블루 subcard '_aws.CloudWatchMetrics' 안에 'Namespace, Dimensions, Metrics' 세 작은 알약. 오른쪽 영역은 'Service, Latency, user' 세 일반 필드 태그를 둔다. 카드에서 오른쪽으로 두 개의 화살표가 갈라져 나간다. 위쪽 화살표는 에메랄드 그린 #10b981 색으로 'CloudWatch Metrics' stat 카드를 가리키며 'extracted: Latency, RequestCount' 캡션, 작은 막대 차트 아이콘을 붙인다. 아래쪽 화살표는 슬레이트 그레이 #475569 색으로 'CloudWatch Logs' stat 카드를 가리키며 'stored: full JSON payload' 캡션, 작은 가로 로그 라인 아이콘을 붙인다. 카드 아래 슬레이트 캡션 'Limit: ≤100 metrics × ≤30 dimensions × ≤1 MB per event'를 표시한 프로페셔널 IT 다이어그램

페이로드 안의 _aws.CloudWatchMetrics 배열이 메트릭의 지시문 역할을 한다. Namespace, Dimensions, Metrics 세 키를 그 안에 두고, CloudWatch는 이 한 줄을 받아 두 가지를 동시에 처리한다. (a) Latency, RequestCount 두 메트릭을 추출해 Metric: 숫자 하나가 찍히는 과정에서 본 그 좌표(namespace × MetricName × dimensions)에 찍는다. (b) 같은 JSON을 그대로 Log에도 쌓는다. 그래서 userpath 같은 일반 필드는 메트릭으로는 가지 않지만 로그로는 살아 있어, 한 줄로 두 검색 경로를 동시에 만든다.

한도는 한 EMF 줄당 메트릭 100개와 dimension 30개다. 그리고 1 MB 이벤트 한도 안에서. 이 한도를 넘으면 메트릭 추출이 그냥 안 된다. 로그는 들어가는데 메트릭이 만들어지지 않는 조용한 실패다. 그래서 한 줄에 너무 많은 메트릭을 넣지 않고, 한 요청 단위로 잘라 쓰는 패턴을 흔히 쓴다. 한 HTTP 요청 한 줄에 그 요청의 latency, status, count만 담는 식이 가장 흔하고 무난한 형태다.

EMF가 PutMetricData와 다른 부분은 비용 구조다. PutMetricData는 US East 기준 API 호출 1,000건당 $0.01로 단가를 잡는다(서울 등 다른 region은 따로 본다). 같은 데이터를 EMF로 보내면 PutMetricData 호출이 0이 되는 대신, PutLogEvents 한 호출이 그 역할을 맡는다. 메트릭 호출 비용이 사라지는 만큼 로그 ingestion 비용이 자란다.

내가 직접 계산한 한 시나리오. 초당 1건의 요청을 30일간 기록한다고 하자. 한 달이면 약 2.59M개의 데이터 포인트가 된다. 두 길의 비용을 같은 기준에서 비교하면 이렇다.

  • PutMetricData 길: 요청 한 번에 호출 한 번, 즉 2.59M 호출. 비용 = 2.59M / 1000 × $0.01 = 약 $25.9.
  • EMF 길: 요청 한 번에 PutLogEvents 한 줄. 평균 300바이트짜리 JSON이면 한 달 약 777 MB(약 0.78 GB) ingestion이고, $0.50/GB로 약 $0.39다.

같은 워크로드에 60배가 넘는 격차가 난다. 작은 페이로드로 메트릭을 자주 찍는 패턴에서 EMF가 압도적이다. 다만 페이로드 크기에 따라 우열이 거꾸로 간다. 같은 빈도에 한 번에 9 KB짜리 큰 JSON을 보내면 ingestion이 약 30배(0.78 GB → 약 23 GB)로 자라 약 $11.7이 되고, 메트릭 수가 적은 워크로드라면 PutMetricData 쪽이 더 작다. 결국 EMF는 PutMetricData 비용을 0으로 만드는 게 아니라 비용을 다른 통장으로 보낸다. 어느 통장이 더 작은지는 메시지 크기 × 메트릭 빈도가 결정한다.

다만 EMF는 Standard log class에서만 동작한다. 뒤에서 다시 짚을 Infrequent Access 클래스는 메트릭 추출 자체를 지원하지 않아서, IA 그룹에 EMF 페이로드를 보내봐야 CloudWatch는 메트릭을 그냥 무시한다. EMF를 쓸 그룹은 Standard에 두는 결심이 자동으로 따라온다.

청구서가 자라는 메커니즘: Retention, IA class, Subscription Filter

데이터의 형태까지 살펴봤으니, 이제 그 데이터에 청구서가 붙는 결심을 본다. 셋이 있고, 셋 다 CloudWatch란 무엇인가: AWS 모니터링의 중심 도구에서 짚은 '관리형이 감추는 비용'의 구체 사례다.

Retention 기본값이 Never expire. 새 Log Group을 만들 때 retention 정책을 따로 정하지 않으면 로그는 영원히 남는다. 이게 왜 함정이냐면, 자기가 명시적으로 Log Group을 만들 때만 결심하는 게 아니기 때문이다. Lambda를 처음 띄우면 그 함수 이름의 Log Group이 자동으로 생기는데, 그것도 기본값이 Never다. 한 달에 100 GB가 들어오는 함수가 1년 동안 그대로 쌓이면 1.2 TB의 로그가 storage에 살아 있다. CloudWatch Logs storage는 GB와 월당 별도 단가를 매기므로(CloudWatch Logs 가격), US East 기준 $0.03/GB와 월로 계산하면 1.2 TB는 매월 $36.9를 storage에서 따로 청구한다. 함수가 더 이상 호출되지 않아도 청구서는 계속 자란다. "내가 안 쓰는 함수의 옛 로그가 매월 돈을 먹고 있다"는 상황이 생긴다.

PutRetentionPolicy가 받는 retentionInDays는 정해진 enum 값만 받는다. 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653. 총 22개 값이다. 0 같은 sentinel은 없다. 무한 보관으로 되돌리고 싶으면 별도 DeleteRetentionPolicy API를 부른다. 결국 retention 정책을 한 번도 정한 적 없는 그룹이 기본 Never이고, 그 상태가 청구서를 조용히 자라게 만드는 메커니즘이다. 운영 입장에서는 새 Log Group이 생기는 시점마다 retention을 동시에 정해 두는 자동화 한 줄(IaC, 또는 account-level default)이 거의 필수에 가깝다고 나는 본다.

16:9 가로 enum 다이어그램. 흰 배경, 둥근 모서리, 절제된 그림자. 위쪽에 'retentionInDays: fixed enum + Never expire is the default' 제목. 아래쪽에 'days' 라벨이 붙은 가로 시간축이 있고, 그 위로 슬레이트 그레이 #475569 둥근 알약 노드 17개를 비례 위치에 둔다. 각 알약에는 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653의 값을 적는다. 30 알약 옆에는 작은 에메랄드 그린 #10b981 체크 아이콘과 'common production default' 태그, 3653 알약 옆에는 '~10 years (max)' 태그를 붙인다. 축의 가장 오른쪽 바깥, 3653 너머에는 가는 슬레이트 점선으로 분리된 소프트 레드 #ef4444 둥근 박스 안에 큰 무한대 기호 ∞와 'Never expire: implicit default when retentionInDays is unset (or 0)' 라벨을 적는다. 축 아래에 슬레이트 캡션 'New Log Groups (including Lambda auto-created) start unset; bills grow silently until PutRetentionPolicy is called'을 표시한 프로페셔널 IT 다이어그램

Infrequent Access 로그 클래스. 2023년 11월에 GAIA 클래스ingestion이 50% 싸다. US East 기준 Standard $0.50/GB가 IA에서는 $0.25/GB다. 100 GB짜리 사후 분석용 로그를 IA로 옮기면 한 달 ingestion 비용이 $50에서 $25로 내려간다. 같은 데이터에 단가가 절반이 붙는다.

대신 못 쓰는 기능이 명확하다. Live Tail, metric filters(메트릭 추출과 그 위에 거는 알람), Embedded Metric Format, Subscription filters. 이 넷은 IA에서 동작하지 않는다. 반대로 Logs Insights 쿼리는 IA에서도 같은 가격으로 그대로 들어가고(AWS는 일부 명령에 제약을 둔다), storage 비용도 같으며, 민감정보 마스킹(sensitive data protection)도 동일하게 동작한다. 이걸 한 줄 표로 묶으면 결심이 쉽다. 운영 알람이 걸려야 하거나 다음 hop으로 보내야 하는 로그는 Standard, 사후 분석에만 보는 로그는 IA다. 두 가지로 분류해 두면 같은 데이터 양에 청구서가 절반 가까이 줄어든다. 한 가지 단서. 한 번 만든 Log Group의 클래스는 변경할 수 없다. 새로 만들 때 결심해야 하고, 잘못 정했으면 새 Log Group을 만들어 옮기는 수밖에 없다.

Subscription Filter 한도. 한 Log Group에 subscription filter2개까지만 붙일 수 있다. 이 한도는 firm이라 늘릴 수 없다. 그래서 Lambda에 한 개, Kinesis Firehose에 한 개 걸어 두면 그 Log Group은 끝이다. 세 번째 다음 hop이 필요한 시나리오, 예를 들어 Datadog으로도 보내고 S3로도 archive하고 Slack 알람도 띄우고 싶을 때는, 흔히 Kinesis 한 개로 모은 다음 거기서 다시 fan-out하는 패턴으로 우회한다.

2024년 1월에 AWS는 **account-level subscription filter**를 추가했다. region당 하나, 모든 Log Group을 한 묶음으로 다음 hop에 보낸다. multi-account 관측 토폴로지를 짤 때 큰 도움이 되는 옵션인데, 본격적인 이야기는 따로 다룬다.

Retention을 정하는 한 줄

새 Log Group을 만들 때 retentionInDays를 같이 정한다. 그게 운영 결심의 첫 줄이다. 다음 줄은 로그 메시지를 JSON으로 쓰겠다는 결심. 그 다음 줄은 이 로그가 운영 알람에 쓰일지 사후 분석에만 쓰일지를 구분해 Standard와 IA로 나누는 결심이다. 셋 다 코드 한 줄, IaC 한 줄이지만 셋 중 하나라도 빠지면 청구서가 다른 형태로 자란다. 셋이 같이 있어야 예상한 형태가 된다.

CloudWatch Logs는 결국 {timestamp, message} 두 필드짜리 그릇이고, 그 그릇을 결심으로 채우는 게 운영의 시작이다. 두 필드를 그대로 두면 자유 텍스트와 무한 보관, Standard 클래스라는 가장 비싼 기본값에 머무른다. 그 채워진 그릇에 어떻게 쿼리를 발행하는지, Logs Insights는 뒤에서 짚는다.

참고 자료

YouTube 영상

채널 보기
AI를 위한 선형대수학 - 소개 | 선형대수학
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
마지막편, 트라이 노드를 50% 이상 줄이는 방법? 압축 트라이 성능 분석 | Trie 자료구조 이야기
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학
AI 추천 시스템의 원리, 벡터 사이의 각도와 코사인 유사도 | 선형대수학