PHpullh
개발자 면접 준비/데이터베이스

데이터베이스

트랜잭션과 격리 수준 면접: 이상 현상부터 잠금까지

네 가지 이상 현상을 먼저 이해하고, 격리 수준과 잠금을 그 위에 얹어 설명하는 순서를 잡습니다.

용어를 외운 답과 현상을 아는 답

트랜잭션 질문은 다른 주제보다 암기로 통과하기 쉬워 보입니다. 원자성·일관성·격리성·지속성 네 단어를 말하고, 격리 수준 네 개를 표로 그리면 뭔가 답한 것처럼 보이기 때문입니다. 그런데 면접관이 실제로 확인하는 것은 그 표가 아닙니다. “동시에 두 요청이 같은 재고를 줄이면 어떻게 되나요” 같은 구체적인 상황에서, 어떤 일이 벌어지고 무엇으로 막을지 말할 수 있는가입니다.

그래서 이 페이지는 순서를 뒤집습니다. 격리 수준을 먼저 설명하지 않고, 무엇이 잘못될 수 있는지를 먼저 봅니다. 이상 현상 네 가지를 몸으로 알고 나면 격리 수준은 “그중 무엇까지 막아 주는가”라는 한 문장으로 정리되고, 잠금은 “격리 수준이 막아 주지 않는 부분을 내가 직접 막는 도구”가 됩니다. 이 구조로 말하면 표를 외우지 않아도 답이 흔들리지 않습니다.

네 가지 이상 현상

동시에 돌아가는 트랜잭션 사이에서 벌어지는 문제는 이름이 붙어 있습니다. 이름을 아는 것보다 각각을 두 줄짜리 상황으로 설명할 수 있는 것이 중요합니다.

더티 리드는 아직 커밋되지 않은 값을 다른 트랜잭션이 읽는 것입니다. A가 잔액을 1000에서 0으로 바꾸는 중이고 아직 커밋 전인데, B가 그 0을 읽고 판단을 내립니다. 그런데 A가 롤백하면 B는 존재한 적 없는 값으로 결정을 내린 셈이 됩니다. 이것은 거의 모든 환경에서 기본적으로 막혀 있습니다.

반복 불가능 읽기는 한 트랜잭션 안에서 같은 행을 두 번 읽었는데 값이 달라지는 것입니다. 첫 번째 조회와 두 번째 조회 사이에 다른 트랜잭션이 그 행을 바꾸고 커밋했기 때문입니다. 보고서를 만드는 중간에 숫자가 바뀌는 상황을 떠올리면 됩니다.

팬텀은 같은 조건으로 여러 행을 두 번 조회했는데 없던 행이 새로 나타나는 것입니다. 값이 바뀐 것이 아니라 조건에 맞는 행의 집합이 바뀐 것이라 성격이 다릅니다. “이 시간대에 예약이 없으면 예약을 만든다” 같은 로직이 팬텀에 직접 당합니다.

갱신 손실은 실무에서 가장 자주 사고를 내는 현상입니다. 두 트랜잭션이 같은 값을 읽고, 각자 계산해서, 각자 씁니다. 나중에 쓴 쪽이 먼저 쓴 쪽의 결과를 덮어써서 갱신 하나가 사라집니다. 재고 차감, 포인트 적립, 조회수 증가처럼 “읽고-계산하고-쓰는” 모든 코드가 후보입니다. 그리고 중요한 점은, 이 현상이 격리 수준을 올린다고 자동으로 해결되지는 않는다는 것입니다.

TIP

네 가지를 외우기 어렵다면 대상으로 구분하십시오. 더티 리드는 커밋되지 않은 값, 반복 불가능 읽기는 같은 행의 값, 팬텀은 행의 집합, 갱신 손실은 내가 쓴 결과입니다. 앞의 세 개는 읽기가 흔들리는 문제이고 마지막 하나는 쓰기가 사라지는 문제입니다.

격리 수준은 어디까지 책임지는가

격리 수준은 “동시성 때문에 생기는 이상 현상을 어디까지 막을 것인가”를 고르는 손잡이입니다. 위로 올릴수록 안전해지지만 대기와 실패가 늘어납니다.

