AWS SQSλ₯Ό μ΄μš©ν•˜μ—¬ ν…Œλ„ŒνŠΈ 별 비동기 μž‘μ—… μ€„μ„Έμš°κΈ°

2025. 2. 2. 16:47ㆍBackend

졜근 νšŒμ‚¬μ—μ„œ 진행 쀑인 μž‘μ—…μ—μ„œ μ•„λž˜μ™€ 같은 μš”κ΅¬μ‚¬ν•­μ„ λ§Œλ‚˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

  • μ„œλΉ„μŠ€ λ‚΄μ—λŠ” 수 λ§Žμ€ ν…Œλ„ŒνŠΈκ°€ 쑴재
  • 각 ν…Œλ„ŒνŠΈ λ‚΄μ—μ„œλŠ” νŠΉμ • μ΄λ²€νŠΈκ°€ λ°œμƒν•˜κ³ , 이λ₯Ό μ²˜λ¦¬ν•˜λŠ” μ›Œμ»€ 둜직이 ν•„μš” (ν•œλ²ˆ 싀행에 500ms ~ 5s, ν…Œλ„ŒνŠΈ λ‚΄ 데이터 양에 λΉ„λ‘€)
  • 이벀트 λ°œμƒ λΉˆλ„λŠ” 높지 μ•ŠμŒ
  • 이 λ•Œ 이듀 μ›Œμ»€ λ‘œμ§μ€ ν…Œλ„ŒνŠΈ λ³„λ‘œ μ˜€λ‘œμ§€ ν•˜λ‚˜μ”©λ§Œ λ™μž‘ν•΄μ•Ό 함. (ν…Œλ„ŒνŠΈ λ‚΄ λ™μ‹œμ„± X)

ν•΄λ‹Ή κΈ€μ—μ„œλŠ” 이 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±μ‹œν‚€κΈ° μœ„ν•œ λ°©μ•ˆμ— λŒ€ν•΄ 닀뀄보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

졜초 κ΅¬ν˜„

μ΄ˆκΈ°μ—λŠ” 이 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±μ‹œν‚€κΈ° μœ„ν•΄, μ•„λž˜μ™€ 같은 λ°©ν–₯μ„±μœΌλ‘œ μž‘μ—…μ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. 각 ꡬ쑰에 λŒ€ν•œ μ„€λͺ…은 μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

image

  • μ„œλ²„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλŠ” μœ μ €μ˜ νŠΉμ • 행동에 따라 이벀트λ₯Ό λ°œν–‰ν•˜μ—¬ SQS둜 μ „λ‹¬ν•©λ‹ˆλ‹€.
  • μ „λ‹¬λœ μ΄λ²€νŠΈλŠ” 사전에 λ“±λ‘λœ AWS λžŒλ‹€λ₯Ό νŠΈλ¦¬κ±°ν•˜μ—¬ μ†ŒλΉ„λ©λ‹ˆλ‹€.
  • 이 λ•Œ λžŒλ‹€μ—μ„œλŠ” μž‘μ—…μ— μ•žμ„œ, ν…Œλ„ŒνŠΈ 별 μœ μΌμ„±μ„ 보μž₯ν•˜κΈ° μœ„ν•΄ ν…Œλ„ŒνŠΈ μ‹λ³„μžλ₯Ό ν†΅ν•œ 뢄산락을 μš”μ²­ν•©λ‹ˆλ‹€. 이 λ•Œ 뢄산락은 Mysql의 Named Lock을 톡해 κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • Named Lock을 λŒ€κΈ°ν•˜λ‹€κ°€ μ΅œλŒ€ λŒ€κΈ° μ‹œκ°„(10s)을 λ„˜μ–΄κ°€λŠ” μΌ€μ΄μŠ€λŠ” DLQλ₯Ό 톡해 큐에 λ‹€μ‹œ 넣어주도둝 κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

졜초 κ΅¬ν˜„ λ°©μ‹μ˜ 문제

μœ„μ²˜λŸΌ κ΅¬ν˜„ν•˜κ³ λ‚˜λ‹ˆ 크게 두가지 λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μ²«λ²ˆμ§ΈλŠ” λΉ„μš© λ¬Έμ œμž…λ‹ˆλ‹€.

