티스토리 뷰
[Open source contribution] Apache Paimon 오픈소스 기여 경험기 - Serializable인데 serialVersionUID가 없으면 생기는 일 — Paimon COSNLoader 수정
ebson 2026. 9. 3. 13:33Serializable인데 serialVersionUID가 없었습니다
Apache Paimon은 여러 클라우드 스토리지를 파일시스템 플러그인으로 붙입니다. 각 스토리지에는 플러그인을 로드하는 FileIOLoader 구현이 하나씩 있고, 텐센트 클라우드 COS용은 COSNLoader입니다. 이 클래스에 직렬화 식별자가 빠져 있어, 이번 글에서는 그것을 채운 apache/paimon#8555를 이야기하려 합니다.
시작은 인터페이스를 확인하는 데서였습니다. FileIOLoader는 Serializable을 상속합니다.
public interface FileIOLoader extends Serializable {
즉 이 인터페이스를 구현하는 모든 로더는 직렬화 대상입니다. Paimon에서 파일시스템 로더가 직렬화되는 이유는, 엔진이 작업을 분산 실행할 때 이런 구성요소가 노드 사이로 전달되어야 하기 때문입니다. 그런데 COSNLoader에는 serialVersionUID가 선언되어 있지 않았습니다.
// AS-IS
public class COSNLoader implements FileIOLoader {
private static final String COSN_JAR = "paimon-plugin-cosn";
private static final String COSN_CLASS = "org.apache.paimon.cosn.COSNFileIO";
...
}
serialVersionUID가 없으면 컴파일러가 만들어 냅니다
Serializable 클래스에 serialVersionUID를 직접 선언하지 않으면, 자바 직렬화 런타임이 클래스의 구조를 바탕으로 값을 자동으로 계산해 씁니다. 문제는 이 자동 계산이 클래스의 세부에 민감하다는 점입니다. 필드나 메서드 구성이 조금만 바뀌어도 계산된 값이 달라질 수 있고, 그러면 예전에 직렬화한 객체를 새 코드로 역직렬화할 때 두 값이 어긋나 InvalidClassException이 날 수 있습니다. 심지어 컴파일러 구현에 따라 값이 달라질 여지도 있습니다.
값을 명시적으로 선언해 두면, 클래스 진화에 대한 호환성을 개발자가 직접 통제하게 됩니다. 그래서 자바 세계에서는 Serializable 클래스에 serialVersionUID를 명시하는 것을 관례로 권합니다.
확인해 보니 이 관례는 이미 형제 로더들에 적용되어 있었습니다. COSNLoader와 마찬가지로 PluginFileIO 패턴을 쓰는 GSLoader, AzureLoader, OSSLoader, S3Loader, OBSLoader는 모두 serialVersionUID를 선언하고 있었습니다. 이 로더들은 실제 파일시스템 접근을 담당하는 안쪽의 PluginFileIO 서브클래스와 바깥의 로더 클래스 양쪽에 식별자를 두고 있었습니다.
흥미로운 점은 COSNLoader도 절반은 이미 이 관례를 따르고 있었다는 것입니다. COSNLoader 안에는 PluginFileIO를 상속한 COSNPluginFileIO 서브클래스가 있고, 여기에는 serialVersionUID가 이미 선언되어 있었습니다. 빠져 있던 것은 바깥을 감싸는 COSNLoader 클래스 쪽뿐이었습니다. 이 패턴을 쓰는 여섯 로더 가운데 안쪽에는 선언해 두고 바깥 로더 클래스에만 빠뜨린 것은 COSNLoader가 유일했고, 안쪽에는 있는데 바깥에만 없다 보니 눈에 잘 띄지 않는 빈자리였습니다.
형제와 같은 형태로 채우기
고치는 방향은 형제 로더들과 똑같은 자리에 똑같은 형태로 식별자를 더하는 것이었습니다.
// TO-BE
public class COSNLoader implements FileIOLoader {
private static final long serialVersionUID = 1L;
private static final String COSN_JAR = "paimon-plugin-cosn";
...
}
1L이라는 값은 클래스의 첫 직렬화 버전을 뜻하는, 형제 로더들이 쓰는 것과 같은 초기값입니다. 손댄 것은 필드 선언 한 줄뿐입니다.
이 변경은 눈에 보이는 동작을 바꾸지 않습니다. serialVersionUID는 직렬화·역직렬화의 호환성 판정에만 쓰이는 값이라, 로더가 플러그인을 로드하는 방식이나 결과는 그대로입니다. 달라지는 것은 이 클래스의 직렬화 형태가 이제 개발자가 정한 식별자로 관리된다는 점, 그리고 형제 로더들과 같은 규약을 따르게 된다는 점입니다.
작은 변경의 범위를 좁게 지키기
이 변경은 paimon-filesystems/paimon-cosn 모듈에 있습니다. 형제 로더와 비교하다 보면 다른 사소한 차이도 눈에 들어오지만, 성격이 다른 변경을 한 PR에 섞지 않는 것이 이 저장소의 방침이라 serialVersionUID 하나만 더했습니다.
Paimon은 빌드에 Maven을 쓰고, 저는 바꾼 모듈만 스코프로 잡아 mvn -pl paimon-filesystems/paimon-cosn clean install로 검증했습니다. 이 빌드에는 spotless·checkstyle·rat 검사가 함께 걸리는데, 이들은 형식을 대신 맞춰 주는 것이 아니라 위반이 있으면 빌드를 실패시킵니다. 형식을 자동으로 정렬해 주는 것은 mvn spotless:apply 쪽이라는 구분을 이때 알게 됐습니다. 이런 종류의 변경은 관찰 가능한 동작을 바꾸지 않아 새 테스트로 못 박을 결과가 없어, 모듈이 여전히 온전히 빌드되는지를 확인하는 선에서 마무리했습니다. squash 머지라 PR 제목이 곧 커밋 메시지가 되어 [module] Description 형식을 지켜야 해서, 제목을 [fs] Declare serialVersionUID in COSNLoader for Serializable consistency로 정했습니다. 본문은 템플릿의 ### Purpose와 ### Tests 두 섹션으로 나눴습니다.
형제와의 일관성이라는 근거
돌아보면 이 수정의 근거는 "이렇게 하는 것이 옳다"는 일반론보다, "형제들이 이미 이렇게 하고 있다"는 저장소 안의 구체적인 사실이었습니다. Serializable에 serialVersionUID를 두는 것이 좋은 관례라는 지식은 출발점이었지만, 그것을 이 클래스에 적용해도 되는지는 형제 로더들이 이미 같은 선택을 하고 있다는 점에서 확인할 수 있었습니다.
닮은 클래스들이 나란히 있을 때 그중 하나만 규약에서 벗어나 있으면, 그 자리가 대개 메워야 할 빈자리라는 것을 이번에도 실제로 해 보며 알게 됐습니다. 무엇을 고칠지 밖에서 찾아오는 대신 저장소 안에서 형제 코드를 읽어 근거를 세우는 방식이, 작은 변경일수록 더 분명하게 통한다는 감각이 남았습니다.
'OPEN SOURCE' 카테고리의 다른 글
- Total
- Today
- Yesterday
- 백엔드 아키텍처
- 백엔드 성능 설계
- DB 인덱스 성능
- spring batch 5
- TTL 설계
- InterruptedException
- 캐시 성능 비교
- Initialization-on-Demand Holder Idiom
- 트랜잭션 관리
- 스레드 생명주기
- Redis vs DB
- Double-Checked Locking
- Hot Key 문제
- Cache Avalanche
- 캐시 장애
- Eager Initialization
- 백엔드 성능
- 동시성처리
- mybatis
- DB 트랜잭션
- Java Performance
- Redis 성능 개선
- 캐시와 인덱스
- Cache Penetration
- Enum 기반 싱글톤
- Redis 캐시 전략
- Spring Batch
- 백엔드 성능 튜닝
- Cache Aside
- 트래픽 처리
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |

