하게 된 이유

https://chainbell.tistory.com/entry/MSA-%EC%B5%9C%EC%86%8C-%EC%84%A4%EA%B3%84-%EC%9A%B4%EC%98%81-%EC%A0%81%EC%9A%A9?category=863960 

 

MSA 최소 설계 (운영 적용)

하게 된 이유 책 내용 그대로 따라하기엔 업무에서 바로 적용을 할 수가 없었다. 이벤트 처리기와 저장소, (여기에 정리하지 않은) DTO 기반 API 구현과 중복 이벤트에 대한 처리 등등 필요는 하지

chainbell.tistory.com

이전 MSA 구성을 최소한으로 적용하여 실제 운영 적용했던 경험을 바탕으로 더 개선할 수 없을까 고민하고 있었다.

이 구성은 간단하고 이해하기 쉬워 만족하긴 했지만 어디까지나 각 서버간 상호 작용에 중점을 둔 구성이었다.

프로젝트 내부적 정리는 되지 않은 상태였기 때문에 이번엔 내부적은 개선을 고려해보기로 했다. 

 

적용한 개념 

내부 구조의 정리를 위해 전에 알아봤던 개념들을 오랜만에 꺼내왔다. 

도메인 기반 설계(Domain Driven Design)의 구성 단위이자, MSA 개발의 최소 단위인 Aggregate이다. 

https://chainbell.tistory.com/entry/1-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4-%ED%8C%A8%ED%84%B4MSA%EC%B5%9C%EC%86%8C-%EB%8B%A8%EC%9C%84-%EC%95%A0%EA%B7%B8%EB%A6%AC%EA%B1%B0%ED%8A%B8aggregate?category=863960 

 

1. 마이크로서비스 패턴(MSA)최소 단위 : 애그리거트(aggregate)

애그리거트(aggregate)란? 도메인 객체(Domain object)들을 하나로 취급하는 객체망이다. 애그리거트의 단 하나의 Root Entity와 N개의 기타 객체들(Entity, DTO, DAO, VO, 서비스 로직 객체 등등)로 되어 있다...

chainbell.tistory.com

 

그림으로 보는 구성

기본적인 틀은 MSA Batch Server이다. 하지만 API 서버에도 동일하게 적용할 수 있다.

위 그림에서는 이해를 돕기 위해 Aggregate에서 전달, 반환용 객체는 Aggregate의 범위에서 외부로 걸쳐있는 것처럼 묘사하였다. 

순번대로 설명을 하면 

1) 스케쥴 예약이 된 Batch 서버에서 요청을 담은 Queue로부터 요청을 Pop하여 추출한다.

2) Pop한 요청에 해당하는 Service Method를 호출한다. 

3) Service Method는 Domain Aggregate에 Primary Key를 전달한다.

4) 전달된 Primary Key를 가지고 Aggregate 내에 구현되어있는 실질적인 Service Logic을 실행한다. 

5) 실행된 Service Logic의 결과를 정해진 반환 객체 형태로 변환한다.

6) 반환 객체 형태로 변환된 결과값을 반환한다. 

7) 3)의 과정을 다른 Aggregate에서 진행한다.

8) 4)의 과정을 다른 Aggregate에서 진행한다.

9) 5)의 과정을 다른 Aggregate에서 진행한다.

10) 6)의 과정을 다른 Aggregate에서 진행한다.

11) 3~6, 7~10의 과정으로 반환받은 결과를 정리하여 실행 성공 여부를 반환한다. (API 서버였다면 정리된 결과를 클라이언트에게 반환하면 된다.)

 

이전의 최소 설계와 다른 점은?

이번에 중점을 둔 구간은 3~6번의 과정을 다루는 Aggregate의 구간이다. 최소 적용했던 이전 설계는 Service Class에서 모든 Service Logic을 담고 있기 때문에, Business Logic 별 Service Logic의 정리가 되어 있지 않았다. 그로 인한 Service Logic의 재활용도가 낮았고, Service Class 간의 호출에도 유동적으로 대처할 수 없었다. 

하지만 위 구조로 재설계하면 실질적인 Service Logic은 Domain Aggregate내에 위치하게 되며, Service Class는 Domain으로부터 빌려오기만 하면 된다. 

 

단점은 없을까?

1) 개발 단계에서 Domain, Service Class간 설계에 신경을 많이 써야 한다.

2) 설계의 실수가 발생한 채로 운영까지 배포하게 되면 되돌리기 위한 추가 공수가 발생하게 된다.

3) Domain과 Service Class 사이의 명확한 경계를 제안하는 명세가 필요하게된다. 

 

서버 프로젝트의 구성은?

Aggregate를 적용하기 전에는 Service Package들이 각각 Service Logic과 그에 해당하는 sql들을 보유했다.

하지만 그 역할을 각 Domain이 맡게 되면서 Service Class는 필요한 기능을 명세하는 수준의 코드가 되고, Domain Aggregate의 영역에 Service Logic이 포함되기 때문에 명세와 로직의 구분을 눈으로 볼 수 있도록 위와 같이 분리 시켰다. 

 

위 구상의 적용은 가능할까?

개인 프로젝트로는 위 구성으로 만들어봤자, 이 구조를 위한 프로젝트를 만들어질 뿐이다. 그래서 현재 실무에서 적용할 수 있을지 생각해봤는데, 마침 딱 좋은 프로젝트가 있다. 

실제 적용을 할 순 없겠지만, 동료 개발자와 의논을 해봐야겠다. 

하게 된 이유

