📘 OEA

1. 개요

운영 시스템을 다루다 보면 기능 자체보다 더 신경 쓰이는 순간이 있습니다.

바로 실행 중인 상태를 바꿔야 할 때입니다.

데이터를 적재하는 시스템은 평소에는 잘 돌아가도, 운영 중에 보관 주기를 적용해야 하거나 현재 처리 위치를 기록해야 하는 순간이 오면 이야기가 달라집니다. 이때 구조가 단순하면 구현은 쉽지만, 실제 운영에서는 생각보다 불안정한 구간이 생깁니다.

이번에 정리한 두 가지 사례도 그랬습니다.

첫째는 통계 테이블 retention 처리였습니다.

둘째는 chk 파일 체크포인트 갱신 방식이었습니다.

겉으로 보면 하나는 DB 문제이고 하나는 파일 문제입니다. 하지만 운영 관점에서 보면 두 문제는 꽤 닮아 있습니다.

  • 지금도 읽히고 있는 데이터를 정리해야 한다.
  • 지금도 쓰이고 있는 상태를 갱신해야 한다.
  • 그 과정에서 중간 상태가 노출되면 안 된다.
  • 서버가 죽거나 작업이 중단돼도 복구 가능한 형태여야 한다.

즉 두 문제의 핵심은 모두 같았습니다.

운영 중인 상태를 직접 수정하는 방식은 생각보다 취약하다.

그래서 이번에는 기능별로 각각 최적화한 것이 아니라, 두 문제를 같은 원칙으로 풀었습니다.

  • 통계 retention은 active/buffer 이중화 + 스왑
  • 체크포인트 갱신은 temp file 작성 + 원자적 교체

둘 다 본질적으로는 현재 사용 중인 대상을 직접 건드리지 않고새 상태를 준비한 뒤 마지막 순간에 바꿔 끼우는 방식입니다.


2. 왜 운영 중인 상태를 직접 수정하면 위험한가

운영 중인 상태를 직접 수정하는 방식은 처음에는 가장 단순해 보입니다.

통계 테이블이 오래됐으면 그 테이블에서 바로 지우면 되고, chk 파일을 갱신해야 하면 기존 파일을 열어서 내용을 덮어쓰면 됩니다. 개발 초기에는 이 방식이 빠르고 이해하기도 쉽습니다.

문제는 시스템이 ”한 번에 한 일만 하는” 상황에서만 이 방식이 편하다는 점입니다.

실제 운영에서는 다음 조건이 함께 붙습니다.

  • 데이터는 계속 들어오고 있다.
  • 누군가는 같은 대상을 읽고 있다.
  • 상태를 갱신하는 중간에 프로세스가 죽을 수 있다.
  • 같은 상태를 짧은 주기로 반복 갱신할 수 있다.

이 조건이 붙는 순간, 직접 수정 방식은 취약해집니다.

예를 들어 retention을 위해 현재 쓰고 있는 테이블을 바로 정리하면, 쓰기와 정리가 같은 대상을 두고 충돌할 수 있습니다. 반대로 chk 파일을 직접 덮어쓰다가 프로세스가 중간에 죽으면, 다음 기동 시점에 읽을 파일이 온전하지 않을 수 있습니다.

이 문제는 단순히 ”예외 처리 잘 하면 된다” 수준이 아닙니다.

더 본질적인 문제는 상태 전환 과정 자체가 외부에 노출된다는 점입니다.

운영 중인 시스템에서 더 안전한 방식은 대체로 비슷합니다.

  1. 현재 사용 중인 대상은 그대로 둔다.
  2. 새 상태를 다른 공간에서 준비한다.
  3. 마지막 순간에 짧고 명확한 전환만 수행한다.

이번 두 사례도 정확히 이 원칙으로 정리했습니다.


3. 통계 retention 처리에서 생긴 문제

통계 데이터는 계속 쌓입니다.

당연한 이야기지만, 계속 쌓이는 데이터를 영원히 같은 테이블에만 보관할 수는 없습니다. 어느 시점에는 retention 기준에 따라 정리해야 합니다. 그런데 통계 테이블은 단순 보관 테이블과는 조금 다릅니다.

문제는 이 테이블이 운영 중에도 계속 쓰이고, 동시에 조회도 된다는 점입니다.

이런 테이블에 retention을 적용할 때 가장 쉽게 떠오르는 방식은 다음과 같습니다.

  • 기준 시점보다 오래된 데이터를 DELETE한다.
  • 또는 일정 시점에 TRUNCATE하거나 재생성한다.

하지만 이 방식은 실제 운영에서는 썩 마음이 편하지 않습니다.

현재 쓰기 대상인 테이블을 직접 정리하면, 그 시점의 쓰기 흐름과 retention 작업이 같은 테이블을 공유하게 됩니다. 읽기 측에서도 정리 작업이 끝나기 전까지는 ”지워지는 중인 테이블”을 바라보게 됩니다.

즉 retention은 끝났더라도, 그 과정이 깔끔하지 않습니다.

이번 경우에는 retention의 핵심을 ”어떻게 지울 것인가”보다 “어떻게 현재 쓰기 대상을 직접 건드리지 않을 것인가”로 다시 봤습니다.


4. 통계 테이블을 이중화한 이유

그래서 선택한 방식이 main과 buffer 두 개의 통계 테이블을 두는 구조였습니다.

구조는 단순합니다.

  • 현재 활성 테이블 하나에만 기록한다.
  • 다른 하나는 비활성 테이블로 둔다.
  • retention 기준을 넘으면 활성 테이블을 바로 지우는 대신, 쓰기 대상을 반대편 테이블로 스왑한다.
  • 이후 정리 대상이 된 비활성 테이블은 retention 조건이 맞을 때 별도로 비운다.

이 방식의 장점은 명확합니다.

retention 시점에도 현재 쓰고 있는 테이블을 직접 정리하지 않아도 됩니다. 쓰기 경로는 active table 하나만 보면 되고, retention은 inactive table 기준으로 분리할 수 있습니다.

즉 문제를 이렇게 바꾼 셈입니다.

  • 이전 방식: “쓰고 있는 테이블을 어떻게 잘 지울까”
  • 변경 방식: “지울 테이블을 먼저 쓰기 대상에서 빼자”

운영 구조는 대체로 후자가 더 안전합니다.


5. 개선된 통계 retention 방식

개선 이후 통계 테이블 동작 방식은 다음과 같습니다.

현재 활성 테이블은 MAIN 또는 BUFFER 중 하나입니다. 저장은 활성 테이블에만 이뤄집니다. 조회는 두 테이블을 함께 읽어 합쳐서 보여줍니다. 그래서 전환 중에도 조회 경로를 별도로 끊을 필요가 없습니다.

retention 관리 시점에는 먼저 비활성 테이블을 봅니다.

  • 이미 비활성 테이블 데이터가 retention 기준보다 오래됐다면 그쪽을 정리한다.
  • 반대로 활성 테이블의 최소 시각이 retention 기준을 넘기 시작하면, 현재 비활성 테이블이 비어 있는지 확인한다.
  • 비활성 테이블에 아직 보존 대상 데이터가 남아 있으면 스왑하지 않는다.
  • 비활성 테이블이 비어 있으면 그때 활성/비활성을 바꾼다.

이 구조에서 중요한 점은 스왑이 먼저고 정리는 나중이라는 점입니다.

현재 쓰는 테이블을 정리하는 것이 아니라, 먼저 ”이제 여기는 더 이상 쓰지 않는다”는 상태를 만든 뒤에 나중에 정리하는 방식입니다. 이 순서 덕분에 retention 작업이 현재 쓰기 흐름과 직접 충돌하지 않게 됩니다.

추가로 기동 시점 처리도 넣었습니다.

만약 재시작 시점에 main과 buffer 양쪽에 데이터가 함께 남아 있다면, 이것을 비정상 상태로 보지 않고 buffer를 main으로 병합한 뒤 초기화합니다. 운영 시스템은 항상 깨끗하게만 종료되지 않기 때문에, 시작 시점 복구 전략까지 같이 가져가는 편이 낫습니다.

결국 이 구조는 retention을 위한 이중화이기도 하지만, 동시에 비정상 종료 이후 상태를 다시 한 방향으로 정렬하는 구조이기도 합니다.


6. chk 파일 기록 방식의 문제

두 번째 문제는 chk 파일이었습니다.

