NestJS기반 백엔드 서버에 카프카를 도입하고자 할 때,
kafkajs + @nestjs/microservices 를 조합해서 손쉽게 카프카를 사용할 수 있었습니다.
외부 채널링을 구현하며 기존 예약 서버의 로직을 재활용할 필요가 있었습니다.
신규 서버는 리소스가 한정된 상태였고, Cron, 예약 API 등 여러 기능을 모놀리식하게 처리해야 했습니다.
이에 따라, 단일 서버 내 자원을 효율적으로 사용하기 위해 Non-Blocking한 동기 패턴을 Kafka를 통해 구현했습니다.
OnModuleInit 인터페이스를 활용하여 응답 토픽을 미리 구독합니다.
export class Producer implements OnModuleInit {
constructor(
@Inject(KAFKA_CLIENT)
private readonly clientKafka: ClientKafka
) {}
async onModuleInit(): Promise<void> {
this.clientKafka.subscribeToResponseOf(
`foo.get`
)
}
}
foo.get.reply라는 응답 토픽도 사전에 생성해 두어야 합니다.
이후 send() 메서드를 통해 메시지를 전송하면 RPC 구조로 응답을 받을 수 있습니다.
const result = await lastValueFrom(
this.clientKafka
.send<
KafkaReplyDto,
KafkaProduceDto<FooGetDto>
>(`foo.get`, {})
.pipe(timeout(3000))
)
이후 send() 메서드를 통해 메시지를 전송하면 RPC 구조로 응답을 받을 수 있습니다.
@MessagePattern(`foo.get`)
async consumer(
@Payload() data: KafkaProduceDto<FooGetDto>,
@Ctx() context: KafkaContext
) {
return 'producer result에 들어갈 값'
}
NestJS는 Kafka의 헤더를 활용하여 foo.get.reply에 자동 응답을 반환합니다.
✅ 헤더에는 응답 토픽명과 파티션 정보가 포함되어 있어, 정확한 응답을 보장합니다.
• 비동기 구조를 유지하면서도 RPC 패턴으로 동기적 응답을 받을 수 있음
• 예약 로직에서 응답을 기다리는 동안 서버가 Block되지 않아 자원 활용도가 높아짐
비동기 통신에서는 네트워크 이슈에 따른 중복 호출이 발생할 수 있습니다.
→ Producer / Consumer 모두 멱등성을 보장해야 합니다.
응답 토픽의 구독자는 consumer group 내 파티션 수만큼만 활성화됩니다.
→ 소비자 인스턴스 수 > 파티션 수이면 일부 인스턴스가 간헐적으로 메시지 유실이 생길 수 있습니다.
ECS의 블루/그린 배포는 일시적으로 인스턴스를 2배로 늘립니다.
→ 블루, 그린이 동시에 떠있는 시점에 인스턴스 수가 파티션 수를 초과할 수 있습니다.
NestJS의 Kafka RPC 구조는:
• 기존 로직을 재활용하며 확장성이 높고
• 서버 자원을 효율적으로 사용 가능하다는 장점이 있지만 복잡성이 높습니다.
• 응답 패턴 특성상 파티션-인스턴스 매핑, 배포 전략, 멱등성에 대한 설계가 반드시 필요합니다.
상황에 맞게 잘 활용하면 MSA 구조에서 유용한 선택지가 될 수 있습니다.