책 내용 그대로 따라하기엔 업무에서 바로 적용을 할 수가 없었다. 

이벤트 처리기와 저장소, (여기에 정리하지 않은) DTO 기반 API 구현과 중복 이벤트에 대한 처리 등등 

필요는 하지만 당장 그정도의 환경을 동시에 적용을 할 수 없고, 주변 사람들을 설득할 수도 없었다. 

당장 쓰고 있는 환경을 기반으로 바로 적용할 수 있도록 최소화 시킬 필요가 있었다. 

 

구성도

서버 환경을 네 개의 구간으로 나눴다. 

  • API Gateway
    • 클라이언트 App에서 Restful API로 붙는 외부 Endpoint이다. 실질적인 서비스 로직을 가지지 않고, Micro Server Container로 필요한 요청들을 호출한다. 
  • Micro Service Server List(초록색 구간)
    • 복수의 Micro Service Server Container들로 구성되어있다.
    • API Gateway를 통해 전달된 클라이언트의 요청을 처리, 반환한다. 
    • 각 Container들은 아래 구성들을 별도로 보유한다. 
      • API Server : 사용자의 요청을 바로 처리해줄 실질적인 서비스 로직이 위치하는 서버. API Gateway로부터 요청된 필요 데이터를 처리/반환한다. 
      • On-memory DB : 성능 향상을 위한 휘발성 데이터 저장소. 캐싱이 필요하지 않다면 없어도 무방하다. 
      • RDB : 해당 Container 내부에서만 사용하는 비휘발성 데이터 저장소. main DB로 활용할 수 있다면 RDB가 아니어도 상관없다.  
  •  Micro Service Scheduled Batch List(파란색 구간)
    • 복수의 Micro Service Batch Container들로 구성되어있다. 
    • Micro Service Server의 요청에 대한 비동기 처리를 진행한다. 
    • 각 Container들은 아래 구성들을 별도로 보유한다. 
      • Service Batch : 사용자의 요청 혹은 서비스를 위한 데이터 처리를 바로 진행하지 않아도 되는 서비스 로직이 위치하는 서버. 
      • Event Queue : Container간 처리 요청 Event를 Queue에 등록한다. 비동기 처리를 위한 구성이기 때문에 Restful API를 통한 직접 호출이 발생할 필요가 없다. 
      • Event DB : Event Queue를 통해 전달된 요청의 진행 상태(Event 시작~종료), 결과를 저장한다. 
  • 구독형 서버
    • 클라이언트와 서버간 상시 연결되어 상호 전달이 가능한 서버
      • 위 구성도에서는 서버로부터 데이터를 수신받기 위한 역할을 가지고 있다. 
    • 아래 구성들을 별도로 보유한다. 
      • Queue : Micro Service Batch에서 클라이언트에게 전달하고 싶은 데이터를 등록할 공간이다. 
      • Event Transfer Batch : Queue에 들어온 요청을 추출하여 클라이언트에게 전달할 Subscribe Server에 전달한다.
      • Subscribe Server : 클라이언트와 서버가 직접 연결하게 되는 구간이다. Websocket이나 MQTT 등을 통한 연결이 이루어진다. 

적용 경험

위 구성은 책 내용을 기반으로 일부를 간소화한 구조이지만, 실제 업무에서 적용시키기는 훨씬 나았다. 

안써본 기술과 프레임워크 적용을 위한 윗 사람들 설득이 필요없고, 이미 사용하고 있는 환경으로 손쉽게 적용이 가능했다. API Gateway는 개발 공수가 커서 못만들었지만, Micro Service Server/Batch의 운영 개발/적용까지 완료했다. 

* 이런 작은 구성 적용하려고 설득했던 걸 생각하면 진짜 일하기 힘들다

운영까지 하면서 얻은 장단점을 나열해보자면

  • 장점 
    • 대기능 별 서버와 DB를 분리 개발하여 타 기능에 영향을 주지 않고 개발할 수 있다. 
    • 각 서버의 역할에 따른 성능 개선을 진행할 수 있다. 
    • 서비스 로직이 각 서버에 귀속되어, 소스코드가 엉키는 일이 발생하지 않는다. 
      • 회원 정보과 회원에게 귀속된 상품에 정보를 한 서버에서 관리하다보면 중복된 코드와 쿼리가 발생하고, 서비스 로직 간 호출이 거미줄처럼 엉킨 상황에서 쉽게 로직을 바꿀 수 없는 상황이 생가보다 쉽게 발생한다. 
      • 물론 DDD 기반 개발이 잘 이루어졌다면 예방할 수 있겠지만, 서버 구성 상 물리적으로 분리된 것이 더 안전하다. 
  • 단점 
    • 관리를 잘못하면 Server Container가 마구잡이로 늘어날 수 있다. (비용은 덤)
    • 하나의 API 개발이 Gateway + N개의 서버 개발로 작업이 늘어날 수 있다. 
    • Queue에 등록되는 Event의 실패 처리에 대한 안전 규칙이 필요하다. 

등이 있다. 

하지만 단점의 경우 살짝 귀찮아지는 정도지만, 장점에선 안전한 개발과 더 쉬운 성능 개선이 보장되어 적용을 하지 않을 이유가 없었다.

또한 Container 별 요청 성공/실패 확률을 체크해봤을 때, 한 달 기준 성공률 100%를 찍은 서버도 있었다. 의심이 되서 로직과 구성에 대한 검증을 진행해봤지만, 단순화된 로직과 구조를 통한 안정화된 서버로 인한 성공률인 것을 확인했다.

