Search

신규 버전을 배포하기 위해 몇번의 배포가 필요할까?

태그
면접질문
CS
운영
작성 상태
작성 완료
작성일
2024/09/20
참고 링크
참고 링크 2

무중단 배포 환경에서 신규 버전의 배포

분산된 서버 환경에 새로운 버전이 도입되기 위해선 몇번의 배포가 필요할까?

배포 환경 가정

1.
10대의 WAS 서버가 있다. 한번의 배포란, 특정 빌드 파일을 10대의 서버에 순차적으로 새 버전을 실행시키는 것을 말한다.
2.
데이터베이스는 하나의 Source DB와 하나의 Replication DB로 이중화 되어있다.
3.
데이터베이스의 스키마 변경은 미리 작업을 해두었다. 즉, 데이터베이스는 언제든 구버전의 기능과 신버전의 기능을 위해 준비되어있다.
4.
배포는 각 WAS에 순차적으로 진행되는 Rolling 방식이다.
5.
배포 도중 언제라도 사용자는 서비스를 사용할 수 있어야 한다.
6.
신규 버전은 구버전에서 제공하지 않던 기능을 제공하는 것이 아니라 내부 구현이 변경된 것이라 프론트엔드 레벨의 변경은 없다.

첫번째 생각

두번이면 되지 않을까?
기존 버전에서 생성한 데이터가 데이터베이스에 있을테니, 신규 버전의 쓰기와 읽기 기능, 기존 버전의 읽기 기능이 포함된 중간 버전을 먼저 배포해야 한다. 그리고 모든 서버에 중간 버전이 배포되면 기존 버전의 읽기 기능이 삭제된 신규 버전을 배포하면 된다.

문제점

1.
5번째 서버에 빌드 파일이 배포되는 시점에, 1 ~ 4번째 서버에는 중간 버전이 배포가 되어있다. 따라서, 그 시점에 사용자의 쓰기 요청이 1 ~ 4번째 서버에서 처리된다면 데이터베이스에는 신규 버전의 형태로 데이터가 쓰인다. 그런데, 동일한 사용자의 조회 요청이 6 ~ 10번째 서버에서 처리된다면 이 서버들에는 아직 신규 버전의 읽기가 불가능하므로 적절한 응답을 보내줄 수 없다.
2.
모든 서버에 중간 버전의 배포가 되었다 하더라도, 그 다음 버전에서 기존 버전의 읽기 기능이 삭제되면 오래 전 이미 생성된 데이터를 조회할 수 없을 것이다.

개선 방향

첫번째 문제점을 해결하기 위해 다음과 같은 아이디어를 생각해봤다.
배포 시작 후 쓰기 요청을 보낸 사용자로부터 온 조회 요청은 중간 버전의 배포가 완료된 서버로 보내도록 처리하면 되지 않을까?
이 방식은 얼핏 보면 잘 동작할 것 같다. 그러나, 한 사용자의 조회 요청에 다른 사용자의 데이터가 포함될 수 있다면 적용할 수 없다. 한 발 더 나아가서 조회 요청은 무조건 중간 버전이 배포된 서버에 보내도록 로드밸런싱하는 것을 생각해 볼 수 있다. 그러나, 이는 다른 문제를 가져온다.
만약, 첫번째 서버만 배포가 진행되고, 두번째 서버를 배포하는 중이라 해보자. 그럼 그 시점에 조회 요청을 처리할 수 있는 서버는 첫번째 서버 오직 한대다. 이는 원래대로면 10대가 나눠 가졌을 트래픽이 한대의 서버에 몰리는 것을 의미한다. 첫번째 서버의 정상 동작을 보장할 수 없고 응답 속도가 느려지거나 최악의 경우 응답을 하지 않을 수도 있다.
이러한 사실들로부터 다음과 같은 결론에 도달할 수 있다.
모든 서버에서 신규 읽기가 가능해지기 전에 신규 쓰기가 동작하면 안된다.
두번째 문제점을 해결하기 위해 다음과 같은 아이디어를 생각해봤다.
읽기 요청을 처리할 때, 기존 버전의 데이터를 신규 버전의 형태로 변형한다. 이후 기존 버전의 데이터가 충분히 빠른 시간 내에 일괄 처리할 수 있을 정도로 적어진다면, 일괄적으로 변형한다.
두번째 문제점을 다르게 표현하면 “기존 버전의 데이터가 남아있는 한 기존 버전의 데이터를 읽을 수 있어야한다.”이다. 따라서 기존 버전을 대응하기 위한 코드와 신규 버전의 코드가 공존해야 하며, 구조가 필연적으로 복잡해진다는 것을 의미한다.
이 사실들을 통해 다음과 같은 결론에 도달할 수 있다.
하위 호환성을 유지하기 위한 노력과 하위 버전의 데이터를 최신 버전의 데이터로 변경하는 특별한 노력이 동시에 이루어져야 한다.

두번째 생각

