Skip to content

[Feat/#29] SQS, DLQ를 통한 실패 추적 파이프라인 구현 - #30

Merged
jeong1112 merged 10 commits into
developfrom
feat/#29
Aug 10, 2026
Merged

jeong1112 merged 10 commits into
developfrom
feat/#29

Conversation

@jeong1112

Copy link
Copy Markdown
Contributor

Related Issue 🚀

Work Description ✏️

기존에 SNS를 통해 실패를 추적하는 구조였지만, record 처리 중 발생한 예외를 내부에서 catch하고 최종적으로 성공 응답을 반환하는 구조여서 개별 실패 이벤트 처리가 실패해도 SNS 입장에서는 Lambda 호출이 성공한 것으로 볼 수 있어, 재시도나 DLQ로 이어지지 않을 수 있었습니다.
따라서 실패 이벤트를 명시적으로 큐에 저장하고, 처리 실패한 메시지만 재시도/DLQ로 이동시키는 구조가 필요하다고 판단하여 SQS와 DLQ를 도입했습니다.

작업 내용

  • 푸시 실패 이벤트 처리 구조를 SNS -> Lambda에서 SNS -> SQS -> Lambda로 변경
  • 실패 이벤트용 SQS Queue와 DLQ 추가
  • SNS Failure Topic이 SQS Queue로 메시지를 전달하도록 subscription 및 queue policy 추가
  • SQS batch item failure 응답을 사용해 실패한 메시지만 재시도되도록 처리
  • 푸시 실패 및 실패 처리 오류를 Slack webhook으로 알림
  • 로컬 테스트용 SQS 이벤트 샘플 추가

기존 구조

SNS Push Failure Topic
        │
        ▼
SnsHandler Lambda
        │
        ├──▶ DynamoDB 실패 history 기록
        │
        ├──▶ DynamoDB token/user 삭제
        │
        ├──▶ SNS endpoint 삭제
        │
        └──▶ SNS topic unsubscribe

변경된 구조

SNS Push Failure Topic
         │
         ▼
SQS PushFailureQueue
         │
         ▼
   SqsHandler Lambda
         │
         ├──▶ DynamoDB 실패 history 기록
         ├──▶ Endpoint Cleanup
         └──▶ Slack 실패 알림
         │
         │ 처리 실패
         ▼
      SQS Retry
         │
         │ 반복 실패
         ▼
       SQS DLQ

@jeong1112 jeong1112 self-assigned this Aug 10, 2026
@jeong1112 jeong1112 added 🎁 feature 새로운 기능을 개발하거나 추가, 변경할 경우 size/S labels Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jeong1112, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9257a6de-459c-4135-8a6c-c1da5b83595e

📥 Commits

Reviewing files that changed from the base of the PR and between b9d79ab and 6232cf7.

📒 Files selected for processing (3)
  • src/main/java/com/sopt/push/lambda/SqsHandler.java
  • src/main/java/com/sopt/push/service/SlackAlertService.java
  • template.yaml

Summary by CodeRabbit

  • 새 기능

    • 푸시 전송 실패 이벤트를 안정적으로 처리하고, 실패한 메시지만 재처리할 수 있습니다.
    • 토큰 누락·미등록·잘못된 엔드포인트를 구분해 기록하고 필요한 정리를 수행합니다.
    • 처리 실패 및 푸시 실패 상황을 Slack 알림으로 전달합니다.
    • 실패 이벤트 보관을 위한 대기열과 재처리 흐름을 추가했습니다.
  • 문서

    • 프로젝트 구조, 빌드·테스트 방법, 코딩 규칙 및 보안 설정 지침을 추가했습니다.
    • 푸시 실패 이벤트 예시를 제공합니다.

Walkthrough

SNS 기반 푸시 실패 처리를 SQS와 DLQ 기반 파이프라인으로 변경했습니다. SqsHandler가 배치 레코드를 처리하고, SlackAlertService가 실패 알림을 전송합니다. 환경 변수, IAM 권한, 이벤트 샘플과 프로젝트 지침도 추가했습니다.

Changes

푸시 실패 처리 파이프라인

Layer / File(s) Summary
SQS 및 Lambda 파이프라인 구성
template.yaml, events/sqs-push-failure.json, AGENTS.md
SNS-to-SQS 구독, SQS 큐, DLQ, IAM 권한과 SQS 트리거를 추가했습니다. 배치 크기 10과 부분 배치 실패 보고를 활성화했습니다. 이벤트 샘플과 프로젝트 운영 지침을 추가했습니다.
Slack 알림 서비스와 환경 설정
src/main/java/com/sopt/push/config/EnvConfig.java, src/main/java/com/sopt/push/service/SlackAlertService.java, src/main/java/com/sopt/push/config/AppFactory.java, template.yaml
Slack 웹훅 URL을 선택적으로 읽습니다. SlackAlertService를 생성하고 AppFactory에서 제공합니다. Block Kit 알림, 토큰 마스킹, 예외 및 비정상 응답 처리를 추가했습니다.
SQS 레코드별 실패 처리
src/main/java/com/sopt/push/lambda/SqsHandler.java
SQS 레코드와 중첩 Message payload를 해석합니다. 토큰 상태에 따라 실패 이력 생성, 엔드포인트 정리, Slack 알림을 수행합니다. 레코드별 예외를 BatchItemFailure로 반환합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

당근을 문 토끼가 큐를 타고,
실패 소식을 Slack에 전해요.
SQS는 차곡차곡 받고,
DLQ는 놓친 메시지를 품어요.
Lambda가 토큰을 살피면,
알림도 깡총, 처리는 착착!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 SQS와 DLQ를 이용한 실패 추적 파이프라인 구현이라는 주요 변경 사항을 명확하게 요약합니다.
Description check ✅ Passed 관련 이슈, 작업 내용, 기존 구조와 변경 구조를 포함해 템플릿의 필수 내용을 대부분 충족합니다.
Linked Issues check ✅ Passed SQS, DLQ, SNS-SQS 연동, 부분 배치 실패 처리, Slack 알림을 구현해 이슈 #29의 목표를 충족합니다.
Out of Scope Changes check ✅ Passed AGENTS.md와 테스트 이벤트를 포함한 모든 변경 사항이 실패 추적 파이프라인 구현 목표와 직접 관련됩니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#29

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
AGENTS.md (2)

7-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

새 SQS 경로를 문서에 반영하십시오.

이 PR은 SqsHandler와 events/sqs-push-failure.json을 추가합니다. 현재 설명은 SNS 기반 구조만 기술합니다.

📝 제안 변경
-- `lambda/`: handlers for API Gateway, EventBridge, and SNS.
+- `lambda/`: handlers for API Gateway, EventBridge, and SQS.
 - `service/`, `repository/`, `client/`: business logic, DynamoDB access, and AWS SDK providers.
 - `dto/`, `domain/`, `enums/`, `common/`, `util/`, `config/`: request models, persistence models, shared constants, utilities, and wiring.
 - `src/main/resources/logback.xml`: logging configuration.
-- `events/`: SAM payloads for API Gateway, SNS, and EventBridge.
+- `events/`: SAM payloads for API Gateway, SNS, SQS, and EventBridge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 7 - 11, Update the AGENTS.md project structure
description to document the new SQS path and events/sqs-push-failure.json
payload, alongside the existing lambda handlers and event fixtures. Ensure the
description reflects both SNS and SQS-based flows without removing the existing
entries.

25-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

새 SQS 핸들러 실행 예시를 추가하십시오.

template.yaml의 SnsHandlerFunction은 SQS PushFailureQueue로 전달되므로, events/sns-event-single.json 예시는 실제 이벤트와 다릅니다. AGENTS.md 25-26 번 줄에 events/sqs-push-failure.json 기반 예시와 SQS 핸들러 테스트 스크립트 경로도 함께 기록하세요. 기존 test-sns-handler.sh는 SNS 이벤트 파일만 지원하므로 새 SQS 경로도 별도 스크립트 또는 인수 지원으로 다뤄야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 25 - 26, Update the AGENTS.md local testing examples
to use events/sqs-push-failure.json for the SQS PushFailureQueue flow, and
document the corresponding SQS handler test script path. Add or extend the
testing script so it supports invoking the SQS handler with that event, while
preserving the existing SNS test example and behavior.
template.yaml (3)

228-232: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

논리 ID SnsHandlerFunction이 실제 SQS 핸들러와 일치하지 않습니다.

FunctionName과 Handler는 SQS로 변경했지만, 리소스 논리 ID는 SnsHandlerFunction으로 남아 있습니다. 이후 템플릿을 읽는 사람이 트리거 종류를 잘못 이해할 수 있습니다.

논리 ID를 변경하면 CloudFormation이 리소스를 교체합니다. 이번 PR에서 FunctionName이 이미 변경되어 교체가 발생하므로, 같은 배포에서 논리 ID도 함께 정리하는 편이 비용이 낮습니다.

또한 AGENTS.md 25번 줄의 sam local invoke SnsHandlerFunction 예시와 새 이벤트 파일 events/sqs-push-failure.json의 조합도 함께 갱신하십시오.

♻️ 제안 변경
-  SnsHandlerFunction:
+  SqsHandlerFunction:
     Type: AWS::Serverless::Function
     Properties:
       FunctionName: !Sub "sopt-push-notification-lambda-sqs-${Stage}"
       Handler: com.sopt.push.lambda.SqsHandler::handleRequest
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template.yaml` around lines 228 - 232, Rename the CloudFormation logical ID
SnsHandlerFunction to reflect the SQS handler while preserving its current
FunctionName and Handler settings. Update the AGENTS.md invocation example to
use the new logical ID, and ensure it invokes the events/sqs-push-failure.json
event fixture.

149-153: 📐 Maintainability & Code Quality | 🔵 Trivial

DLQ 알람 추가를 검토하십시오.

PushFailureDeadLetterQueue는 maxReceiveCount: 5 초과 메시지를 받습니다. 현재 템플릿에는 DLQ 적재를 감지하는 알람이 없습니다. 메시지가 DLQ에 쌓여도 운영자가 인지하지 못합니다.

ApproximateNumberOfMessagesVisible 메트릭에 대한 CloudWatch 알람을 추가하십시오. 이 파이프라인의 목적이 실패 추적이므로, DLQ 적재 자체가 중요한 신호입니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template.yaml` around lines 149 - 153, Add a CloudWatch alarm for
PushFailureDeadLetterQueue using the ApproximateNumberOfMessagesVisible metric,
so any messages accumulating in the DLQ trigger operational notification.
Configure the alarm consistently with existing template alarms and reference the
queue’s name or dimensions correctly.

33-38: 🔒 Security & Privacy | 🔵 Trivial | ⚖️ Poor tradeoff

Slack 웹훅 URL을 Lambda 환경 변수 평문으로 전달합니다.

NoEcho: true는 CloudFormation 스택 파라미터 출력만 가립니다. Lambda 환경 변수 값은 콘솔과 lambda:GetFunctionConfiguration 응답에서 평문으로 노출됩니다. Slack 인커밍 웹훅 URL은 그 자체가 인증 자격 증명입니다.

가능하면 SSM Parameter Store SecureString 또는 Secrets Manager에 저장하고, 환경 변수에는 파라미터 이름만 전달하십시오. 이 경우 EnvConfig는 이름을 읽고 SlackAlertService가 값을 조회하도록 변경해야 합니다.

즉시 도입이 어려우면 최소한 실행 역할과 lambda:GetFunctionConfiguration 권한 범위를 좁게 유지하십시오.

위 판단은 "Do not commit AWS credentials, real device tokens, production ARNs, or local params-dev.json files containing secrets; keep environment-specific values in AWS/SAM parameters" 가이드라인의 시크릿 취급 원칙에 근거합니다.

Also applies to: 55-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template.yaml` around lines 33 - 38, Stop passing SlackFailureWebhookUrl as a
plaintext Lambda environment value. Store the webhook URL in SSM Parameter Store
SecureString or Secrets Manager, pass only its parameter or secret name through
the environment, and update EnvConfig and SlackAlertService to resolve the value
at runtime using the Lambda execution role. Remove the direct webhook URL
parameter flow while preserving Slack failure alert behavior.

Source: Coding guidelines

src/main/java/com/sopt/push/lambda/SqsHandler.java (2)

119-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

17개 위치 인자 생성자 호출의 가독성이 낮습니다.

CreateHistoryDto 생성자에 13개의 null을 위치로 전달합니다. 인자 순서가 하나만 어긋나도 컴파일 오류 없이 잘못된 필드에 값이 저장됩니다. HistoryService.createLog가 이 값을 그대로 DynamoDB에 저장하므로 오류를 발견하기 어렵습니다.

CreateHistoryDto에 빌더 또는 실패 이력 전용 정적 팩터리 메서드를 추가하십시오. 예: CreateHistoryDto.pushFailure(id, userIds, messageIds).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/sopt/push/lambda/SqsHandler.java` around lines 119 - 143,
CreateFailureLog의 위치 인자 기반 CreateHistoryDto 생성자를 제거하고, CreateHistoryDto에 실패 이력
전용 정적 팩터리 메서드(예: pushFailure) 또는 빌더를 추가해 필요한 id, 사용자 ID, 메시지 ID와 실패 상태·푸시 타입만
명시적으로 설정하도록 변경하십시오. createFailLog에서는 해당 명시적 생성 경로를 사용하고 기존
HistoryService.createLog 호출은 유지하십시오.

103-117: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

파싱 불가 메시지가 5회 재시도됩니다.

extractFailureMessage는 objectMapper.readTree에서 예외를 던질 수 있습니다. handleRequest는 이 예외를 배치 실패로 보고합니다. 본문 형식이 잘못된 메시지는 재시도해도 절대 성공하지 않습니다. maxReceiveCount: 5에 도달할 때까지 5회 재처리되고, 그때마다 notifyProcessingFailure가 Slack 알림을 보냅니다.

파싱 오류처럼 재시도가 무의미한 오류는 배치 실패로 보고하지 않고 즉시 DLQ 대상으로 분류하거나, 로그와 1회 알림 후 메시지를 성공 처리하는 방식을 검토하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/sopt/push/lambda/SqsHandler.java` around lines 103 - 117,
Update extractFailureMessage and its handleRequest caller so JSON parsing
failures from record bodies are treated as non-retryable: classify the message
for DLQ handling or, per the existing design, log and notify once before
acknowledging it successfully. Ensure malformed messages do not propagate as
batch failures or trigger repeated notifyProcessingFailure calls across retries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@events/sqs-push-failure.json`:
- Line 6: Update the sample payload values in the event body: replace the Token
value with an explicitly non-real example such as example-device-token, and
replace the Korean free-text MessageId with an identifier-formatted example
suitable for SqsHandler.extractFailureMessage and createFailLog.messageIds.

In `@src/main/java/com/sopt/push/lambda/SqsHandler.java`:
- Around line 97-101: Prevent duplicate failure histories on SQS retries by
making the failure-log write in processRecord idempotent, using the SQS
messageId or another deterministic identifier instead of UUID.randomUUID() and
Instant.now() as the record key. Update createFailLog and the underlying
HistoryService.createLog flow to reuse that deterministic key, while preserving
the existing cleanup and Slack notification behavior.

In `@src/main/java/com/sopt/push/service/SlackAlertService.java`:
- Around line 34-53: Update notifyPushFailure and notifyProcessingFailure to
build their field maps with LinkedHashMap in the declared insertion order
instead of Map.of, and add the required java.util.LinkedHashMap import. Preserve
the existing field names, values, and ordering shown in each method so
createPayload produces stable Slack field layouts.
- Around line 55-80: Update SlackAlertService.send() and the
SqsHandler.processRecord() alert flow so alerts are aggregated per SQS batch or
dispatched asynchronously instead of performing one blocking httpClient.send()
call per record. Ensure batch processing does not accumulate up to ten HTTP
request timeouts and preserves failure-log handling without duplicate records on
retry.

In `@template.yaml`:
- Around line 155-163: PushFailureQueue의 VisibilityTimeout을 연결된 함수 timeout의 6배
이상으로 조정하십시오. 현재 Function.Timeout이 60초이므로 VisibilityTimeout을 최소 360초로 설정하고, 기존 큐
처리 및 RedrivePolicy 설정은 유지하십시오.

---

Nitpick comments:
In `@AGENTS.md`:
- Around line 7-11: Update the AGENTS.md project structure description to
document the new SQS path and events/sqs-push-failure.json payload, alongside
the existing lambda handlers and event fixtures. Ensure the description reflects
both SNS and SQS-based flows without removing the existing entries.
- Around line 25-26: Update the AGENTS.md local testing examples to use
events/sqs-push-failure.json for the SQS PushFailureQueue flow, and document the
corresponding SQS handler test script path. Add or extend the testing script so
it supports invoking the SQS handler with that event, while preserving the
existing SNS test example and behavior.

In `@src/main/java/com/sopt/push/lambda/SqsHandler.java`:
- Around line 119-143: CreateFailureLog의 위치 인자 기반 CreateHistoryDto 생성자를 제거하고,
CreateHistoryDto에 실패 이력 전용 정적 팩터리 메서드(예: pushFailure) 또는 빌더를 추가해 필요한 id, 사용자 ID,
메시지 ID와 실패 상태·푸시 타입만 명시적으로 설정하도록 변경하십시오. createFailLog에서는 해당 명시적 생성 경로를 사용하고 기존
HistoryService.createLog 호출은 유지하십시오.
- Around line 103-117: Update extractFailureMessage and its handleRequest caller
so JSON parsing failures from record bodies are treated as non-retryable:
classify the message for DLQ handling or, per the existing design, log and
notify once before acknowledging it successfully. Ensure malformed messages do
not propagate as batch failures or trigger repeated notifyProcessingFailure
calls across retries.

In `@template.yaml`:
- Around line 228-232: Rename the CloudFormation logical ID SnsHandlerFunction
to reflect the SQS handler while preserving its current FunctionName and Handler
settings. Update the AGENTS.md invocation example to use the new logical ID, and
ensure it invokes the events/sqs-push-failure.json event fixture.
- Around line 149-153: Add a CloudWatch alarm for PushFailureDeadLetterQueue
using the ApproximateNumberOfMessagesVisible metric, so any messages
accumulating in the DLQ trigger operational notification. Configure the alarm
consistently with existing template alarms and reference the queue’s name or
dimensions correctly.
- Around line 33-38: Stop passing SlackFailureWebhookUrl as a plaintext Lambda
environment value. Store the webhook URL in SSM Parameter Store SecureString or
Secrets Manager, pass only its parameter or secret name through the environment,
and update EnvConfig and SlackAlertService to resolve the value at runtime using
the Lambda execution role. Remove the direct webhook URL parameter flow while
preserving Slack failure alert behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fefa7bbd-0815-4112-aa59-9fd1e7932819

📥 Commits

Reviewing files that changed from the base of the PR and between 0f87ef2 and b9d79ab.

📒 Files selected for processing (7)
  • AGENTS.md
  • events/sqs-push-failure.json
  • src/main/java/com/sopt/push/config/AppFactory.java
  • src/main/java/com/sopt/push/config/EnvConfig.java
  • src/main/java/com/sopt/push/lambda/SqsHandler.java
  • src/main/java/com/sopt/push/service/SlackAlertService.java
  • template.yaml

Comment thread events/sqs-push-failure.json
Comment thread src/main/java/com/sopt/push/lambda/SqsHandler.java Outdated
Comment thread src/main/java/com/sopt/push/service/SlackAlertService.java Outdated
Comment thread src/main/java/com/sopt/push/service/SlackAlertService.java Outdated
Comment thread template.yaml
- cleanup 실패로 SQS 재시도될 때 실패 history가 최대 5번 중복 생성되는 문제를 줄임
- token이 이미 DB에 없는 경우는 userId 없이 실패 이력을 남기도록 처리
@jeong1112
jeong1112 merged commit cd26656 into develop Aug 10, 2026
2 checks passed
@jeong1112
jeong1112 deleted the feat/#29 branch August 10, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎁 feature 새로운 기능을 개발하거나 추가, 변경할 경우 size/S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 실패 추적을 위한 SQS, DLQ 도입

1 participant