아마도 반박이 나올 사항 

위 구조는 흔히 Admin, CMS와 같은 관리자를 위한 구성이 아니다. 최종 서비스 사용자를 위한 클라이언트와 서버 간의 통신을 위한 구조이기 때문에, "저러면 관리자 페이지에서 join을 걸어서 결과를 조회할 수 없어!" 라는 주장에 대하여 긍정할 수 밖에 없다. 

하지만 관리자 페이지에서 join걸자고 유저 사용성을 위한 안정된 구조를 쓰지 않는 것은 말이 안된다. 그리고 join을 쓰지 않고도 소프트웨어 기획과 설계 단계에서 충분히 해결 할 수 있다고 생각한다. 

하지만

새로운 운영 경험과 관리를 통한 결과를 내놓았지만, 생각보다 고비가 빨리 왔다. 일부 구성원들(상급자 포함)이 귀찮다고 적용을 반대하는 의견을 조금씩 내놓고 있다. 

생각의 차이인지, 그냥 귀찮음의 차이인지 모르겠다. 나라고 편해서 한게 아닌데...

이벤트 소싱이란?

이벤트를 위주로 비지니스 로직을 구현하고, 애그리거트를 일련의 이벤트로 DB에 저장하는 기법이다. 

 

뭐라구요?

위 설명으로 이해할 수 있을리가 없다. 

 

일반적인 RDB의 활용과 구현법을 생각해보자.

회원 정보 혹은 상품 정보 등을 저장할 때는 주로

  • 1개의 table은 서버 입장에서 객체의 집합체(저장소)
  • 1개의 row는 1개의 객체

의 구조로 사용하거나 혹은

  • 사용자가 특정 API를 호출했다는 이력을 저장하기 위한 이력형 table

로 사용한다. 

여기서 우리가 사용할 것은 두번째 방법인 이력형 테이블(historical table)이다. 

 

이벤트의 이력을 기준으로 애그리거트를 활용한다!

애그리거트는 어디까지나 객체의 정책이기 때문에 그에 맞춰서 실행하면 되지만, 약간 다른 방식을 사용한다. 

순서는 

  1. 호출자는 원하는 애그리거트의 상태 변화를 이벤트 DB 테이블에 저장한다.
  2. 이벤트 DB 테이블의 변경점을 발견한 앱이 이벤트를 읽는다.
  3. 읽어들인 이벤트에 속하는 애그리거트의 인스턴스를 생성한 후 이력 정보에 있는 기능을 수행한다

라는 3개의 과정을 통해 실행된다. 

 

ex) 주문 정보 애그리거트가 있다고 하자. 

발생한 요청은 주문 정보 애그리거트가 가지고 있는 주문 생성 기능이다.

호출자는 애그리거트의 상태가 [없음]에서 [주문 생성]으로 변경되는 작업을 요청하기 때문에, 해당 이벤트를 DB에 저장한다. 이후 해당 이벤트 DB를 모니터링하고 있던 소프트웨어는 이벤트 로그에 기록된 애그리거트의 작업을 실행시킨다.

 

그렇구나... 하고 지나갈 법 하지만 매우 무책임한 과정이다. 저장은 하면 되지만 모니터링은 어떻게 할 것인가? DB에 저장하면 신규 이벤트인지 알게 뭔가?

 

이벤트 감지는?

참고 서적에서 제시한 방법은 아래 두 가지이다. 

  • 폴링(Pollin)
    • 주기적으로 이벤트 발행 테이블을 조회한다. 
      • 미쳤냐?
  • 트랜잭션 로그 테일링(참고 서적에선 이 방법을 추천)
    • 해당 테이블의 트랜잭션 로그에서 insert를 감지하여 메시지 브로커로 이벤트를 전달한다. 
    • 메시지 브로커를 구독하고 있던 앱에서는 전달 받은 이벤트를 실행한다. 

그리고 둘 다 내 취향은 아니다. 

폴링은 비용 소모가 너무 크고, 트랜잭션 로그 테일링은 설명만 들어도 어렵다. 교수님은 분명 소프트웨어에서 Simple is beautiful만큼 중요한 덕목은 없다고 하셨다. 

내가 원하는 방식은 트랜잭션 로그 테일링에서 나온 메시지 브로커를 데이터의 저장과 전달의 역할이 가능한 MQ로 구현하여 메시지 브로커만을 사용하는 것이다. 이벤트 이력 저장은 이벤트가 전달된 다음에 하면 되잖아!

 

적용 범위는 서버 프로젝트 내부

위 과정은 사실 MSA가 적용된 서버 간 통신 방식이 서버 프로젝트 내에 적용된 방식이다. 때문에 위 내용만 봐서는 단일 프로젝트 내에서 객체 간 호출을 하려고 DB에 저장하고 조회해야 하는 심각한 뻘짓이다. 하지만 뻘짓이라고 위 과정을 뺄 순 없다. 

 

왜 이렇게 해야 하는가?

애그리거트는 도메인의 철저한 관리를 주목표로 한 정책과 주고를 가지고 있다. (비록 뇌피셜이지만, Root Entity와 PK만을 통한 참조 정책은 이것을 노린 것이 분명하다.) 애그리거트 간 호출은 하나의 프로젝트 내에서 작동할지 몰라도, 역할은 다른 서버에서 작동하는 것처럼 구현하여 철저한 이력 관리를 할 수 있어야 한다는 것이다. 

 

