리멤버 1차 면접 · 전체 최종본

2026-08-12(수) · Data Engineer 팀장 + 팀원 1명 · 이 한 페이지에 전부
목차 ① 1분 자기소개 ② 포트폴리오 8항목 ③ 과제 요약·숫자 ④ 태스크 4개 동작 ⑤ 정합성 8개 ⑥ 공백 정책 2,249 vs 156 ⑦ 코드 구조 ⑧ 예상 질문 압축 ⑨ AI 활용 질문 ⑩ 리멤버 스택 ⑪ 질문할 것들

① 1분 자기소개

여러 곳에서 다른 이름으로 들어오는 데이터를 '결국 같은 대상인지' 판단해 연결해 온 데이터 엔지니어입니다.

초기 멤버로 합류한 보안 데이터 회사에서 5년간, 오픈소스 제품과 버전, 취약점, 12종 리눅스 배포판의 보안 정보를 수집하고 연결하는 파이프라인을 만들었습니다. 같은 제품이 표기 차이로 갈라지고 다른 제품이 하나로 합쳐지는 문제를 식별 규칙부터 원본 보존, 변경 이력, 재처리 구조까지 고쳤고, 그 위에서 수십억 행 MySQL과 80개 넘는 크롤러가 도는 시스템을 안정화했습니다.

이제는 이 노하우를 '일하는 사람들의 커리어와 기회를 연결하는' 리멤버의 데이터 위에서 쓰고 싶습니다. 명함·프로필·회사 데이터는 제가 해 온 식별과 정합성 문제가 그대로 있으면서, 그 품질이 직장인들의 실제 기회로 이어지는 데이터입니다. 행동 로그 집계를 넘어 그 신뢰를 떠받치는 데이터 전문가로 리멤버의 성장에 함께하고 싶어 지원했습니다.

② 포트폴리오 — 제목 틀 + 숫자 1개

#항목숫자 1개
01OS 패키지 취약점 수집 정확도 — 고쳐진 버전까지 맞아야 스캐너가 신뢰된다12종 배포판 × 4가지 제공 방식
02라이브러리 컴포넌트 테이블 재설계 — 슬로우 쿼리→실행계획→원인은 collation·식별자 정책예상 스캔 행 130만 → 6,000
03인덱스 최적화·용량 절감 — 지울 근거(95개 쿼리 실행계획)를 먼저 만들고 지웠다311개 중 224개 삭제 · 9.6→4TB
04RAW 보존·재파싱 ETL — 외부 재요청 없이 재처리. 이번 과제 설계의 원형재처리 외부 요청 0건
05K8s·Airflow 운영 — 노드 증설 + pool·backoff·resource request를 함께 조정Pod 할당 실패 주 70~100 → 1~5건
06DML Broker — 80개 크롤러의 DB 쓰기를 공통 규칙 하나로 (bounded queue + 429)lock 오류 일 3~4건 → 4개월 0건
07AWS→IDC 이전 — 수집 시간 분산으로 버티고, 준비 후 DB 역할 분리 (임시복구→구조개선)장애 월 10~20건 → 1~2건
08Grafana 모니터링 — 서버 자원·DB·수집 누락·고객사 동기화를 같은 시간축에서10~20대 서버

단위 방어 — 130만→6,000은 실행계획상 예상 스캔 행 수(응답시간 아님) · "2일→5분"은 설계 산정 예시(실측 아님) · IDC 이관 확인 범위는 전후 행 수 대조까지.

③ 과제 요약 · 외울 숫자

