๊ด๋ จ ๊ธ
- AWS SQS + Spring Boot 3 + kotlin ์ธํ๋ผ ๊ตฌ์ถํ๊ธฐ
- AWS SQS Consumer ์๋ฌ๋ฅผ DLQ ๋ก ์ฒ๋ฆฌํ๊ธฐ
- message converter ๋ฅผ ์ด์ฉํ sqs message serializer
- AWS SDK ๋ก DLQ ์ ์์ธ ๋ฉ์์ง ์ฒ๋ฆฌํ๋ 2๊ฐ์ง ๋ฐฉ๋ฒ
์์ ์๊ฐ์ ์ฐ๋ฆฌ๋ SQS ๋ฅผ ์์ฑํ๊ณ Spring Boot 3 ๋ก producer/consumer ๋ฅผ ๊ตฌํ ํ์๋ค.
์ด๋ฒ ์๊ฐ์๋ Consumer Application ์์ ๋ฐ์ํ ์ ์๋ ์คํจ์ ๋์ฒํ๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ ์ค ํ๋์ธ DLQ ๋ฅผ ์์๋ณผ ๊ฒ์ด๋ค.
๋ํ ์ค์ต์ ํตํด Spring Boot 3 ์ kotlin ์ผ๋ก ๊ฐ๋จํ DLQ Consumer ๋ฅผ ๊ตฌํํด๋ณด๋๋ก ํ์.
๋ชฉ์ฐจ
- DLQ
- AWS ์์ DLQ ๊ตฌ์ฑํ๊ธฐ
- DLQ Consumer ๊ตฌํํ๊ธฐ
- FIFO ์ DLQ
DLQ
์์คํ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํจํ๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ฐ๋ฆฌ๋ ์คํจ์ ์ ๋์ฒํ ์ ์๋ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ ๊ณ ์ํด์ผ ํ๊ณ ์ด๋ฅผ ๋ด๊ฒฐํจ์ฑ (fault tolerant) ๋ผ๊ณ ํ๋ค.
AWS SQS ์์๋ ์ด๋ฌํ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด DLQ ๋ผ๋ ๊ฒ์ ์ ๊ณตํ๋ค.
DLQ ๋ Enterprise Integration Pattern ์ด๋ผ๋ ์ฑ
์์ ์๊ฐ๋ Dead-Letter-Channel
์ด๋ผ๋ ํจํด์ AWS ์์ ๊ตฌํํ managed queue ์๋น์ค์ด๋ค.
์์ ์ฑ
์์๋ Dead Letter Channel
์ ์๋์ ๊ฐ์ด ์ค๋ช
ํ๋ค.
๋ฉ์์ง ์์คํ ์์ ๋ฉ์์ง๋ฅผ ์ ๋ฌํ ์ ์๊ฑฐ๋ ์ ๋ฌํด์๋ ์ ๋๋ค๊ณ ํ๋จ๋๋ฉด ๋ฉ์์ง๋ฅผ Dead Letter Channel ํน์ Dead Letter Queue ๋ก ์ด๋์์ผ ๋ณด๊ดํฉ๋๋ค.
์ฆ, Consumer Application ์์ ํน์ ํ์ ๋ฉ์์ง๋ฅผ ์๋นํ๋ค ์ฒ๋ฆฌ์ ์คํจํ๋ค๋ฉด ์ด๋ฅผ Dead Letter Channel ๋ก ์ฎ๊ธด๋ค๋ ๊ฒ์ด๋ค.
DLQ ๋์ ์๋ฆฌ๋ ์ง์ queue ๋ฅผ ๋ง๋ค๋ฉด์ ์์๋ณด์
AWS ์์ DLQ ๊ตฌ์ฑํ๊ธฐ
์ฐ๋ฆฌ๋ ์ง๋ ์๊ฐ์ Spring Boot 3 ๋ก AWS SQS ์ธํ๋ผ๋ฅผ ๊ตฌ์ถํ์๋ค.
์์ ๋ง๋ queue ์ธ member-event
์ ๋ํ DLQ ๋ฅผ ๊ตฌ์ฑํ ๊ฒ์ด๋ค.
DLQ ์ญ์ ์ผ๋ฐ์ ์ธ queue ์ ๋์ผํ๋ฏ๋ก aws sqs console ์์ ์ ๊ท ํ๋ฅผ ๋ง๋ค์ด์ฃผ๋ฉด ๋๋ค.
์ด๋ฆ์ ๊ตฌ๋ถํ๊ธฐ ์ฝ๊ฒ member-event-dlq
๋ก ์ค์ ํ๊ณ ์์ ์ค์ต๊ณผ ๋์ผํ ์ค์ ์ผ๋ก queue ๋ฅผ ๋ง๋ค๋ค ๋ณด๋ฉด redrive policy ์ ๋ํ ์ค์ ์ ํด์ผํ๋ค.
์ด๋ค queue ์ ๋ํด DLQ ์ค์ ์ ํ ๊ฒ์ธ๊ฐ? ์ ๋ํ ์ค์ ์ด๋ค. ์ฐ๋ฆฌ๋ member-event
์ ๋ํ DLQ ๋ฅผ ๊ตฌ์ฑํ ๊ฒ์ด๋ฏ๋ก ์์ ์์ฑํ ํ์ ARN ์ ์ถ๊ฐํ๋ค.
๊ทธ๋ผ ์์ ๊ฐ์ด 2๊ฐ์ ํ๊ฐ ์์ฑ๋์๋ค.
์ด์ member-event
์ DLQ ๋ก member-event-dlq
๋ฅผ ์ฐ๊ฒฐ์์ผ์ฃผ๊ธฐ ์ํด member-event
ํ์ ์ค์ ์ ๋ณ๊ฒฝํด๋ณด์.
์์ ์ค์ ์์ 2๊ฐ์ง๋ฅผ ์ ์ ์๋ค.
- ์ด๋ค ํ๋ฅผ DLQ ๋ก ์ค์ ํ ๊ฒ์ธ๊ฐ
- ์ต๋ retry ํ์๋ ์ผ๋ง์ธ๊ฐ
๊ธฐ๋ณธ์ ์ผ๋ก queue ์ message ๋ฅผ ์ฒ๋ฆฌ ์๋ฃํ๋ฉด ํด๋น message ๋ฅผ ์ ๊ฑฐํ๋ ์ฑ ์์ด ์กด์ฌํ๋ค.
ํ์ง๋ง ์ด๋ค ์ด์ ์์์ธ์ง ์ฒ๋ฆฌ๋ฅผ ๋ชปํ์ฌ ์ญ์ ๊ฐ ์๋ ๋์๋ค์ ๋ค๋ฅธ ์์ ํ๋ก ์ฎ๊ธฐ๋ ๊ฒ์ด DLQ ์ ํต์ฌ์ด๋ค.
์์ ๋ณด์ด๋ miximum receives ๋ ๋ช๋ฒ ์ฌ์๋ ๋์ DLQ ๋ก ์ฎ๊ธธ ๊ฒ์ธ๊ฐ? ๋ฅผ ๊ฒฐ์ ํ๋ ํ๋๋ค.
์์ ์ค์ ์ 3๋ฒ retry ๋์ ์คํจํ๋ฉด DLQ ๋ก ์ฎ๊ธฐ๋ ๊ฒ์ ์๋ฏธํ๋ค.
DLQ Consumer ๊ตฌํํ๊ธฐ
DLQ Consumer ์ญ์ ์ผ๋ฐ Queue ์ ๋์ผํ๋ฏ๋ก ์์ ์ค์ต๋ ๋ง๋ค์๋ SQS Listener ์ ๋์ผํ๊ฒ ์ฐ๋ํด์ฃผ๋ฉด ๋๋ค.
@Component
class MemberEventListener {
@SqsListener("member-event")
fun listen(message: String) {
println("received message ($message)")
}
@SqsListener("member-event-dlq")
fun listenDLQ(message: String, ack: Acknowledgement) {
println("DLQ received message ($message)")
}
DLQ ์ ์ ์์ ์ผ๋ก ๋ฉ์์ง๊ฐ ์ ์ฎ๊ฒจ์ง๋์ง ํ์ธํ๊ธฐ ์ํด์๋ ์ค์ต ํ๊ฒฝ์ ์ฝ๊ฐ ๋ณ๊ฒฝํด์ผ ํ๋ค.
์๋์ ์ผ๋ก member-event consumer ์์ ์คํจํ๊ธฐ
DLQ ๋ origin queue ์ ์ฒ๋ฆฌ๊ฐ ์คํจํ์์ ๋ ๋ฉ์์ง๊ฐ ์ ์ก๋๋ฏ๋ก, member-event
ํ๊ฐ ๋ฉ์์ง๋ฅผ ์๋นํ๋๋ผ๋ ack ๋ฅผ ์ํํ์ง ์๋๋ก ์ค์ ์ ๋ฐ๊ฟ ๋ณผ ๊ฒ์ด๋ค.
@Configuration
class SqsConfiguration {
@Bean
fun defaultSqsListenerContainerFactory(sqsAsyncClient: SqsAsyncClient) =
SqsMessageListenerContainerFactory
.builder<Any>()
.configure { options: SqsContainerOptionsBuilder ->
options
.acknowledgementMode(MANUAL) // ACK ์๋
}
.sqsAsyncClient(sqsAsyncClient)
.build()
}
์๋ Configuration Bean ์ ๋ฑ๋กํ๋ฉด Consumer instance ๋ฅผ ์์ฑํ ๋ ์๋ commit mode ์ธ consumer ๋ฅผ ์์ฑํ ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด DLQ ๋ก ๋ฉ์์ง๊ฐ ๋ค์ด์จ ๊ฒ์ ํ์ธํ ์ ์๋ค.
Queue Type ๊ณผ DLQ
์์ ์ฐ๋ฆฌ๋ SQS ์๋ 2๊ฐ์ง ํ์ ์ด ์กด์ฌํ๋ค๊ณ ํ์ธํ๋ค.
DLQ ๋ฅผ ๊ตฌ์ฑํ ๋๋ ํ ๊ฐ์ง ์ฃผ์ํด์ผ ํ ์ ์ด ์กด์ฌํ๋ค.
๋ฐ๋ก FIFO Type ์ Queue ์์๋ DLQ ๋ฅผ ๊ตฌ์ฑํ ๊ฒฝ์ฐ ๋์ผ message group ์์ ๋ค๋ฅธ message ๋ค์ด Blocking ๋๋ค๋ ๊ฒ์ด๋ค.
First In First Out ์ ํตํด Exactly Once ๋ฅผ ๋ณด์ฅํ๋ FIFO ๋ ๋ฉ์์ง ์์๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด ๋ด๋ถ์ ์ผ๋ก ์ ๋ ฌ๊ณผ์ ์ ์ํํ๋ค.
๊ทธ๋์ ํน์ ๋ฉ์์ง๊ฐ ์ฒ๋ฆฌ๋์ง ์์ผ๋ฉด ํด๋น ๋ฉ์์ง๊ฐ ์กด์ฌํ๋ origin queue ์ message group ์ DLQ ์ ์ํด Blocking ๋๋ค๋ ์ ์ ๋ช ์ฌํด์ผ ํ๋ค.
๋๊ธ