* 위 내용을 책으로 보기 전에, 실무에서 유사한 구조를 직접 설계/구현한 적이 있었다. 그 당시 목적은 데이터의 소실을 우려하여 모든 이벤트를 이력으로 남기는 것이었다. 이후 이력을 베이스로 정확한 데이터 오차 검증을 로직으로 구현하였다. 솔직히 처음에만 반짝 뜬 후 빛의 속도로 꼬라박아 사라진 서비스지만, 분명 꽤 많은 이벤트가 발생했음에도 단 하나의 오차도 발생하지 않은 성과를 이루었다. 이런 경험 때문에 위 내용을 책에서 읽으면서 뿌듯하기도 했고, 이 방식을 쓰는 이유를 이해하는데 큰 시간이 걸리지도 않았다. 

 

설명하지 않은 내용들 

책에는 이벤트의 전달 파라미터 변경, api 포맷의 변경 등의 조치를 위한 이벤트 버전 관리, 이벤트 이력 용량이 커짐에 따른 방안, 이벤트 중복 발생에 대한 처리 등 여러 예외 처리 과정을 추가적으로 언급하고 있다. 

하지만 이런 부분들은 당장 내가 위에서 취향에 안맞아 다른 방식을 쓰는 것과 같이 책의 저자가 구현한 방식에 맞춘 방식이기 때문에, 여기서 언급하지는 않았다. 

애그리거트(aggregate)란?

도메인 객체(Domain object)들을 하나로 취급하는 객체망이다. 

애그리거트의 단 하나의 Root EntityN개의 기타 객체들(Entity, DTO, DAO, VO, 서비스 로직 객체 등등)로 되어 있다. 

예를 들어 주문 정보 애그리거트는 Root Entity가 주문 정보의 PK(primary key)를 저장하고 기타 객체들이 이외의 처리들을 담당한다.  

 

* 마이크로서비스 패턴을 구성하는 최소 단위라고 생각하면 편하다. 

 

*비교 설명*

Restful API 호출 시 서비스 로직이 실행되면

  • 기존
    • 순차적으로 도메인 객체들이 실행됨
  • MSA
    • 서비스 로직이 애그리거트의 루트 엔티티를 호출하고, 애그리거트에 등록된 하위 엔티티를 실행함.

애그리거트간 규칙

애그리거트 단위 개발에선 Root Entity 없이 도메인 객체들로만 서비스 로직을 구현하던 이전과 다르게, 정확한 중심점(Root Entity)이 생긴다. 그렇기에 반드시 지켜져야하는 하위 규칙들이 생긴다. 

1. 애그리거트 간 참조는 각 애그리거트 Root Entity Root Entity가 보유한 PK를 통해서만 진행되어야 한다. 

    - 이것을 통해 애그리거트 내 포함된 서비스 로직에 필요한 불변값(const)를 강제시킨다. 

2. 애그리거트는 다른 애그리거트가 자신의 하위 서비스 로직을 호출하는 것을 허가하면 안된다. 이것은 도메인 객체들 사이의 로직이 뒤섞이는 것을 방지하기 위한 예방책으로 반드시 지켜져야 한다. 

3. 하나의 transaction에서는 하나의 애그리거트만을 생성/수정 해야 한다.

 

애그리거트 작성 범위는?

회원 관리 서버는 회원 관리 기능들만 담당하듯이, 마이크로 서비스 패턴은 한 단락의 기능을 하나의 서버 프로젝트로 구성한다. 하나의 서버 프로젝트 내의 도메인 객체와 서비스 로직들의 위치를 애그리거트로 대체/구성시킨다. 

어디까지나 서버 내 객체의 단위의 정책에 가깝기 때문에, monolithic 구조(통합 기능 서버)에서도 활용할 수 있다. MSA를 당장 적용하기 힘든 상태라면, 애그리거트만 적용하더라도 소스코드 정리 및 이후 서버 분리 과정에서의 이점을 챙길 수 있다. 

 

언급되지 않은 내용 

원래는 DDD 애그리거트 패턴(Domain-Driven Design Aggregate Pattern) 개발의 단위인 애그리거트이다. 

*경험 기반*

Django python 서버에는 api 개발 중 클라이언트에게 반환할 필요 없이 비동기로 처리하기 위한 기능이 구현되어있다. 

해당 기능이 뭔지, 어떻게 사용하는지는 여기선 중요하지 않으니 관련 글만 참조한다. 

https://realpython.com/asynchronous-tasks-with-django-and-celery/

 

Asynchronous Tasks With Django and Celery – Real Python

This tutorial shows how to integrate Celery and Django and create Periodic Tasks

realpython.com

 

백엔드 개발 중 해야할 작업을 나중으로 미루는 것은 성능 및 서비스 안전성에 긍정적인 효과를 줄 때가 많다. 마이크로서비스 패턴(MSA)의 구조 역시 이러한 철학을 바탕으로 되어있고, 메시징 큐(MQ)를 통한 비동기 구조로 채택하는 경우가 많다.

 

하지만 아주 작은 작업을 위해 별도의 서버와 프로젝트를 구성하지 못하는 경우도 있다. 그래서 기능 중 일부를 위와 같은 기능으로 비동기화하여 그 기능을 동일 서버에서 처리하도록 하는 경우가 있다. 그리고 이런 기능은 아마 당장은, 혹은 그냥저냥 잘 굴러갈 수 있다. 

 

잘 굴러가는데 왜 뻘글을 쓰고있냐면, 이게 장기적으로 매우 나쁜 선택지가 됐기 때문이다. 

 

발단은 서버의 성능 확인을 위한 부하 테스트 작업이었다. 

