open atlas
↑ К треку
AWS на практике AWS · 05 · 04

Логи в масштабе: Logs Insights, подписки-пайплайны и счёт

Логи в масштабе: Logs Insights тарифицируется по сканированным ГБ — сужай окно; структурированный JSON делает логи запрашиваемыми; subscription filters везут события в дешёвый поиск; а ingest, не storage, — счёт, который DEBUG раздувает в 10-100x за ночь.

AWS Senior ◷ 19 min
Уровень
ОсновыJuniorMiddleSenior

Поздним вечером в пятницу ты мержишь однострочное изменение: переключаешь prod-уровень логов на DEBUG, чтобы поймать плавающий баг в checkout. Это срабатывает — баг находишь в понедельник и откатываешь. Через четыре недели финансы пишут в канал: счёт AWS вырос, и вся дельта — это CloudWatch Logs. Не storage — ingest. Три недели каждый запрос логировал полное тело, заголовки и десяток строк DEBUG, а CloudWatch Logs тарифицирует за каждый принятый ГБ (около $0.50/ГБ) ещё до того, как ты сохранишь или запросишь хоть байт. Сервис, делающий 2 ТБ/месяц логов на DEBUG вместо 200 ГБ на INFO, — это строка ingest в 10x, которую никто не заметил, потому что всплеск приходит месяцем позже, на чужой дашборд. Хуже того, у лог-групп был дефолтный Never Expire, так что каждый из этих debug-байтов ещё и накапливает storage вечно. Фикс не геройский — политики retention, уровень логов, правило сэмплирования, — но урок таков: в масштабе логи — это и поверхность стоимости, и поверхность запросов, и обе нужно эксплуатировать осознанно. Этот урок — о том, как запрашивать логи, не сжигая деньги, везти их куда-то дешевле для поиска и не дать счёту за ingest застать тебя врасплох.

Logs Insights: запросы к лог-группам по требованию, тарификация по сканированным байтам

CloudWatch Logs Insights — это специализированный язык запросов, который сканирует одну или несколько лог-групп за выбранный диапазон времени и возвращает строки. Грамматика мала и композируема: fields выбирает колонки, filter оставляет подходящие события, stats ... by агрегирует, parse извлекает структуру из строки, sort и limit формируют вывод. Что делает его мощным — это автообнаружение: если твои логи — структурированный JSON, Insights автоматически обнаруживает каждый ключ верхнего уровня как запрашиваемое поле. Так что level, errorCode, requestId и числовой durationMs все адресуемы без объявления схемы — можно stats count(*) by errorCode или вычислить настоящий pct(durationMs, 99) (p99) прямо из поля.

Механизм, управляющий стоимостью и скоростью, — один и тот же: Insights тарифицирует за каждый сканированный ГБ данных логов на запрос (примерно $0.005/ГБ скана, иллюстративно, зависит от региона), и латентность запроса определяется тем, сколько байтов ему пришлось прочитать. filter не экономит стоимость скана так, как это сделал бы индекс — Insights всё равно читает события в диапазоне времени, чтобы вычислить фильтр; скан ограничивают окно времени и какие лог-группы ты указываешь. Так что запрос по @message за полгода и сорок лог-групп может сканировать терабайты, стоить реальных денег и занимать минуты. Сеньорская привычка — жёстко сужать окно (начни с 15 минут вокруг инцидента, расширяй только при нужде) и запрашивать наименьший набор групп, способный содержать ответ.

-- Триаж инцидента: топ кодов ошибок за последние 30 минут (узкое окно = малый скан)
fields @timestamp, level, errorCode, requestId
| filter level = "ERROR"
| stats count(*) as n by errorCode
| sort n desc
| limit 20

-- Самые медленные эндпоинты по p99, вычисляемому из числового поля
fields route, durationMs
| filter ispresent(durationMs)
| stats pct(durationMs, 99) as p99, count(*) as calls by route
| sort p99 desc
| limit 10

-- Прослеживаем один запрос от начала до конца через весь трейс по correlation id
fields @timestamp, level, service, msg, durationMs
| filter requestId = "req-7f3a91c2"
| sort @timestamp asc

Архитектура логов: группы, потоки и почему структура бьёт свободный текст

Лог-группа — это единица retention, политики доступа и подписки — всё в ней разделяет эти настройки. Лог-поток — одна append-only последовательность событий из одного источника внутри группы: одно окружение выполнения Lambda, один контейнер, один хост. Запрашиваешь и настраиваешь на уровне группы; поток — просто место, куда физически ложатся байты. (Урок 01 разобрал, что лог-группа есть; здесь это важно, потому что retention и подписки крепятся к группе, а стоимость скана запроса — это байты группы в окне.)