μœ„ κ΅¬μ‘°μ—μ„œλŠ” μ΄λ²€νŠΈκ°€ 큐에 λ“€μ–΄κ°€κ³  λ‚˜λ©΄ 일단 λžŒλ‹€λ₯Ό λ„μš°κ³  κ±°κΈ°μ—μ„œ 뢄산락을 λŒ€κΈ°ν•˜λ©° ν…Œλ„ŒνŠΈ 별 λ™μ‹œμ„±μ„ μ œμ–΄ν•©λ‹ˆλ‹€. λžŒλ‹€μ˜ κ²½μš°μ—” μ‹€μ œ μ‹€ν–‰μ‹œκ°„μ— λΉ„λ‘€ν•˜μ—¬ λΉ„μš©μ΄ λ°œμƒν•˜κΈ° λ•Œλ¬Έμ—, μ‹€μ œ 둜직의 λ™μž‘ 외에 λΆˆν•„μš”ν•˜κ²Œ 락을 λŒ€κΈ°ν•˜λŠ” μ‹œκ°„μ΄ λͺ¨λ‘ λΉ„μš©μ— ν¬ν•¨λ˜κ²Œ λ©λ‹ˆλ‹€. μ΄λ²€νŠΈκ°€ μŠ€νŒŸμ„±μœΌλ‘œ 많이 λ°œμƒν•˜λŠ” μƒν™©μ—μ„œλŠ” 단 10초 κ°„ 싀행될 수 μžˆλŠ” 만큼의 이벀트만 μ†ŒλΉ„λ˜κ³ , λ‚˜λ¨Έμ§€ μ΄λ²€νŠΈλ“€μ€ μ „λΆ€ 10초 κ°„ λŒ€κΈ° ν›„ μž¬μ‹œλ„ν•˜κ²Œ λ˜λŠ”λ° λ™μ‹œμ„± μ œμ–΄λ₯Ό μœ„ν•œ 락 λŒ€κΈ°μ— 거의 λͺ¨λ“  μ‹œκ°„μ΄ μ†ŒλΉ„λ˜μ–΄ λΉ„μš©μ μΈ μΈ‘λ©΄μ—μ„œ μ—„μ²­λ‚œ λ‚­λΉ„κ°€ λ°œμƒν•©λ‹ˆλ‹€.

λ‘λ²ˆμ§ΈλŠ” 혼작 μ œμ–΄ λ¬Έμ œμž…λ‹ˆλ‹€.

μœ„ κ΅¬μ‘°μ—μ„œλŠ” λ™μ‹œμ— λ°œμƒν•œ μ΄λ²€νŠΈλ“€ 쀑, 락을 νšλ“ν•œ 단 ν•˜λ‚˜μ˜ ν”„λ‘œμ„ΈμŠ€λ₯Ό μ œμ™Έν•˜λ©΄ μž¬μ‹œλ„ λ‹¨κ³„μ—μ„œ κ·ΈλŒ€λ‘œ λ™μΌν•œ κ²½μŸμžλ“€κ³Ό 경합이 λ°œμƒν•©λ‹ˆλ‹€. μ΄λŠ” μœ„μ—μ„œ μ–ΈκΈ‰ν•œ λΉ„μš© 문제λ₯Ό λ”μš± μ‹¬ν™”μ‹œν‚΅λ‹ˆλ‹€.

ν•œλ²ˆ κ°œμ„ λœ κ΅¬ν˜„ λ°©μ•ˆ

λ‹€μŒμ€ 졜초 κ΅¬ν˜„μ—μ„œ λ°œμƒν•˜λ˜ 문제λ₯Ό ν•΄μ†Œν•œ κ΅¬μ‘°μž…λ‹ˆλ‹€. 적용된 μ•„μ΄λ””μ–΄λŠ” μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