비동기로 등록된 작업의 스레드 작업 우선순위는 당장 서버로 진입하는 request보다 후순위로 밀리는 것이 당연하다. 이는 실제 테스트를 통해 확인하였다. 하지만 결국 예약된 비동기 작업 또한 처리가 되어야 하고, 서버 OS의 스케쥴링 규칙과 작업의 지연 시간 증가로 작업 실행 우선 순위는 점점 상승하게 된다. 이 순위가 당장 처리되어야 할 진입 request의 우선순위보다 밀리게 될 때 문제가 발생했다. 

 

0.00x~0.x 초 사이로 빠르게 처리되어야 할 reqeust의 작업이 대량의 비동기 작업들에 밀려 x초 단위로 지연이 발생한 것이다. 

 

쉬벌

 

서버의 부하가 많을 때 발생하는 조건부 지연이지만, 백엔드 개발이란게 원래 부하 받을 때 안전하게 돌아가라고 하는 개발인데 이게 고려가 안된 것이다. 

그리고 사실 이건 해결책을 내기도 힘든게 별도 비동기 처리용 서버를 당장 새로 만들 수도 없고, 그렇다고 이제와서 동기(synchronous) 처리를 하기에도 애매하다. 

 

그렇다. 이건 그냥 훗날 똥만 될 뿐이다. 

https://stackoverflow.com/questions/61529777/redis-pipeline-vs-mget

 

Redis pipeline vs mget

I'm looking into using either MGET or pipeline, but I can't seem to find the information on MGET that I'm looking for My use case is to replace 50 GET calls with either MGET or pipeline What I fo...

stackoverflow.com

https://stackoverflow.com/questions/42706480/redis-mget-limitations/42709273

 

Redis MGET Limitations

We're planning to use MGET for one of our Systems. During benchmarking, we were able to retrieve values for 1 Million Keys in one MGET call in lettuce and were quite surprised. What I've been tryi...

stackoverflow.com


Redis는 single thread 동작을 하기 때문에, 어떠한 single command라고 해도 명령어 실행이 끝나기 전까지는 다른 redis client에 대한 block이 발생한다.
이런 현상은 list 단위의 key 조회 명령어인 mget에도 동일하게 적용된다.
반면에 pipeline 방식은 일괄 처리를 위한 명령어일 뿐, mget과 같은 단일 동작을 위한 기능을 가진 명령어가 아니기 때문에 block이 일어나지 않는다.

어느 정도의 일괄 조회는 상관 없으나 만약 대량의 key 조회가 필요하다면,
pipeline을 사용한 일괄 조회가 더 낫다.

* 그런데 공식 문서에서 언급된 부분은 아직 찾지 못했다. 하지만 꽤 많은 글에서 위와 같은 한계점이 있다고 언급하고 있다.

'공부 - 개념, 철학 > Redis' 카테고리의 다른 글

[Redis] Python으로 Redis 사용하기  (0) 2020.04.04
[Redis] Redis 란  (0) 2020.02.24

마이크로 서비스 패턴을 왜?

어렴풋하게는 알지만, 정확하게 혹은 다른 회사는 어떻게 구현했는가를 알고싶었다. 

이 책은 모놀리틱 서비스였던 운영 환경을 마이크로 서비스 패턴으로 이관하고 재설계하는 과정을 서술했기에,

마침 딱 읽기 좋겠다고 생각했다. 

공부하려고 산 책

문제 발생

이제 7장 읽고 있는데 생각보다 내용이 어려움

그리고 생각보다 쓸모없는 내용 많음

다 좋은 내용인건 맞는데 자체 개발한 라이브러리와 프레임워크를 홍보한다는 느낌을 받았음

물론 배울 내용이 많으니까 기본 철학과 개념만 알면 나한테 맞게 어레인지가 가능할 수준임

 

결론

이 책에서 공부하는 걸 바탕으로 정리는 할 건데 이 책의 내용은 거의 없지않을까 싶음

그리고 이 책 내용을 그대로 긁어서 올린 블로그 많던데 진심인지 모르겠음. 

이거 그대로 긁어서 이해하고 맞춰 갈 수 있는 그런 내용이 아닌데 왜 그랬지...

 

1. 사용하게 된 이유 

NGINX의 마이크로 캐싱을 통한 API 서버 성능 개선 작업을 진행하고 있다. 

이미 사용 중인 기능의 DB 스키마와 로직 개선에 대한 기능 테스트야 하던 대로 진행 했지만, 기존 방식으로는 확인하기 힘든 테스트가 생겨버렸다. 

NGINX 마이크로 캐싱을 통한 API 대량 요청에 대한 기대 성능을 측정해봐야 하는데, 기존의 부하 테스트는 테스트 할 때마다 외부에서 호출해주기 위한 배치 프로세스를 별도로 개발/배포하여 사용해야 했다.

개발하고 정리하는 것만 해도 시간 모자른데, 부하 테스트용 코드를 또 개발해야 하는 것은 너무 비효율적이었다. 

그러던 중 팀장님의 권유로 nGrinder에 대해 알게 되어 적용해보기로 했다.

 

2. nGrinder

부하 테스트용 오픈 소스 grinder를 Naver에서 가다듬은 버전이다. 

Web GUI를 제공하며 가상 유저 수와 API 호출 횟수, 그리고 테스트 결과에 대한 시각화를 제공한다. 

그런데 생각보다 제대로 정리된 글이 없다. 구버전 대상 설명이거나, 끝까지 설명하지 않은 경우가 많아서 정리한다. 

 

3. 구성

GUI를 제공하는 nGrinder-Controller

