Jinx - ColumnDiffer 리팩토링 회고
정확성과 안전성을 최우선으로...
최근에 한 작업은 ColumnDiffer의 주요 로직을 다시 정의하는 것이었다.
단순히 코드 개선이나 리팩토링이 아니라 DDL Diff 엔진이 어떻게 생각해야하는지에 대한 구조적 재고찰이기도 하여서,
해당 부분에 대한 글을 남겨두면 좋을 것 같다.
작업 전까지 ColumnDiffer에서는 컬럼 rename을 자동으로 추론하기 위한 여러 휴리스틱 기법을 사용하고 있었다.
이 방식은 간단한 예시에서는 어느정도 그럴듯하게 동작하지만 실제 스키마에서는 예상치 못한 rename오탐(false positive)를 만들어낼 위험이 존재했다.
스키마 변경은 DB 운영에서 가장 위험한 영역이기 때문에 절대 결코 가볍지 않다.
그 과정에서 자연스럽게 마주하게 된 주제가 바로 **스키마 매칭(schema matching)**이다. 데이터베이스 연구 분야에서 20년 가까이 다뤄져 온 고난도 문제이고, 단순한 문자열 비교나 휴리스틱으로 해결될 영역이 아니다.
이번 글에선 jinx에서 왜 rename 휴리스틱을 제거했는지,
기존 방식이 어떤 문제를 가지고 있었는지,
그리고 이를 통해 어떤 생각의 전환이 생겼는지를 정리해볼 예정이다.
1. 컬럼 rename 탐지 - 알고보니 "연구 분야"였다
스키마 매칭은 학계에서는 오래전부터 독립된 연구 영역으로 다뤄졌고, 기업용 ETL/데이터 마이그레이션 툴에서도 가장 난이도 높은 기능 중 하나다.
예를 들어 다음과 같은 문제들이 있다.
- 컬럼명이 바뀌었을까? (semantic similarity)
- 타입이 달라도 사실상 같은 의미일까?
- nullability가 바뀌었는데 rename인지 단순 변경인지?
- 합쳐진 컬럼인지? 분리됐는지?
- 여러 후보가 있는데 어떤 rename이 가장 타당한지?
이러한 모든 판단은 메타데이터만으로는 불완전한 정보이고 정확히 예측하기 위한 근거가 절대적으로 모자라다.
내가 jinx에서 처음 만들었던 rename 시스템은 지금 돌이켜보면 2000년대 설계된 논문 수준의 초기형 휴리스틱 알고리즘에 가까웠음.
- 이름 유사도 기반
- 타입 비교 기반
- nullability/precision 조건 약간 반영
지금 생각하면 매우 단순하고 취약한 구조였고 실제 운영 스키마에서는 false positive가 발생할 여지가 충분했다.
솔직히 상당히 위험한 문제라는 걸 뒤늦게 더 깊게 인지하게 되었다.. 스키마 매칭은 단순 메타데이터 규칙으로 접근할 문제가 아니었음.
2. 기존 방식의 구조적 한계 - 얕은 휴리스틱
- 정확도 문제
유사도 스코어 기반 매칭은 다음과 같은 오판 위험이 높다.
- 문자열 패턴이 우연히 비슷한 경우
- 타입이 비슷하기만 해도 rename으로 착각
- enum/precision 등의 차이를 제대로 반영하지 못함
- false positive 발생 시 데이터 무결성이 즉시 위협받음
- 메타데이터만으로 rename을 추론하는 것은 본질적으로 불완전하다
애초에 rename인지 아닌지는 엔티티 개발자가 의도적으로 판단해야만 알 수 있는 정보이기 때문이다. 그냥 도구가 'rename인가?' 라고 추측하게 되는 상황 자체가 위험하다. 만약 데이터백필까지 지원한다면 false positive 하나로 운영 DB는 초토화 될 것..
3. 기존 방식 개선을 위해 시도했던 (나름의)고급화 전략들
1단계 — Exact 해시 매칭
이름을 제외한 속성이 완전히 동일한 경우 즉시 rename으로 매칭.
String attributeHash = getAttributeHashExceptName(column);
2단계 — CoarseKey 기반 버킷화
CoarseKey key = new CoarseKey( column.getTableName(), column.getJdbcType(), column.getJavaType(), column.isNullable() );
이런 key가 동일한 컬럼끼리만 비교하도록 하여 후보군을 크게 축소했다.
3단계 — 가중치 기반 유사도 모델
- 유형(type), enum, LOB 등 핵심 요소 -> High weight
- 길이, precision, default -> Medium
- 주석 등 의미 없는 요소 -> Low
결과적으로 rename 탐지의 정확도는 향상되었고 성능 또한 평균적으로 O(n) 에 가깝게 최적화되었다.
하지만 그래도 절대 안된다.. rename을 확신할 수 있는 근거는 만들수 없었음. 결국 메타데이터만으로 rename을 판단하는 것은 근본적으로 불완전하다.
4. 이 모든 이유로 rename 탐지를 제거하기로 결정
이러한 기술적 배경하에 나는 다음과 같은 결론을 내렸다.
- rename은 단순 기능이 아니라 “의미론적 스키마 매칭” 문제다.
- 이 문제는 학계에서도 오랫동안 연구되는 난제 수준의 영역이다.
- 단순 휴리스틱 기반 rename은 절대 운영 DB 환경에서 쓰이면 안 된다.
- DDL diff의 최우선 목표는 “추측이 아닌 확정적 결과”다.
그래서 rename 기능을 잠정 중단하고 완전한 deterministic 비교 방식으로 돌아가는 것이 적합할 것이라고 생각했다.
사실상 리네임 탐지 기능은 완전 폐지라고 봐도 될 것 같다.
조금 더 찾아본 바로는, Jaro-Winkler / N-Gram / Token 기반 유사도.. 타입/nullable/precision 등 구조적 특징 기반 구조적 similarity, 약어/동의어 사전 기반 semantic matching
등등의 방안이 있지만.. 솔직히 아주 높은 유사도를 검출할만한 알고리즘을 내가 개발하게 된다면.. 당연히 좋은 일이겠지만 해당 분야에 나의 모든 리소스를 투자할 만큼의 여력은 없다는 것이 지금의 결론이다. 그리고 투자한다고 해서 절대 절대 만들거라고 장담할 수 없음. 해당 분야에서 난 문외한이나 마찬가지다.
이러한 지점들을 지나서, 사실은 Jinx의 역할을 축소하는 방향으로도 고려중이다. 방대한 dialect나, 아주 예민한 DB 마이그레이션이라는 분야가 내가 감당하기에는 쉽지 않을 것 같다는 것이 현재의 생각.
gradle 플러그인도 등록 대기 상태이긴 하지만, 굳이 자리를 찾자면 정말 간단한 ddl 생성기 정도로만 사용하게 하는게 가장 좋을 듯 싶다.