image

  • 각 μ΄λ²€νŠΈλ“€μ€ 이벀트 νŽ˜μ΄λ‘œλ“œ 내에 μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό κ°–μŠ΅λ‹ˆλ‹€.
  • 이벀트둜 인해 트리거된 λžŒλ‹€ μ›Œμ»€μ—μ„œ 뢄산락 νšλ“μ„ μ‹œλ„λŠ” ν•˜λ‚˜, λŒ€κΈ°λŠ” ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • 뢄산락 νšλ“μ— μ‹€νŒ¨ν•œ μ΄λ²€νŠΈλ“€μ€ νŽ˜μ΄λ‘œλ“œ λ‚΄ μž¬μ‹œλ„ 횟수λ₯Ό ν•˜λ‚˜ κ°μ†Œμ‹œν‚¨μ±„ 큐에 λ‹€μ‹œ λ°€μ–΄λ„£μŠ΅λ‹ˆλ‹€.
  • 이 λ•Œ SQS Delay Seconds μ˜΅μ…˜μ„ λ„μž…ν•˜μ—¬ 뢄산락 νšλ“ ν”„λ‘œμ„ΈμŠ€μ˜ 둜직이 λλ‚œ λ’€ λžŒλ‹€μ— 전달될 수 μžˆλ„λ‘ 텀을 κ°€μ§‘λ‹ˆλ‹€.
  • μž¬μ‹œλ„ νšŸμˆ˜κ°€ λŠ˜μ–΄λ‚ μˆ˜λ‘ ν˜„μž¬ ν•΄λ‹Ή ν…Œλ„ŒνŠΈμ— λŒ€ν•œ μ΄λ²€νŠΈκ°€ μ „λ°˜μ μœΌλ‘œ ν˜Όμž‘ν•œ 것이기 λ•Œλ¬Έμ—, μž¬μ‹œλ„ νšŸμˆ˜κ°€ λŠ˜μ–΄λ‚ μˆ˜λ‘ λŒ€κΈ° μ‹œκ°„μ΄ μ§€μˆ˜μ μœΌλ‘œ μ¦κ°€ν•˜λŠ” ExponentialBackoffλ₯Ό λ„μž…ν•˜μ—¬ Delay Secondsλ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€.
  • λͺ¨λ“  μ‹€νŒ¨ μ΄λ²€νŠΈλ“€μ΄ λ™μΌν•œ Delay Secondsλ₯Ό κ°€μ§ˆ 경우 λ‹€μŒ νŽ˜μ΄μ¦ˆμ—μ„œ λ™μΌν•˜κ²Œ κ²½μŸν•  것이기 λ•Œλ¬Έμ—, Backoff μ‹œκ°„μ„ 계산할 λ•Œ λžœλ€μ„±μ„ μΆ”κ°€ν•©λ‹ˆλ‹€.
  • 이전 방식에 λΉ„ν•΄ 평균 μž¬μ‹œλ„ νšŸμˆ˜κ°€ λŠ˜μ–΄λ‚  것이기 λ•Œλ¬Έμ— μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό λ„‰λ„‰ν•˜κ²Œ 주도둝 ν•˜κ³ , μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό λ„˜μ€ κ²½μš°μ—” DLQλ₯Ό 톡해 κ°œλ°œμžλ“€μ΄ μ•Œ 수 있게 ν•©λ‹ˆλ‹€.

μ•„μ˜ˆ 접근을 λ‹¬λ¦¬ν•˜μ—¬ (with SQS FIFO Queue)

μœ„μ™€ 같이 κ°œμ„ μ„ μ§„ν–‰ν–ˆμ§€λ§Œ, μ—¬μ „νžˆ ν•œκ³„λŠ” μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€.
무엇보닀 락의 λŒ€κΈ° μ‹œκ°„μ„ μ΅œμ†Œν™”ν•˜κΈ΄ ν–ˆμœΌλ‚˜ μ–΄μ¨Œλ“  λžŒλ‹€ μ›Œμ»€λŠ” μ‹€ν–‰λ˜λŠ” 것은 계속 거슬리던 μ§€μ μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

λžŒλ‹€ λ ˆλ²¨μ—μ„œλŠ” 이 문제λ₯Ό ν•΄κ²°ν•  수 μ—†λ‹€κ³  νŒλ‹¨ν•˜κ³ , SQS μͺ½ λ¬Έμ„œλ₯Ό 더 ν™•μΈν•΄λ΄€λŠ”λ° κ²°λ‘ μ μœΌλ‘œλŠ” SQS FIFO Queueλ₯Ό 이미 지원을 ν•˜κ³  μžˆμ—ˆκ³  이λ₯Ό μ‚¬μš©ν•˜λ©΄ λ˜λŠ” λ¬Έμ œμ˜€μŠ΅λ‹ˆλ‹€.