10초 버전 2022·2023년 사업장 명단을 사업자번호로 맞춰 신규·변경·그대로·이번에 안 보임을 나누고, 그 판단 근거를 전부 남기는 파이프라인. Airflow DAG 하나(연도 파라미터), 결과는 DuckDB. 구조 한 줄 RAW 원문 보존 → STG 정규화·품질 판정 → 사업자번호 조인(IS DISTINCT FROM) → 마스터+이력+부재 표시+검산 8종을 한 트랜잭션. 하나라도 틀리면 통째로 롤백. 단일 writer라 직렬 + max_active_runs=1.
외울 숫자 (식으로)
30,513 + 2,174 = 32,687   초기+신규=최종
21,087 = 4,000 + 17,087   공통키=변경+no-op
23,261 + 9,426 = 32,687   활성+부재
이력 5,196 (컬럼별 1건씩)
8/8 통과 · 불일치 0 · ERROR 0
먼저 인정할 한계 3개 실행 기록은 트랜잭션 밖(exactly-once는 업무 데이터까지) · 동시 backfill 미보장 · stage()가 한 해 분량 메모리 적재.

정규화가 없었다면 근로자수 11.0 표기 차이만으로 공통 21,087건이 전부 변경으로 잡혔습니다. 주소 표기 차이도 210건, 사업장명 전각 공백은 30건 실측.

④ 태스크 4개는 어떻게 동작하나

네 태스크가 맡는 일은 처리 대상 결정, 원문 확보, 값 판정, 마스터 반영입니다. 앞 태스크의 결과가 XCom으로 누적돼 마지막 태스크가 전 구간 값을 들고 있습니다. 커넥션은 태스크마다 열고 닫습니다(단일 writer).

태스크하는 일말할 포인트 하나
resolve_source회차의 데이터 구간에서 연도를 뽑고, 파일이 없으면 SKIP. 정책(SyncPolicy)을 여기서 한 번만 확정해 흘려보냄실행 시각과 무관하게 회차 연도가 고정된다. 파일 없는 2024·2025 회차는 실패가 아니라 SKIP
land_raw실행 기록을 RUNNING으로 열고(파일 SHA-256·크기·정책 JSON 포함), 연도 역행과 헤더를 검증한 뒤 임시 테이블로 파싱, 그다음 해당 연도만 교체검증이 먼저, 교체가 나중. 그래서 실패해도 기존 스냅샷이 남는다. 0건 소스는 여기서 끊는다
stage_and_validate행별 정규화(파이썬 순수 함수), 중복 키는 마지막 행 채택, 이슈를 격리 테이블에 기록, 마지막에 품질 게이트has_valid_key(존재했다)와 is_valid(반영해도 된다)를 분리. 묶으면 값 하나 깨진 행이 "사라진 것"이 된다
apply_and_finish후보 임시 테이블 3개로 무엇을 바꿀지 확정 → 건수 집계 → 이력 기록 → 마스터 반영 → 부재 표시 → 검산 8개. 전부 한 트랜잭션건수는 반영 전에(반영 후엔 신규·변경 구분 불가), 이력은 갱신 전에(갱신 후엔 이전 값 못 읽음)

./up.sh 다섯 단계 (물어보면)

이전 산출물 초기화 · 이미지 빌드와 기동 · 준비 대기 · 파이프라인 완료 대기 · 결과 확인과 DB 반출. 스크립트가 DAG를 직접 trigger하지 않습니다DAGS_ARE_PAUSED_AT_CREATION=falsecatchup=True로 스케줄러가 회차를 만들고, 스크립트는 ops_sync_run 상태를 2초 간격으로 폴링합니다. Airflow API 대신 결과 테이블을 보므로 "태스크는 성공인데 데이터가 안 들어간" 상황을 구분할 수 있습니다. 대기를 health · DAG 등록 · 파이프라인 완료 세 구간으로 나눈 이유는 실패 지점을 구분하려는 것입니다.

⑤ 정합성 8개 — 쉬운 설명

갱신한 다음 프로그램이 스스로 8가지를 검산합니다. 하나라도 틀리면 그 회차를 통째로 되돌립니다. 가계부에 옮겨 적고 합계를 맞춰보는 것과 같은데, 코드가 매번 하고 안 맞으면 저장을 안 합니다.

묶음검산쉬운 말 / 틀리면 무슨 뜻인가
개수가 맞나ROW_COUNT_DELTA명부가 2,174줄 늘었으면 새로 추가한 것도 정확히 2,174개 — 갱신하다 줄을 잘못 만들거나 지웠는지
CLASSIFICATION_TOTAL2,174 + 4,000 + 17,087 = 23,261 — 빠뜨린 회사도, 두 번 센 회사도 없는지
PK_UNIQUE같은 사업자번호가 두 줄 있는지 — 회사당 한 줄이라는 전제가 깨졌는지
값이 맞나
가장 중요
ALL_SOURCE_KEYS_PRESENT파일에 있던 회사 중 명부에서 못 찾는 게 0건 — 반영 누락
VALUE_MATCHES_POLICY규칙을 처음부터 다시 적용해 나온 값과 명부의 실제 값을 대조 — 조인·조건문 실수. 개수는 맞는데 값이 틀린 경우를 이것만 잡는다
상태 표시가 맞나ACTIVE_FLAG_CONSISTENT올해 파일에 있는데 "최근 명부에 없음"으로 찍혀 있으면 모순 — 되살리기 누락
NO_FUTURE_LAST_SEEN2023년 작업인데 2024가 적혀 있으면 안 됨 — 연도를 거꾸로 실행
NO_SILENT_NULL_OVERWRITE보존 정책을 켰는데 값을 NULL로 지운 기록이 있으면 안 됨 (기본 정책에서는 의도된 동작이라 건너뜀)
"결국 스스로 계산한 값끼리 비교 아닌가요?" — 일부는 그렇다고 먼저 인정한다. 증가분·분류 합계는 내부 일관성 검증에 가깝고, 값·누락 검증은 스테이징을 다시 조인해 대조하며, 별도 확인 스크립트가 2023년 원본과 명부를 직접 맞대 불일치 0건을 확인한다. 완전히 독립된 검증은 아니고 같은 규칙을 다른 경로로 다시 계산해 대조하는 수준이다.

8개는 어떻게 정했나 — 처음부터 8개로 정한 것이 없고, 구현하면서 "이게 틀리면 조용히 넘어간다" 싶은 지점마다 하나씩 추가한 결과입니다(개수 3 · 값 2 · 상태 3).

⑥ 공백 정책 — 실측 2,249 vs 156

2023년 파일의 빈 값을 최신 상태로 볼지 추출 누락으로 볼지 확정할 수 없어 정책으로 분리했습니다. 두 정책을 각각 실행해 대조한 값입니다. 소스와 코드는 같고 플래그 하나만 다릅니다.

결과 차이와 분해
정책주소 결측마스터행
기본(덮어쓰기)2,24932,687
보존(preserve)15632,687

2023년에 주소가 비어 온 회사가 2,249건. 그중 2,093건은 2022년에 주소가 있던 회사(정책이 갈리는 구간), 나머지 156건은 2022년에도 없던 회사입니다. 2,249 − 2,093 = 156. 156은 하한선 — 지킬 기존 값이 없어 어느 정책이든 NULL입니다.

카운터가 어떻게 움직이나 (2023)
정책변경변경없음공백보존
기본4,00017,0870
보존2,90318,1842,095

보존이 2,095건 일어났는데 변경은 1,097건만 줄었습니다. 보존은 컬럼 단위, 분류는 행 단위이기 때문입니다. 주소만 비어 온 행은 변경없음으로 넘어가고(17,087+1,097=18,184), 주소와 근로자수가 같이 바뀐 행은 여전히 변경입니다. 2,095 = 주소 2,093 + 업종 1 + 성립일자 1.

"왜 공백을 NULL로 바꿨나요" — 층을 먼저 나눈다

① 정규화 층 — 비교를 가능하게 하려고 전각 공백(U+3000)이나 no-break space가 섞여 있었습니다(사업장명에서만 30건). 그대로 두면 '', ' ', NULL이 서로 다른 값이 되어 표기 차이만으로 UPDATE로 잡힙니다. 빈 문자열보다 NULL을 고른 것은 SQL에서 ''가 "값이 있다"로 취급되기 때문입니다.
② 병합 정책 층 — 명세를 따랐고, 추정하지 않았다 과제가 "최신 파일 값으로 갱신"을 정의했으므로 기본은 NULL로 덮어씁니다. "이건 추출 누락일 것"이라고 추정해 명세와 다른 동작을 기본값으로 넣지 않았습니다. 대신 보존 경로를 플래그로 남겼고 두 정책 모두 검산 8개를 통과합니다.
③ 되돌릴 수 있다는 점으로 닫는다 원본이 RAW에 그대로 있어 정책을 바꾸면 재계산할 수 있고, 변경 이력에 "소스 값이 비어 있어 NULL로 갱신(보존 정책 off)" 사유와 컬럼별 전·후 값이 남습니다. 보존을 켠 상태에서 값이 지워지면 NO_SILENT_NULL_OVERWRITE가 잡습니다.
검증이 정책을 알고 있다 — 검증을 "마스터 값은 소스 값과 같아야 한다"로 굳혔다면 보존 정책은 무조건 실패합니다. 정책 플래그를 SQL CASE에 넣어 기대값을 다시 계산하기 때문에 두 경로가 모두 통과합니다.

⑦ 코드 구조 — 모듈을 왜 나눴나

변경 이유가 서로 다른 코드를 한 파일에 두지 않으려고 병합이 한 덩어리였을 때는 정책을 한 줄 바꾸는 작업이 이력 기록과 검증 쿼리까지 같이 열게 만들었습니다. 지금은 후보 생성(master_candidates)은 변경 감지 규칙이나 정책이 바뀔 때, 이력(master_history)은 남길 항목이 바뀔 때, 변이(master_mutations)는 마스터 컬럼이 바뀔 때, 검증(reconciliation)은 검증 항목이 늘 때 각각 열립니다.
순서가 설계라서, 파일 경계로 순서가 보인다 건수는 반영 전에, 이력은 갱신 전에, 검증까지 한 트랜잭션. master_sync는 그 순서만 조립하고 각 단계 구현을 갖고 있지 않아서 181줄 한 파일을 읽으면 순서가 그대로 보입니다.
트랜잭션 경계 소유자를 하나로 고정 atomic을 여는 곳은 apply_master 하나뿐이고 나머지 모듈은 BEGIN·COMMIT을 모릅니다. 커밋 지점이 여러 곳에 생기는 사고를 파일 경계로 막았습니다. 테스트 표면도 갈라집니다 — 정규화·품질은 DB 없이 순수 함수로, 후보 생성은 임시 테이블 결과로, 검증은 숫자만으로 확인합니다.
merge.py가 비어 있는 이유 로직 없는 공개 파사드입니다(39줄, 재수출과 __all__뿐). 내부를 어떻게 나눠도 DAG·CLI·테스트가 부르는 진입점(merge.stage, merge.apply_master)은 그대로 두려는 경계이고, __all__로 "여기까지가 쓰라고 만든 표면"을 코드로 표시했습니다.
비용도 인정한다 파일이 늘어 호출 흐름을 따라가려면 한 번 더 점프해야 합니다. 그래서 순서를 master_sync 한 곳에 모아 비용을 줄였습니다. 처음부터 이 구조는 아니었고 구현 중에는 한 모듈이었는데, 트랜잭션 경계와 이력 순서가 코드에서 드러나지 않는다고 판단해 제출 전에 나눴습니다. include/pipeline/ 19개 모듈 중 가장 큰 파일이 quality.py 209줄, 병합 관련 4개는 59~159줄입니다.

⑧ 예상 질문 압축

과제는 Insert·Update만 정의했는데 왜 is_active를 넣었나요 2022년에만 있는 9,426건을 어떻게든 처리해야 하고 물리 삭제는 되돌릴 수 없습니다. 폐업인지 이번 공시에서 빠진 것인지 구분할 근거가 없어 사실만 기록했습니다. SYNC_ABSENT_ACTION=ignore로 끄면 명세 그대로 Insert·Update만 하고, 물리 삭제는 아예 허용하지 않습니다.
사업자번호가 9자리면 0을 채우면 되지 않나요 보정으로 만든 키가 실재하는 다른 사업장과 겹치면 두 사업장이 한 줄로 합쳐지고 되돌릴 수 없습니다. 자릿수 보정 없이 격리합니다. 검증번호 불일치 9건은 두 연도에 같은 값으로 반복돼 오탈자로 단정하지 않고 WARN으로만 남겼습니다.
파일 안에 중복 키가 있으면요 원천에 갱신 시각이 없어 원본 행 순서를 결정적 기준으로 삼고 마지막 행을 채택합니다. 앞 행은 PK_DUPLICATE로 격리하고 메시지에 채택된 행 번호를 남깁니다. 실데이터에는 0건이라 발동한 적은 없고, 마스터 PK 제약과 PK_UNIQUE 검산이 이중으로 막습니다.
같은 회차를 두 번 돌리면요 / 2023 반영 후 2022를 돌리면요 RAW·STG는 해당 연도만 교체, 마스터는 달라진 행만 갱신, 실행 기록은 같은 run_id면 UPSERT로 초기화합니다. 그래서 두 번째 실행은 신규·변경 0건입니다. 연도 역행은 YearRegressionError로 막습니다 — 최신 값이 조용히 과거 값으로 덮이는 것보다 실패가 낫습니다.
SCD Type 2를 왜 안 썼나요 연 1회 전체 스냅샷이라 특정 시점 상태는 RAW와 회차로 재구성됩니다. 실제로 답해야 하는 질문은 "이 사업장 주소가 언제 어떤 값에서 바뀌었나"에 가깝고, 그건 컬럼 단위 변경 로그가 더 직접 답합니다. 마스터 행수를 시점마다 늘리지 않는 것도 이유입니다.
데이터가 수천만 행이면요 stage()가 한 해 분량을 메모리에서 정규화하는 지점이 먼저 걸립니다. 정규화를 SQL로 내리거나 청크로 나눠야 하고, 그때는 단위 테스트 가능한 순수 함수라는 지금의 장점을 일부 포기합니다. 저장소도 단일 writer 파일에서 벗어나야 합니다. 지금 구조는 연 1회 3만 행 배치에 맞춘 선택입니다.
테스트는 무엇을 했나요 정규화 규칙, 품질 게이트 경계값, 병합 시나리오(신규·변경·무변경·재등장·부재), 트랜잭션 롤백, CLI, 리포트, 조회 스크립트, 설정 일관성, DAG 구조 계약입니다. 로컬 101 passed / 1 skipped(Airflow 미설치 환경의 DAG 테스트). 롤백은 검산 실패를 만들어 마스터와 이력이 병합 전으로 돌아오는지 확인하고, 검증 쿼리 자체가 예외를 던지는 경우도 따로 테스트합니다.

⑨ "AI 잘 쓰세요?" — 답변

많이 씁니다. 특히 팀이 쓰도록 파이프라인에 넣은 쪽을 신경 썼습니다. 사내 CI/CD 중앙 kit에 Claude 기반 PR 리뷰를 넣어 여러 저장소에 같은 방식으로 적용했습니다. Jira 브랜치, 테스트, PR 자동 생성, AI 리뷰, Slack 알림, 이미지 푸시가 한 흐름으로 돕니다.

오래 고민한 것은 프롬프트보다 경계였습니다. diff에 secret scan을 먼저 돌리고 통과한 뒤에만 외부 모델을 호출합니다. 실행 kit 버전은 커밋 SHA로 못 박아 main 같은 움직이는 참조를 신뢰하지 않고, 변경 경로가 매핑되지 않으면 기본 실패로 둬서 조용한 초록불을 막았습니다.

"과제도 AI가 쓴 건가요?" — 가장 위험한 꼬리 질문 도구를 썼다고 먼저 인정 → 설계 판단은 내가 했고 코드로 방어 가능 → 즉시 증명 제안: "어느 파일이든 열어서 왜 그렇게 했는지 설명하겠습니다." 한 방 예시 — IS DISTINCT FROM을 쓴 이유는 <>가 NULL을 만나면 변경을 놓치기 때문이고, 2023년 주소가 비어 온 2,093건이 실제로 그 경우.
하지 말 것 · 되물을 것 "AI 없이도 됩니다"(방어적) · 도구 이름 나열 · 검증 얘기 없이 "거의 다 AI로" · 사용량 축소해서 말하기.
되묻기 — "리멤버는 AI 리뷰나 코드 생성을 파이프라인 안에 두고 계신가요? 저는 외부 모델 호출 전 secret scan으로 경계를 잡았는데, 데이터를 다루는 팀에서는 그 경계를 어디에 두시는지 궁금합니다."

