PHpullh
개발자 면접 준비/시스템 디자인

시스템 디자인

큐와 비동기 메시징 면접: 중복과 순서를 설명하는 법

큐를 넣는 이유부터 최소 한 번 전달, 멱등 소비자, 파티션 순서, 데드 레터, 아웃박스까지 실제 대화 순서대로 따라갑니다.

먼저 문제부터 봅니다

이번 페이지는 개념 설명을 뒤로 미루고 문제부터 놓겠습니다. 큐는 개념만 외우면 아는 것처럼 느껴지지만, 실제 면접에서는 “그래서 그 메시지가 두 번 오면 어떻게 되죠”라는 한 문장에 대부분 무너지기 때문입니다.

문제는 이렇습니다. 주문이 결제까지 완료되면 그 뒤에 여러 가지가 일어나야 합니다. 구매자에게 알림을 보내고, 판매자 정산 대기 항목을 만들고, 재고 시스템에 확정을 알리고, 분석용 이벤트를 적재합니다. 지금은 이 모든 일이 결제 완료 요청 안에서 동기로 처리되고 있어서 응답이 느리고, 알림 발송이 실패하면 결제까지 오류로 돌아갑니다. 어떻게 고치시겠습니까.

이 문제가 좋은 이유는 큐를 넣어야 하는 이유가 명확하면서도, 큐를 넣은 다음 생기는 문제가 전부 등장하기 때문입니다. 알림은 두 번 가면 곤란하고, 정산은 두 번 만들어지면 사고이며, 재고 확정은 순서가 뒤집히면 안 되고, 분석 이벤트는 조금 늦어도 됩니다. 한 문제 안에 요구 수준이 다른 네 가지가 들어 있습니다.

큐를 넣는 이유는 세 가지뿐입니다

“비동기로 처리하겠습니다”라고 말하기 전에 왜 큐인지부터 정리하면 좋습니다. 이유는 대개 셋 중 하나이고, 어느 것인지에 따라 이후 설계가 달라집니다.

첫째는 결합을 끊기 위해서입니다. 결제 처리가 알림 서비스의 가용성에 묶여 있는 것이 지금 문제의 핵심입니다. 중간에 큐가 있으면 알림 서비스가 잠깐 죽어도 결제는 성공합니다. 이 경우 중요한 것은 지연이 아니라 장애 격리입니다.

둘째는 속도 차이를 흡수하기 위해서입니다. 순간적으로 몰리는 요청을 큐가 받아 두고 하류가 자기 속도로 처리합니다. 이때 큐는 완충 장치이고, 적체가 얼마나 쌓여도 되는지가 설계 값이 됩니다.

셋째는 재시도를 안전하게 하기 위해서입니다. 동기 호출에서 실패하면 요청 하나가 통째로 실패하지만, 큐에 있는 메시지는 성공할 때까지 다시 시도할 수 있습니다. 대신 이 세 번째 이유를 고르는 순간 중복 처리라는 대가가 따라옵니다.

이 문제에서는 세 가지가 모두 해당됩니다. 그래서 답의 첫 문장은 “결제 응답 경로에서 알림·정산·재고·분석을 떼어 내겠습니다”가 되고, 그 다음 문장은 곧바로 “대신 중복과 순서를 다뤄야 합니다”가 되어야 합니다.

TIP

비동기로 옮길 수 있는 작업과 옮기면 안 되는 작업을 구분하는 기준은 간단합니다. 사용자가 화면에서 결과를 즉시 확인해야 하거나, 실패했을 때 요청 자체를 되돌려야 하는 작업은 동기로 남깁니다. 나머지는 후보입니다. 이 문제에서 결제 승인 자체는 동기로 남고 네 가지 후속 작업이 옮겨집니다.

전달 보장은 “정확히 한 번”이 아닙니다