체크포인트 파일은 결국 ”지금 어디까지 처리했는가”를 나타내는 운영 상태입니다. 이런 파일은 작아 보여도 중요도가 높습니다. 적재 시스템 입장에서는 다음 기동 시점의 출발점을 결정하기 때문입니다.

기존에 이런 종류의 파일을 다룰 때 흔히 나오는 방식은 기존 파일을 열고 내용을 바꿔서 다시 쓰는 방식입니다. 말하자면 복사해서 수정하고, 원래 위치에 다시 덮어쓰는 식입니다.

이 방식이 위험한 이유는 분명합니다.

첫째, 갱신 중간 상태가 파일에 그대로 남을 수 있습니다.

프로세스가 쓰는 도중 죽으면, 다음 기동 시점에는 ”이전 파일”도 아니고 ”새 파일”도 아닌 어중간한 상태를 읽게 될 수 있습니다. 체크포인트 파일에서는 이런 상태가 가장 위험합니다.

둘째, 파일 전체가 하나의 상태 스냅샷인데, 수정 과정은 부분 갱신처럼 보일 수 있습니다.

체크포인트 파일은 헤더와 개별 항목이 함께 움직이는 구조가 많습니다. 이때 일부만 먼저 바뀌고 나머지가 늦게 바뀌면, 논리적으로는 한 번에 바뀌어야 할 상태가 중간 형태로 노출됩니다.

셋째, 동시에 여러 갱신 경로가 붙을수록 직접 덮어쓰기 방식은 더 불안합니다.

여기서 말하는 동시성은 반드시 멀티스레드 락 충돌만 뜻하지 않습니다. 짧은 간격으로 상태 갱신이 반복되거나, 서로 다른 흐름에서 같은 파일을 계속 다시 만드는 구조 자체도 운영상 취약성을 키웁니다.

이 문제 역시 핵심은 같습니다.

현재 사용 중인 파일을 직접 수정하지 말자.


7. chk 파일을 temp file로 바꿔 쓰게 만든 이유

개선 방향은 파일 시스템에서 자주 쓰는 패턴으로 잡았습니다.

  • 기존 chk 파일을 직접 수정하지 않는다.
  • 새 내용을 메모리에서 완성한다.
  • 그 내용을 temp file에 먼저 쓴다.
  • 쓰기가 정상적으로 끝난 뒤에만 기존 chk 파일과 교체한다.

이 구조의 핵심은 쓰기와 노출을 분리하는 것입니다.

기존 파일을 직접 수정하면 쓰는 과정이 곧바로 외부에 드러납니다. 반면 temp file에 먼저 쓰면, 외부에서는 교체 직전까지 여전히 이전 chk 파일만 보게 됩니다.

그리고 마지막 교체를 원자적으로 수행하면, 외부에서는 둘 중 하나만 보게 됩니다.

  • 이전 chk 파일
  • 완성된 새 chk 파일

중간에 절반만 써진 파일은 보지 않게 만드는 것이 핵심입니다.

이 방식은 서버 장애 상황에서도 의미가 있습니다.

temp file을 쓰는 도중 프로세스가 죽으면, 원본 chk 파일은 그대로 남습니다. 반대로 교체까지 끝났다면 새 상태는 이미 완성된 파일로 보장됩니다. 즉 비정상 종료가 발생해도 ”애매한 체크포인트 파일”을 남길 가능성을 크게 줄일 수 있습니다.


8. 개선된 chk 갱신 방식

개선 이후 chk 갱신 흐름은 대체로 다음과 같습니다.

  1. 현재 chk 파일을 읽는다.
  2. 필요한 헤더와 항목을 반영해 새 라인 목록을 메모리에서 만든다.
  3. 필요하면 암호화까지 모두 끝낸다.
  4. 결과를 원본 파일이 아닌 .tmp 파일에 먼저 쓴다.
  5. 마지막에 atomic move로 원본 chk 파일을 교체한다.

여기서 중요한 점은 원본 파일은 마지막 한 순간에만 바뀐다는 것입니다.

이런 구조는 단순히 ”좀 더 안전하게 썼다” 정도가 아닙니다. 운영 의미가 꽤 분명합니다.

  • 체크포인트를 부분 상태가 아닌 완성된 스냅샷으로 다룬다.
  • 비정상 종료가 나도 최소한 이전 체크포인트는 보존된다.
  • 파일 갱신 과정을 외부에 노출하지 않는다.