가장 아래인 READ UNCOMMITTED는 더티 리드까지 허용합니다. 실무에서 의도적으로 고를 이유가 거의 없습니다. READ COMMITTED는 커밋된 값만 읽게 해서 더티 리드를 막습니다. 많은 환경의 기본값이며, 한 트랜잭션 안에서 문장마다 최신 커밋 상태를 보기 때문에 반복 불가능 읽기와 팬텀은 여전히 일어납니다. REPEATABLE READ는 트랜잭션이 시작 시점의 일관된 스냅숏을 보게 해서 같은 행을 다시 읽어도 값이 유지됩니다. SERIALIZABLE은 동시에 실행된 결과가 어떤 순서로 하나씩 실행한 결과와 같도록 보장합니다.

여기서 면접에서 점수가 갈리는 문장이 하나 있습니다. 격리 수준은 읽기의 일관성을 주로 다루지, 애플리케이션이 값을 읽어서 계산한 뒤 다시 쓰는 흐름 전체를 지켜 주지는 않는다는 것입니다. 스냅숏을 보고 계산한 값을 그대로 덮어쓰면, 그 사이 다른 트랜잭션이 커밋한 변경은 사라집니다. 구현에 따라 갱신 충돌을 감지해 실패시키는 경우도 있지만, 그때도 애플리케이션이 실패를 받아 재시도해야 합니다. 즉 격리 수준을 올리는 것은 “문제가 사라진다”가 아니라 “문제가 조용한 오염 대신 시끄러운 오류로 바뀐다”에 가깝습니다.

SQL · 갱신 손실 재현과 세 가지 수정

-- 준비
CREATE TABLE stocks (
  product_id BIGINT PRIMARY KEY,
  quantity   INT    NOT NULL CHECK (quantity >= 0),
  version    BIGINT NOT NULL DEFAULT 0
);
INSERT INTO stocks (product_id, quantity) VALUES (1, 10);


-- ============ 1. 문제 재현: READ COMMITTED 에서의 갱신 손실 ============
-- T1                                    | T2
BEGIN ISOLATION LEVEL READ COMMITTED;    -- BEGIN ISOLATION LEVEL READ COMMITTED;

SELECT quantity FROM stocks              --
 WHERE product_id = 1;   -- 10 을 읽음   --
                                         -- SELECT quantity FROM stocks
                                         --  WHERE product_id = 1;   -- 역시 10 을 읽음
UPDATE stocks SET quantity = 9           --
 WHERE product_id = 1;   -- 앱이 10-1 을 계산해 넣음
COMMIT;                                  --
                                         -- UPDATE stocks SET quantity = 9
                                         --  WHERE product_id = 1;   -- 같은 계산 결과
                                         -- COMMIT;
-- 결과: 2개를 팔았는데 남은 수량은 9. 차감 하나가 사라졌다.
-- 두 트랜잭션 모두 정상 커밋되었고 오류도 없다. 조용히 틀린다.


-- ============ 2. 수정 A: 원자적 갱신 (읽고-쓰기를 아예 하지 않는다) ============
UPDATE stocks
   SET quantity = quantity - 1           -- 현재 값을 DB가 직접 읽어 계산
 WHERE product_id = 1
   AND quantity >= 1;                    -- 재고 부족이면 0행 갱신 -- 앱이 이를 검사
-- 갱신된 행 수가 0이면 "재고 부족"으로 처리한다. 가장 단순하고 가장 빠르다.


-- ============ 3. 수정 B: 비관적 잠금 (계산이 복잡해 원자 갱신이 불가능할 때) ============
BEGIN;
SELECT quantity FROM stocks
 WHERE product_id = 1
   FOR UPDATE;                           -- 이 행에 쓰기 잠금. 다른 트랜잭션은 대기
-- ... 애플리케이션에서 할인/한도 등 복잡한 계산 수행 ...
UPDATE stocks SET quantity = :computed WHERE product_id = 1;
COMMIT;                                  -- 커밋 시점에 잠금 해제
-- 대가: 같은 행에 경합이 몰리면 줄을 서게 되고, 대기 시간이 처리량을 깎는다.


-- ============ 4. 수정 C: 낙관적 잠금 (충돌이 드물 때) ============
-- 읽을 때 version 을 함께 가져온다
SELECT quantity, version FROM stocks WHERE product_id = 1;   -- quantity=10, version=7

-- 쓸 때 "내가 읽은 그 버전이 그대로일 때만" 갱신한다
UPDATE stocks
   SET quantity = 9, version = version + 1
 WHERE product_id = 1
   AND version = 7;
-- 갱신된 행 수가 0이면 그 사이 누군가 바꾼 것이다. 읽기부터 다시 시도한다.
-- 대가: 재시도 로직이 애플리케이션에 반드시 있어야 한다.