비동기 설계에서 가장 자주 나오는 오해가 여기 있습니다. 메시지 전달을 정확히 한 번으로 만들 수 있다고 믿는 것입니다. 실무의 기본값은 최소 한 번입니다. 이유는 단순합니다. 소비자가 메시지를 처리한 뒤 확인 응답을 보내려는 순간 죽으면, 브로커 입장에서는 처리됐는지 알 방법이 없습니다. 유실을 막으려면 다시 보내야 하고, 다시 보내면 중복이 생깁니다. 유실과 중복 중 하나를 골라야 하는 상황에서 대부분의 시스템은 중복을 고릅니다.

그래서 올바른 문장은 “중복이 오지 않게 하겠습니다”가 아니라 “중복이 와도 결과가 한 번만 반영되게 하겠습니다”입니다. 전달을 한 번으로 만드는 대신, 효과를 한 번으로 만드는 것입니다. 이것이 멱등성입니다.

멱등성을 구현하는 방법은 작업 성격에 따라 다릅니다. 상태를 특정 값으로 덮어쓰는 작업은 그 자체로 멱등합니다. 주문 상태를 결제 완료로 바꾸는 일은 몇 번을 해도 결과가 같습니다. 반면 무언가를 더하거나 새로 만드는 작업은 멱등하지 않습니다. 정산 항목 생성, 잔액 증가, 알림 발송이 여기 해당합니다. 이런 작업에는 처리 이력을 남기는 방법을 씁니다. 메시지마다 고유한 키를 정하고, 처리 전에 그 키를 기록하는데, 기록이 유니크 제약에 걸리면 이미 처리한 메시지로 보고 그냥 확인 응답만 보냅니다.

여기서 중요한 것은 키를 무엇으로 잡느냐입니다. 브로커가 붙여 주는 메시지 아이디는 재발행 시 바뀔 수 있어 신뢰하기 어렵습니다. 업무적으로 “같은 일”을 뜻하는 값을 키로 잡아야 합니다. 이 문제라면 결제 아이디와 작업 종류의 조합이 자연스러운 키입니다.

CONSUMER · 멱등 처리 · 재시도 · 데드 레터

MAX_ATTEMPTS = 5

def handle(msg):
    # 1) 멱등 키는 브로커의 message_id가 아니라 업무상 "같은 일"의 식별자로 잡는다.
    #    재발행되면 message_id는 바뀌지만 이 키는 그대로다.
    key = msg.payload["payment_id"] + ":settlement"

    # 2) 형식이 깨진 메시지는 재시도해도 절대 성공하지 않는다. 즉시 DLQ.
    if not is_valid(msg.payload):
        dlq.send(msg, reason="schema_invalid", attempts=msg.attempts)
        broker.ack(msg)
        return

    try:
        with db.transaction():
            # 3) 처리 이력과 실제 효과를 같은 트랜잭션에 넣는다.
            #    따로 두면 이력만 남고 효과가 사라지는 경우가 생긴다.
            inserted = db.execute(
                "INSERT INTO processed_messages (idem_key, handler, created_at) "
                "VALUES (?, 'settlement', now()) ON CONFLICT DO NOTHING", key)
            if inserted == 0:
                return                       # 이미 처리한 메시지 -- 아무것도 하지 않는다

            db.execute(
                "INSERT INTO settlements (payment_id, seller_id, amount, status) "
                "VALUES (?, ?, ?, 'PENDING')",
                msg.payload["payment_id"], msg.payload["seller_id"],
                msg.payload["amount"])

        broker.ack(msg)                      # 커밋이 끝난 뒤에만 ack

    except TransientError as e:
        # 4) 일시적 실패는 지수 백오프 + 지터로 재시도. 동시에 몰려 다시 터지지 않게.
        if msg.attempts + 1 >= MAX_ATTEMPTS:
            dlq.send(msg, reason=str(e), attempts=msg.attempts + 1)
            broker.ack(msg)                  # 본 큐에서는 빼서 뒤 메시지를 막지 않는다
        else:
            delay = min(2 ** msg.attempts, 60) + random_jitter(0, 5)
            broker.nack(msg, retry_after_sec=delay)

    except Exception as e:
        # 5) 정체를 모르는 오류는 ack하지 않는다. 조용히 삼키면 유실이 된다.
        log.error("unhandled", key=key, err=e)
        broker.nack(msg, retry_after_sec=30)