λ‹€λ§Œ FIFO Queue라고 ν•΄μ„œ μ•„μ˜ˆ λ™μ‹œμ„±μ΄ 1둜 μ œν•œλ˜λŠ” 것이라면 λ„μž…μ΄ 어렀웠을텐데, SQSμ—μ„œλŠ” FIFO Queue λ‚΄λΆ€μ μœΌλ‘œ Message Group을 κ΄€λ¦¬ν•˜κ³  같은 그룹의 λ©”μ‹œμ§€λ“€μ— ν•œν•΄ λ™μ‹œμ„±μ„ μ œν•œν•˜κ³  μžˆμ–΄μ„œ ν…Œλ„ŒνŠΈ λ³„λ‘œ μœ μΌν•œ Message Group IDλ₯Ό μƒμ„±ν•˜λ„λ‘ μ œμ–΄ν•˜λ©΄ 그런 λ¬Έμ œλ„ μ—†μ—ˆμŠ΅λ‹ˆλ‹€.

κ·Έλ ‡κ²Œ λžŒλ‹€ λ ˆλ²¨μ—μ„œμ˜ 뢄산락을 νšλ“ν•˜λŠ” λ‘œμ§μ„ μ œκ±°ν•˜κ³  효과적으둜 λΉ„μš© μ΅œμ ν™”λ₯Ό 달성할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ 이λ₯Ό λ„μž…ν•˜λŠ” κ³Όμ •μ—μ„œλ„ λ¬Έμ œλŠ” μ‘΄μž¬ν–ˆμŠ΅λ‹ˆλ‹€.

κ³ μ •λœ Visibility Timeoutκ³Ό FIFO Queue 문제

SQS에 λžŒλ‹€λ₯Ό 뢙일 λ•Œ κ³ λ €ν•΄μ•Όλ˜λŠ” μ„€μ • 쀑 ν•˜λ‚˜λŠ” Visibility Timeoutμž…λ‹ˆλ‹€. Visibility Timeoutμ΄λž€ μ•„λž˜ κ·Έλ¦Όκ³Ό 같이, λžŒλ‹€μ—μ„œ SQS λ©”μ‹œμ§€λ₯Ό μˆ˜μ‹ ν•œ 이후 μ‹œμ λΆ€ν„° ν•΄λ‹Ή λ©”μ‹œμ§€κ°€ λ‹€λ₯Έ λžŒλ‹€μ— μ˜ν•΄ κ°€μ Έκ°€μ§ˆ 수 μžˆλŠ” μ‹œμ μ„ μ»¨νŠΈλ‘€ν•˜λŠ”λ° μ΄μš©λ©λ‹ˆλ‹€. λžŒλ‹€μ—μ„œ λ©”μ‹œμ§€λ₯Ό μˆ˜μ‹ ν•œλ‹€κ³  SQSμ—μ„œ λ©”μ‹œμ§€κ°€ λΉΌλ‚΄μ–΄μ§€λŠ” 것이 μ•„λ‹Œ, λžŒλ‹€κ°€ λ©”μ‹œμ§€λ₯Ό μ •μƒμ μœΌλ‘œ μ²˜λ¦¬ν•˜μ§€ λͺ»ν•˜λŠ” μΌ€μ΄μŠ€λ₯Ό μœ„ν•΄ 일단은 큐 내뢀에 λ‚¨κ²¨λ‘λ˜ λ‹€λ₯Έ λžŒλ‹€μ—κ²Œ λ³΄μ—¬μ§€λŠ”λ°κΉŒμ§€λŠ” μ‹œκ°„μ΄ κ±Έλ¦¬λŠ” κ²ƒμœΌλ‘œ μ΄ν•΄ν•˜λ©΄ λ©λ‹ˆλ‹€.

image

이 λ•Œ λžŒλ‹€μ˜ μ΅œλŒ€ νƒ€μž„μ•„μ›ƒμ€ 15뢄이기 λ•Œλ¬Έμ—, Visibility Timeout μ—­μ‹œ 15λΆ„ μ΄μƒμœΌλ‘œ μž‘μ•„μ€˜μ•Όν•©λ‹ˆλ‹€. (λ¬Όλ‘  λžŒλ‹€μ˜ μ‹€ν–‰ μ‹œκ°„μ„ μ •ν™•νžˆ μ•Œ 수 μžˆλ‹€λ©΄ 그보닀 μ§§μ•„μ§ˆ 수 μžˆκ² μœΌλ‚˜ DB IOκ°€ ν•„μš”ν•œ μž‘μ—…μ΄λΌλ©΄ μ„£λΆˆλ¦¬ μ˜ˆμΈ‘ν•˜κΈ° μ–΄λ ΅μŠ΅λ‹ˆλ‹€.) 이게 일반 SQS Queueμ—μ„œλŠ” 단지 ν•΄λ‹Ή λ©”μ‹œμ§€ 처리만 지연이 λ˜λŠ” 문제둜 μ΄μ–΄μ§€μ§€λ§Œ FIFO Queueμ—μ„œλŠ” 이야기가 λ‹€λ¦…λ‹ˆλ‹€.