Когда ты отвечаешь за лог-хозяйство десятков сервисов, выбор схемы лога — это выбор между запрашиваемым корпусом и непоисковываемой грудой строк. Что отделяет одно от другого — это дисциплина схемы. Эмить структурированный JSON с единообразной формой — level, timestamp, requestId/traceId, service и типизированные поля вроде durationMs и errorCode — и Insights с любым downstream-индексом поиска трактуют каждый ключ как полноценное поле. Протяни correlation ID (тот же requestId или traceId) от точки входа через каждый downstream-вызов (урок 02 связал это с трейсами), и единственный filter requestId = "..." реконструирует весь запрос через сервисы. Анти-паттерн — логирование свободным текстом: logger.info("user " + id + " did " + action + " in " + ms + "ms"). Теперь каждый запрос — это regex по @message, p99 означает писать хрупкий parse под каждый формат, а корреляция — надежда. Свободный текст дёшев в записи и разорителен в эксплуатации в масштабе.

Почему это работает

Почему filter не делает Insights дешёвым так, как это делает индекс в базе данных? Потому что у Insights нет индекса. Это движок scan-on-read: для каждого запроса он стримит сырые события за диапазон времени из хранилища лог-группы и вычисляет твой конвейер над ними. Стадия filter выполняется во время этого скана — она выбрасывает несовпадающие строки из результата, но байты уже прочитаны и уже оплачены. Это противоположность базе данных, где индекс позволяет трогать только совпадающие строки. Практическое следствие: две ручки, которые реально режут стоимость Insights, — это окно времени (меньше байтов в диапазоне) и набор лог-групп (меньше прочитанных групп). Добавление новых filter-клауз заостряет ответ, но почти ничего не даёт счёту. Если ты ловишь себя на повторном прогоне одного и того же широкого скана ради счётчика — это сигнал заменить его на metric filter, который вычисляет счёт один раз на этапе ingest и стоит ноль на чтении.

Subscription filters и пайплайны: вези логи туда, где искать дешевле

Subscription filter крепится к лог-группе и стримит каждое подходящее событие, в near-real-time, в назначение. Паттерн может быть пустым (всё) или совпадать со структурным термином ({ $.level = "ERROR" }). Три канонических назначения: Lambda (трансформировать, обогатить, маршрутизировать или алармить на каждое событие), Kinesis Data Streams / Firehose (буферизовать поток событий и доставлять пачками) и Amazon OpenSearch (полнотекстовый поиск плюс дашборды). Подписки — это ещё и то, как делается межаккаунтная, межрегиональная агрегация: лог-группы каждого spoke-аккаунта подписываются на назначение в центральном logging-аккаунте, так что логи всех команд ложатся в одно поисковое место с одной политикой доступа и одним режимом retention.

Это архитектурный аварийный люк из кривой стоимости CloudWatch Logs. CloudWatch Logs удобен — это куда всё ложится по умолчанию, — но хранить и искать большие объёмы в CloudWatch дорого. Subscription filter → Firehose → S3, запрашиваемый через Athena, даёт долговечное, дешёвое, долгосрочное хранилище логов, которое ты держишь за копейки за ГБ и платишь только за сканированный запрос в Athena. Или subscription filter → OpenSearch даёт Kibana-стиль полнотекстового поиска и дашборды на горячее окно. Компромисс явен: CloudWatch Logs меняет стоимость на нулевую настройку и удобство; S3+Athena и OpenSearch меняют настройку и операционное владение на стоимость на порядок ниже в масштабе. Обычная сеньорская форма — и то, и другое: короткий горячий retention в CloudWatch (14–30 дней) для триажа в Insights плюс подписка, которая ответвляет всё в S3 для дешёвого длинного хвоста.

# Стримим все события лог-группы в Firehose → S3 (дешёвое долгосрочное хранилище)
aws logs put-subscription-filter \
  --log-group-name /myapp/checkout \
  --filter-name ship-to-s3 \
  --filter-pattern "" \
  --destination-arn arn:aws:firehose:us-east-1:111122223333:deliverystream/logs-to-s3 \
  --role-arn arn:aws:iam::111122223333:role/CWLtoFirehoseRole

# Или стримим только события ERROR в OpenSearch через трансформирующую Lambda (горячий полнотекст)
aws logs put-subscription-filter \
  --log-group-name /myapp/checkout \
  --filter-name errors-to-opensearch \
  --filter-pattern '{ $.level = "ERROR" }' \
  --destination-arn arn:aws:lambda:us-east-1:111122223333:function:ship-to-opensearch

Счёт за ingest: как флаг DEBUG раздувает расход на логи в 10-100 раз

CloudWatch Logs берёт деньги по трём осям, а люди смотрят только на неправильную. Ingestion тарифицируется за каждый втолкнутый ГБ (~$0.50/ГБ, иллюстративно) — это доминирующий рычаг, и он срабатывает в момент прихода байта, до любого storage или запроса. Storage тарифицируется за ГБ-месяц на то, что ты держишь. Insights scan тарифицируется за запрошенный ГБ. Так что failure mode пишет себя сам: включи DEBUG в prod или начни логировать каждое тело запроса и заголовок — и ingest может скакнуть в 10–100 раз за ночь. Счёт приходит месяцем позже, на финансовый дашборд, давно после того, как тот, кто переключил флаг, забыл о нём — ровно история «DEBUG в пятницу». А поскольку дефолтный retention — Never Expire, эти раздутые debug-байты ещё и накапливают storage вечно; никто не задал политику, так что ничего никогда не удаляется.