이 코드에서 면접 질문이 나올 자리는 세 곳입니다. 처리 이력과 실제 효과를 같은 트랜잭션에 넣은 것, 커밋 이후에만 확인 응답을 보낸 것, 그리고 재시도해도 소용없는 오류를 즉시 데드 레터로 보낸 것입니다. 세 번째가 특히 중요합니다. 형식이 잘못된 메시지를 다섯 번 재시도하는 것은 시간과 자원을 버리는 일이고, 그 사이 뒤에 있는 정상 메시지들이 밀립니다. 오류를 “다시 하면 될 것”과 “다시 해도 안 될 것”으로 나누는 판단이 소비자 코드의 품질을 가릅니다.

순서는 전역이 아니라 구획 안에서만

“메시지 순서가 보장되나요”라는 질문에 “네”라고 답하면 그 뒤가 곤란해집니다. 분산된 큐에서 전역 순서를 지키려면 결국 한 줄로 세워야 하고, 한 줄로 세우면 병렬 처리가 불가능해집니다. 그래서 실무의 보장은 구획 단위입니다. 같은 파티션 또는 같은 그룹 키에 들어간 메시지끼리만 발행 순서대로 처리됩니다.

이 사실을 알면 설계가 뒤집힙니다. 순서를 지키고 싶으면 브로커 설정을 뒤질 것이 아니라, 순서를 지켜야 하는 범위를 정하고 그것을 키로 잡아야 합니다. 이 문제에서 재고 확정은 상품 단위로 순서가 지켜지면 충분하므로 상품 아이디를 키로 씁니다. 알림은 사용자 단위로만 순서가 맞으면 되니 사용자 아이디를 키로 씁니다. 분석 이벤트는 순서가 아예 필요 없으므로 아무 키나 써서 최대한 고르게 퍼뜨립니다.

여기서 반드시 따라오는 대가를 말해야 합니다. 키의 종류가 적으면 특정 파티션에만 트래픽이 몰려 그 하나가 병목이 됩니다. 그리고 한 파티션은 한 소비자만 읽으므로, 소비자를 파티션 수보다 많이 띄워도 남는 소비자는 아무 일도 하지 않습니다. 즉 순서 보장 범위를 좁게 잡을수록 병렬 처리 폭이 넓어지고, 넓게 잡을수록 처리량이 줄어듭니다. 이 맞바꿈을 소리 내어 말하는 사람이 드뭅니다.

더 나은 답도 있습니다. 애초에 순서에 의존하지 않게 만드는 것입니다. 메시지에 버전 번호나 발생 시각을 넣고, 소비자가 자기가 이미 본 것보다 오래된 메시지를 무시하게 하면 순서가 뒤집혀도 최종 상태가 같아집니다. 순서 보장을 인프라에 요구하는 대신 데이터로 해결하는 방식이며, 면접에서 이 이야기를 꺼내면 대화의 수준이 한 단계 올라갑니다.

저장과 발행을 한 번에: 아웃박스

큐를 넣으면 바로 새로운 함정이 생깁니다. 결제를 데이터베이스에 저장하고 메시지를 발행하는 두 동작은 서로 다른 시스템에 대한 쓰기라서 함께 성공한다는 보장이 없습니다. 저장은 됐는데 발행이 실패하면 정산이 영영 만들어지지 않고, 발행은 됐는데 트랜잭션이 롤백되면 존재하지 않는 결제에 대한 정산이 만들어집니다. 둘 다 실제로 일어나는 사고입니다.