이 세 가지 수정은 우열이 아니라 용도가 다릅니다. 계산이 데이터베이스 안에서 한 문장으로 표현되면 첫 번째가 언제나 최선입니다. 계산이 복잡하거나 외부 값이 섞이면 두 번째나 세 번째로 갑니다. 그리고 충돌 빈도가 갈림길입니다. 인기 상품 하나에 주문이 몰리는 상황에서 낙관적 잠금을 쓰면 재시도가 폭증해 오히려 느려집니다. 반대로 사용자 프로필 편집처럼 같은 행을 두 사람이 동시에 만질 일이 드물면 낙관적 잠금이 대기 없이 훨씬 잘 돕니다.

“그냥 SERIALIZABLE 쓰면 되지 않나요”

이 문장은 면접에서 자주 나오고, 거의 항상 약한 답으로 채점됩니다. 틀린 말이라서가 아닙니다. 실제로 SERIALIZABLE은 위의 네 가지 현상을 모두 막습니다. 문제는 그 뒤에 붙어야 할 문장이 빠져 있다는 점입니다.

첫째, SERIALIZABLE은 애플리케이션에 새로운 의무를 만듭니다. 충돌이 감지되면 트랜잭션이 실패하고, 그 실패는 버그가 아니라 정상 동작입니다. 실패한 트랜잭션을 처음부터 다시 실행하는 코드가 없다면 사용자에게는 그냥 오류가 늘어난 것으로 보입니다. 재시도를 말하지 않고 SERIALIZABLE만 말하면 절반만 답한 것입니다.

둘째, 재시도에는 조건이 있습니다. 트랜잭션 안에서 이미 외부에 무언가를 보냈다면 다시 실행할 수 없습니다. 결제 승인 API를 호출한 뒤 실패해서 재시도하면 두 번 결제됩니다. 그래서 SERIALIZABLE과 재시도를 쓰려면 트랜잭션이 순수해야 하고, 이는 트랜잭션 경계를 어떻게 그을 것인가라는 더 큰 설계 문제로 이어집니다.

셋째, 비용이 균일하지 않습니다. 경합이 없는 구간에서는 부담이 크지 않지만, 같은 데이터에 몰리는 구간에서는 실패율이 빠르게 올라갑니다. 그래서 실무의 선택은 대개 “기본은 낮은 격리 수준으로 두고, 정말 필요한 소수의 트랜잭션에만 높은 수준이나 명시적 잠금을 쓴다”입니다. 이 문장을 말할 수 있으면 SERIALIZABLE을 아는 사람과 써 본 사람의 차이가 드러납니다.

긴 트랜잭션이 만드는 피해

격리 수준과 잠금을 잘 골라도 트랜잭션이 길면 전부 무너집니다. 트랜잭션이 잡은 잠금은 커밋이나 롤백까지 풀리지 않기 때문에, 트랜잭션 길이는 곧 다른 작업이 기다리는 시간입니다.

가장 흔한 원인은 트랜잭션 안에서 외부를 호출하는 것입니다. 결제 게이트웨이 호출, 이메일 발송, 다른 서비스의 API 호출이 트랜잭션 안에 들어가면, 그 외부가 느려질 때 데이터베이스의 잠금이 그만큼 오래 유지됩니다. 상대가 30초 만에 응답하면 우리 데이터베이스도 30초 동안 그 행을 붙잡고 있는 셈입니다. 사용자 입력을 기다리는 것은 더 심합니다. 화면을 열어 둔 채 점심을 먹으러 가면 트랜잭션이 한 시간 열려 있게 됩니다.

피해는 대기만이 아닙니다. 여러 버전을 유지하는 방식의 데이터베이스에서는 오래 열린 트랜잭션이 옛 스냅숏을 필요로 하기 때문에, 이미 갱신된 옛 버전들을 정리하지 못하고 계속 쌓아 두게 됩니다. 그 결과 디스크가 늘고 조회가 느려지는 현상이 트랜잭션과 상관없어 보이는 곳에서 나타납니다. 이 연결을 설명할 수 있으면 꽤 깊이 있는 답으로 읽힙니다.

그래서 원칙은 단순합니다. 트랜잭션 안에는 데이터베이스 작업만 넣습니다. 외부 호출은 트랜잭션 밖으로 빼고, 저장과 외부 호출이 함께 성공해야 한다면 큐와 비동기 메시징 면접에서 다룬 아웃박스 방식으로 의도만 먼저 저장한 뒤 나중에 실행합니다. 일괄 처리 작업도 한 트랜잭션에 수십만 행을 넣지 말고 적당한 크기로 나눠 커밋합니다.

