작성일 2026-08-10 (D-2) 면접 2026-08-12 · Data Engineer 팀장 1명, 팀원 1명 전제 일과 병행. 총 학습시간 4시간으로 끝내는 것을 목표로 한다. 원본 면접 준비 가이드(전체판) · 스터디 지도 · 과제 아키텍처 페이지
이 문서의 원칙은 하나다. 어려운 말로 정확하게 말하는 것보다, 쉬운 말로 정확하게 말하는 것이 어렵다. 그래서 모든 항목을 초등학생 설명 → 정확한 정의 → 내 사례 연결 3단으로 적었다. 면접장에서는 1단으로 시작해 상대가 더 파면 2단, 3단으로 내려간다.
0. 시간표 — 4시간 안에 끝내기
| 언제 | 시간 | 무엇 | 왜 |
|---|---|---|---|
| D-2 밤 (8/10) | 90분 | 2장 과제 + 3장 A그룹(과제에 쓴 기술) | 가장 많이 물어볼 곳. 여기만 되면 절반은 넘긴다 |
| D-1 밤 (8/11) | 90분 | 4장 리멤버 스택 + 6장 안 해본 것 스크립트 | 남의 집 지도와 경계선. 외우는 게 아니라 개념만 |
| D-1 밤 | 30분 | 2-2·2-3을 소리 내어 5회 | 30초 설명은 입으로 외워야 현장에서 나온다 |
| 당일 아침 | 30분 | 8장 치트시트 1장만 | 숫자와 첫 문장만 다시 본다 |
하지 말 것: 전체판 가이드(755줄)를 처음부터 다시 읽는 것. 이미 만든 답변 스크립트는 지금 단계에서 도움이 안 된다. 문장을 외우면 질문이 살짝만 비틀려도 무너진다. 구조와 숫자만 잡는다.
5장 포트폴리오는 읽지 않는다. 본인이 직접 한 일이라 이미 알고 있다. 표에 적은 것은 "물어보면 이 각도로 꺼내라"는 앵커 한 줄씩이다. 훑기만 한다.
1. 팀장이 5년차에게 확인하려는 것 6가지
경력 5년차 데이터 엔지니어 면접에서 실제로 판정되는 건 지식의 양이 아니다. 아래 6개 신호다. 답변마다 이 중 하나는 반드시 태우고 지나간다.
| 신호 | 팀장이 속으로 하는 질문 | 내가 태울 재료 |
|---|---|---|
| ① 데이터를 먼저 봤나 | 요구사항만 읽고 코딩부터 시작하는 사람인가 | 구현 전 두 CSV 전수 조사 → 1 vs 1.0, 전남 vs 전라남도 210건 발견 |
| ② 되돌릴 수 있게 만들었나 | 사고 나면 이 사람 코드는 복구 가능한가 | RAW 선보존, 컬럼별 변경 이력, 한 트랜잭션 커밋, 멱등 재실행 |
| ③ 모르는 값을 함부로 고치지 않나 | 데이터를 오염시킬 사람인가 | 9자리 사업자번호에 0을 채우지 않고 격리, checksum 불일치 9건은 WARN만 |
| ④ 자기 설계의 한계를 아나 | 자기 코드를 과대평가하는가 | "실행 메타데이터까지 exactly-once는 아니다", "동시 backfill은 지금 안전하다고 말 못 한다" |
| ⑤ 안 해본 걸 안 해봤다고 하나 | 같이 일할 때 믿을 수 있는가 | Spark·Kafka 프로덕션 없음, AWS는 EC2만, 개인정보 운영 없음 |
| ⑥ 혼자 결정했나 팀을 통했나 | 영향 범위 큰 변경을 혼자 지르는가 | 인덱스 224개 삭제 전 95개 쿼리 실행계획 확인, 팀장·CTO 리뷰 |
③④⑤가 5년차와 3년차를 가르는 지점이다. 신입은 "했습니다"를 말하고, 5년차는 "여기까지 했고 여기부터는 안 했습니다"를 말한다.
2. 과제 — 여기가 면접의 절반
2-1. 10초 버전
2022년 회사 명단과 2023년 회사 명단을 사업자번호로 맞춰서, 새로 생긴 곳·바뀐 곳·그대로인 곳·이번에 안 보이는 곳을 나누고, 그 판단 근거를 전부 남기는 파이프라인입니다.
2-2. 초등학생 버전 (30초) — 이걸 먼저 말한다
작년 반 명단이랑 올해 반 명단을 들고 비교한다고 해보세요. 이름·주소·전화번호가 적혀 있고요.
그냥 눈으로 비교하면 실수가 납니다. 작년에는 "1명"이라고 썼는데 올해는 "1.0명"이라고 썼으면 사람은 같다고 보지만 컴퓨터는 다르다고 봐요. 주소도 작년엔 "전남", 올해는 "전라남도"라고 쓰면 이사 간 것처럼 보이고요.
그래서 저는 세 칸으로 나눠서 일했습니다. 첫째 칸에 받은 종이를 원본 그대로 붙여두고, 둘째 칸에서 글씨체를 통일해서 옮겨 적고, 셋째 칸에서 작년 명단이랑 한 줄씩 맞췄습니다. 나중에 "이거 왜 바뀐 거예요?" 물으면 셋째 칸만 보면 안 되니까, 원본을 첫째 칸에 남겨둔 거예요.
그리고 "올해 명단에 없다"고 지우지 않았습니다. 전학 간 건지, 명단 만드는 사람이 빠뜨린 건지 저는 모르거든요. 그래서 지우는 대신 "올해는 안 보였음"이라고 표시만 해뒀습니다.
세 칸 = RAW / STG / MASTER. 이 비유 하나로 계층 설계·재처리·soft delete가 한 번에 설명된다. 면접에서 용어를 먼저 꺼내지 말고 비유를 먼저 던진 뒤, 상대가 고개를 끄덕이면 "이걸 RAW/STG/MASTER라고 부릅니다"로 붙인다.
2-3. 면접용 버전 (30초)
2022 초기 적재와 2023 동기화를 DAG 하나로 처리했습니다. 연도만 파라미터로 받아서 같은 코드가 두 회차를 돌립니다.
입력 파일을 RAW로 원문 보존하고, STG에서 사업자번호·주소·근로자수·날짜를 같은 형식으로 정규화하면서 품질을 판정합니다. 그다음 사업자번호로 조인해서 신규·변경·no-op를 나누고, 마스터 반영 + 변경 이력 + 부재 표시 + 정합성 검사 8종을 한 트랜잭션으로 커밋합니다. 하나라도 실패하면 이력까지 롤백하고 회차를 FAILED로 마감합니다.
DuckDB 파일에 동시에 쓰지 않도록 태스크를 직렬로 두고
max_active_runs=1로 제한했습니다.
2-4. 데이터 흐름 (이 그림을 머리에 넣는다)
flowchart LR
CSV["연도별 CSV<br/>2022 · 2023"] --> RAW
subgraph RAW["RAW · 원문 보존"]
R1["raw_workplace<br/>규칙 바뀌면 파일 재수신 없이 여기서 재계산"]
end
RAW --> STG
subgraph STG["STAGING · 정규화 + 품질 판정"]
S1["stg_workplace<br/>has_valid_key / is_valid 분리"]
S2["ops_quarantine<br/>격리 · 확인 필요"]
end
S1 --> MERGE{"사업자번호 조인<br/>IS DISTINCT FROM"}
MERGE -->|없던 키| INS["신규 2,174"]
MERGE -->|5속성 중 1개라도 다름| UPD["변경 4,000"]
MERGE -->|전부 동일| NOP["no-op 17,087"]
MERGE -->|소스에 없음| ABS["is_active=false 9,426"]
INS --> TX
UPD --> TX
NOP --> TX
ABS --> TX
subgraph TX["단일 트랜잭션 — 전부 성공 or 전부 롤백"]
M1["master_workplace<br/>사업장당 1행 (과제 결과물)"]
M2["master_change_log<br/>컬럼별 전·후 값"]
CHK["정합성 검사 8종"]
end
TX --> OPS["ops_sync_run<br/>회차별 입력·정책·건수·검증결과"]이 그림에서 반드시 말할 3가지
- RAW가 앞에 있는 이유 — 규칙이 바뀌어도 원본 파일을 다시 받지 않는다
- 네모 하나(
TX)로 묶인 이유 — 절반만 반영된 상태를 남기지 않는다 ops_sync_run이 밖에 있는 이유 — 여기가 exactly-once가 아닌 지점이다 (2-9 참고)
2-5. 실행 구조
flowchart TB
subgraph IMG["Airflow 이미지 (빌드 시 굽는다)"]
CODE["DAG · 파이프라인 코드"]
DATA["입력 CSV (NFC 정규화)"]
end
subgraph RUN["docker compose"]
AF["Airflow scheduler + webserver<br/>max_active_runs=1 · 태스크 직렬"]
PG[("PostgreSQL<br/>Airflow 메타데이터 전용")]
VOL[("named volume<br/>DuckDB 작업 파일")]
end
IMG --> AF
AF <--> PG
AF <--> VOL
VOL -->|두 회차 SUCCESS 이후에만| OUT["warehouse/employment_insurance.duckdb"]
UP["./up.sh<br/>검토자는 Docker만 있으면 됨"] --> RUN왜 바인드 마운트를 안 했나 (물어보면 강한 답): 호스트 경로를 그대로 붙이면 한글 파일명과 Drive 동기화 폴더에서 파일 접근이 실패했다. 실제로 OSError: Resource deadlock avoided를 봤고, macOS는 한글을 NFD로 저장하는데 리눅스 컨테이너 안에서는 바이트 단위 조회가 어긋났다. 그래서 입력과 코드를 이미지에 굽고 CSV 이름을 NFC로 정규화했다. 재현성 문제를 실제 실패로 겪고 고쳤다는 점이 이 답변의 값이다.
2-6. 테이블 6개 — 왜 마스터 하나로 안 끝냈나
이 질문은 거의 확실히 나온다. "과제는 마스터 테이블 하나만 요구했는데 왜 6개죠?"
마스터만 만들면, Update가 규칙대로 됐는지 확인할 방법이 없습니다.
| 계층 | 테이블 | 한 줄 역할 | 없으면 못 하는 것 |
|---|---|---|---|
| RAW | raw_workplace | 받은 원문 그대로 | 규칙 바꿀 때 파일 재수신 필요 |
| STG | stg_workplace | 정규화 + 품질 판정 | 무엇이 왜 제외됐는지 설명 불가 |
| MASTER | master_workplace | 사업장당 1행 최신 상태 (과제 결과물) | — |
| MASTER | master_change_log | 컬럼별 전·후 값 | "무엇이 바뀌었나" 답변 불가 |
| OPS | ops_quarantine | 격리·확인 필요 기록 | 버린 데이터가 사라짐 |
| OPS | ops_sync_run | 회차별 입력·정책·건수·검증결과 | "어느 회차가 이걸 했나" 추적 불가 |
변경 이력은 SCD2 대신 컬럼 단위 로그로 갔다. 비교 컬럼이 5개뿐이고 "무엇이 바뀌었나"가 검증의 핵심이었기 때문이다.
2-7. 설계 결정 7개 — 각 1줄 + 되받아치는 말
압박 질문이 오면 먼저 "그 지적 맞습니다"를 인정하고, 왜 그럼에도 이 선택인지로 간다.
| # | 결정 | 한 줄 근거 | 반론이 오면 |
|---|---|---|---|
| 1 | 데이터 먼저 전수 조사 | 1 vs 1.0, 전남 vs 전라남도 210건. 정규화 없었으면 변경 건수가 5천 건 넘게 부풀었다 | "샘플만 봐도 되지 않나요?" → 3만 행이라 전수가 가능했고, 표기 차이는 샘플에 안 걸린다 |
| 2 | 키는 고치지 않는다 | 9자리 사업자번호에 0을 채우면 실재하는 다른 사업장과 충돌한다. 되돌릴 수 없는 오염 | "그럼 그 행은 버리나요?" → 마스터에서만 제외하고 격리 테이블에 남긴다. 버리지 않는다 |
| 3 | 2023 빈 값 → NULL 갱신이 기본 | 과제가 "최신 파일 값으로 갱신"이라고 정의했으니 literal 적용. 보존은 SYNC_PRESERVE_EXISTING_ON_NULL 옵션 | "그건 데이터 손실 아닌가요?" → 맞다. 주소 결측 2,249건 중 2,093건은 2022에 값이 있어 추출 누락 가능성이 있다. 그래서 옵션을 만들고 두 정책 모두 검증을 통과시켰다. 운영이면 데이터 소유자와 먼저 합의할 문제 |
| 4 | 2023에 없는 9,426건 삭제 안 함 | 폐업·추출 누락·공개 범위 변경을 구분할 수 없다. 과제 동작도 Insert·Update뿐 | "그건 과제가 요구 안 한 확장 아닌가요?" → 맞다. 해석이 들어간 확장이다. 그래서 부재를 무시하는 옵션도 뒀고, 5개 업무 속성 결과는 그대로 재현된다 |
| 5 | 테이블 6개 | 2-6 표 | — |
| 6 | 한 트랜잭션 | 마스터·이력·부재·검증 8종을 묶고, 하나라도 실패하면 이력까지 롤백 | "성능은요?" → 3만 행 규모라 문제 없었다. 수백만 행이면 청크와 publish pointer로 나눈다 |
| 7 | DuckDB | 연도별 CSV 전체 읽기 + 집합 조인 + 전수 검증이라 컬럼 지향이 맞았다. read_csv, IS DISTINCT FROM | "SQLite는요?" → 행 단위 OLTP에 맞는 엔진이다. 약 3만 행에서 성능 자랑하려는 선택은 아니었다. 동시 writer가 필요해지면 중앙 DB로 옮긴다 |
2-8. 외울 숫자 — 7개면 충분
덧셈으로 외운다. 개별 숫자를 외우지 말고 식을 외운다.
30,513 + 2,174 = 32,687 초기 마스터 + 신규 = 최종 마스터
21,087 = 4,000 + 17,087 공통 키 = 변경 + no-op
23,261 + 9,426 = 32,687 활성 + 최근 소스 부재
5,196 = 2,876 + 1,972 + 310 + 30 + 8 주소·근로자수·이름·날짜·업종
8/8, 불일치 0 연도별 정합성 + 2023 최종 대조함정 질문 대비: "변경된 사업장이 4,000인데 이력이 5,196이면 계산이 안 맞는데요?" → 한 사업장에서 주소와 근로자수가 함께 바뀔 수 있다. 이력을 컬럼별로 한 건씩 남겼기 때문이다.
품질 판정 결과: 실제 파일 ERROR 0건, 2023 WARN 29건, INFO 2,249건(주소 결측). 테스트: 제출 시점 컨테이너 107개, 현재 동일 소스 재검증 108개 (기록 시점을 나눠 말한다).
2-9. 먼저 인정할 한계 3개 — 지적당하기 전에 내가 말한다
이게 신호 ④다. 순서를 바꾸면 방어가 되고, 내가 먼저 말하면 판단력이 된다.
- exactly-once는 업무 데이터까지다. 마스터·이력·부재·검증 8종은 한 트랜잭션이지만, 성공 상태와 건수(
ops_sync_run)는 그 커밋 뒤에 따로 기록된다. 두 단계 사이에서 죽으면 데이터는 반영됐는데 상태는 RUNNING으로 남을 수 있다. → 운영이면 같은 DB일 때 한 트랜잭션으로 묶고, 저장소가 갈리면 outbox와 오래된 RUNNING 재대조로 복구한다. - 동시 backfill은 지금 안전하다고 주장하지 않는다.
max_active_runs=1+ 직렬 태스크라는 단일 회차 가정에 기대고 있고, 일부 조회가run_id대신source_year범위를 쓴다. → 병렬을 허용하려면 모든 접근을run_id또는 immutable snapshot으로 격리하고(run_id, key)유일성, lease, publish pointer를 둔다. - 메모리에 한 해 분량을 올린다.
stage()가 그렇게 동작하므로 수백만 행에서는 청크 처리가 먼저다.
문서·구현 차이 3건도 먼저 말한다 (이건 진짜 점수가 된다):
- README에 "절단 의심 주소 4건 quarantine"이라고 적었지만 실제로는 수동 프로파일링 후보이고 runtime rule이 없다
- 대표 실행 로그는 동일 core를 쓴 CLI 경로다 (clean extraction에서 Airflow 두 회차도 같은 결과 확인)
- 제출 문서는 107 tests, 현재 재검증은 108 tests
말하는 법: "제출 이후에 문서를 실행 산출물과 다시 대조했는데 세 군데 어긋난 부분을 찾았습니다. 먼저 정정하고 시작하겠습니다."
2-10. "코드리뷰 해보세요" 대본 — 두 방향 다 준비한다
(A) 내 과제 코드를 셀프 리뷰하라고 하면 — 잘한 점 자랑이 아니라, 한계를 심각한 순서로 + 조건과 함께. 2-9의 한계 3개가 그대로 재료다.
"제가 리뷰어라면 세 가지를 지적하겠습니다. 심각한 순서로 — ①
stage()가 한 연도를 메모리에 올립니다. 3만 행이면 20MB라 지금은 문제없지만 수백만 행에서 첫 번째로 무너지는 지점이고, 그때는 정규화를 DuckDB SQL로 내리거나 청크 처리로 바꿔야 합니다. ② 성립일자가 과거로 이동한 17건을 WARN으로만 남기고 판정하지 않았습니다. 소급 정정인지 추출 오류인지 데이터만으로 알 수 없어서인데, 운영이라면 이 WARN이 사람에게 도달하는 경로와 원천 정정 요청 절차까지 있어야 '기록'이 '처리'가 됩니다. ③ 품질 규칙 12종이 코드에 있어 규칙 변경마다 배포가 필요합니다. 12개 규모에선 리뷰·테스트를 타는 지금이 안전하고, 수십 개로 늘면 선언적 정의로 분리하겠습니다."
닫는 말: "실제로 제출 전 재리뷰에서 테스트 헬퍼가 운영 경로(apply_master 한 트랜잭션)와 다른 경로를 검증하던 결함을 찾아 고쳤습니다. 테스트가 통과하는 것보다 무엇을 통과하는지가 중요하다는 걸 다시 배웠습니다."
(B) 남의 코드를 리뷰하라고 하면 — 평가 대상은 버그 개수가 아니라 접근 순서와 화법이다.
- 맥락 먼저 (30초): "목적과 호출 주체가 뭔가요? 가장 걱정되는 부분이 있으신가요? 신규인가요, 수정인가요?" — 바로 코드로 뛰어들지 않는 것 자체가 점수.
- 읽는 순서를 소리 내어 선언: "입출력 계약 → 정확성 → 경계 조건 → 실패 시나리오 → 테스트 순으로 보고, 스타일은 마지막에 몰아 말씀드리겠습니다."
- 파이프라인 코드 체크리스트 6개: 정확성(유실·중복 경로, NULL을
=로 비교하나) → 멱등성(두 번 돌리면 중복 적재되나) → 트랜잭션 경계(중간에 죽으면 절반 반영이 남나) → 경계값(빈 파일·0건·중복 키) → 조용한 실패(예외 삼키기, 실패해도 0 반환) → 테스트(운영 경로를 타나). - 화법 = 심각도 라벨 + 질문형: blocker "이 UPDATE에 WHERE가 없는데 의도된 건가요?" / major "재실행하면 중복 적재될 것 같은데 UPSERT가 앞에 있어야 하지 않을까요?" / nit "취향 차이일 수 있는데 매직 넘버는 상수로." 좋은 점 하나는 꼭 언급.
- 요약으로 마감: "blocker 1, major 1, 나머지 nit — blocker만 고치면 머지해도 된다고 생각합니다."
하지 말 것: 스타일부터 지적, "다 다시 짜야 해요", 확신 없는 단정. A→B로 이어지면 "체크리스트 6개는 제 과제의 검증 8종과 같은 관점입니다"로 연결한다.
3. 기술 용어 — 초등학생 설명 카드
각 카드는 쉬운 말 / 정확히 / 내 연결 3줄이다. 면접에서는 1줄로 시작한다.
A그룹 — 과제에서 직접 쓴 것 (여기는 확실히 알아야 함)
멱등성 (idempotency)
- 쉬운 말: 같은 버튼을 열 번 눌러도 결과가 한 번 누른 것과 같은 성질. 엘리베이터 버튼을 여러 번 눌러도 엘리베이터는 한 번만 온다.
- 정확히: 같은 입력으로 재실행해도 상태가 같은 값으로 수렴한다.
- 내 연결: 같은 2023 스냅샷을 다시 넣으면 신규 0, 속성 Update 0, 변경 이력 증가 0. 단, RAW·STG 교체와 상태 컬럼 UPDATE, 실행 기록은 다시 쓴다. 그래서 "아무것도 안 쓴다"는 과장이고, 정확한 표현은 "업무 결과가 안 변한다"다.
트랜잭션
- 쉬운 말: 계좌이체. 내 통장에서 빠지고 상대 통장에 들어가는 게 둘 다 되거나 둘 다 안 되거나 둘 중 하나여야 한다. 하나만 되면 사고다.
- 정확히: 여러 작업을 하나의 원자 단위로 묶어 전부 성공 또는 전부 롤백.
- 내 연결: 마스터 반영 + 변경 이력 + 부재 표시 + 검증 8종을 한 트랜잭션. 검증이 실패하면 이력까지 되돌리고 회차를 FAILED로 마감.
RAW / STG / MASTER
- 쉬운 말: 영수증 원본 / 가계부에 옮겨 적은 것 / 이번 달 최종 정산표. 정산이 틀리면 영수증을 다시 보면 된다.
- 정확히: 원문 보존 계층 / 정규화·품질판정 계층 / 소비자가 쓰는 최신 상태 계층.
- 내 연결: 정규화 규칙이 바뀌어도 공단에 파일을 다시 요청하지 않고 RAW에서 재계산한다.
IS DISTINCT FROM
- 쉬운 말: 보통 SQL에서 "빈칸"과 "빈칸"을 비교하면 컴퓨터가 "몰라"라고 답한다. 이건 "둘 다 빈칸이면 같은 거야"라고 제대로 답해주는 비교 방식이다.
- 정확히: NULL-safe 비교.
NULL = NULL은 UNKNOWN이지만NULL IS NOT DISTINCT FROM NULL은 TRUE. - 내 연결: 값→NULL, NULL→값 변경을 놓치지 않으려고 5개 속성 비교에 사용. hash 비교 대신 직접 비교를 고른 이유는 어느 컬럼이 바뀌었는지 이력을 남겨야 했고, Python과 SQL 사이 NULL·문자열 직렬화 규칙 차이로 hash가 어긋날 위험을 피하려는 것.
soft delete
- 쉬운 말: 지우개로 지우지 않고 옆에 연필로 "오늘은 안 보임"이라고 적어두는 것.
- 정확히: 물리 삭제 대신 상태 플래그로 비활성 표시.
- 내 연결:
is_active=false,absent_since_year=2023. 내가 확인한 사실은 "최근 소스에 없었다"까지다. 폐업이라고 단정하지 않았다.
backfill / catchup / max_active_runs
- 쉬운 말: 밀린 일기를 몰아 쓰는 것(backfill). 몰아 쓰되 한 번에 한 장씩만 쓰게 막아둔 게
max_active_runs=1. - 정확히: 과거 구간 재실행 / 스케줄러가 미실행 구간을 자동 생성 / 동시 실행 DAG run 수 상한.
- 내 연결:
@yearly+catchup=true로 2022·2023이 자동 생성된다. 다만max_active_runs=1은 동시 실행만 막지 연도 순서를 보장하지 않아서, 2023 뒤 2022가 들어오는 역행은 merge 전에 별도 가드로 막았다.
데이터 품질(DQ)과 차단선
- 쉬운 말: 시험지 채점할 때 "이건 0점 처리", "이건 빨간 줄만 긋고 넘어감"을 미리 정해두는 것.
- 정확히: ERROR는 반영 차단, WARN은 기록 후 진행, INFO는 관측만.
- 내 연결: 키 결측·형식 오류·근로자수 파싱 오류·중복 탈락은 ERROR로 제외. checksum·날짜 이상은 외부 정답이 없어 고치지 않고 WARN. 주소 결측은 INFO.
has_valid_key를 더 넓은is_valid와 분리해서, 속성이 ERROR여도 업무 키가 유효하면 "현재 소스에서 본 키"로 계산해 잘못된 부재 판정을 막았다. - 검증 8종: 키 유일성 / 행 수 증분 / 분류 합계 / 소스 키 포함 여부 / 정책값 일치 / NULL 보호 / 활성 상태 / 미래
last_seen_year존재 여부.
reconciliation (대조)
- 쉬운 말: 이사하고 나서 짐 개수를 다시 세는 것.
- 정확히: 이관·재처리 후 원본과 결과를 다시 맞춰 누락·차이를 찾는 절차.
- 내 연결: 2023 STG와 활성 마스터 직접 대조 불일치 0건. IDC 이관에서도 전후 테이블 행 수를 대조했다.
B그룹 — 리멤버 스택 (개념만. 운영 경험처럼 말하지 않는다)
CDC (Change Data Capture)
- 쉬운 말: 책 전체를 다시 베끼는 대신, 어제 이후 고친 부분만 옮겨 적는 것.
- 정확히: 원본 DB의 변경 로그(MySQL이면 binlog)를 읽어 변경분만 전파.
- 내 연결: 내가 운영한 CDC는 약 1시간 단위로 binlog 파일을 전달하는 배치형 동기화다. Kafka 기반 실시간 스트리밍과는 처리 방식도 지연 기준도 다르다. 이 문장을 먼저 말한다.
Kafka / MSK
- 쉬운 말: 여러 사람이 넣고 여러 사람이 꺼내 쓰는 줄 서는 컨베이어 벨트. 꺼내 간 사람마다 "나는 몇 번째까지 가져갔다"는 표시를 따로 들고 있다.
- 정확히: topic을 partition으로 나눠 순서를 보장하고, consumer group이 offset을 관리하는 분산 로그.
- 내 연결: 프로덕션 운영 경험 없음. topic·partition·consumer group의 역할까지만 정확히 설명한다.
Spark
- 쉬운 말: 한 사람이 다 계산하기엔 너무 큰 숙제를, 반 전체에 나눠주고 걷어서 합치는 방식. 나눠주는 사람이 Driver, 푸는 사람들이 Executor.
- 정확히: Driver가 DAG를 짜고 Executor가 파티션 단위로 분산 실행.
- 내 연결: 프로덕션 운영 경험 없음. "단일 노드에서 메모리·I/O·처리시간 한계를 먼저 측정하고, 분산 비용보다 이익이 커질 때 검토한다"가 내 답.
Iceberg / S3 Tables
- 쉬운 말: 파일 창고에 물건을 쌓아두고, "지금 이 순간 창고에 뭐가 들어있는지" 목록표를 따로 관리하는 방식. 목록표를 바꾸면 창고를 안 건드려도 테이블 내용이 바뀐다.
- 정확히: 실제 데이터는 Parquet 파일이고 Iceberg metadata(snapshot → manifest → data file)가 현재 테이블 구성을 정의한다. S3 Tables는 그걸 AWS가 관리형으로 제공.
- 내 연결: 개념 학습만. 다만 "파일을 테이블처럼 쓰면 운영 작업이 생긴다"는 지점은 내 문제의식과 붙는다.
compaction / snapshot 만료 / orphan file
- 쉬운 말: 작은 상자가 너무 많아지면 꺼낼 때마다 상자를 다 열어봐야 하니까 큰 상자로 합치는 것(compaction). 옛날 목록표를 언제까지 보관할지 정하는 게 snapshot 만료. 목록표에서 빠졌는데 창고에 남아 있는 물건 치우는 게 orphan file 정리.
- 정확히: CDC가 작은 커밋을 자주 하면 small file·snapshot·미참조 파일이 누적되므로 유지보수가 운영의 일부가 된다.
- 내 연결: 리멤버 공개 사례는 목표 파일 크기를 512MB로 잡았다. 보편 정답은 아니고 해당 워크로드의 선택이라는 단서를 같이 붙인다.
StarRocks (OLAP)
- 쉬운 말: 창고(S3)에서 매번 꺼내 세면 느리니까, 자주 보는 것만 앞쪽 진열대에 미리 정리해두는 것.
- 정확히: 레이크하우스 데이터를 저지연으로 서빙하는 MPP 분석 엔진.
- 내 연결: 프로덕션 경험 없음. 전환 판단 기준만 말한다 — 소비 쿼리, 동시 사용자, freshness, 비용. "분석용 대량 조회가 운영 트랜잭션을 방해하거나 집계 응답시간을 운영 DB에서 못 맞출 때" 검토한다.
Lake Formation
- 쉬운 말: 도서관 출입증(IAM)과 "이 책의 이 페이지만 볼 수 있음" 권한(Lake Formation)은 다른 것이다.
- 정확히: IAM은 AWS API 호출 가능 여부, Lake Formation은 DB·테이블·컬럼·행 단위 데이터 권한. 둘 다 있어야 접근된다.
- 내 연결: 개념 학습만. 개인정보 다루는 팀이면 재처리 산출물에도 같은 정책이 걸려야 한다는 게 내가 주목한 지점.
Glue PySpark Full Load
- 쉬운 말: 이사할 때 원래 집에서 직접 짐을 빼면 살고 있는 사람이 불편하니까, 똑같은 집(복제본)을 하나 만들어서 거기서 뺀다.
- 정확히: Full Load 전용 Aurora replica를 만들고 Glue PySpark가 JDBC로 병렬 스캔해 S3 Tables에 적재.
- 내 연결: 대량 이관의 첫 목표는 속도보다 영향 격리라는 원칙. 내 IDC 이관에서도 같은 순서였다.
C그룹 — 내 포트폴리오 용어 (물어보면 파고들 곳)
collation
- 쉬운 말: "APPLE"과 "apple"을 같은 단어로 볼지 다른 단어로 볼지 정하는 규칙. 두 테이블이 이 규칙을 다르게 갖고 있으면 서로 대조할 때 인덱스를 못 쓴다.
- 내 연결: 뷰를 통한 조인이 인덱스를 못 타고 약 130만 건 전체 스캔, 테이블 직접 조인은 인덱스를 타고 약 6,000건. 실행계획으로 확인했다. 단위 주의 — 이건 실행계획상 예상 스캔 행 수지 응답시간이나 CPU 감소율이 아니다.
실행계획 (EXPLAIN)
- 쉬운 말: DB에게 "너 이 질문에 답하려고 어떤 순서로 몇 개나 뒤져볼 거야?"를 미리 물어보는 것.
- 내 연결: 인덱스 311개 중 224개를 지울 때 "아마 안 쓸 것"이 아니라, 분석 엔진이 쓰는 95개 쿼리의 실행계획을 전부 확인해서 근거를 만들었다.
backpressure / 429 + Retry-After
- 쉬운 말: 식당이 꽉 찼는데 손님을 계속 안으로 들이면 안에서 사고가 난다. "지금은 자리 없어요, 20분 뒤에 오세요"라고 밖에서 말해주는 것이 backpressure.
- 정확히: 처리 용량 초과 시 요청을 무한히 쌓지 않고 호출자에게 거절과 재시도 시점을 돌려준다.
- 내 연결: 80개 넘는 크롤러의 DB 쓰기를 HTTP Broker 하나로 모으고 bounded queue를 뒀다. 큐가 차면 429 + Retry-After. 큐가 찼는데 계속 받아서 메모리에 쌓으면 장애가 더 커진다가 핵심 문장.
OLTP vs OLAP
- 쉬운 말: 편의점 계산대(한 사람 한 번씩 빠르게)와 월말 매출 집계(전체를 한 번에 훑기). 잘하는 일이 다르다.
- 내 연결: 과제에서 DuckDB를 고른 이유이자, StarRocks 전환 판단 기준의 뼈대.
4. 리멤버 스택 — 남의 집 지도
말하기 전 안전 문구: "공개된 AWS 기술 블로그와 채용 공고에서 본 구조입니다. 내부 규칙이나 임계값은 모르고, 제가 운영해 본 스택도 아닙니다."
이 경계를 먼저 긋고 들어가면 그다음 설명은 전부 가산점이 된다. 안 긋고 아는 척하면 한 번에 무너진다.
flowchart LR
subgraph OLTP["운영 (OLTP)"]
AUR[("Aurora MySQL<br/>writer")]
REP[("Full Load 전용<br/>replica")]
end
AUR -->|binlog| DBZ["Debezium<br/>변경 이벤트 추출"]
DBZ --> MSK["Amazon MSK (Kafka)<br/>topic · partition"]
AUR -.복제.-> REP
REP -->|"JDBC 병렬 스캔"| GLUE["AWS Glue PySpark<br/>Full Load"]
MSK -->|"PK upsert"| S3T
GLUE -->|"초기 적재"| S3T
subgraph LAKE["레이크하우스"]
S3T[("S3 Tables · Iceberg<br/>Parquet + metadata")]
MAINT["compaction · snapshot 만료<br/>orphan file 정리"]
OBS["PyIceberg 자체 수집기<br/>file count · size · partition"]
end
S3T --- MAINT
S3T --- OBS
S3T --> SR["StarRocks on EKS<br/>저지연 OLAP"]
S3T --> ATH["Athena · BI"]
SR --> APP["대시보드 · API"]
LF["Lake Formation<br/>DB·table·column·row 권한"] -.권한 판정.-> S3T
LF -.-> ATH이 그림에서 내가 꺼낼 수 있는 질문 3개 (역질문으로 써도 좋다):
- Full Load와 CDC의 접속점을 어떻게 잡으셨나요? (아래 그림)
- S3 Tables의 관리형 maintenance 실행 여부를 무엇으로 확인하시나요? (PyIceberg 관측)
- StarRocks와 Athena의 책임 경계는 freshness 기준으로 나누시나요?
Full Load ↔ CDC 접속점 — 가장 물어보기 좋은 지점
sequenceDiagram
participant DB as Aurora replica
participant FL as Glue Full Load
participant CDC as Debezium/Kafka
participant T as S3 Tables
Note over DB,CDC: ① 기준점 먼저 고정 (binlog position / GTID)
DB->>CDC: 이 위치부터 이벤트 수집 시작
DB->>FL: 스냅샷 읽기 (기준점 시각 기준)
FL->>T: 초기 전량 적재
Note over CDC: 그동안 발생한 변경은 버퍼에 쌓임
CDC->>T: ② 기준점 이후 이벤트를 PK upsert로 재적용
Note over T: ③ 키 · 건수 · 핵심 속성 대조왜 이게 어려운가 (초등학생 버전): 책을 통째로 베끼는 동안에도 원본은 계속 고쳐진다. 그래서 "몇 시 몇 분까지의 내용을 베낀 건지" 시각을 먼저 못 박고, 그 이후 고쳐진 부분만 따로 모았다가 나중에 덧칠한다. 덧칠할 때는 같은 줄을 두 번 덧칠해도 결과가 같도록 "줄 번호(PK) 기준으로 덮어쓰기"를 쓴다.
내 과제와의 연결: 여기서 PK upsert가 하는 역할이 내 과제의 IS DISTINCT FROM 비교 + 사업자번호 기준 merge와 같은 문제다. 재적용해도 결과가 같아야 한다는 요구가 동일하다. 이 연결을 말할 수 있으면 "과제만 한 사람"에서 "구조를 이해한 사람"으로 올라간다.
5. 포트폴리오 8개 — 앵커 한 줄씩 (읽기만, 외우지 않음)
본인이 한 일이므로 내용은 이미 안다. 각 항목에서 "면접에서 꺼낼 각도" 하나씩만 정리했다.
| # | 항목 | 꺼낼 각도 한 줄 | 숫자 1개 |
|---|---|---|---|
| 01 | OS 패키지 취약점 수집 정확도 | CVE ID만 맞아서는 부족하다. 어느 버전에서 고쳐졌는지까지 맞아야 스캐너가 신뢰된다 | 12종 배포판 × 4가지 제공 방식(API·OVAL·Tracker·Git) |
| 02 | 라이브러리 컴포넌트 테이블 재설계 | CPU 부하를 보고 서버를 키우는 대신 슬로우 쿼리→실행계획으로 내려가니 원인이 collation·식별자 정책이었다 | 실행계획 예상 스캔 행 130만 → 6,000 |
| 03 | 인덱스 최적화 & 용량 절감 | 지워도 되는 근거를 먼저 만들고 지웠다 (95개 쿼리 실행계획 전수 확인) | 인덱스 311개 중 224개 삭제 · 9.6TB→4TB · 고객사 DB 생성 12h→6h |
| 04 | RAW 보존·재파싱 ETL | 외부에서 다시 받아야만 재처리되던 구조를 원본 선보존으로 바꿨다 → 과제 설계의 원형 | 적용 소스의 재처리 외부 요청 0건 (2일→5분은 설계 산정 예시, 실측 아님) |
| 05 | K8s·Airflow 운영 | 노드 증설과 pool·backoff·resource request를 함께 조정했다. 최초 클러스터 설계자라고 말하지 않는다 | Pod 할당 실패 주 70~100건 → 주 1~5건 |
| 06 | DML Broker | 80개 크롤러가 각자 재시도를 관리하던 걸 공통 운영 규칙 하나로 모았다. 각 담당자가 자기 크롤러를 이관 | lock·connection 오류 하루 3~4건 → 적용 후 4개월 0건 |
| 07 | AWS→IDC 이전 | 장비가 없어서 먼저 수집 시간 분산으로 버티고, 준비된 뒤 DB 역할을 분리했다 (임시복구→구조개선 순서) | 월 10~20건 → 5건 미만 → 전환 후 1~2건 |
| 08 | Grafana 모니터링 | 서버 자원·DB·수집 누락·고객사 동기화를 같은 시간축에서 봤다 | 10~20대 서버 (탐지시간 감소율은 미확인) |
추가 카드: MySQL 8.4 최대 약 30억 행 사전 검증에서 HDD 쓰기 회귀를 보고 innodb_flush_method 기본값 변화로 원인을 좁혔다. 하드웨어별 기준을 나누고 단계적으로 적용했다.
단위 방어 3개 (과장으로 오해받기 쉬운 지점):
130만 → 6,000은 실행계획상 예상 스캔 행 수다. 응답시간·CPU 감소율은 기록으로 확인되지 않았다.2일 → 5분은 설계 산정 예시다. 실측 성과로 말하지 않는다.- IDC 이관에서 확인된 것은 전후 테이블 행 수 대조까지다. 명시적 rollback 실행 여부는 기록으로 확인되지 않는다.
6. 안 해본 것 스크립트 — 5년차의 가장 강한 무기
각 항목은 없다 → 대신 있는 것 → 어떻게 채울지 3박자. 미안해하지 않고 담백하게.
| 질문 | 없다 | 대신 있는 것 | 채우는 법 |
|---|---|---|---|
| Spark·Kafka | 프로덕션 운영 경험 없음 | Python·SQL·Airflow·Kubernetes·MySQL로 수십억 행 DB와 80개 크롤러의 스케줄링·pool/backoff·재처리·동시성·backpressure | 팀의 실제 데이터량과 지연 요구 기준으로 실행 모델부터 |
| OLAP DB (StarRocks·ClickHouse) | 프로덕션 없음 | 과제의 DuckDB 전수 검증, 실무의 MySQL 실행계획과 대규모 배치 | 소비 쿼리·동시 사용자·freshness·비용 확인 후 판단 |
| AWS | RDS·S3·EFS 운영 경험 없음. 제출 자료의 관련 태그는 최종 검수에서 놓친 잘못된 표기 | EC2에서 self-managed MySQL과 애플리케이션 운영. 온프레미스 SeaweedFS 설계·단계적 이관 | 질문받으면 바로 정정. SeaweedFS 경험을 S3 운영이라고 말하지 않는다 |
| 개인정보 데이터 운영 | 리멤버 수준의 개인정보 운영 경험 없음 (내 경력은 보안 메타데이터 중심) | 최소 권한·마스킹·보존/삭제·재처리 산출물 동일 정책·접근 로그라는 설계 원리 | 수집 목적·접근 주체·보존 삭제 규칙·감사 경계를 먼저 확인 |
| MDM 전용 조직 운영 | 없음 | 식별자 정책 재설계, 원천 레지스트리 전수 대조, RAW·이력·재처리 구조 | 자동 연결 / 사람 검토 / 보류로 나누고 false merge·false split 비율을 표본 검사 |
AWS 태그 오기는 면접 초반에 내가 먼저 꺼낸다. 지적당하면 실수지만, 먼저 말하면 검수 태도가 된다.
답변 만들면 안 되는 3개 — 확인된 사건이 없으므로 그럴듯하게 지어내지 않는다:
- 대인 갈등 사례 (상대 우려·내 양보·최종 결정이 확인된 사건 없음)
- 직장에서의 개인 실패 (실제 손실과 본인 책임이 함께 확인된 사건 없음)
- 받은 피드백으로 행동이 바뀐 사례
실패 경험을 물으면: "직장 실패 대신 최근 제출 과제에서 사전 발견한 near-miss로 답해도 될까요?"로 먼저 묻는다. → 독립 재리뷰에서 README 필수 항목 누락, production에서 안 쓰는 경로를 검증한 테스트, 재패키징 후 실행 권한 문제를 발견했다. 제출물 소유자의 검수 실패로 받아들이고 README 복구, production-path 테스트, 파일 모드·인코딩 검사, 한글 경로 clean extraction E2E를 추가했다. 제출 전 Airflow 두 회차 성공과 source-master 불일치 0을 확인했다. (AI 도구도 썼지만, 지적 내용을 요구사항과 실행 결과로 확인하고 제출을 결정한 책임은 나에게 있다.)
7. 역질문 3개 — 하나만 골라도 됨
과제 얘기가 끝날 때쯤 던지면 대화가 이어진다. 회사 복지 말고 팀의 실제 판단 기준을 묻는다.
- "Full Load와 CDC의 접속점을 어떻게 잡으셨는지 궁금합니다. 공개 자료로는 전용 replica를 쓰신 것까지 봤는데, 기준점 이후 이벤트 재적용에서 어려웠던 부분이 있으셨을까요?"
- "식별·매핑에서 자동 연결과 사람 검토를 나누는 기준이 있으신가요? false merge와 false split 중 어느 쪽 비용을 더 크게 보시는지 궁금합니다."
- "데이터 품질 규칙 중 파이프라인을 멈추는 것과 경고만 남기는 것을 나누는 기준이 팀에 정해져 있나요?"
8. 당일 아침 30분 치트시트
이 페이지만 보고 들어간다.
첫 90초
여러 곳에서 다른 이름으로 들어오는 데이터를 '결국 같은 대상인지' 판단해 연결해 온 데이터 엔지니어입니다. 5년간 오픈소스 제품과 버전, 취약점, 12종 Linux 배포판의 보안 정보를 맞춰 왔습니다. 잘못 연결되거나 빠진 데이터는 식별 규칙부터 저장 구조와 재처리 방법까지 고쳤습니다. 리멤버에서는 명함·프로필·회사 데이터를 오래 믿을 수 있게 만들고 싶습니다.
과제 30초 (2-3 그대로) RAW 보존 → STG 정규화·품질판정 → 사업자번호 조인으로 신규/변경/no-op → 마스터·이력·부재·검증 8종을 한 트랜잭션 → DuckDB 단일 writer라 직렬 + max_active_runs=1.
숫자
30,513 + 2,174 = 32,687
21,087 = 4,000 + 17,087
23,261 + 9,426 = 32,687
5,196 = 2,876 + 1,972 + 310 + 30 + 8
8/8 · 불일치 0 · ERROR 0 · 테스트 107(제출)/108(현재)먼저 말할 정정 2개
- 제출 자료의 AWS 태그(RDS·S3·EFS)는 검수에서 놓친 오기. 실제 운영은 EC2 self-managed MySQL.
- 문서·구현 차이 3건 (절단 주소 4건 quarantine 표기 / 대표 로그가 CLI 경로 / 107 vs 108 tests).
먼저 인정할 한계 3개 exactly-once는 업무 데이터까지(실행 기록은 별도 커밋) · 동시 backfill 미보장 · stage() 메모리 적재.
답변 길이 규칙 상황 1문장 → 선택 2문장 → 검증 1문장 → 한계 1문장. 길어지면 여기로 되돌아온다.
말하기 순서 규칙 비유 먼저 → 용어는 나중. 상대가 끄덕이면 그때 RAW/STG/MASTER, 멱등성, IS DISTINCT FROM을 붙인다.
마지막 한 줄 확인한 것과 확인 못 한 것을 같은 문장 안에서 구분하면, 나머지 답변의 신뢰도가 같이 올라간다.
출처 ai/repo-notes/remember-data-engineer-assignment-claude.md · human/reports/2026-08-05-remember-data-engineer-interview-prep.md · human/reports/2026-08-07-remember-interview-study-map.md · human/study/content/remember-data-engineering/01~07 · human/career/remember-interview/assignment.html · 기준일 2026-08-10