흔한 오답은 “트랜잭션 커밋 직후에 발행하겠습니다”입니다. 커밋과 발행 사이에서 프로세스가 죽으면 그대로 유실입니다. 또 다른 오답은 “두 시스템을 분산 트랜잭션으로 묶겠습니다”인데, 운영 복잡도가 커서 실무에서 잘 선택되지 않습니다.

표준적인 답이 아웃박스 패턴입니다. 결제를 저장하는 바로 그 트랜잭션 안에서 발행할 내용을 아웃박스 테이블에 한 행으로 함께 기록합니다. 하나의 데이터베이스 트랜잭션이므로 결제와 발행 의도는 같이 커밋되거나 같이 사라집니다. 그리고 별도의 프로세스가 아웃박스에서 미발행 행을 읽어 큐로 보내고 발행 표시를 남깁니다. 이 프로세스는 보낸 뒤 표시 전에 죽을 수 있으므로 같은 메시지를 다시 보낼 수 있습니다. 즉 아웃박스도 최소 한 번이고, 그래서 앞에서 만든 멱등 소비자가 여기서 다시 필요해집니다. 두 조각이 맞물리는 이 설명을 할 수 있으면 이 주제는 거의 끝난 것입니다.

적체와 역압, 그리고 데드 레터 운영

큐는 무한한 버퍼가 아닙니다. 생산 속도가 소비 속도보다 계속 빠르면 적체가 쌓이고, 처리 지연이 분 단위에서 시간 단위로 늘어나며, 결국 보관 한계에 닿아 메시지가 사라지거나 브로커가 발행을 거부합니다. 그래서 큐를 도입하면 반드시 두 지표를 봐야 합니다. 쌓여 있는 메시지 수와, 발행 시각부터 처리 시각까지의 지연입니다. 앞의 것은 규모를 알려 주고 뒤의 것은 사용자 체감을 알려 줍니다.

적체가 늘 때 소비자를 늘리는 것이 첫 대응이지만, 앞에서 말했듯 파티션 수가 상한입니다. 그리고 소비자를 늘려 하류 데이터베이스나 외부 API를 더 세게 두드리면 그쪽이 먼저 무너집니다. 그래서 역압을 함께 이야기해야 합니다. 하류가 한계에 가까우면 소비 속도를 스스로 제한하고, 흐름별로 우선순위를 나누어 분석 이벤트 같은 덜 급한 소비를 늦추고 정산 같은 흐름을 지킵니다. 급하지 않은 작업을 위해 아예 별도 큐를 두는 것도 자주 쓰는 방법입니다. 한 큐에 성격이 다른 메시지를 섞으면 느린 것이 급한 것을 막습니다.

데드 레터 큐도 만들어 두는 것으로 끝나지 않습니다. 운영 관점에서 세 가지가 있어야 실제로 쓸모가 있습니다. 메시지가 들어오면 알림이 가야 하고, 왜 들어왔는지 원인과 원본을 함께 볼 수 있어야 하며, 고친 뒤 다시 본 큐로 넣는 절차가 있어야 합니다. 세 번째가 없으면 데드 레터 큐는 그냥 쓰레기통이 되고, 장애 후에 “저기 쌓인 만 건은 어떻게 하나요”라는 질문에 아무도 답하지 못하게 됩니다.

WARNING

재시도 정책을 정하지 않고 “실패하면 재시도합니다”라고만 말하면 위험합니다. 간격 없이 즉시 재시도하면 이미 힘들어하는 하류를 더 세게 때려 장애를 키웁니다. 최대 횟수, 지수적으로 늘어나는 간격, 간격에 섞는 지터, 그리고 포기 후 행선지까지 네 가지를 함께 말해야 답이 완성됩니다.

답변 비교: 약한 답과 강한 답

앞의 연습 문제에 대한 두 답입니다.