WARNING

데드락은 설정으로 없앨 수 있는 것이 아닙니다. 두 트랜잭션이 서로 다른 순서로 두 자원을 잡으면 언제든 발생합니다. 줄이는 방법은 자원 접근 순서를 코드 전체에서 일정하게 맞추고 트랜잭션 범위를 짧게 하는 것이며, 남는 부분은 데드락 오류를 정상 경로로 받아 재시도해야 합니다. “데드락이 안 나게 만들겠습니다”는 지킬 수 없는 약속입니다.

연습 문제와 두 개의 답

문제는 이렇습니다. 한정 수량 상품의 주문 처리에서 재고보다 많이 팔리는 일이 가끔 발생합니다. 코드는 재고를 조회해 충분한지 확인한 뒤 주문을 만들고 재고를 갱신합니다. 원인이 무엇이고 어떻게 고치시겠습니까.

약한 답 — “동시성 문제입니다. 트랜잭션 격리 수준이 낮아서 그런 것 같으니 SERIALIZABLE로 올리겠습니다. 그래도 안 되면 재고 갱신 부분에 락을 걸면 됩니다. 재고 확인과 주문 생성이 하나의 트랜잭션 안에 있는지도 확인하겠습니다.”

강한 답 — “현상을 먼저 정확히 말씀드리면, 재고를 읽는 시점과 쓰는 시점 사이에 다른 요청이 끼어들어 두 요청이 같은 재고 값을 보고 각자 통과한 갱신 손실입니다. 오류가 나지 않고 조용히 틀리기 때문에 로그로는 잘 안 보입니다.

가정을 확인하면, 이 상품은 한정 수량이라 같은 행에 경합이 심하게 몰리고, 재고 차감 계산 자체는 단순히 1을 빼는 것이며, 초과 판매는 절대 허용되지 않는다고 보겠습니다.

해결책은 세 가지를 놓고 봤습니다. 첫째는 격리 수준을 SERIALIZABLE로 올리는 것입니다. 정확하지만 경합이 몰리는 이 상황에서는 직렬화 실패가 자주 발생하고, 그러면 애플리케이션에 재시도가 반드시 있어야 합니다. 지금 코드에는 재시도가 없어서 오류만 늘어날 가능성이 큽니다. 둘째는 낙관적 잠금입니다. 버전 컬럼을 두고 갱신이 0행이면 재시도하는 방식인데, 같은 행에 경합이 몰리는 한정 수량 상품에서는 재시도가 계속 실패해 오히려 느려집니다. 셋째는 조건부 원자 갱신입니다. 재고를 읽어서 계산하지 않고 quantity를 quantity에서 1을 뺀 값으로 갱신하되 수량이 1 이상이라는 조건을 WHERE에 붙이는 방식입니다.

저는 세 번째를 고르겠습니다. 읽고-계산하고-쓰는 구간이 아예 사라지므로 갱신 손실이 구조적으로 발생하지 않고, 갱신된 행 수가 0이면 재고 부족으로 바로 처리하면 됩니다. 잠금 대기도 최소화됩니다. 여기에 마지막 방어선으로 수량 컬럼에 음수를 금지하는 제약을 걸어 두겠습니다. 애플리케이션 검사는 새 코드 경로에서 뚫릴 수 있지만 제약은 뚫리지 않습니다.

주문 생성과 재고 차감은 같은 트랜잭션에 두되, 결제 승인 같은 외부 호출은 트랜잭션 밖으로 빼겠습니다. 트랜잭션이 외부 응답을 기다리는 동안 재고 행 잠금을 붙잡고 있으면 그 상품 전체 주문이 멈춥니다.

대가는 있습니다. 차감 로직이 SQL 한 문장에 묶여서 복잡한 할인이나 한도 계산이 들어오면 이 방식으로는 표현이 어렵습니다. 그때는 비관적 잠금으로 옮기고 대기 비용을 감수하겠습니다. 검증은 같은 상품에 동시 주문을 다수 발생시키는 부하 테스트로 하고, 판매 수량과 재고 감소량이 일치하는지 확인하겠습니다.”

왜 강한 답이 이기는가

두 답 모두 트랜잭션, 격리 수준, 잠금을 언급합니다. 사용한 단어의 수준은 비슷합니다. 차이는 생각이 진행된 순서입니다.

