들어가기에 앞서
3월에 인생에서 겪어본 가장 큰 변화를 경험할 뻔한 여러 일들을 겪었다. 커리어 패스를 다른 쪽으로 바꾸어 원하는 목표에 좀 더 근접하도록 설정하는 그런 일(이직)이었는데 모종의 사정으로 인하여 상당 기간 보류가 필요해졌다. 나중에 이 이야기에 대해서, 이직을 무사히 마친 뒤에 정리해보고 싶다.
그래서 지금은 당분간 다시 원 소속 회사에서의 근무를 지속하게 되었다. 그리고 그동안 고민해두었던 안건 하나를 마무리 짓기로 결정했다. 내가 떠난 후에는 정말 이런 것을 안할 회사이기 때문에 해두고 가고 싶었기도 하고 예전부터 요청이 꾸준히 있었던 사항.
Java 업그레이드
당시 이걸 준비하던 때에는 Java 1.7을 프로덕션 환경에서 사용하고 있었는데, 개발 환경은 1.8을 사용하고 있었던 상황이 있었다. 서비스를 운영/개발하는 쪽과 인프라 담당 부서가 나눠져 있었으며 인프라 쪽에는 매우 보수적으로 나올 거라 생각하고 있었다.
하지만 장기적인 상황을 봤을 때 반드시 업그레이드는 필요했다. 그리고 작년부터 추진하여 올해 4월에 결국 업그레이드를 마쳤다. 현재 무사히 잘 서비스되고 있기에 앞으로 비슷한 업그레이드를 추진할 분들을 위해 관련 경험을 공유하고자 한다.
업그레이드 배경
업그레이드를 추진하게 된 배경에는 소프트웨어적 이유와 비소프트웨어적 이유가 존재한다.
소프트웨어적 이유
가장 큰 이유는 Java 7이 보안 업데이트까지 모두 종료된 버전이란 점에서 지속적인 보안 업데이트가 가능한 LTS 라인업으로 오기 위함이다.
더불어 Spring Boot를 사용하게 되면서 주요 의존성들이 점차 Java 8을 베이스라인으로 대응하기 시작하는 점도 한 몫한다. 이 이야기는 추후에 Java 7에 대한 대응이 안되는 패키지가 생길 수도 있다는 것을 의미한다. 특히 Spring Boot 2로 기존 서비스 기반을 변경할 것까지 염두한다면 반드시 체크할 필요하다.
그리고 Java 8부터 대응되는 여러 신규 표준 라이브러리 API를 사용할 수 있다는 점이 한몫 했다.
특히 Calendar
나 Date
로 대변되는 날짜/시각 처리 API는 사용이 불편할 뿐더러 소프트웨어 개발자의 실수를 유발하기에 충분했다. 현 프로젝트에서는 Java 8의 time API를 backport해둔 threeten bp를 사용하고 있긴 했지만 JPA에서 Threeten Backport에서 제공하는 Date/DateTime 객체를 사용하려면 기본 컨버터가 아니라 Threeten에서 제공하는 컨버터를 따로 설정해야하는 불편함도 있었기에 언젠가는 제거하려고 염두하고 있었다.
그리고 Stream<T>
, Optional<T>
과 Java 8 부터 지원되는 몇몇 리플렉션 API들을 사용하고 싶었다. 덤으로, JPA를 쓰지 않더라도 MyBatis 3.5.0부터는 Mapper 메소드에 Optional<T>
를 리턴 타입으로 쓸 수 있어서 null 값을 직접 다루지 않도록 설계할 수도 있으니 참고 바란다.[1]
비 소프트웨어적 이유
비 소프트웨어적 이유로는 회사에 들어오는 신규 인력(외주 포함)이 Java 8 또는 그 이후 버전을 기준으로 교육을 받고 오기 때문인 점과 추후 Java 8로의 전환을 염두하고 개발과 테스팅은 Java 8에서 진행하되 프로덕션 환경은 7을 유지하는 환경의 특수성과 맞물려 문제를 많이 일으켰다.
핵심적이지 않은 업무에 대해서, 현 회사에서는 비용 문제로 인해 외주 인력과 함께 작업하는 경우가 많은데 외주 인력의 경우에는 소프트웨어 개발 특히 Java 버전별 특성에 대한 이해 수준 편차가 꽤 커서 새로 들어온 사람의 경우는 CI에서 빌드를 깨먹는 경우가 꽤 많다.
프로그램 작성에 있어 모든 걸 알고 작업하기란 쉽지 않기에 어느 정도 Stack overflow나 다른 블로그를 참고하여 코드를 작성하는데 도움을 받는데, 이게 운영환경인 Java 7에서 지원되지 않는 API이거나 사용법이 다른 코드인지 모르고 개발/테스팅 환경에서 별 고민없이 사용하는 경우가 많았다. 백포트 라이브러리를 써야하는데, 바쁘면 리뷰하다가 놓치게 되고 그게 문제를 일으켰다.
CI까지는 Java 8을 사용했으므로, CI 빌드까지는 문제가 없더라도 배포가 이루어지면 서비스가 올라가지 않는 문제가 생기기 시작해서 결국 기술적으로 bootstrap classpath 를 설정하여 Java 1.7로 크로스컴파일 하도록 설정하여 CI 차원에서 빌드가 깨지면 알림을 보내는 걸로 조치하긴 했다.
그러나 매번 새로운 사람이 올 때마다 교육을 해야하고, CI에서 이런 소소한(?) 이유로 빌드나 테스트가 실패했다고 알림을 받는 것도 중요한 업무시간을 낭비하게 되는 사유가 되므로 결국은 공식적 절차를 통해 운영-테스트-개발 환경을 모두 동일한 Java 8로 맞추는 작업을 진행하게 되었다.
진행 순서
대략적으로 아래와 같은 과정을 거쳐서 진행했다.
- 먼저 내부에서 Java 8로 프로젝트를 돌릴 수 있게 작업을 수행하고 빌드해보고 테스트 한다.
기본적인 확인은 내부에서 진행하는 것으로도 충분하다. 빌드가 안될 수도 있고, 유지보수가 더이상 되지 않는 3rd party 패키지 소프트웨어로 인해 문제가 일어날 수도 있다. - 1단계를 무사히 마친 후 점진적으로 이때 발견된 수정사항들을 프로덕션에 반영한다. 그리고 다시 Java 8로 테스트한다.
자동화 테스트를 포함하여 손 테스트를 하더라도 모든 업무를 빠짐없이 테스트한다. 여기서 충분한 시간을 투자하지 않으면 업그레이드가 완료된 시점에 안되는 몇몇 업무로 인해 업그레이드 배포를 취소하고 복구 해야한다. (특히 컨테이너를 쓰지 않는 경우 다운타임으로 이어짐) - 기본적인 기능 테스트를 모두 마쳤다면 다음은 성능 테스트이다.
기본 JVM 옵션으로 성능이 저하되진 않았는지 or 파라미터 튜닝이 필요한지(TPS, GC 성능 등), 더이상 사용할 수 없는 옵션이(예를 들면 -XX:MaxPermSize 같은, JVM 메모리 구조상 PermGen이 아예 사라지기 때문) 적용되어있는지 등 체크 필요하다. - 큰 문제 없이 잘 된다면 그동안의 불평불만(?)과 하고 싶은 일을 회사 업무 형식에 맞게 문서를 작성하고 추진하고 싶은 근거를 차곡차곡 정리하여 보고 자료를 작성하고 보고한다.
- 잘 설득하여 추진되면, 업무 협조가 필요한 부서에도 협조를 구하고 내 서버가 아니라면 고객사의 담당자도 설득한다.
- 상용 Web Application Server를 사용하고 있다면 (예를 들어 TmaxSoft의 JEUS 등) 해당 소프트웨어가 Java 8을 잘 지원할 수 있는지 체크해야한다. 해당 소프트웨어 제작사의 엔지니어를 통해 기술자문을 요청하고 추후 업그레이드 진행시 기술지원을 요청한다.
- 업그레이드를 진행할 때 발생될 수 있는 장애 상황과 개별 장애 상황에 대한 복구 계획을 정리한다. 복구 계획을 정리해둠으로써 유사시에 타 부서간의 업무 협조를 위한 사항을 공유할 수도 있고, 윗 사람에게 좀 더 믿음을 줄 수 있다.
- 서버 환경 변수를 변경하거나, 새로운 컨테이너 이미지를 배포하는 식으로 업그레이드 작업을 진행한다.
업그레이드 준비에 참고한 자료들
아래 자료들 위에도 다양한 사이트와 경험을 참고했다.
- allegro Tech 블로그의 How to migrate to Java 8
- Oracle 공식 문서 JDK 8 Adoption Guide
- LINE의 LINE의 OpenJDK 적용기: 호환성 확인부터 주의 사항까지
회사에서 Oracle JDK 라이센스 못 사준다고 해서 이것도 같이 봐야 했다. 다만 JDK를 변경한다는 점에서 충분히 유익한 테스트 자료와 참고할 자료가 많으니 꼭 살펴보자.
업그레이드 진행
서버에서 쓰고 있는 Web Application Server가 상용 WAS 제품군이었기에, 해당 회사에서 출장 나온 엔지니어와 함께 업그레이드 작업을 진행했다. 상황이 허락된다면 Blue-Green 디플로이 전략을 비슷하게 활용하거나 트래픽 흐름을 조절하여 기존 운영 서비스는 그대로 두고 Java 8을 올린 서비스 쪽으로 트래픽의 일부를 흘려보는 방법을 선택할 수도 있는데, 이번 경우에는 구조상 다운타임이 불가피했다. (자세한 서버 구성을 공개하기 어려운 점 양해 바란다.)
다행이도(?) Java 8로의 업그레이드는 이미 여러 선구자들 덕분에 상용 WAS 제품군을 포함하여 큰 문제 없이 진행되었고, 서버 애플리케이션 수준의 테스트는 1년 이상 Java 8로 개발/테스트 환경을 돌리면서 큰 문제가 없었기에 큰 문제 없이 업그레이드를 마쳤다. WAS Engine에 특정화된 문제가 생기지 않는 한 큰 문제는 없었던 셈.
이후에 부하 테스트를 포함하여 약 4시간 정도 모니터링을 진행했고 메모리나 쓰레드 풀에 큰 이상이 없어서 작업 종료를 선언했다. TPS는 미묘하게 나아진 정도인데 유의미한 수준은 아니었던 것 같다.
해당일에 나는 집에 못들어갈 생각하고 속옷과 갈아입을 옷을 챙겨 왔었는데 그냥 평범하게 저녁밥 먹고 집에 들어갈 수 있었다.
후기
CI 서버에서 빌드할 때 Java 7과 크로스컴파일 해가면서 장기간 1.8로 개발/테스트를 해본 덕분에 큰 문제 없이 넘어왔고, Java 8로 운영한지 3주 정도 되어가는데 현재까지 큰 문제가 없기에 무사히 마무리 되었다고 판단한다.
그리고 얼마 전에 Threeten backport 의존성을 build.gradle
파일에서 제거하고 모든 호출을 Java 8 표준 라이브러리로 대체하도록 작업했고 역시나 큰 문제 없이 테스트를 마쳐 배포되었다. 이것은 Java 8 넘어와서 프로젝트 환경을 개선을 첫걸음이고, 점차 코드 리팩토링 작업 등으로 이어질 것이다.
생각보다 간단하게 끝났다고 생각할 수도 있고, 실제로도 과정은 약간의 다운타임이 있었던 것을 제외하면 간단했다. 제일 좋았던 것은, 업그레이드 도중 실패할 경우를 대비해 작성해둔 생각할 수 있는 모든 경우에 대한 복원 시나리오가 쓸모 없어진 것이다. 이것은 반드시 있어야 하지만 쓰지 않아야 좋은 것이다.
굳이 Java 8로 프로덕션 환경을 업그레이드한 결과에 대해 만족도를 묻는다면, 나는 매우 만족이라고 답할 수 있을 것 같다. 당장 드라마틱하게 운영환경이 바뀌거나 성능이 나아진 것은 아니지만, 다음에 내가 아닌 누군가가 와서 새로운 걸 시도하려 한다면 어느 정도 업그레이드가 되어있어 기반 환경에 대한 걱정 없이 새로운 걸 시도할 수 있었으면 하기 때문이다.
- 1.https://blog.mybatis.org/2019/01/mybatis-350-released.html ↩