약한 답 — “결제 완료 후 처리들을 큐로 옮기겠습니다. 결제가 끝나면 메시지를 발행하고, 소비자가 받아서 알림을 보내고 정산을 만들고 재고를 갱신합니다. 실패하면 재시도하고, 계속 실패하면 데드 레터 큐로 보냅니다. 이러면 결제 응답도 빨라지고 알림 장애가 결제에 영향을 주지 않습니다.”

강한 답 — “문제를 다시 정리하면, 지금은 결제 응답이 네 개의 후속 작업 가용성에 묶여 있습니다. 목표는 응답 시간 단축보다 장애 격리 쪽이 더 큽니다.

가정을 말씀드리면, 알림은 몇 초 늦어도 되고 중복 발송은 불편하지만 치명적이지 않으며, 정산은 절대 중복 생성되면 안 되고, 재고 확정은 상품 단위 순서가 지켜져야 하며, 분석 이벤트는 유실이 조금 있어도 된다고 보겠습니다. 요구 수준이 다르므로 네 가지를 한 소비자에 묶지 않겠습니다.

구조는 세 가지를 놓고 비교했습니다. 결제 트랜잭션 안에서 직접 발행하는 방법, 커밋 직후 발행하는 방법, 아웃박스를 두는 방법입니다. 첫째는 롤백되면 유령 메시지가 남고, 둘째는 커밋과 발행 사이에서 죽으면 유실됩니다. 그래서 결제 저장과 같은 트랜잭션에 아웃박스 행을 쓰고 별도 프로세스가 발행하는 방식을 고르겠습니다.

전달은 최소 한 번으로 보겠습니다. 그래서 정산 소비자는 결제 아이디와 핸들러 이름을 멱등 키로 삼아 처리 이력 테이블에 유니크 제약을 걸고, 이력 기록과 정산 생성을 같은 트랜잭션에 넣겠습니다. 알림 소비자도 같은 키 구조를 쓰되 발송 실패는 유실 허용 범위이므로 재시도 횟수를 짧게 잡겠습니다.

순서는 전역으로 보장하지 않고, 재고 확정만 상품 아이디를 파티션 키로 잡아 상품 단위 순서를 지키겠습니다. 추가로 메시지에 버전 값을 넣어 오래된 메시지를 무시하게 해서 순서 보장에 덜 의존하게 만들겠습니다.

재시도는 최대 다섯 번, 지수 백오프에 지터를 섞고, 형식 오류처럼 재시도가 무의미한 실패는 즉시 데드 레터로 보내겠습니다. 데드 레터에는 유입 알림과 재투입 절차를 함께 두겠습니다. 지표는 적체량과 처리 지연 두 가지를 보고, 소비자 증설의 상한이 파티션 수라는 점을 감안해 파티션 수를 처음부터 여유 있게 잡겠습니다.

받아들이는 대가는 두 가지입니다. 사용자가 결제 직후 정산 항목을 즉시 보지 못할 수 있고, 아웃박스 프로세스라는 운영 대상이 하나 늘어납니다. 대신 결제 경로가 후속 작업의 장애로부터 완전히 분리됩니다.”

무엇이 달랐는가

두 답에 등장하는 부품은 사실 거의 같습니다. 큐, 재시도, 데드 레터가 모두 나옵니다. 그런데도 평가가 갈리는 이유는 추론의 순서 때문입니다.

강한 답은 문제를 자기 말로 다시 정의하면서 시작합니다. “응답 시간보다 장애 격리가 목표”라는 한 줄이 뒤따르는 모든 결정의 기준이 됩니다. 약한 답은 목표를 정하지 않았기 때문에 “응답이 빨라진다”와 “장애가 전파되지 않는다”를 결과로 나열만 하고, 둘 중 무엇을 위해 무엇을 포기했는지 말하지 못합니다.

