베이스캠프 Sprint7

21.03.02부터 21.03.05까지 베이스캠프 Sprint7을 진행했다.

웹 서버 스케일 아웃을 컨셉으로 한 Sprint였다. 사실 Sprint 6까지는 한 대의 서버로 모든 것을 처리하던 웹 서비스였다. 그리고 입사 전 과거 나의 웹 개발 경험은 딱 Sprint 6까지 였던 것 같다. 웹 서비스를 위해 코드를 작성하고 배포하고 리팩토링하는 정도였다. 당연히 서버도 한 대였다. 만들었던 서비스가 스케일 아웃을 할만큼의 트래픽을 받아본 적도 없었고, 그렇기 때문에 Sprint 7부터는 그동안의 경험과는 다른, 정말 현업에 가깝고, 또 현업이 아니면 겪을 수 없는 경험을 할 수 있었다. 그리고 Sprint 7을 시작할 때 스케일 아웃에 대해 얼마나 무지했냐면 스케일 아웃과 스케일 업의 차이조차 모르고 있었다. 관련하여 블로그에 차이점을 정리하였다. Scale Out vs Scale Up

베이스캠프 Sprint7에서는 확장에 있어서 고가용성의 목적도 충족하기 위해 스케일 아웃을 채택하였다.

세션 구현

웹 서버를 두 대로 늘리면서 발생한 가장 첫 번째 문제는 메모리를 공유하지 않는다는 점이었다. 기존에 서버가 한 대일때는 해당 서버의 메모리에 담아두고 써도 문제가 되지 않았다. 그런데 두 대 이상의 서버로 운영 시 클라이언트 요청이 A서버로 들어와서 A 메모리에 연산결과를 담아두고 다음 요청이 B서버로 들어오는 경우 A의 연산결과를 알 방법이 없다. 대표적으로 문제가 되는 부분이 세션이었다. 기존의 세션은 모두 메모리에 저장되는 구조였기 때문이다. 따라서 두 서버가 공유하는 메모리, 즉 공유 스토리지 역할을 하는 무언가가 필요했고, 그것을 Redis와 DB를 이용해서 각각 구현하는 과제가 주어졌다. 각각을 구현하기 전에 구조를 설계하는 회의를 진행했는데 거기서 spring profile을 이용하여 profile에 따라 모듈을 갈아끼는 구조를 제시했고, 실제 구현에 채택되었다. 특히 나름 객체지향 원칙을 준수한 구조라고 생각해서 뿌듯했다.

제시한 구조는 공통 인터페이스인 SessionFactory를 작성하고 이를 implement하는 구현 클래스인 RedisSessionFactory와 DbSessionFactory를 작성하여 profile이 redis-session일때는 SessionFactory로 RedisSessionFactory를 Bean으로 등록하고, profile이 db-session일때는 DbSessionFactory를 Bean으로 등록하는 방식이다.

이를 위한 구조를 만들기 위해 spring profile에 대해서도 공부할 수 있었다.

그리고 redis를 이용한 세션 관리는 현업에서도 많이 쓰는 걸로 알고 있는데, redis 관련 지식이나 경험이 전무했기 때문에 직접 구현해보고 싶었지만 아쉽게도 다른 팀원이 맡게 되었다.

L7 헬스체크

스케일 아웃을 위해 필수적인 요소가 바로 로드밸런서이고, 로드밸런서가 각 서버에 요청을 분배하기 위해서는 각 서버의 상태, 즉 요청을 받을 수 있는 상태인지 체크해야한다. 그걸 바로 헬스체크라고 하는데, 이를 application level에서 하는 걸 L7 헬스체크라고 한다. 모니터링 url을 지정해놓고(ex. /monitor/l7check) API 요청을 보내는 방식이다. 서버가 죽으면 당연히 로드밸런서에서 요청을 할당을 안하겠지만 서버가 살아있는데도 서버가 요청을 받지 않도록 하고 싶을 때(예를 들어 무중단 배포) 활용할 수 있다.

L7 health check를 위해 spring actuator의 HealthIndicator를 활용했다. API GET 요청을 통해 서버의 up/down 여부를 status code로 반환했다. Up일때는 OK(200), down일 때는 Service Unavailable(503). 그리고 후에 무중단 배포 시의 활용을 고려하여 PUT 요청을 통해 up/down 상태를 변경할 수 있도록 API를 구현했다.

여기서 한계점은 ACL 체크를 하지 않는다는 점이다. 어느 호스트에서나 down API 요청을 보내면 서버의 상태를 down으로 바꿀 수 있기 때문에 위험한 구조이다. (각 서버의 RIP를 알고 있다면 누구나 서버의 상태를 down으로 바꿔서 서비스 장애를 유발시킬 수 있다). ACL을 추가하는 작업은 추후에 필요해보인다.

Entity 변경

Sprint 6에서 DB 모델링 검수 결과에 따라 DB 모델에 변화가 생기면서 JPA Entity에도 코드 변경이 필요했다. 이 부분은 충돌 방지를 위해 한 사람이 맡는게 좋을 것 같아 내가 진행했다. 다행히 테이블이나 관계의 수정은 없고 테이블명이나 칼럼명의 수정 정도였지만, 그래도 코드를 일괄적으로 바꾸는 건 꽤나 섬세함이 필요한 작업이었다. 이때 일괄수정하면서 유용하게 활용한 기능이 있다.

  • Intellij refactor 기능
    • 함수명, 변수명을 바꾸면 이를 참조하는 모든 곳에서의 이름도 바꿔준다.
  • Intellij replacement시 preserve case 옵션
    • replace 시 대소문자를 보존해준다
    • 예를 들어 해당 옵션을 켜고 airportFromTo -> flightPath을 하면
      • airportFromTo -> flightPath
      • AirportFromTo -> FlightPath
    • 로 알아서 바꿔준다.

그 외

그 외에 다른 팀원들이 스케일 아웃을 위해 한 작업들은 아래와 같다.

  • NHN Cloud의 Object Storage 연동
  • NHN Cloud Deploy로 배포 설정
    • 사실 처음에는 Deploy 기능이 왜 필요한지 몰랐다. 그런데 서버가 100대가 된다고 생각하면 그걸 하나씩 들어가서 배포하는 건 정말 귀찮을 뿐만 아니라 실수로 인한 장애의 가능성도 농후하다는 것을 깨달았다.

Leave a comment