마이그레이션 전에 테이블 300개를 먼저 정리했다
ASP 시절 테이블 300개와 컬럼 수백 개를 코드와 저장 이력으로 추적해 옮길 것과 버릴 것을 가른 기록이다.
Spring Boot 전환을 시작하고 가장 먼저 연 것은 코드가 아니라 데이터베이스였다. 테이블이 300개였고, 컬럼이 100개를 넘는 테이블도 여럿이었다. 오래 운영된 시스템이라 데이터는 정확했지만, 어떤 값이 지금도 쓰이는지 알려 주는 것은 없었다.
설계 문서도 운영 문서도 없었다. 컬럼 이름과 값만 남아 있었고, 그 이름이 무엇을 뜻하는지 확인하려면 코드를 직접 읽어야 했다. 이 상태로 스키마를 그대로 옮기면 새 시스템은 기능뿐 아니라 알 수 없는 컬럼까지 함께 물려받는다. 옮길 것과 버릴 것을 가르는 일이 전환의 첫 작업이 됐다.
코드보다 스키마를 먼저 본 이유
처음에는 코드부터 옮기고 데이터는 나중에 맞추려고 했다. 그런데 새 도메인 모델을 그리려면 어떤 값이 실제로 존재하는지부터 알아야 했다. 컬럼 하나가 살아 있는지 죽어 있는지 모르는 상태에서는 엔티티 경계를 정할 수 없었다.
순서를 바꿔서 스키마를 먼저 조사했다. 새 시스템에서 다룰 개념을 정하는 작업과 옛 컬럼을 판정하는 작업을 같이 진행했다. 결과적으로 이 순서가 맞았다. 옮길 대상이 줄어들자 만들 코드도 같이 줄었다.
열어 보니 네 가지가 한꺼번에 있었다
스키마를 훑으면서 나온 문제는 종류가 뚜렷했다. 하나씩 따로 나온 것이 아니라 한 테이블 안에 네 가지가 섞여 있는 경우가 많았다.
| 문제 | 어떻게 보였나 | 판정 기준 |
|---|---|---|
| 안 쓰는 컬럼 | 읽는 코드도 쓰는 코드도 없다 | 참조와 저장 이력이 둘 다 없으면 버린다 |
| 이름만 봐선 모르는 컬럼 | 약어와 일련번호로만 되어 있다 | 값과 호출 지점을 보고 뜻을 확정한다 |
| 타입이 제각각인 컬럼 | 같은 뜻인데 어디는 문자열, 어디는 숫자 | 의미 기준으로 하나로 통일한다 |
| 같은 값 중복 저장 | 여러 테이블이 같은 값을 따로 들고 있다 | 소유자를 한 곳으로 정한다 |
네 가지는 판정 방법이 달랐다. 안 쓰는 컬럼은 증거로 확인할 수 있었지만, 중복 저장은 어느 쪽이 원본인지 정하는 판단이 필요했다. 기계적으로 처리할 수 있는 것과 사람이 결정해야 하는 것을 먼저 나눴다.
안 쓰는 컬럼은 코드부터 뒤졌다
가장 먼저 한 일은 컬럼 이름으로 전체 코드를 검색하는 것이었다. 화면, 배치, 프로시저, 관리자 기능까지 참조가 하나라도 있으면 살아 있는 컬럼으로 봤다.
여기서 걸러지는 양이 생각보다 많았다. 기능이 사라졌는데 컬럼만 남은 경우, 임시로 만들었다가 잊힌 경우, 다른 컬럼으로 대체됐는데 옛 컬럼을 지우지 않은 경우가 계속 나왔다.
다만 코드 검색만으로는 확신할 수 없었다. 이름이 동적으로 조합되거나 조회 도구에서 직접 쓰이는 경우까지 잡을 수는 없었다. 참조가 없다는 사실만으로 바로 버리지는 않았다.
코드로 안 잡히면 데이터가 답했다
두 번째 기준은 저장 이력이었다. 그 컬럼에 최근 몇 년 동안 값이 들어온 적이 있는지 확인했다. 참조도 없고 몇 년째 새 값도 없으면 죽은 컬럼으로 판정했다.
flowchart TD
A["컬럼 하나"] --> B{"코드에서 참조하나?"}
B -- "있음" --> C["옮긴다"]
B -- "없음" --> D{"최근 몇 년간 저장된 값이 있나?"}
D -- "있음" --> E["보류하고 사람이 확인"]
D -- "없음" --> F["버린다"]
둘 다 애매한 컬럼은 보류로 두고 따로 모았다. 이 목록은 운영을 아는 사람과 같이 봤다. 코드와 데이터로 답이 안 나오는 것은 기억에 의존할 수밖에 없었고, 그마저 없으면 옮기되 새 시스템에서 쓰지 않는 쪽으로 분류했다.
판정 결과
- 옮김: 참조가 있거나 최근 저장 이력이 있음
- 버림: 참조 없음 + 장기간 저장 없음
- 보류: 한쪽만 해당. 확인 뒤 결정
이름만 봐서는 모르는 컬럼을 다뤘다
약어로만 되어 있거나 뒤에 숫자가 붙은 컬럼이 많았다. 이름으로는 뜻을 알 수 없어서 두 가지를 함께 봤다. 실제 저장된 값의 형태와, 그 컬럼을 읽는 코드가 값을 무엇으로 쓰는지였다.
값만 보면 짐작에 그쳤다. 코드에서 그 값이 조건으로 쓰이는지, 화면에 표시되는지, 계산에 들어가는지를 확인해야 뜻이 정해졌다. 뜻이 정해진 컬럼은 새 스키마에서 의미가 드러나는 이름으로 바꿨다.
이름을 바꾸면 옛 이름과의 연결이 끊기므로 매핑을 반드시 기록했다. 새 이름만 남기고 옛 이름을 지우면 나중에 옛 데이터를 다시 볼 때 대조할 방법이 사라진다.
타입이 제각각인 컬럼을 하나로 모았다
같은 뜻인데 테이블마다 타입이 다른 값이 있었다. 어디는 문자열, 어디는 숫자였고, 날짜를 문자열로 담아 둔 곳도 있었다. 값 자체는 같은 것을 가리키는데 비교하려면 매번 변환이 필요했다.
새 스키마에서는 의미를 기준으로 타입을 정했다. 금액은 금액대로, 식별자는 식별자대로 하나의 타입만 쓰게 했다. 변환이 필요한 쪽은 옮기는 시점에 한 번만 처리하고, 새 시스템 안에서는 다시 변환하지 않게 했다.
같은 값을 여러 곳에 저장하던 것을 정리했다
같은 값을 여러 테이블이 각자 들고 있는 경우가 있었다. 한쪽만 갱신되면 값이 서로 달라지는데, 어느 쪽이 맞는지 판단할 기준이 없었다.
이 문제는 기술 판단이 아니라 소유권 판단이었다. 값마다 원본을 하나로 정하고 나머지는 참조로 바꿨다. 원본을 정할 때는 그 값을 처음 만드는 흐름이 어디인지를 기준으로 삼았다.
매핑표를 만들고 그걸로 옮겼다
판정이 끝난 뒤에는 옛 컬럼과 새 컬럼의 대응을 표로 만들었다. 이 표가 이관 작업의 유일한 기준이 됐다. 표에 없는 컬럼은 옮기지 않았고, 표에 있는데 값이 비면 원인을 확인했다.
flowchart TD
A["옛 스키마"] --> B["판정: 옮김 · 버림 · 보류"]
B --> C["매핑표"]
C --> D["이관 실행"]
D --> E["건수와 값 대조"]
E --> F{"차이가 있나?"}
F -- "없음" --> G["확정"]
F -- "있음" --> H["옛 DB에서 원본 확인"]
H --> C
이관은 한 번에 끝내지 않고 테이블 단위로 나눠서 돌렸다. 한 번에 전체를 옮기면 어디서 틀어졌는지 찾기 어렵고, 되돌리는 범위도 커진다. 단위를 작게 하니 실패해도 그 테이블만 다시 하면 됐다.
데이터가 안 맞으면 옛 DB를 봤다
옮긴 뒤 건수나 값이 맞지 않는 경우가 나왔다. 그때마다 새 데이터를 고치는 대신 옛 데이터베이스에서 원본을 확인했다. 새 쪽에서 추측으로 값을 채우면 그 순간부터 어느 쪽이 맞는지 알 수 없게 된다.
옛 데이터베이스를 그대로 살려 둔 것이 이 과정에서 가장 도움이 됐다. 대조할 원본이 있으니 차이가 났을 때 판단할 근거가 있었다. 원본과 비교해 옮기는 규칙이 틀렸으면 매핑표를 고치고 그 테이블만 다시 옮겼다.
되돌릴 수 있게 남겨 뒀다
이관 작업은 언제든 되돌릴 수 있는 상태로 진행했다. 옛 스키마와 데이터를 지우지 않았고, 새 쪽에 문제가 생기면 옛 쪽을 다시 볼 수 있었다.
되돌릴 수 있게 지킨 것
1. 옛 스키마와 데이터를 지우지 않는다
2. 이관은 테이블 단위로 나눠 실행한다
3. 매핑표를 고칠 때마다 해당 테이블만 다시 옮긴다
4. 대조가 끝난 테이블만 확정으로 표시한다
되돌릴 수 있다는 사실 자체가 작업 속도를 올렸다. 한 번에 맞혀야 한다는 부담이 없으니 애매한 컬럼도 일단 판정하고 확인하는 쪽으로 갈 수 있었다.
정리하고 남은 것
스키마 정리를 먼저 한 덕분에 새 시스템에서 다룰 개념이 훨씬 줄었다. 옮기지 않기로 한 컬럼만큼 만들지 않아도 되는 코드가 생겼고, 이름이 정해진 컬럼은 도메인 모델에 그대로 반영됐다.
다만 전부 해결한 것은 아니다. 보류로 분류한 컬럼 중 일부는 끝까지 뜻을 확정하지 못하고 값만 옮겼다. 옛 데이터베이스를 참조로 남겨 둔 상태도 계속 유지할 수는 없어서, 언제 끊을지는 이후 과제로 남았다.
문서가 없는 시스템에서 확실한 근거는 두 가지뿐이었다. 코드가 그 값을 읽는가, 그리고 데이터가 최근에도 들어오는가. 이 두 가지로 답이 안 나오면 남은 방법은 사람에게 묻는 것이었고, 그것도 안 되면 판단을 미루고 기록으로 남기는 수밖에 없었다.