다음으로 강한 답은 네 가지 후속 작업의 요구 수준이 서로 다르다는 것을 가정으로 명시합니다. 그 한 문단이 있었기 때문에 “한 소비자에 묶지 않겠다”, “알림은 재시도를 짧게”, “재고만 파티션 키를 잡겠다” 같은 세부 결정이 전부 근거를 갖습니다. 약한 답은 네 작업을 한 덩어리로 다루므로, 면접관이 “정산이 두 번 생기면요”라고 묻는 순간 준비되지 않은 상태로 방어에 들어가게 됩니다.

세 번째 차이는 후보 비교입니다. 강한 답은 발행 시점을 세 가지로 놓고 각각이 어떤 상황에서 깨지는지 말한 뒤 아웃박스를 고릅니다. 약한 답에는 “결제가 끝나면 메시지를 발행”이라는 한 문장만 있는데, 이 문장은 사실 세 후보 중 가장 위험한 두 번째에 해당합니다. 비교를 하지 않았기 때문에 자기가 무엇을 골랐는지도 모르는 상태입니다.

마지막 차이는 대가를 스스로 꺼내는 태도입니다. 강한 답은 사용자가 즉시 결과를 못 볼 수 있다는 점과 운영 대상이 늘어난다는 점을 먼저 인정합니다. 약한 답에는 대가가 하나도 없습니다. 비동기화는 반드시 “결과가 나중에 반영된다”는 비용을 만드는데, 그 비용을 말하지 않으면 아직 그 비용을 겪어 본 적이 없다는 뜻으로 읽힙니다.

이어지는 질문 여섯 개

메시지를 정확히 한 번 전달할 수는 없나요?

전달 자체를 한 번으로 만드는 것과 효과를 한 번으로 만드는 것을 구분해서 답해야 합니다. 일부 환경이 제한된 범위에서 그런 보장을 제공하지만 외부 API 호출처럼 브로커 밖으로 나가는 효과에는 적용되지 않습니다. “중복이 온다고 가정하고 멱등하게 만든다”가 가장 안전한 답입니다.

멱등 키는 무엇으로 잡나요?

브로커가 붙인 아이디를 그대로 쓰겠다고 하면 재발행 시 무너집니다. 업무적으로 같은 일을 가리키는 값, 여기서는 결제 아이디와 핸들러 이름의 조합을 쓰고, 데이터베이스 유니크 제약이 최종 방어선이라고 말하는 것이 좋습니다.

처리 이력 테이블이 무한히 커지지 않나요?

보관 기간을 정했는지 확인하는 질문입니다. 재시도가 일어날 수 있는 최대 기간보다 넉넉하게 잡고 그 이후 행은 정리한다고 답합니다. 너무 짧게 잡으면 오래 지연됐던 메시지가 다시 처리될 수 있다는 점도 함께 짚으면 좋습니다.

소비자를 늘리면 처리량이 그만큼 늘어나나요?

파티션 수가 상한이라는 점, 그리고 하류가 병목이면 소비자만 늘려도 소용없다는 점을 말해야 합니다. 이 두 가지를 모르면 “스케일 아웃하면 됩니다”라는 답에서 멈추게 됩니다.

데드 레터에 쌓인 메시지는 어떻게 처리하나요?

재투입 절차가 있는지를 봅니다. 원인을 고친 뒤 본 큐로 다시 넣되, 이때도 소비자가 멱등하므로 이미 처리된 것은 자연히 걸러진다고 설명하면 앞의 설계와 연결됩니다.

큐 대신 그냥 데이터베이스 테이블을 써도 되지 않나요?

규모가 작을 때는 실제로 합리적인 선택이라는 점을 인정하는 편이 좋습니다. 다만 폴링 부하, 여러 소비자 간 잠금 경합, 적체 관측 도구 부재를 언급하고 어느 시점에 전용 브로커로 옮기는지 기준을 말할 수 있으면 실전 감각이 드러납니다.

자주 나오는 실수