⑩ 공고·리멤버 스택 — 어떤 기술인지 한 줄씩

직무 키워드(공고): 정교한 매핑 · MDM · 데이터 정합성 — "같은 회사가 여러 이름으로 들어와도 한 회사로 알아보고, 잘못 연결하지 않게 관리하는 일".

기술어떤 기술인가
Aurora MySQL + binlog운영 DB와 그 변경 기록. 모든 동기화의 원천
Debezium · MSK(Kafka)binlog를 변경 이벤트로 바꿔(CDC) 컨베이어 벨트(topic·partition)로 흘리는 실시간 스트리밍
Glue PySpark Full Load전용 replica에서 초기 전량 적재 — 원본 영향 격리가 목적. 기준점(binlog position) 고정 후 CDC로 이어붙임
S3 Tables · IcebergParquet 파일 더미를 목록표(metadata·snapshot)로 테이블처럼 관리하는 레이크하우스. CDC는 PK upsert로 반영
운영 유지보수compaction(작은 파일 합치기) · snapshot 만료 · orphan file 정리 — 파일을 테이블처럼 쓰는 대가
PyIceberg쿼리 엔진 밖에서 파일 수·크기·스냅샷 상태를 관측하는 Python 도구
StarRocks on EKS · Athena자주 보는 걸 앞 진열대에 정리해 저지연 서빙(OLAP) / 애드혹·BI 조회
Lake FormationIAM(출입증)과 별개로 DB·테이블·컬럼·행 단위 데이터 권한을 판정
안전 문구 먼저 — "공개된 기술 블로그와 채용 공고에서 본 구조이고, 내부 기준값은 모르며 제가 운영해 본 스택은 아닙니다." + 내 CDC 경험은 약 1시간 단위 binlog 파일 전달의 배치형이라 실시간 스트리밍과 처리 방식·지연 기준이 다르다고 먼저 말한다.

⑪ 질문할 것들

과제·데이터 대화 흐름에서 (우선)
  1. Full Load와 CDC의 접속점을 어떻게 잡으셨나요? 기준점 이후 이벤트 재적용에서 어려웠던 부분이 궁금합니다.
  2. 식별·매핑에서 자동 연결과 사람 검토를 나누는 기준이 있으신가요? false merge와 false split 중 어느 쪽 비용을 크게 보시는지요.
  3. 품질 규칙 중 파이프라인을 멈추는 것과 경고만 남기는 것을 나누는 기준이 팀에 정해져 있나요?
기술 도전·아키텍처
  1. 지금 팀이 가장 집중해서 풀고 있거나, 6개월~1년 내에 풀려는 핵심 기술 과제는 무엇인가요?
  2. 신규 개발과 기술 부채(리팩토링·성능 최적화)의 균형은 어떤 기준으로 우선순위를 정하시나요?
  3. 새 기술·도구(AI 코딩 도구, 신규 라이브러리 등)를 도입할 때의 논의와 결정 과정이 궁금합니다.
개발 문화·협업
  1. 코드 리뷰 방식, 테스트 작성 기준, CI/CD 배포 주기는 어떻게 되나요?
  2. 개발팀과 PO/PM·디자인·데이터 등 타 직군의 협업 방식(스크럼·스프린트 등)이 궁금합니다.
  3. 기술 개선점이나 제품 아이디어를 직접 제안하고 적용해볼 수 있는 분위기인가요?
합류 후 역할·온보딩
  1. 합류하면 첫 1~3개월 동안 어떤 과제를 가장 먼저 해결하기를 기대하시나요?
  2. 이 포지션에서 성과를 잘 내는 분들은 주로 어떤 방식으로 일하나요?