회사 서비스의 로그는 서비스 내부에서 다음과 같은 목적으로 저장되고 있었다.
- 사용자의 행동이 발생할 때마다 OR 내부 서비스와 외부 ERP / API 인터페이스 호출마다
- Controller → Service → DAO 흐름 단위의 로그 데이터를 DB에 저장
그리고 관리자가 필요할 때 에러를 검색할 수 있는 관리 페이지에서 조회할 수 있도록 설계되어 있었다.
문제는 실제 운영 상황에서는 이 데이터를 거의 사용하지 않는다는 점이었다.
실제 운영 모니터링은 대부분 DataDog, Whatap 같은 APM 도구를 통해 이루어지고 있었고, DB 로그 테이블은 사실상 방치된 상태였다. 게다가 이 서비스는 오픈된 지 8년이 넘었지만 단 한 번도 삭제 작업이 이루어지지 않았다.

그 결과... 이 운영 DB 용량의 50% 이상이 쓸모없는 로그성 데이터였다.
RDS 환경에서 커지는 비용 문제
DB는 AWS RDS를 사용하고 있었다.
RDS의 특징 중 하나는 스토리지 자동 확장이다.
데이터가 늘어나면 자동으로 저장 공간이 증가하고, 그만큼 운영 비용도 계속 증가한다.
즉,
- 사용하지 않는 로그 데이터가
- DB 용량의 대부분을 차지하고
- 그로 인해 운영 비용까지 증가하는 상황
이었다. 따라서 로그 데이터를 정리하는 작업이 필요했다.
삭제 전략
데이터를 줄이는 방법으로는 다음과 같은 방법도 고려할 수 있었다.
새로운 인스턴스를 만들고 마이그레이션
하지만 운영 중인 서비스에서 이 작업을 수행하기에는 부담이 컸다.
또한 로그 데이터는 고객사가 필요할 경우 조회할 수 있어야 했기 때문에 완전히 제거할 수는 없었다.
그래서 내부 정책에 따라 데이터 보존 기간을 설정하고 다음과 같은 방식으로 정리하기로 했다.
- 보존 기간보다 오래된 데이터를 일괄 삭제
- 이후에는 스케줄러를 통해 주기적으로 삭제
아무 생각 없이 DELETE 했다가 생긴 일
DELETE FROM LOG_TABLE
WHERE CREATE_DATE < SYSDATE - 보존기간;
개발 환경에서 테스트했을 때는 시간이 조금 걸리긴 했지만 정상적으로 삭제가 되었다.
그래서 그대로 운영 환경에서도 삭제 작업을 진행했다.
그런데 삭제 작업을 진행하던 중 인프라 팀에서 지금 삭제되는 데이터 때문에 Archive Log가 계속 쌓여서 DB 남은 용량이 급격히 줄어든 다는 연락이 왔다.
DELETE는 생각보다 무거운 작업이다
DELETE는 Redo 로깅 최소화가 불가능하다.
대량 데이터를 한 번에 삭제하면 아래와 같은 일이 발생한다.
- Undo 생성
- Redo 생성
- Archive Log 증가
결국 내가 실행한 쿼리는 대량 Archive Log 생성기와 다름없었다.
해결 방법
그래서 삭제 방식을 다음과 같이 변경했다.
- Cursor + Fetch로 테이블을 한 번만 Full Scan
- 삭제는 작은 단위(batch) 로 수행
- 일정 건수마다 Commit
예시 구조는 대략 이런 형태였다.
CURSOR c_log IS
SELECT ROWID INTO ROWIDS
FROM LOG_TABLE
WHERE CREATE_DATE < SYSDATE - 보존기간;
FETCH ...
DELETE FROM LOG_TABLE
WHERE ROWID IN ROWIDS;
COMMIT;
이 방식의 장점은 다음과 같다.
- 테이블 Full Scan은 한 번만 수행
- DELETE는 소량 단위로 처리
- Undo / Redo 크기 감소
이 방법으로도 절대적인 삭제하는 용량 자체는 줄일 수 없기 때문에 한번 삭제 작업하고 쌓이는 Archive Log의 유지 기간인 7일 ~ 10일 이후에 지우는 방향으로 변경되어 두달이상은 간간히 삭제했던 것 같다.
Enterprise Edition이었다면..?
만약 DB가 Oracle Enterprise Edition이고 날짜 기준으로 Range 파티션이 나뉘어 있다면
ALTER TABLE LOG_TABLE DROP PARTITION P_2021;
같은 방식으로 거의 즉시 삭제가 가능하다.
장점은 다음과 같다.
- 빠른 삭제
- Undo / Redo 부담 감소
- 조회 성능 개선
하지만 회사에서 사용하는 DB는 Oracle Standard Edition이었기 때문에 이 방법은 사용할 수 없었다.
이번 작업을 하면서 느낀 점
쓸데없는 데이터는 바로바로 지우자
불필요한 데이터를 주기적으로 삭제해주면 단순히 용량을 줄여서 비용을 아끼는 것뿐만 아니라, 조회 성능에도 직접적인 도움이 된다.쓸데없는 데이터 스캔이 줄어들고, 일정 기간의 데이터만 유지하면 전반적인 성능도 안정적으로 유지할 수 있다
대량 삭제시 DELTE는 하지 말자
이건 SQLP 공부하며 알게된 방법인데 ...
대량 데이터를 삭제할 때는 DELETE를 그대로 사용하는 건 비효율적인 경우가 많다.데이터와 인덱스를 하나씩 지우면서 리두(REDO), 언두(UNDO) 로그가 많이 발생하고, 그만큼 성능 부담도 커지기 때문이다.그래서 보통은 유지할 데이터만 임시 테이블로 옮긴 뒤, 기존 테이블을 DROP하고 새로 만드는 방식을 사용한다.이 방식은 로그 사용량을 줄일 수 있을 뿐 아니라, DELETE 이후에도 남는 빈 공간 문제까지 같이 해결할 수 있다.DELETE는 데이터를 지워도 물리적인 공간이 줄어들지 않고, 이후 INSERT 시 재사용되는 구조라 테이블이 비효율적으로 커질 수 있다.반면 테이블을 새로 구성하면 이런 불필요한 공간 자체가 사라져서 더 깔끔하게 운영할 수 있다.
그럼에도 delete를 써야만 한다면 ...
임시 테이블을 활용해 데이터를 보존하는게 좋은건 알지만 실제 운영 상황에선 해당 방법을 선택하기엔 부담이 있다. 그래서 울며겨자먹기로 DELETE를 해야한다면 한 트랜잭션 당 삭제되는 데이터에 제한을 두면서, 삭제될 데이터가 풀스캔이 되야한다면 풀스캔 한번과 여러번의 FETCH 이후 DELETE로 Redo 용량을 적게 쓰도록 노력하면 될 것 같다. 우리 회사에 전문 DBA가 있는 것이 아니라 해당 방법이 정답이 아닐 수도 있지만 일단 이번 건은 이 방법으로 사용되지 않는 7년 간의 데이터를 잘 마무리하고, 삭제 스케줄러를 동작시킬 수 있었다.