중복을 예외 상황으로 취급하는 것이 첫 번째입니다. “보통은 한 번만 옵니다”라고 말하는 순간, 중복이 정상 동작이라는 사실을 모른다는 것이 드러납니다. 재시도가 있는 모든 시스템에서 중복은 기본값입니다.

두 번째는 처리보다 먼저 확인 응답을 보내는 것입니다. 메시지를 받자마자 ack하고 처리하면 처리 중 죽었을 때 메시지가 사라집니다. 반대로 처리 후 ack가 원칙이고, 그래서 중복이 생기는 것이며, 그래서 멱등성이 필요한 것입니다. 이 인과가 머릿속에 연결되어 있는지가 그대로 드러나는 지점입니다.

세 번째는 모든 오류를 같은 방식으로 재시도하는 것입니다. 재시도로 회복되는 오류와 절대 회복되지 않는 오류를 구분하지 않으면, 잘못된 메시지 하나가 큐 전체를 몇 시간씩 막습니다.

네 번째는 순서를 당연히 지켜진다고 말하는 것입니다. 파티션 개념 없이 “큐니까 FIFO입니다”라고 답하면 소비자를 두 대 이상 띄우는 순간 순서가 깨진다는 사실을 모르는 것으로 읽힙니다.

다섯 번째는 저장과 발행을 두 개의 독립된 성공으로 다루는 것입니다. 아웃박스를 몰라도 괜찮지만, 최소한 “이 둘이 함께 성공한다는 보장이 없다”는 문제를 인식하고 있어야 합니다. 문제 인식이 있으면 면접관이 힌트를 주며 대화를 이어갈 수 있지만, 인식조차 없으면 대화가 끊깁니다.

여섯 번째는 큐를 넣으면 사용자 경험이 그대로일 것이라 가정하는 것입니다. 비동기로 옮긴 결과는 나중에 반영됩니다. 화면에서 그 사이를 어떻게 보여 줄지, 실패했을 때 사용자에게 어떻게 알릴지까지 말해야 설계가 완성됩니다.

준비 체크리스트

  • 지금 만들고 있는 시스템에서 동기로 처리 중인 작업 중 비동기로 옮길 수 있는 것 세 개를 고르고, 옮길 수 없는 것 한 개와 그 이유를 적어 보십시오.
  • 멱등 키를 무엇으로 잡을지 각 작업마다 한 줄로 정하고, 그 키에 유니크 제약을 걸 위치를 정하십시오.
  • 소비자 함수를 직접 작성해 처리 이력 기록과 실제 효과가 같은 트랜잭션에 들어가는지 확인하십시오.
  • 오류를 재시도 가능·불가능 두 종류로 나누고, 각각의 행동을 코드에 반영해 보십시오.
  • 순서를 지켜야 하는 범위를 하나 고르고, 그 범위를 파티션 키로 표현했을 때 병렬 처리 폭이 얼마나 줄어드는지 계산해 보십시오.
  • 아웃박스 테이블 스키마를 직접 그려 보고, 발행 프로세스가 죽었다 살아났을 때 어떤 일이 벌어지는지 설명해 보십시오.
  • 데드 레터 큐에 대해 알림·조회·재투입 세 가지 절차를 각각 한 문장으로 적어 보십시오.
  • 적체량과 처리 지연에 대한 경보 기준을 숫자로 정해 보십시오. 기준이 없으면 지표는 장식입니다.
  • 5분 타이머를 켜고 위 문제에 답한 뒤, 문제 재진술·가정·후보 비교·대가 네 가지가 모두 나왔는지 확인하십시오.

비동기 자체가 아직 낯설다면 언어별 비동기 처리 비교를 먼저 보시고, 계약 설계와 멱등성의 연결은 API 설계 면접에, 저장소 쪽 정합성은 트랜잭션과 격리 수준 면접과 데이터 모델링 면접 가이드에 이어집니다. 전체 흐름은 백엔드 개발 로드맵과 심층 가이드에서 확인하실 수 있습니다.

관련 언어 학습으로 복습하기