# Ограничиваем накопление storage: осознанный retention на лог-группу
aws logs put-retention-policy --log-group-name /myapp/checkout --retention-in-days 14

Долговечные фиксы, в порядке рычага: задай retention на лог-группу (например, 14–30 дней горячими, остальное архивируй в S3 через подписку выше), чтобы storage вышел на плато; опусти prod-уровень логов с DEBUG до INFO и сэмплируй высокообъёмные debug-строки (1 из N) вместо логирования всех; выбрасывай шумные поля до ingest (полные тела запросов, base64-блобы, избыточные заголовки); используй metric filter, чтобы считать вещи вместо повторных сканов Insights ради того же числа; и критически алармь на саму метрику ingest логов (IncomingBytes на лог-группу), чтобы скачок ingest в 10x запейджил кого-то в тот же день, а не в тот же месяц. Ingest — это рычаг: срез объёма у источника бьёт любую настройку storage.

Выбери лучший вариант

Твоя команда держит 18 месяцев логов горячими в CloudWatch Logs для редких compliance-запросов, плюс 30 дней для живого триажа. Счёт за Logs доминируют storage и широкие сканы Insights по старым данным. Нужно дешёвое долгосрочное хранение, по которому всё ещё можно искать, не теряя быстрого триажа на свежих логах. Выбери лучшую архитектуру.

Викторина

Ты запускаешь запрос Logs Insights с узким `filter errorCode = 'E_TIMEOUT'` за 6-месячное окно по всем своим лог-группам, и он медленный и неожиданно дорогой. Почему?

Вспомните перед уходом
  1. 01
    Что задаёт стоимость и скорость запроса Logs Insights и как держать его дешёвым?
  2. 02
    Почему ingest — доминирующая стоимость CloudWatch Logs и какие фиксы останавливают раздув от DEBUG?
Итог

Эксплуатация логов в масштабе — это две проблемы: поверхность запросов и поверхность стоимости, и обе нужно вести осознанно. Logs Insights — это маленький композируемый язык запросов (fields, filter, stats … by, parse, sort, limit), который сканирует лог-группы по требованию; структурированный JSON автообнаруживает каждый ключ как запрашиваемое поле, так что можно stats count(*) by errorCode или вычислить pct(durationMs, 99) напрямую. Но у Insights нет индекса: он тарифицирует за сканированный ГБ и читает каждое событие в окне, прежде чем фильтр выбросит несовпадающие строки, так что ручки, режущие стоимость и латентность, — это окно времени и набор лог-групп, а не фильтр — жёстко сужай, чтобы триажить инцидент. Лог-группа — единица retention, доступа и подписки; лог-поток — append-only последовательность одного источника внутри неё. Дисциплина схемы (level, timestamp, протянутый requestId/traceId, типизированные поля) — вот что делает логи запрашиваемыми и коррелируемыми через сервисы и трейсы; свободный текст вынуждает хрупкий regex и убивает корреляцию. Subscription filter стримит подходящие события в near-real-time в Lambda (трансформ/аларм), Kinesis/Firehose (буфер + доставка) или OpenSearch (полнотекстовый поиск) и это ещё и то, как ты агрегируешь межаккаунтно и межрегионально в центральный logging-аккаунт. Это побег из кривой стоимости CloudWatch: держи короткое горячее окно (14-30 дней) в CloudWatch для триажа в Insights и ответвляй всё через Firehose в S3 (запрашиваемый через Athena) для дешёвого долгосрочного поискового хранения или в OpenSearch для горячих полнотекстовых дашбордов — удобство против стоимости, и обычно и то, и другое. Сам счёт кусается на ingestion: CloudWatch Logs берёт за принятый ГБ (~$0.50/ГБ), за ГБ-месяц хранения и за сканированный ГБ, и доминирующий рычаг — ingest. Prod-флаг DEBUG или логирование полных тел запросов может раздуть ingest в 10-100x за ночь, всплеск всплывает месяцем позже, а дефолтный Never-Expire даёт этим байтам копить storage вечно. Чини это, задавая retention на группу, сэмплируя и выбрасывая шумные debug-логи, отбрасывая тяжёлые поля, используя metric filters вместо повторных сканов ради счётчиков и алармя на метрику IncomingBytes, чтобы следующий всплеск запейджил тебя в тот же день. Цены здесь иллюстративны и зависят от региона — сверяйся со страницей цен CloudWatch. Теперь, когда видишь медленный и дорогой запрос в Logs Insights, твой первый ход — сузить окно времени, а не добавить фильтры; а когда кто-то предлагает логировать полные тела запросов в проде, ты можешь назвать конкретную цифру того, сколько это будет стоить в следующем месяце.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.