μ•žμ„œ μ΄μ•ΌκΈ°ν–ˆλ“― FIFO Queue λ‚΄λΆ€μ—λŠ” Message Group이 μ‘΄μž¬ν•˜κ³  이 κ·Έλ£Ή λ³„λ‘œ λžŒλ‹€ λ™μ‹œμ„±μ„ μ—„κ²©νžˆ μ œν•œν•©λ‹ˆλ‹€. λ˜ν•œ λžŒλ‹€μ—μ„œ λ©”μ‹œμ§€λ₯Ό μˆ˜μ‹ ν•˜λ”λΌλ„ κ·Έ μ¦‰μ‹œ νμ—μ„œ λΉΌλ‚΄μ§€λŠ” μ•ŠλŠ”λ‹€κ³  이야기 ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€. 그런 μƒν™©μ—μ„œ λžŒλ‹€κ°€ μ˜ˆμƒν•˜μ§€ λͺ»ν•œ 문제둜 인해 λΉ„μ •μƒμ μœΌλ‘œ λ™μž‘ν•˜κ²Œ λœλ‹€λ©΄, Visibility Timeout μ„€μ •κ³Ό 맞물렀 ν•΄λ‹Ή Message Group은 Visibility Timeout이 λ°œμƒν•˜κ³  ν•΄λ‹Ή λ©”μ‹œμ§€κ°€ λ‹€μ‹œ μ •μƒμ μœΌλ‘œ 처리될 λ•ŒκΉŒμ§€ κ·Έ 이후 λͺ¨λ“  λ©”μ‹œμ§€κ°€ μ§€μ—°λ˜κ²Œ λ©λ‹ˆλ‹€.

Visibility Timeout 문제의 ν•΄κ²°

λ‹€ν–‰νžˆλ„ AWS SQSλŠ” ChangeMessageVisibility μ»€λ§¨λ“œλ₯Ό 톡해 νŠΉμ • λ©”μ‹œμ§€ λ‹¨μœ„λ‘œ Visibility Timeout 값을 λ³€κ²½ν•˜λŠ” 것이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

ν•Έλ“€λŸ¬ 둜직 전체에 try-catchλ₯Ό κ±Έμ–΄μ£Όκ³  μ—λŸ¬λ₯Ό μΊμΉ˜ν•˜λŠ” λΆ€λΆ„μ—μ„œ ChangeMessageVisibility μ»€λ§¨λ“œλ₯Ό μ‹€ν–‰ ν›„ λžŒλ‹€λ₯Ό μ’…λ£Œμ‹œν‚΅λ‹ˆλ‹€. κ·Έλ ‡κ²Œ 되면 FIFO Queueμ—μ„œ νŠΉμ • λ©”μ‹œμ§€κ°€ μ‹€νŒ¨ν•˜λ”λΌλ„, ν•΄λ‹Ή λ©”μ‹œμ§€λŠ” 이후 λ©”μ‹œμ§€λ“€μ˜ 처리λ₯Ό 막지 μ•Šκ³  λΉ λ₯΄κ²Œ μž¬μ‹œλ„ ν˜Ήμ€ DLQ둜 전달될 κ²ƒμž…λ‹ˆλ‹€.

μ•„λž˜λŠ” NodeJS둜 μž‘μ„±λœ μ˜ˆμ‹œμž…λ‹ˆλ‹€.

export const handler: SQSHandler = async event => {
  for (const record of event.Records) {
    try {
      // λ©”μ‹œμ§€ 처리 둜직
    } catch (err) {
      const sqsClient = new SQSClient({
        ...SQS Clientλ₯Ό μœ„ν•œ μ„€μ • μΆ”κ°€
      })
      await sqsClient.send(
        new ChangeMessageVisibilityCommand({
          QueueUrl: 'your-queue-url',
          ReceiptHandle: record.receiptHandle,
          VisibilityTimeout: 5, // 5초 λ’€ λ‹€μ‹œ λžŒλ‹€μ— 전달됨
        }),
      )
      throw err
    }
  }
}

μ°Έκ³