약한 답은 현상을 이름 붙이지 않고 “동시성 문제”라는 큰 범주로 넘어갑니다. 어떤 이상 현상인지 특정하지 못했기 때문에 대책이 추측이 됩니다. “SERIALIZABLE로 올리고, 그래도 안 되면 락을 건다”는 문장은 사실 “무엇이 문제인지 모르니 강한 것부터 순서대로 시도해 보겠다”와 같습니다. 강한 답은 첫 문단에서 갱신 손실이라고 특정하고, 왜 로그에 안 보이는지까지 말합니다. 현상을 정확히 지목하면 뒤의 모든 판단이 검증 가능해집니다.

두 번째 차이는 제약 조건을 명시했는가입니다. 강한 답은 “같은 행에 경합이 몰린다”와 “계산이 단순하다”를 가정으로 꺼냅니다. 이 두 문장이 있었기 때문에 낙관적 잠금이 탈락하고 원자 갱신이 선택되는 흐름이 자연스럽습니다. 약한 답에는 경합 정도에 대한 언급이 없어서, 어떤 상황에서도 같은 답을 했을 것처럼 들립니다.

세 번째 차이는 후보를 늘어놓고 각각을 왜 버렸는지 말한 부분입니다. 특히 SERIALIZABLE을 “틀린 답”으로 치지 않고 “재시도가 없어서 지금은 맞지 않는 답”으로 다룬 점이 중요합니다. 조건이 바뀌면 그 답이 맞을 수도 있다는 것을 아는 사람으로 보입니다. 약한 답은 SERIALIZABLE을 만능 스위치처럼 다뤄서, 그 선택이 만드는 실패와 재시도 의무를 모른다는 인상을 남깁니다.

네 번째 차이는 대가와 검증입니다. 강한 답은 원자 갱신이 복잡한 계산에 약하다는 한계를 스스로 밝히고, 그 경우 어디로 옮길지까지 말합니다. 그리고 부하 테스트로 어떻게 확인할지 붙입니다. 약한 답은 확인 방법이 없습니다. 검증 계획이 없는 수정안은 “고쳤다고 믿는다”와 다르지 않고, 이 주제의 사고는 대부분 조용히 일어나기 때문에 믿음만으로는 부족합니다.

이어서 나오는 질문들

격리 수준을 올리면 갱신 손실도 막히지 않나요?

구현마다 다르다는 점을 짚어야 합니다. 충돌을 감지해 트랜잭션을 실패시키는 방식이라면 데이터는 지켜지지만 애플리케이션이 그 실패를 받아 재시도해야 합니다. “막힌다”와 “오류로 바뀐다”는 다른 이야기라는 것을 구분해서 답하면 좋습니다.

SELECT FOR UPDATE는 언제 쓰나요?

읽은 값을 근거로 곧 쓸 것이 확실하고, 그 계산을 SQL 한 문장으로 표현할 수 없을 때입니다. 남용하면 대기가 길어지므로 잠금 범위를 필요한 행으로 좁히고 트랜잭션을 짧게 유지한다는 말을 함께 붙이는 것이 안전합니다.

낙관적 잠금에서 재시도는 몇 번이 적당한가요?

숫자보다 정책을 봅니다. 횟수 상한과 간격, 그리고 최종 실패 시 사용자에게 무엇을 보여 줄지가 있어야 합니다. 재시도해도 계속 실패한다는 것은 경합이 구조적으로 심하다는 신호이므로 비관적 방식이나 대기열 방식으로 바꿀 시점이라고 덧붙이면 좋습니다.

여러 서비스에 걸친 작업은 트랜잭션으로 묶을 수 있나요?

하나의 데이터베이스 트랜잭션으로는 묶이지 않는다는 점을 분명히 하고, 각 단계를 되돌리는 보상 작업을 설계하거나 아웃박스로 순서를 보장하는 방향을 말합니다. 여기서 분산 트랜잭션을 무조건 답으로 꺼내면 운영 비용을 모르는 것으로 읽힙니다.

읽기 전용 조회도 트랜잭션으로 감싸야 하나요?

여러 테이블을 조회해 하나의 화면을 구성하는데 그 사이 값이 바뀌면 곤란한 경우라면 감싸는 것이 맞습니다. 단건 조회를 굳이 감쌀 필요는 없습니다. 목적이 일관된 스냅숏인지 아닌지로 구분해 답하면 됩니다.

데드락이 발생하면 어떻게 대응하나요?