첫번째 생각에서의 결론을 다시 적으면 다음과 같다.
1.
모든 서버에서 신규 읽기가 가능해지기 전에 신규 쓰기가 동작하면 안된다.
2.
하위 호환성을 유지하기 위한 노력과 하위 버전의 데이터를 최신 버전의 데이터로 변경하는 특별한 노력이 동시에 이루어져야 한다.
이 중 두번째 부분은 변경점이 어떤 종류의 변경점인지에 따라 방법이 달라진다. 사용자의 개입 없이 데이터를 변형할 수 있는 부분이라면 데이터베이스에 접근해 데이터를 변형하는 작업을 따로 진행하는 것이 가능하다. 반면, 사용자로부터 추가 데이터 입력이 필요하거나 사용자가 기능을 사용하며 자연스럽게 파생되는 데이터가 필요하다면, 코드 레벨에 점진적인 데이터 변형을 추가해야 할 것이다. 후자의 경우 배포를 새로 하는 것으로 문제를 해결할 수 있는 것이 아닐 수 있다. 어쩌면 영원히 기존 버전의 코드를 제거할 수 없을 수도 있다. 따라서, 기존에 제공하는 기능에 새로운 기능이 추가되는 경우 코드 레벨에서 유연한 확장성을 가지는 구조를 도입하는 것이 유리할 것이다.
모든 서버에서 신규 읽기가 가능해진다는 것은 모든 서버에서 중간 버전이 실행중인 것을 의미한다. 따라서, 요청이 들어왔을 때 모든 서버에서 중간 버전이 실행중이라면 신규 쓰기를, 그렇지 않다면 기존 쓰기를 하도록 하면 된다.
모든 서버에서 중간 버전이 실행중인지 어떻게 알 수 있을까?
분산 환경에서 로드밸런서는 주기적으로 관리하는 서버의 상태를 확인하는 헬스 체크 작업을 한다. 헬스 체크 작업은 간단하다. 각 서버에 특정 요청을 보내서 그 응답이 정상이라면 서버가 건강한 상태라고 간주하는 것이다. 헬스 체크 요청시 그 서버에서 실행중인 어플리케이션의 버전을 응답하도록 한다면 로드밸런서는 서버들의 상태를 알 수 있다. 그리고 이에 따라 실제 서버에 다른 요청을 보내게 하면 된다.
그러나, 로드 밸런서를 AWS 등의 클라우드 서비스에서 제공되는 것을 쓴다면, 이러한 추가 로직을 구현할 수 없을 가능성도 있다. 이런 경우에는 서버에서 실행중인 어플리케이션의 버전을 수집하는 전용 서버(버전 서버라 칭함)를 추가하고, 각 어플리케이션 서버에서 본 로직을 수행하기 전에 버전 서버에 먼저 요청을 보내 확인하는 방식을 사용할 수 있을 것이다.

세번째 생각

가정에 의문을 던지자 : Rolling 배포여야만 하는가?

무중단 배포 방식에는 Rolling 배포만 있는 것이 아니다. 여러 배포 방식이 있는데, 이를 쉽게 설명한 영상이 유튜브에 있다.
무중단 배포 방식 중 Blue - Green 방식은 기존 버전이 배포된 서버들(Blue)와 신규 버전이 배포된 서버들(Green)을 두고 순간적으로 모든 트래픽을 Green으로 옮기고 문제가 발생하지 않으면 Blue를 종료하는 배포 방식이다. 이 방식을 사용하면 앞선 생각을 구현하기 위한 특이한 헬스 체크 방식이나 특별한 로직이 추가된 로드밸런서가 필요없다. 덤으로 Blue 버전을 바로 종료하지 않으므로 유사시 롤백하기도 쉽다. 로드 밸런서만 Blue로 트래픽을 보내도록 하면 되기 때문이다. 그리고 AWS 와 같은 클라우드 서비스에서 공식적으로 이런 방식의 배포를 지원한다.

문제점

1.
Blue - Green 방식을 사용하기 위해선 Blue 개수만큼의 Green 서버가 필요하다. 즉, 물리적인 자원이 두배로 필요하다.

결론

지금까지 논리적인 추론을 통해 총 몇번의 배포가 있어야 신규 기능을 사용자에게 제공할 수 있을지, 그리고 그 각각의 배포에는 어떤 기능이 제공되어야 하는지 생각해봤다. 다소 김빠지는 결론이지만, 이번에도 역시 상황에 따라 다르다는 결론에 도달했다. 다수의 서버를 통해 서비스를 제공해야 하는 상황에서, 서비스의 중단 없이 새로운 기능을 사용자에게 제공하기 위해 배포 전략을 생각하기 위해선 다음 사항들을 고려해야 한다.
1.
모든 서버에서 신규 기능을 제공할 수 있기 전에 일부 서버에서 신규 기능을 제공한다면 서비스의 일관성에 문제가 생길 수 있다.
a.
이를 해결하기 위해 여러가지 배포 전략을 사용할 수 있다.
b.
혹은 추가 구성 요소를 도입해 문제를 해결할 수 있다.
c.
위 두가지 방식 모두 장단점이 있으니 서비스의 특성에 따라 다른 전략을 취할 수 있다.
2.
하위 호환성을 지키기 위해서 기존 기능과 신규 기능을 동시에 제공하는 것과, 기존 기능을 제거하기 위한 준비를 하는 것을 동시에 해야한다.
a.
기존 기능을 제거하기 위해 사용자의 특정 기능의 사용이 필요할 수 있다. 이런 경우 오랜 기간동안 기존 기능을 제거할 수 없을 수 있다. 따라서, 코드 레벨 혹은 데이터베이스 레벨에서 장기간 이를 제공할 수 있도록 유연한 구조를 가져가야 한다.