테스트 대상 API를 호출하여 부하를 발생 시키는 nGrinder-Agent

테스트 대상 API가 실행되는 Target 서버의 실시간 리소스(CPU, Memory, Network) 정보를 수집하는 nGrinder-Monitor

세 개로 구성되어있다. 

 

사진 1. nGrinder 시스템 아키텍쳐

4. nGrinder 실행 파일 

github.com/naver/ngrinder/tags

 

naver/ngrinder

enterprise level performance testing solution. Contribute to naver/ngrinder development by creating an account on GitHub.

github.com

nGrinder 또한 github에 공개되어 있고, 단독 실행 가능한 war 파일로 버전 별로 공개되어있다. 

 

5. nGrinder 설정 

5-1. nGrinder-Controller

제일 먼저 실행할 것은 nGrinder-Controller이다.

* Controller에서 제공하는 Web 페이지에서 Controller와 버전이 맞는 Agent와 Monitor의 실행 파일을 다운받을 수 있고, Agent의 경우 Controller가 실행되어있지 않다면 실행 되지 않는다.

 

4번의 링크에서 원하는 버전의 ngrinder-controller-(버전).war 파일을 다운받아서 실행할 서버로 업로드를 해준다. 

(아니면 wget으로 해당 서버에서 직접 다운로드를 받아줘도 된다. - 다운로드 링크 우클릭해서 주소 복사 -)

 

* 원하는 서버의 원하는 경로에 다운로드를 끝냈다면 실행만 시켜주면 된다. 별도의 톰캣 설정 없이 단독으로 실행 가능하도록 빌드 되어있다. 

사진 2. 4번의 링크를 들어가면 나오는 github 웹페이지
사진 3. 2번 사진에서 원하는 버전을 골라 들어가면 나오는 웹 페이지

사진 3의 하단에 ngrinder-controller.war 파일을 다운받으면 된다. 

 

사진 4. 해당 war파일을 다운받아 원하는 경로에 위치시켰다.

실행 파일을 받았으니 실행을 시켜보자. 

 

* 실행 스크립트는 아래와 같다. 

nohup java -XX:MaxPermSize=200m -jar ngrinder-controller-(버전).war --port (원하는 포트) &

예시) nohup java -XX:MaxPermSize=200m -jar ngrinder-controller-3.5.3.war --port 8080 &

 

사진 5. controller war 파일 실행 및 프로세스 확인
사진 6. Controller 화면이 나오는 것을 확인!

기본 계정은 

아이디 : admin

비밀번호 : admin

이다

그리고 이하 설명은 한국어 베이스로 진행할 것이기 때문에 언어는 한국어로 설정할 것이다. 

사진 7. 정상적으로 진행 됐다면, 이렇게 나온다. 아무코토 없다.

 

5-2. nGrinder-Agent

Controller가 준비됐으니, 부하 발생기인 Agent를 준비해보자.

Agent의 실행 파일은 Controller를 실행시키면 얻을 수 있다. 

사진 8. 우측 상단 계정을 (여기선 admin) 누르면, 여러 기능을 볼 수 있다.

사진 8을 따라 우측 상단의 계정을 클릭하면, 플로팅 메뉴가 나오는 것을 알 수 있다. 

해당 메뉴에서 "에이전트 다운로드"를 클릭하면, ngrinder-agent-(Controller의 IP).tar 압축 파일이 다운로드 되는 것을 알 수 있다. 

* 해당 압축 파일은 실행 가능한 자바 프로젝트의 압축 파일이기 때문에, 사용할 위치에서 압축만 풀어주면 된다. 

사진 9. Agent 파일을 원하는 위치에 놓고 압축을 풀어준다.
사진 10. 압축을 푼 후 보면 ngrinder-agent 폴더가 형성된 것을 확인할 수 있다. 
사진 11. 10번 사진에서 생성된 폴더에 들어가보면, 실행 파일이 들어가있는 lib 폴더와 리눅스/윈도우 용 스크립트 파일, 그리고 초기 설정용 __agent.conf파일이 있다.

사진 10과 11을 보면 압축을 풀었을 때 nGrinder-agent 폴더가 생성된 것과 폴더 하위 경로에 실행 스크립트/실행 파일/__agent.conf 파일이 있는 것을 확인할 수 있다. 

여기서 먼저 해야할 것은 __agent.conf 파일의 수정이다. 

사진 12. __agent.conf 파일 내부 내용

하단에 주석이 되어있는 것은 사용하지 않는다. (추가적인 기능이 있는 것이겠지만, 기본적으로 사용하지 않았다. 자료도 적고 테스트 진행에 있어서 필요하지 않았다.)

필요한 것은 상단의 agent.controller_host 이다. 

 

현재 작성하고 있는 예시는 시험용 예시이기 때문에 Controller와 Agent가 하나의 기기(노트북)에서 함께 실행하지만, 

본래의 목적 달성을 위해서는 Controller와 Agent는 다른 서버 머신에 있어야 한다. 이 때 실행되는 Agent가 Controller를 바라보게 되는데 그것을 위해 Controller_host, 즉 Controller의 IP와 Controller_port를 필요로 하게 된다. 

* Controller가 설정 수정 없이 디폴트로 수정되었다면, Web 접근용 포트 이외에 Agent 접근용 16001 포트와 부하 테스트 사용을 위한 12000~12009 포트가 함께 개방된다. 

 

지금 예시에서는 localhost로 실행되기 때문에 그냥 실행하겠다. 

실행 방법은 윈도우의 경우 run_agent.bat 혹은 run_agent_bg.bat을 실행하고, 리눅스의 경우 run_agent.sh 혹은 run_agent_bg.sh를 실행하면 된다. (_bg는 백그라운드로 실행하겠다는 의미이다. 실제 동작 확인을 하고 싶다면 run_agent를, 아니면 run_agent_bg를 실행시키면 된다.)

사진 13. nGrinder-agent의 실행 및 프로세스 확인

예시에서는 _bg파일로 스크립트를 실행하였고, Agent 자바 프로세스가 정상적으로 실행된 것을 확인할 수 있다. 

그럼 Controller에서 정상적으로 인식했는지 확인해보자

사진 14. 우측 상단 계정을 클릭 후 에이전트 관리

 

우측 상단 계정 클릭 후, 에이전트 관리로 들어가보자 

사진 15. Agent가 등록되어 있는 것을 확인

Agent의 정보가 등록된 것을 확인할 수 있다. 

주의) 내 경우 실무 적용 시 aws subnet 환경에서 해당 작업을 진행했는데, Controller의 외부 접근용 IP를 __agent.conf에 설정 후 실행 시 Connection refused 에러가 발생했었다. 정확한 이유는 모르겠지만 내부 IP로 재설정 후 실행했더니 해결되었다. 

 

여기까지만 해도 부하 테스트를 충분히 진행할 수 있기 때문에 보통 여기서 설명이 끊긴 경우가 대부분이었다. 

하지만 어림도 없지. 팀장님께선 리소스의 모니터링도 볼 수 있어야 한다고 하셨다. 

팀장님은 모니터가 좋다고 하셨어

 

5-3. nGrinder-Monitor

마지막 Monitor를 보자. 

Monitor는 nGrinder-architecture 구성 중에서 target server, 부하를 받을 서버에 설치되어야 한다. 

Agent에 의해 부하를 받는 동안에 부하 대상의 CPU, Memory, Network receive/send Queue를 시각화하여 함께 볼 수 있기 때문에 적용하여 손해보는 것은 단 하나도 없다. 

하지만 개인적으로 처음 설정할 때 가장 고통 받았던 것이 Monitor다. 

 

설정 이전에 이해를 돕기 위한 설명!

Agent는 Agent가 Controller를 보는 것이다. 그렇기에 Agent에 Controller를 등록했다. 

그렇다면 Monitor는 어떨까? 사진 1의 nGrinder architecture를 보면 알 수 있듯이, Controller가 Monitor를 본다고 되어있다. 

 

그럼 이제 Monitor 설치를 해보자 

사진 15. 사진 14와 똑같은 사진이다.

* 사진은 위에 올려보기 싫으니까 똑같은거 다시 올렸다. 

플로팅 메뉴에서 모니터 다운로드를 확인할 수 있다. 다운받자.

사진 16. Agent와 동일하게, 압축 파일을 받았다

이후 Agent와 동일하게 tar -xvf 명령어로 압축을 풀어서 생성되는 ngrinder-monitor 폴더로 들어가보자.

사진 17. Agent를 잘못 들어온 것이 아니다. Monitor 맞다

Agent와 거의 비슷해보이고 __agent.conf 파일이 있어 잘못들어왔나 싶겠지만, Monitor경로로 제대로 들어온 것이 맞다!

사진 18. Monitor의 __agent.conf 내용

수정하지 않은 __agent.conf의 내용이다. 

실행 모드는 monitor로 되어있는데, 이것은 Monitor가 원래 Agent와 동일한 프로젝트였던 것으로 추측한다. 

* 증거 없이 이렇게 추측하는 이유는, 구버전의 설정을 설명한 다른 글들에서 볼 때 Monitor의 __agent.conf 포맷이 사진 18과는 다르게 Agent의 그것과 포맷이 동일하기 때문이다. 뻘소리니 그냥 Monitor설정이구나 하고 넘어가자 

그 아래 주석 처리 되어있는 monitor.binding_ip는 리소스 모니터링을 할 대상 서버 머신의 IP이다. 주석처리가 되어있다면 디폴트로 Monitor가 실행되고 있는 서버 머신의 리소스를 보게된다. 

monitor.binding_port는 monitor가 실행될 때 개방하는, nGrinder-Controller가 nGrinder-Monitor로 접근하기 위한 port이다. nGrinder-Controller의 기본 설정으로 지정되어있기 때문에 수정은 하지 않겠다. 

 

그럼 실행을 해보자 

사진 19. Controller, Agent, Monitor 모두 실행 중인 것을 확인

여기까지 했으면 거의 다 된 것이다. 

 

마지막으로 남은 Controller에 Monitor를 등록하는 과정을 진행해보자.

사진 20. Controller 성능 테스트 페이지

Controller 상단의 "성능 테스트" 탭을 클릭하면, 위와 같은 페이지에 진입하게 된다. 

우측에 있는 "테스트 생성" 파랑색 버튼을 눌러보자

사진 21. 테스트 설정 페이지 

위와 같은 테스트 설정 페이지가 나오는데, 우리는 여기서 테스트를 하는 방법은 보지 않을 것이다. 

사실 작성은 하고 싶은데, 부하 테스트에 사용할 서버를 올려놓은게 없다. 

여기서 봐야하는 것은 왼쪽 아래에 "테스트 대상 서버"라고 쓰여진 텍스트 박스이다. 텍스트 박스 오른쪽 아래 "+ 추가"를 눌러보면 도메인과 IP를 추가할 수 있는데, IP만 추가해주면 설정이 끝나게 된다.

 