없앨 수 있다고 말하지 않는 것이 첫 조건입니다. 접근 순서 통일, 트랜잭션 축소, 잠금 범위 축소로 빈도를 낮추고, 남는 것은 재시도로 흡수한다고 답합니다. 어떤 두 트랜잭션이 얽혔는지 로그에서 확인하는 절차까지 말하면 운영 경험이 드러납니다.

자주 나오는 실수

가장 자주 보이는 것은 ACID 네 글자를 정의로만 나열하는 것입니다. 정의는 검색하면 나오므로 면접에서 가치가 없습니다. 대신 “이 코드에서 원자성이 깨지면 어떤 데이터가 남습니까”처럼 자기 경험으로 옮겨 말해야 합니다.

두 번째는 격리 수준 표를 외워서 답하는 것입니다. 표는 어떤 수준이 어떤 현상을 막는지를 보여 줄 뿐, 왜 그 수준을 고르는지는 알려 주지 않습니다. 표만 말하고 선택 기준을 못 대면 곧바로 다음 질문에서 막힙니다.

세 번째는 갱신 손실을 격리 수준 문제로만 보는 것입니다. 읽고-계산하고-쓰는 흐름은 애플리케이션 코드가 만든 문제이므로 원자 갱신이나 잠금, 버전 검사처럼 코드 쪽 해결이 필요합니다. 이 구분을 못 하면 격리 수준을 올렸는데도 문제가 남는 상황을 설명하지 못합니다.

네 번째는 애플리케이션 검사만으로 불변 조건을 지킬 수 있다고 믿는 것입니다. 재고가 0보다 크다는 조건을 코드에서만 확인하면 동시 요청 두 개가 나란히 통과합니다. 데이터베이스 제약이 마지막 방어선이라는 사실은 데이터 모델링 면접 가이드에서도 같은 무게로 나옵니다.

다섯 번째는 트랜잭션 안에 외부 호출을 넣는 것입니다. 코드만 보면 자연스러워 보이지만 잠금 유지 시간이 남의 서비스 응답 시간에 묶입니다. 면접에서 이 지점을 스스로 짚으면 실제로 장애를 겪어 본 사람으로 보입니다.

여섯 번째는 “테스트로 확인했습니다”라고만 말하는 것입니다. 동시성 문제는 순차 테스트로 거의 재현되지 않습니다. 동시 요청을 실제로 발생시키는 방식으로 검증했는지를 말해야 설득력이 생깁니다.

준비 체크리스트

  • 네 가지 이상 현상을 각각 두 줄짜리 상황으로 적어 보고, 자기 코드에서 해당될 만한 위치를 하나씩 찾아 보십시오.
  • 지금 쓰는 데이터베이스의 기본 격리 수준이 무엇인지 직접 확인하십시오. 모르고 쓰는 사람이 의외로 많습니다.
  • 읽고-계산하고-쓰는 코드를 검색해 목록으로 만들고, 각각을 원자 갱신으로 바꿀 수 있는지 판단해 보십시오.
  • 버전 컬럼을 이용한 낙관적 잠금을 실제로 한 번 구현하고, 갱신 0행일 때의 재시도 경로까지 만들어 보십시오.
  • SELECT FOR UPDATE를 쓴 트랜잭션이 얼마나 오래 열려 있는지 측정해 보십시오. 예상보다 긴 경우가 많습니다.
  • 트랜잭션 안에서 외부 API를 호출하는 코드가 있는지 찾아 밖으로 빼는 리팩터링을 해 보십시오.
  • 동시 요청을 실제로 발생시키는 테스트를 작성해 초과 판매나 중복 적립을 재현해 보십시오. 재현하지 못한 문제는 고쳤는지 알 수 없습니다.
  • 불변 조건 세 개를 골라 애플리케이션 검사와 데이터베이스 제약 중 어디에 둘지 각각 정해 보십시오.
  • 5분 타이머를 켜고 위 연습 문제에 답한 뒤, 현상 특정·가정 명시·후보 비교·대가와 검증 네 가지가 모두 나왔는지 채점하십시오.

데이터베이스를 이제 시작하는 단계라면 데이터베이스를 일찍 배워야 하는 이유부터 읽고 오시는 편이 좋습니다. 설계 쪽으로 이어가려면 데이터 모델링 면접 가이드와 캐시 설계 면접을, 규모가 커질 때의 이야기는 확장 가능한 서비스 설계를 보시기 바랍니다. 전체 목록은 면접 준비에 있습니다.

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