특히 체크포인트는 잘못 앞서가면 데이터 유실로 이어질 수 있고, 반대로 뒤처지면 중복 처리로 끝날 수 있습니다. 보통 운영 시스템에서는 중복보다 유실이 더 위험합니다. 그런 점에서 chk 파일은 ”빨리 쓰는 것”보다 깨지지 않은 상태로 쓰는 것이 더 중요합니다.


9. 두 문제를 같은 방식으로 본 이유

처음에는 이 둘이 전혀 다른 문제처럼 보였습니다.

  • 하나는 DB retention
  • 하나는 파일 체크포인트

하지만 실제로는 같은 원칙으로 설명할 수 있었습니다.

통계 retention

  • 현재 쓰는 테이블을 직접 지우지 않는다.
  • 다른 테이블을 준비해 두고 활성 대상을 스왑한다.

chk 갱신

  • 현재 읽히는 파일을 직접 수정하지 않는다.
  • 다른 파일에 완성본을 만들고 마지막에 교체한다.

둘 다 본질은 같습니다.

운영 중인 상태를 제자리에서 뜯어고치지 않고, 바깥에서 새 상태를 만든 뒤 짧은 전환으로 갈아끼운다.

이 패턴이 좋은 이유는 단순합니다.

운영 중에는 ”수정 과정”보다 ”전환 순간”이 짧고 명확해야 합니다. 수정 과정이 길게 노출될수록, 장애와 경합과 중간 상태가 끼어들 자리가 많아집니다.

반대로 새 상태를 바깥에서 충분히 준비한 뒤 마지막에만 전환하면, 시스템이 보게 되는 상태는 상대적으로 단순해집니다.


10. 개선 효과

이번 개선의 효과는 단순한 성능 향상보다 운영 안정성 쪽에 더 가깝습니다.

통계 retention 쪽에서는 다음 효과가 있었습니다.

  • retention 작업이 현재 쓰기 경로를 직접 건드리지 않게 됐다.
  • active/inactive가 분리돼 정리 대상이 명확해졌다.
  • 재시작 시점에 양쪽 테이블 상태를 다시 정렬하는 복구 경로가 생겼다.

chk 갱신 쪽에서는 다음 효과가 있었습니다.

  • chk 파일을 직접 덮어쓰다가 중간 상태를 남길 위험을 줄였다.
  • 서버가 쓰는 도중 죽더라도 이전 파일을 보존할 수 있게 됐다.
  • 체크포인트를 부분 수정이 아니라 완성된 스냅샷 교체로 다루게 됐다.

무엇보다 중요한 변화는 두 경우 모두 현재 사용 중인 상태를 직접 수정하는 습관에서 벗어났다는 점입니다.

운영 시스템은 기능 자체보다 상태 전환 방식에서 더 쉽게 흔들립니다. 그런 의미에서 이번 개선은 기능 추가라기보다, 운영 중 상태를 다루는 방식을 한 단계 정리한 작업에 가깝습니다.


11. 결론

운영 시스템에서 위험한 순간은 대개 ”무언가를 바꾸는 순간”입니다.

보관 주기를 적용하려고 데이터를 지울 때, 체크포인트를 기록하려고 파일을 갱신할 때, 시스템은 평소보다 더 쉽게 중간 상태를 드러냅니다. 그리고 실제 장애는 그 중간 상태에서 자주 발생합니다.

이번에 통계 retention과 chk 갱신을 다루면서 정리한 원칙은 하나였습니다.

운영 중인 상태를 직접 건드리지 말자.

통계는 active/buffer 이중화로 풀었고, 체크포인트는 temp file과 원자적 교체로 풀었습니다. 방식은 달라도 공통점은 분명합니다.

둘 다 ”현재 상태를 수정”한 것이 아니라, 새 상태를 준비한 뒤 마지막에 짧게 전환한 것입니다.

운영 환경에서는 이런 구조가 오래 갑니다.

빠르게 고치는 방식보다, 서버가 죽어도 다음 시작점이 설명되는 방식이 더 낫기 때문입니다.

태그:

카테고리:

업데이트: