[업무일지] 미사용 테이블을 바로 DROP하지 않고 2주간 유예한 이유
활성 테이블 265개를 교차 조사해 정리 후보 12개를 추렸습니다. 코드 참조 0건만으로는 사용 여부와 보존 필요성을 모두 증명할 수 없었습니다. 그래서 바로 DROP하지 않고, RENAME 후 2주간 되돌릴 시간을 남겼습니다.
오래 운영한 데이터베이스에는 용도를 바로 설명하기 어려운 테이블이 쌓여 있었습니다. 이름만 보면 임시 테이블 같지만 실제로는 다른 서비스가 읽는 경우가 있었고, 코드 참조가 없어도 감사나 분쟁 대응을 위해 남겨야 하는 데이터도 있었습니다. 사용 여부와 보존 필요성은 같은 질문이 아니었습니다.
작업 환경은 MariaDB 10.x와 Flyway 8.5.x입니다. 아래에서 사용하는 테이블 이름과 폐기 후보 접두어는 실제 명칭이 아닌 예시입니다.
활성 테이블 265개에서 시작했다
스키마 스냅샷에는 CREATE TABLE 문이 323개 있었습니다. 이미 폐기 대상으로 분리된 24개와 시스템 테이블 34개를 제외하니 조사 대상은 265개가 남았습니다.
처음 생각은 단순했습니다. 전체 테이블 이름을 애플리케이션 저장소에서 검색하고, 결과가 0건이면 후보로 표시하는 방식이었죠. 하지만 한 저장소의 검색 결과만으로는 결론을 낼 수 없었습니다. 서비스 코드가 한곳에만 있지 않았거든요.
서비스에는 주 애플리케이션 외에도 오래된 서버, 운영 배치, 외부 연동 코드가 함께 있었습니다. 검색 범위를 세 그룹으로 나눠 주 애플리케이션의 JPA·QueryDSL·MyBatis 코드, 레거시 서버와 배치의 문자열 쿼리, 외부 연동 코드의 ORM과 직접 SQL을 각각 확인했고요.
공통 단어가 들어간 테이블 이름은 단순 문자열 검색 결과가 너무 많아서 FROM, JOIN, 엔티티 매핑처럼 데이터베이스 접근이라고 판단할 수 있는 문맥만 남겼습니다. 반대로 이름이 동적으로 조합되는 쿼리는 검색에서 빠질 수 있으므로 0건을 곧바로 미사용의 증거로 보지 않았습니다.
한 저장소의 0건은 미사용이 아니었다
교차 검색의 필요성은 조회 이력을 담는 한 테이블에서 드러났습니다. 주 애플리케이션 참조는 0건이었지만, Python 기반 연동 저장소에서 같은 테이블을 직접 조회하는 코드가 2곳 있었거든요. 한 저장소만 봤다면 정리 후보로 넘어갔을 테이블이라, 확인하자마자 뺐습니다. 이때부터 참조 0건에는 어느 범위를 검색했는지를 함께 적고, 세 그룹 모두에서 0건인 경우만 후보로 인정했습니다.
호출이 레포 경계를 넘어 정적 검색에 안 잡히는 문제 자체는 API 정리에서도 똑같이 겪어 따로 정리했습니다.
엔티티만 남고 실제 호출 지점을 찾지 못한 테이블이 있는가 하면, 레거시 코드 한곳에서만 조회하는 테이블도 있었습니다. 전자는 제거된 기능의 잔재일 수 있고 후자는 아직 필요한 기능일 수 있었지만 이번 조사만으로는 구분하기 어려웠습니다. 둘 다 보류했고요.
코드 기준으로 명확한 후보는 265개 중 5개뿐이었습니다. 저는 숫자를 늘리기보다 잘못 지울 가능성을 낮추는 쪽을 택했습니다.
코드 참조 0건과 보존 필요성은 다른 문제였다
날짜가 붙은 스냅샷 테이블과 과거 상태를 담은 테이블은 세 그룹에서 참조가 발견되지 않았습니다. 그렇다고 애플리케이션이 읽지 않는다는 이유만으로 삭제할 수는 없었죠. 장애 조사, 정산 확인, 분쟁 대응처럼 코드 밖에서 데이터가 필요해질 수 있기 때문입니다.
이 테이블들은 담당자 확인 없이 후보에 넣지 않았습니다. 운영 측에 생성 목적과 보존 필요성을 물었고, 이벤트나 임시 처리 과정에서 만들어졌으며 더 이상 보존할 이유가 없다고 확인된 7개만 추가했습니다. 정적 검색으로 추린 5개와 운영 확인을 거친 7개를 합쳐 최종 대상은 12개가 됐습니다.
이 과정에서 판단 기준을 두 개로 분리했습니다. 코드가 읽는지는 저장소 교차 검색으로 확인했고, 데이터를 남겨야 하는지는 업무 맥락으로 확인했습니다. 둘 중 하나라도 확실하지 않으면 이번 정리에서 제외했습니다.
DROP 대신 이름부터 바꿨다
12개를 골랐지만 첫 마이그레이션에는 DROP TABLE을 넣지 않았습니다. 각 테이블에 폐기 후보임을 나타내는 접두어를 붙여 이름만 바꿨죠. 이 글에서는 그 접두어를 retired_로 표현합니다.
RENAME도 안전한 선택은 아니었습니다. 정적 검색에서 놓친 호출이 있다면 기존 이름으로 접근하는 요청이나 배치는 실패합니다. 이건 RENAME의 비용이죠. 다만 데이터를 지우지는 않으므로 문제가 발견되면 원래 이름으로 되돌릴 수 있었고, 이 차이 때문에 DROP과 단계를 나눴습니다.
12개의 RENAME은 하나의 Flyway 마이그레이션으로 묶었습니다. 대상과 원래 이름의 대응 관계를 한곳에서 확인할 수 있게 하고, 삭제 마이그레이션과는 분리했습니다.
1
2
3
4
5
6
-- V20260421_01__rename_unused_tables_to_retired.sql
-- 폐기 후보 12개를 삭제하지 않고 접두어만 붙여 원자적으로 이름만 변경
RENAME TABLE
`scan_history` TO `retired_scan_history`,
`temp_settlement` TO `retired_temp_settlement`;
-- ... 나머지 10개도 같은 문장에 묶음
컴파일과 마이그레이션 검증을 통과한 뒤에도 이 변경을 정리 완료로 보지는 않았습니다.
2주 유예로 되돌릴 시간을 확보했다
RENAME 이후에는 최소 2주간 이상 여부를 관찰하기로 했습니다. 문제가 없을 때 별도의 DROP 마이그레이션을 작성하는 순서였죠.
1
2
3
4
-- V20260505_01__drop_retired_tables.sql (RENAME 후 약 2주)
DROP TABLE `retired_scan_history`;
DROP TABLE `retired_temp_settlement`;
-- ... 나머지 10개
이름 변경과 실제 삭제 사이에 사람이 확인할 구간을 둔 셈입니다.
이 선택은 즉시 저장 공간을 회수하지 못하고 폐기 후보가 스키마에 더 남는다는 비용이 있습니다. DROP 작업도 한 번 더 해야 했고요. 대신 조사에서 빠진 호출이나 뒤늦은 보존 요청이 나왔을 때 데이터를 잃지 않고 되돌릴 수 있습니다.
이번 작업에서 가장 위험했던 결론은 “코드 검색 결과가 0건이니 사용하지 않는다”였습니다. 여러 저장소를 교차해도 수동 SQL, 분석 도구, 동적으로 만든 쿼리까지 증명할 수는 없으므로 운영 확인과 RENAME 이후의 관찰로 그 빈틈을 줄였습니다.
아직 DROP까지 끝난 것은 아닙니다. 2주 동안 문제가 드러나지 않아도 수동 SQL이나 드물게 실행되는 작업은 놓칠 수 있죠. 그 한계를 감수하더라도, 이번에는 정리 속도보다 되돌릴 수 있는 기간을 확보하는 편이 나았습니다.