배경 및 목표
개인정보 파기 요청이 들어올 때마다 개발자가 운영 데이터베이스(DB)에 직접 데이터 조작 명령(DML)을 실행하고 있었습니다. 요청은 Slack으로 들어왔고, 실행 기록은 Jira 댓글이나 Slack 스레드에 흩어져 있었습니다.
Slack 운영 요청 채널에서 “개인정보 파기” 키워드로 확인한 요청은 36개월간 59건이었습니다. 전체 기간의 월평균은 약 1.6건이었고, 최근 3개월에는 9건으로 월평균 3건이 접수됐습니다.
왜 중요했는가
전체적으로 요청 자체는 적은 수였지만, 수동 DML 처리는 세 가지 문제를 만들고 있었습니다.
- 되돌릴 수 없는 작업에 검증 단계가 없었습니다. 조건을 잘못 적어도 실행 전에 걸러 줄 장치가 없었습니다.
- 담당자에게 작업 지식이 집중돼 있었습니다. 다른 개발자가 수행하려면 이전에 사용한 쿼리와 파기 조건을 담당자에게 다시 확인해야 했습니다.
- 증적이 흩어져 있었습니다. 무엇을 언제 얼마나 지웠는지가 Slack 스레드에만 남아, 파기확인서 요청이 올 때마다 기록을 다시 모아야 했습니다.
제약
| 제약 | 선택지에 미친 영향 |
|---|---|
| 되돌릴 수 없는 작업 | 실행 전에 대상 건수를 확인하는 프리뷰와, 요청자와 승인자를 분리하는 결재를 필수로 두었습니다 |
| 개발자 아닌 사람도 써야 함 | 운영 화면에서 요청과 결재, 증적 확인이 모두 끝나야 합니다 |
| 진행 중인 요청이 계속 들어옴 | 유형별로 나눠 당장 필요한 것부터 구현하고, 급하지 않은 유형은 보류했습니다 |
목표
- 유형별로 제각각이던 파기 작업을 공통 요청·승인·실행 흐름으로 표준화합니다.
- 되돌릴 수 없는 실행 앞에 프리뷰, 결재, 실행, 증적의 고정된 흐름을 둡니다.
- 실행 권한을 가진 운영자가 화면에서 요청하고, 다른 승인자의 결재를 거쳐 실행할 수 있게 합니다.
맡은 역할과 판단
| 구분 | 내용 |
|---|---|
| 담당 범위 | 요청 수집과 문제 정의부터 아키텍처 설계, 구현, 품질 검증(QA)까지 단독으로 담당했습니다. 단계별 산출물 작성과 티켓별 구현에 LLM을 활용하고, 자동화 범위와 설계 판단, 산출물 검토와 통합 검증을 직접 맡았습니다 |
| 직접 내린 판단 | 1. 요청 유형을 나눠 자동화할 것과 보류할 것을 정하고, 보류한 유형은 서버에서 막음 2. 각 서비스가 내부 전용 API를 통해 자신의 데이터를 파기하도록 책임 분리 3. 요청자와 승인자를 분리하는 2인 관여를 화면이 아니라 서버에서 강제 |
| 협업, 합의 | 아키텍처 문서를 팀에 공유하는 과정에서, 민감정보를 지우는 작업인 만큼 승인과 결재 절차, 어드민 권한 체계가 필요하다는 의견이 나왔습니다. 처음에는 후순위였던 두 항목을 해결 대상으로 올려 설계에 넣었습니다 |
해결 방법과 해결 후보군
문제 정의부터 설계, 티켓 분해, 구현, QA까지 이어지는 대규모 언어 모델(LLM) 활용 절차를 구성했습니다. 단계별 산출물과 작업 의존 관계를 연결하고, 검증에서 발견한 문제는 구현 단계로 되돌려 수정했습니다.
flowchart LR S1["Slack 요청 조사와 유형화"] S2["아키텍처 문서 작성"] S3["작업 티켓 분해와<br/>의존 관계 정의"] S4["작업 병렬 수행"] S5["티켓 기반<br/>테스트 케이스 구성"] S6["브라우저 자동화와<br/>테스트 전 항목 5회 반복"] S1 --> S2 --> S3 --> S4 --> S5 --> S6 S6 -->|문제 발견 시| S4
1. 자동화할 유형과 보류할 유형 나누기
수집한 요청을 유형별로 나눠, 조건이 명확하고 반복되는 유형부터 자동화했습니다. 요청 빈도가 낮고 증적 비교가 성립하지 않는 유형 하나는 보류했습니다.
보류한 유형은 화면에서 탭을 숨기고, 서버에서도 요청 타입 검증과 프리뷰, 실행 단계에서 차단했습니다. API를 직접 호출하더라도 동일한 제한을 적용하기 위해서입니다.
2. 결재와 승인 프로세스
개인정보 파기 업무에 사전 승인 절차를 정립했습니다. 프리뷰로 파기 조건과 대상 건수를 확인하고, 요청서 생성 시 해당 조건과 건수를 기록했습니다. 이후 요청자와 다른 승인자가 결재해야 요청자 본인이 실행할 수 있도록 구성했습니다.
sequenceDiagram participant R as 요청자 participant BO as 파기 백오피스 participant S as 대상 서비스 participant N as Slack participant A as 승인자 R->>BO: 1. 프리뷰 (조건 입력) BO->>S: 대상 건수 조회 S-->>BO: 건수 · 파기 예정 시각 BO-->>R: 지울 대상을 실행 전에 확인 R->>BO: 2. 파기 요청서 생성 Note over BO: 요청서에 조건과 건수 기록 BO->>N: 3. 승인자에게 알림 A->>BO: 4. 승인 / 거절 Note over BO: 요청자 ≠ 승인자 강제 R->>BO: 5. 실행 (요청자 본인만) BO->>S: 파기 호출 S-->>BO: 증적 번호 · 처리 건수 BO->>N: 6. 같은 스레드에 증적 회신
결재 상태와 실행 상태를 분리해, 승인이 끝났지만 아직 실행되지 않은 요청을 구분했습니다. 권한 없는 결재, 이미 끝난 요청서의 재승인, 승인되지 않은 요청서의 실행, 남의 요청 실행, 재실행은 모두 서버가 막습니다.
| 권한 | 할 수 있는 일 |
|---|---|
| 조회 권한 | 요청서와 증적 조회 |
| 실행 권한 | 요청과 실행 (조회 포함) |
| 결재 권한 | 승인과 거절 (조회 포함, 실행 권한은 없음) |
| 최상위 권한 | 전 기능과 권한 부여, 회수 |
실행 권한과 결재 권한을 분리하고, 요청자가 자신의 요청을 승인하는 행위도 서버에서 차단했습니다. 화면은 서버의 권한 판정 결과를 사용하며, API 직접 호출에도 같은 검증을 적용했습니다.
| 후보 | 설명 | 채택 여부 |
|---|---|---|
| 화면에서만 버튼을 감춤 | 권한 없는 사람에게 버튼을 숨김 | ❌ API 직접 호출로 우회 가능 |
| 서버가 판정하고 화면은 그 값을 사용 | 인가를 서버에 두고 화면은 받아 씀 | ✅ 화면을 우회해도 같은 판정을 받음 |
권한 부여와 회수도 화면에서 합니다. 다만 최상위 권한은 화면으로 부여할 수 없고, 자기 자신이나 마지막 한 명의 최상위 권한을 회수하는 것도 서버가 거절합니다. 부여와 회수, 결재, 실행은 모두 감사 로그에 행위자와 함께 남습니다.
3. 개인정보 파기 아키텍처
파기 대상은 여러 서비스에 흩어져 있습니다. 백오피스가 타 서비스의 데이터를 직접 건드리지 않고, 각 서비스가 노출한 내부 전용 API로만 호출하도록 했습니다. 화면에서 들어온 요청이 인가를 지나 프리뷰, 요청서, 결재, 실행, 증적 순으로 넘어가고, 각 단계가 남기는 기록은 저장소와 감사 로그에 쌓입니다.
flowchart LR UI["운영 화면"] SL["Slack"] subgraph BO["파기 백오피스"] AUTH["인가"] PRE["프리뷰"] REQ["요청서"] APV["결재"] EXE["실행"] CERT["증적과 종결"] AUD["감사 로그"] end subgraph ST["저장소"] PDB[("권한")] RDB[("요청서와 증적")] end subgraph EXT["대상 서비스"] A["서비스 A"] B["서비스 B"] C["서비스 C"] D["서비스 D 보류"] end UI --> AUTH AUTH --> PDB AUTH --> PRE AUTH --> APV AUTH --> EXE PRE -->|1 대상 건수 조회| EXT PRE --> REQ REQ -->|2 조건 고정| RDB REQ -->|3 알림| SL APV -->|4 승인 거절| RDB EXE -->|5 파기 호출| EXT EXE --> CERT CERT -->|6 증적 회신| SL CERT --> RDB AUD --> RDB
백오피스는 요청서·확인서·권한과 처리 이력을 관리하고, 각 서비스는 자신의 비즈니스 로직으로 데이터를 파기합니다. 이 구조로 백오피스가 서비스별 데이터 구조와 파기 쿼리를 직접 관리하는 범위를 줄였습니다. API 계약이 유지되는 범위에서 서비스 내부 파기 로직의 변경 영향을 백오피스로부터 분리했습니다.
4. 공통 스키마, API 명세 작성 후 병렬로 진행
LLM을 단계별 산출물 작성과 구현에 활용하고, 자동화 범위와 설계 결정, 산출물 검토는 직접 담당했습니다.
| 단계 | LLM 활용과 산출물 | 직접 판단·검토한 내용 |
|---|---|---|
| 문제 정의 | Slack에서 확인한 파기 요청 59건의 유형 분류 | 요청의 반복성과 조건의 명확성을 기준으로 자동화 범위와 우선순위 결정 |
| 설계 | 아키텍처 문서, 공통 스키마와 API 명세 초안 작성 | 서비스별 파기 책임, 요청자·승인자 분리와 권한 정책 결정 |
| 티켓 분해 | 설계를 구현 티켓으로 나누고 의존 관계 정리 | 공통 스키마와 API 명세를 선행 작업으로 두고 구현 순서 결정 |
| 구현 | 티켓별 구현 산출물 작성 | 공통 설계와 API 명세에 맞는지 검토하고 통합 결과 확인 |
| QA | 테스트 매트릭스 작성과 브라우저 자동화를 통한 반복 검증 | API 응답·데이터 상태·감사 로그를 대조하고 발견한 문제 수정 |
권한과 결재 테이블, 파기 API 명세를 먼저 정의하고 이를 기준으로 서비스별 내부 API 작업을 나눴습니다. 선행 작업이 머지되면 의존성이 해소된 작업을 병렬로 진행했습니다. 티켓별 LLM 구현 산출물을 검토하고 통합하는 방식으로, 설계부터 최종 수정까지 9일이 걸렸습니다.
5. LLM으로 QA 케이스를 뽑고, 브라우저에서 데이터까지 검증
구현 후 작업 티켓을 바탕으로 LLM을 활용해 테스트 매트릭스를 작성하고 전 항목을 5회 반복 실행했습니다. 브라우저 자동화로 화면을 조작한 뒤 API 응답, 실제 데이터 상태, 감사 로그를 대조했습니다. 이 과정에서 발견한 문제 14건을 수정하고, 수정 사항을 다시 검증하는 흐름으로 QA를 진행했습니다.
| 검증 항목 | 방법 |
|---|---|
| 화면 | 브라우저 자동화로 입력과 버튼, 상세 확인 |
| 서버 | 화면 검증을 우회해 API 직접 호출 |
| 데이터 | 실행 후 같은 조건으로 재조회해 파기 여부 확인 |
| 시각 | 예약 시각이 협정 세계시(UTC) 기준으로 정확히 저장됐는지 교차 확인 |
| 로그 | 감사 로그에 누가 무엇을 했는지 남는지 확인 |
수정한 문제 14건 중 대표 사례는 다음과 같습니다.
| 발견한 문제 | 수정 및 검증 |
|---|---|
| UTC 기준으로 업데이트해야 하는 시각을 클라이언트에서 한국 표준시(KST) 기준으로 요청 | 요청 시각의 시간대 처리를 UTC 기준에 맞추고, 실제 저장된 시각과 대조 |
| UI에 필요한 필드가 API 응답에서 누락 | 화면에서 사용하는 필드를 응답에 포함하도록 보완하고, API 응답과 화면 표시를 대조 |
브라우저에서 요청을 보낸 뒤 API 응답과 저장 데이터를 함께 확인해, 화면 동작만으로는 드러나지 않는 시간대 처리와 응답 계약 문제를 찾아 수정했습니다.
결과
구현한 기능을 기존 업무 방식과 비교하면 다음과 같습니다.
| 지표 | 기존 | 구현 결과 |
|---|---|---|
| 파기 실행 방식 | 개발자가 운영 DB에 수동 DML | 백오피스 화면에서 요청과 결재, 실행 |
| 실행 가능한 사람 | 운영 DB 권한을 가진 개발자 | 실행 권한을 가진 요청자 본인, 승인 후 실행 |
| 실행 전 검증 | 없음 | 프리뷰로 대상 건수 확인 후 승인자 결재 |
| 2인 관여 | 없음 | 요청자와 승인자를 분리, 실행은 요청자 본인만 |
| 증적 | Slack 스레드와 Jira 댓글에 흩어짐 | 요청서와 확인서로 조회, 감사 로그에 행위자 기록 |
| 개발 기간 | - | 설계부터 최종 수정까지 9일 |
| QA | - | 테스트 매트릭스 전 항목 5회 반복 검증, 문제 14건 수정 |
- 유형별 파기 작업에 공통 요청·승인·실행·증적 흐름을 적용하고, 서비스별 파기 로직을 분리했습니다.
- 실행 권한을 가진 운영자가 개발자의 직접 DB 작업 없이 화면에서 요청하고, 다른 승인자의 결재를 거쳐 실행할 수 있도록 구현했습니다.