여기까지 설정한 후 테스트를 진행하면, 상세 보고서에서 좌측 하단에 타겟 서버 당 시각화된 리소스 모니터 데이터를 확인할 수 있다. 

Multi-AZ

AWS에서 권장하는 데이터를 AZ(Availability Zone) 여러 곳에 분산하여 저장하는 정책이다.
ex) AWS에서 제공하는 Cache 서버를 구성하는데 단일 클러스터로 구성하였다. 이런 상황에서 AWS 오류로 Cache 서버가 죽어서 서비스에 차질이 생길 수 있으니, 서울-Region-a와 서울-Region-b 두 곳에 분산시켜 Fail Over 대책을 세워야 한다는 것이다

서비스 안정성을 위해서 HA를 위해 Multi-AZ를 시키는 것은 맞지만 이 정책에는 일단 함정이 있다. AWS의 서비스 안정성은 99.99%(서비스 별로 다름)로 AWS측에서 일부 서비스를 재시작하거나 내부 오류가 발생하여 서비스 기능이 일시 중지 될 수 있다. 해당 내용은 공식 매뉴얼에 기재된 것으로 실제 발생할 수 있는 상황이다. 이 때 AWS 오류로 인해 일부 기능이 작동을 하지 않는 경우, Multi-AZ를 적용하지 않아 피해를 본 고객사에게는 피해 보상을 하지 않는다. (물론 Multi-AZ-AZ는 추가적인 인스턴스 혹은 AWS EBS를 대여하는 것이므로 비용이 늘어난다.)

AWS가 죽어봤자 얼마나 죽겠어! 라고 생각할 수 있지만 의외로 잘 죽는다. 2019년 AWS 도쿄 리전의 일부 AV에 속한 캐싱 서버가 오류를 일으켜, 해당 Region과 AZ에 단일 캐싱을 두고 있던 기업들이 피해를 본 적이 있다. (내 기억으론 그 때 넥슨 일부 게임들도 오류가 났었던 꽤 큰 이슈로 알고있다.)

정리하게 된 사유 : ...서버 개발을 하고 있었는데 이젠 aws 운영 환경도 신경써야한다고 반 강제로 하게 되었다. 공부하면서 정리했던 필기를 정리한다

요즘 일하면서 공부하는게 평소 공부하는 것보다 많은 듯 (사실 공부 안함)

AWS 운영 환경을 구성할 때 알아야 할 용어 

AWS Region

- AWS 클라우드 센터가 있는 나라/지역

VPC (Virtual Private Cloud)

- 직역 그대로 "가상의 사설 클라우드"

- 논리적인 AWS Region의 하위 구성 

- 사용자가 정의한 aws 계정 전용 가상 네트워크 

- 가상의 개념으로 개인/기업 전용으로 대여되는 클라우드

AV (Availability Zone)

- 가용 영역

- 물리적인 AWS Region의 하위 구성  

- ex) AWS 서울 Region에는 [서울-Region-a, 서울-Region-b] 과 같이 하나의 Region에서도 여러 지역에 물리적으로 분산을 시켜 놓는다. 

Subnet

- VPC 영역의 IP 범위를 칭한다

라우팅 테이블

- 네트워크 트래픽을 전달할 위치를 결정하는데 사용하는 규칙의 집합

EC2 (Elastic Compute Cloud)

- 흔히들 인스턴스Instance 라고 부르는 것이 EC2 Instance다

- 클라우드에 설치, 실행되는 가상 서버

- 기본적으로 생성만 했다면, Subnet 내 IP만 할당되어 외부에선 접근할 수 없다

- "탄력적 IP 할당" 기능을 통해 외부 접근 가능 IP를 발행, 맵핑하여야 외부 접근이 가능하다

인터넷 게이트웨이

- VPC의 리소스와 인터넷 간의 통신을 활성화하기 위해 VPC에 연결하는 게이트웨이 

- 질럿나옴

AMI (Amazon Machine Image)

- 쉽게 생각하면 컴퓨터에 운영체제 설치할 때 사용하는 ISO 파일이다

- EC2 인스턴스에 설치할 소프트웨어(운영체제)의 설치 이미지 파일이자, 소프트웨어 구성이 기재된 Template이다

- EC2 인스턴스 생성 과정에서 AMI를 선택하는 구간이 있다

AWS EBS (Elastic Block Store)

- 교육방송

- Block 단위로 관리되는 동적 저장 공간

- AWS에서 관리하는 S3에 EBS Volume째로 스냅샷을 백업할 수 있다

- EC2 인스턴스에 저장 공간을 할당할 때 기본값으로 설정하는 저장 방식이다

AWS S3 (Simple Cloud Storage)

- 파일 저장소라고 생각하면 된다

- 나도 이렇게만 알고있다

Route 53

- 도메인 설정 기능

AWS 환경 구성 시 기본 순서 및 구성 방식

AWS Region > VPC > AV > Subnet > EC2 Instance

1) 물리적 근본적 지역을 지정

2) 지역 아래에 사용할 가상 대역(VPC)를 설정

3) 가상 대역에 할당할 Region에 속해있는 데이터 센터(IDC 센터)를 지정

4) 물리적(AV)으로 지정된 것을 VPC 할당을 하여 논리적으로 묶었다면, 이제 논리적 공간(VPC) 내에서 사용할 Subnet(내부 IP)를 구성한다

5) 사설 IP까지 구성이 되었다면, EC2 Instance를 생성할 때 사용할 사설 내부 IP를 할당하여 생성한다

+ Recent posts