분산 환경의 처리는 일반적인 환경의 구성과 많이 다르며 분산시스템의 특이성에 대한 개념들이 있다.

특히나 자주 듣게 되는 단어 중 하나는 결과적 일관성(Eventual Consistency) 라는 개념으로, 분산 시스템(Distributed System) 을 운영하게 되면 흔치않게 접하게 된다.

이는 개념과 관련된 이론이 그만큼 중요한 내용임을 반증한다.

 

먼저 예로 들은 Eventual Consistency 에 대해 설명하자면 이는 분산 컴퓨팅(Distributed Computing) 에서 고가용성(High Availability)을 보장하기 위한 방법의 하나로

"주어진 데이터에 대한 변경이 없다면 해당 Element 에 대한 모든 Access 는 가장 최근에 변경된 내용을 가리킨다" 는 정의를 말한다.

분산 시스템에서 데이터를 조회할 때 모든 시스템이 동일한 데이터를 가질 수 있다고 보장할 수는 없으며 결과적 일관성은 어느 시점에는 데이터가 다를 수 있지만, 결국에는 모든 시스템이 최신의 데이터를 가질 수 있도록 보장된다는 내용이다.

 

분산 환경에서는 데이터에 대해 단지 "시간" 뿐 아니라 "공간(다른 시스템)" 도 고려의 대상이 된다.

 

이는 BASE 원칙의 일종으로 분류되기도 한다. BASE 원칙이란 다음의 원칙들이 포함된다.

- Basically Available : 일반적인 Read / Write 에 대한 동작이 "가능한만큼" 지원된다.

(여기서 가능한만큼 이라 함은, 동작이 가능하나 Consistency 에 대한 보장이 되지않는다는 점이다.)

- Soft state : Consistency 가 보장되지 않기 때문에 상태(State) 에 대해 Solid 하게 정의하지 못하다.

- Eventually consistent : 위에 언급된 Eventual Consistency 개념에 따라 충분한 시간이 흐르면 모든 시스템 환경 내에서 데이터는 최신의 데이터가 보장된다.

 

BASE 원칙은 전통의 트랜잭션 시스템을 위한 ACID 원칙에 반대된다. 이는 분산 환경에서 나타나는 특징이기 때문이다.

이러한 특징에 대해 CAP Theorem 은 다음 3가지 조건을 모두 만족하는 분산 시스템을 만드는 것이 불가능함을 정의한다.

 

- 일관성(Consistency) : 모든 시스템의 데이터는 어떤 순간에 항상 같은 데이터를 갖는다.

- 가용성(Availability) : 분산 시스템에 대한 모든 요청은 내용 혹은 성공/실패에 상관없이 응답을 반환할 수 있다.

- 내구성(Partition Tolerance) : 네트워크 장애 등 여러 상황에서도 시스템은 동작할 수 있다.

 

분산환경 특징 상 3가지 성질을 모두 만족할 수는 없고, 일반적으로 다음과 같이 선택된다.

 

- CP (Consistency & Partition Tolerance) : 어떤 상황에서도 안정적으로 시스템은 운영되지만 Consistency 가 보장되지 않는다면 Error 를 반환한다. (어떤 경우에도 데이터가 달라져서는 안된다.)

이는 매 순간 Read / Write 에 따른 정합성이 일치할 필요가 있는 경우 적합한 형태이다.

 

- AP (Availability & Partition Tolerance) : 어떤 상황에서도 안정적으로 시스템은 운영된다. 또한 데이터와 상관없이 안정적인 응답을 받을 수 있다. 다만 데이터의 정합성에 대한 보장은 불가능하다. (특정 시점에 Write 동기화 여부에 따라 데이터가 달라질 수 있다.)

이는 결과적으로는 일관성이 보장된다는 Eventual Consistency 를 보장할 수 있는 시스템에 알맞는 형태이다.

 

 

서버시스템 및 분산시스템에 있어서 핵심적인 개념이므로 잘 정리해두고 아키텍처를 고려할 때 항상 생각해두자

Dynamic Contents 와 Static Contents 의 차이는 명확하다.

용어가 생소하더라도 개념은 익히 알려진 내용일 것이다. 그럼에도 짚고 넘어가자면,

Static Contents 는 유저/지역 등 어떤 기준을 막론하고 같은 데이터, 즉 정적 데이터를 말하고,

Dynamic Contents 는 유저/지역 또는 어떤 기준에 대하 다를 수 있는, 동적 데이터를 말한다.

 

쉽게 이해하자면 변수와 상수의 차이 정도로 보면 쉽다.

 

너무나도 간단한 개념이지만, 캐싱의 관점에서는 또 다르게 적용될 수 있다.

Static Contents 의 캐싱은 간단하다. 단순히 변하지않는 정적 데이터를 캐싱해주면 된다.

일반적으로 API 등에서 Meta data 를 캐싱하는 경우 In memory 에 캐싱하거나 혹은 설정값으로 관리하는 경우가 많다.

Meta data 가 아닌 종류의 리소스들이라면 CDN 이라는 훌륭한 솔루션이 있고, Cache-invalidation 정책만 조절하여 관리해준다.

 

반면 Dynamic Contents 의 캐싱은 조금 다르다. 계속 변하는 컨텐츠이기 때문에 자체만으로는 캐싱이 불가능하다.

가령 "Wellcome Home" 과 "Wellcome Tom" 이라는 두 종류의 웹페이지가 있다고 해보자. 앞선 페이지는 Static web page 이고 다음 페이지는 Dynamic web page 이다.

Static web page 의 캐싱은 간단하며, 단순히 해당 페이지(컨텐츠)를 저장하지만, Dynamic web page 의 경우 동적 요소를 따로 분리해서 로직으로 저장해주고(web page 의 경우 javascript 객체로 매핑시켜줄 수 있겠다.) static contents 만 캐싱해서 응답을 재구성하다.

 

CDN 및 캐시 솔루션 중에는 Dynamic Contents 에 대한 캐싱을 서비스해주려는 노력들이 꽤 있다.

가령 AWS 의 CloudFront 같은 경우 별도의 Backbone Network 를 구성해서 오리진 서버(비즈니스 로직이 처리될 서버)까지의 Latency 를 줄이고 Region 을 확장하는 방식으로 노력을 하고 있다.

 

 

 

서버 / 네트워크쪽 혹은 클라우드쪽 일을 하는 엔지니어라면 베어메탈 서버라는 단어를 심심치않게 들을 수 있다.

 

Bare-metal Server 란 간단히 요약하자면, "싱글 테넌트 물리 서버 장비(Single tenant physical server)" 라고 할 수 있겠다.

 

근래 들어 가상화된 서버와 클라우드 호스팅과 비교되어 많이 등장하는 개념으로, "싱글 테넌트" 이기 때문에 물리 장비 하나에서 여러 가상 호스트가 떠있지않은, 그야말로 전통의 서버 인프라라고 할 수 있겠다.

 

최근에는 Data Center 를 구축해서도 안에 Multi-tenant 환경을 구축해놓는 방식이 많은데, 그 방식이 아닌 서버 머신 하나에 하나의 환경이 구축되는 환경을 말한다.

 


클라우드 환경에서 AWS 의 솔루션들을 사용하는 데 있어서 AWS Auto Scale 은 그 꽃이라고 할 수 있다.

AWS 의 Auto Scale 솔루션은 서비스의 스케일링(Scaling) 을 자동화할 수 있게 해주는 클라우드 인프라 솔루션이다.

AWS 의 Auto Scaling Group / Launch Configuration / Scaling Plan 와 같은 핵심 구성 요소들을 포함한다.

오토 스케일링 정책을 통한 설정을 바탕으로 몇 개의 인스턴스를 운용할지, 어떤 시점에 Scale 을 늘리고 어떤 시점에 낮출지 결정할 수 있다.

(Scaling 에 대해 잘 모른다면 다음 포스트를 참조하자. 

https://jins-dev.tistory.com/entry/Scaling-%EC%9D%98-%EC%A2%85%EB%A5%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC)


이에 따라 Auto Scale 솔루션은 다음과 같은 장점들을 갖고 있다.

1. Cost Optimization

Scale 이 커져서 AWS 의 인프라들을 많이 사용하게 되면 비용이 증가하고 Scale 을 낮출 경우 비용을 절감시킬 수 있기 때문에 비용적인 측면을 같이 고려해서 사용해야 하며,

이러한 측면이 고려된 설계를 한다면 Auto Scale 을 통해 필요한만큼만 리소스를 사용함으로써 비용을 최적화할 수 있다.

(단, Auto Scale 솔루션 자체는 추가 비용이 들지 않는다.)


2. High Availability

Auto Scale 솔루션을 이용하면 Auto Scaling Group 에 대해 여러 Availability Zone 들에 고르게 인스턴스들을 할당하고 해제할 수 있다.

가령 Scale Out 을 통해 인스턴스들의 숫자가 많아질 때 하나의 영역에만 할당하는 것이 아니라 여러 영역에 고르게 분포시킬 수 있고, 해제할 때에도 균등하게 할당을 해제할 수 있다.

 


3. Well-Architectured Service

Auto Scale 을 통해 실제 요구량보다 많은 리소스를 사용하는 Over-Provisioning(오버 프로비저닝) 이나 실제보다 적은 리소스를 할당하는 Under-Provisioning(언더 프로비저닝) 을 방지할 수 있다.

이는 아키텍처를 설계함에 있어 유연성을 가져다줄 수 있다.


Auto Scale 솔루션은 다음과 같은 수명주기를 갖고 있다.

출처 : https://docs.aws.amazon.com/ko_kr/autoscaling/ec2/userguide/AutoScalingGroupLifecycle.html


수명 주기에 따라 Scale Out 된 인스턴스들은 Pending 상태 이후 In-Service 상태로 전환되며 Scale In 될 때에는 Terminating 프로세스를 수행하게 된다.

진행 중인 인스턴스를 Attach 하거나 Detach 하는 과정 역시 라이프사이클에 포함된다.

위와 같은 수명 주기 내에서 Auto Scale 은 주로 CloudWatch 지표를 바탕으로 이벤트가 트리거되며 이벤트를 통해 Lambda 함수를 호출한다던지, SNS 알림을 생성한다든지 하는 다른 작업을 같이 진행할 수 있다

Amazon EC2 Auto Scaling API 에 대한 호출은 AWS CloudTrail 을 통해 추적해볼 수 있다.

 

분산처리 환경을 구축하는 데 있어서 로드밸런서의 역할은 핵심적이다.

클라우드 환경에서 분산처리를 위한 아키텍처를 설계한다면, AWS 의 Load Balancer 를 이용해볼 수 있다.

 

Amazon Web Service 가 Provisioning 하는 Load Balancer 는 ELB(Elastic Load Banacing) 서비스라 하며, 기본적으로 Logging, Cloud Watch 를 통한 지표, 장애 복구, Health Check 와 같은 기능들을 제공한다.

ELB 서비스의 종류로는 2019년 8월 기준으로 3가지 종류가 있다.

 

 (1) Classic Load Balancer

가장 기본적인 형태이자 초기에 프로비저닝되던 서비스로, 포스팅 등에 나오는 설명에 단순히 ELB 라고 나와있으면 Classic Load Balancer 를 말하는 경우가 많다.

L4 계층부터 L7 계층 까지 포괄적인 로드밸런싱이 가능하며, 따라서 TCP, SSL, HTTP, HTTPS 등 다양한 형태의 프로토콜을 수용할 수 있고 Sticky Session 등의 기능도 제공해준다.

특징적으로 Classic Load Balancer 는 Health Check 를 할 때, HTTP 의 경우 /index.html 경로를 참조한다.

즉, 웹서버 인스턴스의 경로에 /index.html 가 포함되어있지 않다면(404) Health Check 를 실패한다는 뜻이다...

따라서 이럴 때에는 Health Check 를 위한 프로토콜만 TCP 로 바꾸고 포트만 80 으로 체크하게 하는 방법이 있다.

중요한 특징으로... CLB 는 로드밸런서가 url 하나를 가질 수 있다. 즉, CLB 로 매핑되어있는 인스턴스들은 무조건 하나의 URL 을 통해 접근하게 된다.

 

 (2) Application Load Balancer

Classic Load Balancer 이후 출시된 서비스로 HTTP 및 HTTPS 트래픽 로드밸런싱에 최적화되어있다.

L7 계층에서 작동하며 Micro Service Architecture 와 같은 Modern 환경을 겨냥한 많은 강점들이 있는데, WebSocket 이나 HTTP/1.1 이상의 프로토콜에 대한 지원, 향상된 라우팅 정책과 사용자의 인증 까지도 지원을 해준다.

웹서비스를 목적으로 한다면 기존 Classic Load Balancer 가 갖고 있던 장점 이상의 강점들을 많이 포함하고 있다.

단 L7 계층에서 작동하기 때문에 TCP 등에 대한 밸런싱은 지원되지 않는다.

직접 사용해서 구축 할때에 Classic Load Balancer 에 비해 좀 더 구축이 편하고 웹서버 밸런싱에 있어서는 확실히 다양한 옵션이 있다. Health Check 경로 또한 지정할 수 있으며, 기본이 / 로 정해져있다.

CLB와 다르게 ALB 는 host-based Routing 과 path-based Routing 을 지원한다. Content Based Routing 이라고도 하며 ALB 에 연결된 인스턴스들은 여러개의 URL 과 path 를 가질 수 있다.

 

 (3) Network Load Balancer

Load Balancer 중 성능적으로 최적의 로드밸런싱을 지원한다. TCP, UDP 등의 서버를 구축할 때 해당 프로토콜 들에 대해 굉장히 낮은 지연 시간으로 최적의 성능을 보여준다고 한다. 

사용해본적이 없는데... 사용해본다면 좀 더 포스팅 내용을 보강해야겠다.

 

 

로드밸런서에 EIP 를 직접 붙일 수는 없고, 필요하다면 DNS 의 도메인 네임 대신 CNAME 을 사용하거나 Route53 서비스와 같이 사용해야 한다. 그 이유는... ELB 역시 서버이기 때문에, 부하 상황에서 오동작의 위험이 있고 그에 따라 자체적으로 Scale In - Scale Out 을 수행하기 때문이다. (즉, IP 를 지정할 서버가 한대가 아니게 될 수 있다.)

또한, ELB 는 Multi-AZ 환경에서 "내부적으로는" 각 Availability Zone 별로 구성된다. 이 때 트래픽의 분배는 AZ 당 ELB 사정에 맞게 분배되므로... Multi AZ 환경에서는 각 Availability Zone 별 인스턴스의 숫자를 동일하게 맞춰주는 것이 좋다. (동일하게 맞추지 않으면 특정 Zone 의 인스턴스들에 트래픽이 몰릴 수 있다.)

 

로드밸런서를 통해 Cloud 환경에서 아키텍처를 설계할 때에는 Private Subnet 들에 EC2 인스턴스들을 배치해둔 상태에서, ELB로 트래픽을 연결할 수 있도록 인스턴스들을 연결시켜주고, Public 영역에 위치시켜 놓는 식으로 구성을 한다.

 

혹은, ELB 자체는 VPC 내부에 형성시켜 두고, ELB로 들어온 트래픽을 Private Subnet 으로 전개시켜주며, 인터넷에 연결을 위한 Public NAT 를 두고 출력을 해당 NAT 를 거치게 하는 방식도 있다.

 

 

서버의 Scalability 를 관리하기 위한 방법은 굉장히 중요하다.

 

실 서비스의 아키텍처를 구조화할 때도 반드시 고려해야할 부분이고, 이 결정은 실제로 서비스에 큰 영향을 주게 된다.

 

가령 사용자가 갑자기 증가할 경우, 엄청난 수의 요청에 대한 처리를 어떻게 할 것인가는 Backend 에서 가장 중요하게 고려해야할 부분 중 하나이다.

 

Scaling 의 방법에는 크게 Scale Up 과 Scale Out 이 존재한다.

 

Scale Up : 서버 자체의 Spec 을 향상시킴으로써 성능을 향상시킨다. Vertical Scaling 이라 불리기도 한다. 서버 자체의 갱신이 빈번하여 정합성 유지가 어려운 경우(주로 OLTP 를 처리하는 RDB 서버의 경우) Scale Up 이 효과적이다.

 

성능 향상에 한계가 있으며 성능 증가에 따른 비용부담이 크다. 대신 관리 비용이나 운영 이슈가 Scale Out 에 비해 적어 난이도는 쉬운 편이다. 대신 서버 한대가 부담하는 양이 많이 때문에 다양한 이유(자연 재해를 포함한다...)로 서버에 문제가 생길 경우 큰 타격이 불가피하다.

 

Scale Out : 서버의 대수를 늘려 동시 처리 능력을 향상시킨다. Horizon Scaling 으로 불린다. 단순한 처리의 동시 수행에 있어 Load 처리가 버거운 경우 적합하다. API 서버나, 읽기 전용 Database, 정합성 관리가 어렵지않은 Database Engine 등에 적합하다.

특히 Sharding DB 를 Scale Out 하게 된다면 주의해야하는데, 기존의 샤딩 알고리즘과 충돌이 생길 수도 있고(샤드가 갯수에 영향을 받을 경우...) 원하는 대로 부하의 분산이 안생길 수 있으니 각별히 주의할 필요가 있다.

 

스케일 아웃은 일반적으로 저렴한 서버 여러 대를 사용하므로 가격에 비해 뛰어난 확장성 덕분에 효율이 좋지만 대수가 늘어날 수록 관리가 힘들어지는 부분이 있고, 아키텍처의 설계 단계에서부터 고려되어야 할 필요가 있다.

 

 

요즘은 클라우드를 사용하기 때문에 Scaling 에 있어서 큰 이점이 있으며, 서비스의 목표치에 알맞게 Scalability 를 설계하고 두 스케일링 방법을 모두 적용시키는 것이 일반적이다.

 

 

 

실서비스를 준비하는 데 있어서 서버의 성능 테스트는 매우 중요한 부분이다.

(여기서 성능 테스트란 단순히 Performance test 만을 말하지는 않는다. 자세한건 다음을 참조 - 

https://jins-dev.tistory.com/entry/load-testing-%EA%B3%BC-stress-testing-performance-testing-%EC%97%90-%EB%8C%80%ED%95%9C-%EB%B9%84%EA%B5%90)

 

그렇다면 성능 테스트를 어떻게 할 것이고 어떻게 병목현상을 해결하여 개선을 이루어낼 것인가가 관건이며, 이를 위해서는 단순히 코드를 효율적으로 짜는 것 이상의 학습이 필요하다.

"성능" 에 대한 이해와 "테스트" 에 대한 이해, Performance Engineering 에 대한 이해가 모두 필요한 것이다.

 

그렇다면 먼저 서버의 성능 측정에 앞서 알아두어야할 용어들을 정리해보자.

 

(1) Response Time : 클라이언트의 요청을 서버에서 처리하여 응답해주는데까지 걸리는 전체 시간을 말한다.

실제 요청이 들어간 시점부터 네트워크를 거치고 비즈니스 로직의 처리가 끝난 후 다시 클라이언트가 응답을 받는 시점까지 걸리는 시간을 말한다.

 

(2) Think Time : 사용자가 다음 Request 를 보내기 전까지 활동하는 시간을 말한다. 실제 별도의 서버 요청을 하지않고 웹문서를 보거나 컨텐츠를 즐기는 시간을 Think time 으로 분류한다.

첫번째 요청에서 다음 요청을 보낼때까지의 시간이므로 Request Interval 로 부르기도 한다.

 

(3) Concurrent Users : 말그대로 동시에 서비스를 사용하는 유저를 말한다. 웹서버 / 세션서버 등 서버의 종류에 따라 유저의 행동양식에 대한 분류는 매우 다양해지게 된다.

일반적으로 Concurrent User 는 Active User 와 Inactive User 로 분류한다.

 

 - Active User : 실제 Request 를 보내고 Response 를 대기 혹은 Connection 을 잡고 있는 유저를 말한다.

일반적으로는 Request 에 대해 실제 Transaction 을 발생시키는 유저에 대해 언급한다.

여기서 포함되는 유저들의 목록에는 실제로 서버에서 클라이언트의 요청을 받을 시, Connection Pool 을 모두 가져가는 유저들과, Connection Pool 의 할당을 대기하고 있는 Pending User 들 역시 Active User 에 포함된다는 점이다.

 

 - Inactive User : 실제 Request 를 수행하지 않고 대기중인 유저를 말한다. 서버의 종류에 따라 연결을 유지하고 있는 유저의 경우에도 Inactive User 로 보기도 한다.

웹서버의 경우 대부분의 Inactive User 는 Think time 을 갖는 유저로 간주된다.

 

(4) Throughput : 서버가 클라이언트에 송신한 데이터량

 

(5) Vuser : Virtual User - 주로 퍼포먼스 테스트를 위해 만드는 가상 유저

 

(6) MRT : Mean Response Time 의 약자로 유저들에 대한 평균 응답시간을 말한다.

 

(7) TPS : Transaction Per Second 의 약자로 주로 서버 성능의 척도가 된다. 초당 트랜잭션 처리 수를 의미한다.

일반적으로 TPS = AU / MRT 라는 공식에 따라 Active User 의 수에 비례하고 유저들에 대한 평균 응답시간(MRTT)에 반비례한다.

 

(8) Scenario : 테스트를 위한 시나리오를 말한다. 정확한 TPS 측정을 위해서 가장 중요한 부분 중 하나로, 실제 서비스에서 유저가 어느정도 비율로 어떤 API 를 호출할지 예측하는 과정이 필수적이다.

Service Architecture 를 잘 이해하고 있어야됨은 물론이고, 이에 따라 API 호출의 비율과 그에 맞는 튜닝으로 서비스의 질을 향상시켜야 한다.

 

 

위의 용어를 이해했으면 다음으로 퍼포먼스 테스트를 위한 프로세스를 정리해보자.

 

1. 먼저 서비스의 목표와 그를 위한 규모를 측정해야 한다.

 

2. 서비스 내에서 사용자의 요청 빈도 수를 예측하고, 그에 알맞게 테스트 시나리오를 만든다.

 

3. 테스트 시나리오에 맞게 다양한 방향으로 테스트를 진행한다.

여기서 테스트는 Stress Test / Performance Test / Load Test 가 복합적으로 진행되어야 한다. 각 테스트별 한계값 임계치를 측정하는 것이 중요하며, 각각의 테스트 결과는 서로에 영향을 미치게 될 것이다.

 

4. Stress Test / Load Test / Performance Test 를 통해 현재 시스템의 부하 한계치, 퍼포먼스 등이 측정이 되었다면, 모듈별로 분석해서 튜닝하는 작업이 필요하다.

비즈니스 로직 뿐 아니라 Database / Network / Middleware / Infrastructure 등을 모두 튜닝하는 것이 중요하다.

 

5. 튜닝을 통해 테스트를 다시 수행하며 서비스의 한계치를 다시 측정한다. 목표에 이를 때까지 4~5 과정을 반복한다.

 

 

위의 테스트 Process 를 거치면 실제 생각했던것과 다른 상황을 많이 마주치게 된다.

 

가령, 우리는 응답시간이 큰 변동폭이 없을 것이며, 가용 자원량 만큼 TPS 가 올라갈 것이라 예측하고 한계점을 측정하겠지만, 실제로는 가용자원의 양은 처음에 급격이 증가하되, 어느정도 TPS 이상이 확보되면 일정해지며 응답시간은 TPS 증가에 따라 기하급수적으로 올라가게 된다.

 

이는 주로, Service architecture 가 Caching 을 하기 때문에 가용자원에 대한 부분을 상당 부분 부담해주기 때문이며, 응답시간의 경우 부하가 쌓이는 만큼 Connection Pool 에서 유저의 요청에 대한 Pending 이 생기기 때문이다.

 

 

아직 Performance Engineering 에 대해서는 지식이 짧아 정리하지 못했는데, 계속해서 공부를 해보면서 추가 포스팅 하도록 해야겠다.

 

(포스팅의 글이 너무 좋아 참조하였습니다. 

https://bcho.tistory.com/787, http://www.jidum.com/jidums/view.do?jidumId=562dumId=562)

 

iLiFO 지덤 :: 테지덤 성능테스트 – Little’s law 포함

I. 성능테스트의 개요와 원리 Little’s law 가. 시스템 테스트의 정의 사용자가 시스템을 사용하기에 성능상 문제가 있는 지를 검증하여 개선하기 위한 테스트 나. 소프트웨어 성능 테스트의 기본 원리 Little’s law ‘공간 내에 머무는 객체 수(L)’는 ‘객체의 공간유입량(λ)’과 ‘객체가 머무는 시간(W)’에 비례한다. 즉 L = λW이다. 시스템의 성능(TPS)는 트랜잭션을 발생시키는 사용자(Active User)를 평균응답시간(Mean

www.jidum.com

 

성능 엔지니어링 대한 접근 방법 (Performance tuning)

성능 엔지니어링에 대한 접근 방법 조대협 성능 개선, Performance Tuning, 용량 선정 과 같은 튜닝 관련 용어들은 모든 개발자나 엔지니어에게 모두 흥미가 가는 주제일 것이다. 그 만큼 소프트웨어에서 고성능을..

bcho.tistory.com

 

 

실제 라이브 서비스를 위해 서버 산정을 할 때, 서비스 규모에 걸맞는 트래픽을 감당하기 위한 테스팅은 필수적이다.

 

근래에는 클라우드의 발전으로 Scalability 나 Elasticity 를 확보할 수 있는 많은 방법과 기술들이 생겼다고는 해도, 결국 해당 솔루션을 사용하는 것은 "비용" 이며 서버개발자라면 이 비용을 최소화하고 효율적인 서버를 구축해내는 것이 "실력" 을 증명해내는 일이다.

 

여기서 피할 수 없는 백엔드 테스트의 종류들이 나타나는데, 일반적으로 스트레스 테스트 또는 부하 테스트라고 알려진 테스트들이다.

이 테스트들은 중요하지만, 개발자들이라도 차이를 잘 모르는 사람들이 꽤나 있고 서비스의 규모가 작다면 혼용되어 쓰는 경우가 다수이기 때문에 정리해서 알아둘 필요가 있다.

 

(1) Load Testing (부하 테스트)

말그대로 시스템이 얼마만큼의 부하를 견뎌낼 수 있는 가에 대한 테스트를 말한다.

 

보통 Ramp up 이라하여 낮은 수준의 부하부터 높은 수준의 부하까지 예상 트래픽을 꾸준히 증가시키며 진행하는 테스트로, 한계점의 측정이 관건이며, 그 임계치를 높이는 것이 목적이라 할 수 있다.

 

일반적으로 "동시접속자수" 와 그정도의 부하에 대한 Response Time 으로 테스트를 측정한다.

예를들어 1분동안 10만명의 동시접속을 처리할 수 있는 시스템을 만들수 있는지 여부가 테스트의 주요 관건이 된다.

 

(2) Stress Testing (스트레스 테스트)

스트레스 테스트는 "스트레스받는 상황에서 시스템의 안정성" 을 체크한다.

 

가령 최대 부하치에 해당하는 만큼의 많은 동시접속자수가 포함된 상황에서 시스템이 얼마나 안정적으로 돌아가느냐가 주요 관건이 된다. 

스트레스 상황에서도 시스템의 모니터링 및 로깅, 보안상의 이슈나 데이터의 결함 등으로 서비스에 영향이 가서는 안되므로 결함에 대한 테스트가 일부 포함된다.

가령 메모리 및 자원의 누수나 발생하는 경우에 대한 Soak Test 나 일부러 대역폭 이상의 부하를 발생시키는 Fatigue Test 가 포함된다.

 

이처럼 부하가 심한 상황 또는 시스템의 복구가 어려운 상황에서 얼마나 크래시를 견디며 서비스가 운영될 수 있으며 빠르게 복구되는지, 극한 상황에서도 Response Time 등이 안정적으로 나올 수 있는지 등을 검사하게 된다.

예를들어 10만명의 동시접속 상황에서 크래시율을 얼마나 낮출 수 있는가, 혹은 데이터 누락을 얼마나 방지할 수 있는가 에 대한 테스팅이 있을 수 있다.

 

(3) Performance Testing (퍼포먼스 테스트)

Load Test 와 Stress Test 의 모집합 격인 테스트의 종류이다. 리소스 사용량, 가용량, 부하 한도(Load Limit) 를 포함해서 응답시간, 에러율 등 모든 부분이 관건이 된다.

 

특정 상황에서 어떤 퍼포먼스를 보이는지에 대한 측정이 주가 되며, 서비스의 안정성보다는 퍼포먼스 자체에 집중한다.

 

주로 서비스적 관점에서는 Performance Test 보다는 Load Test 나 Stress Test 가 더 중점이 되며, 시스템 전체적인 성능의 양적 측정과 같은 관점에서 Performance Test 로 분류한다.

보통 그럴 때에 수행하는 테스트로 Benchmark 라고 하기도 한다.

 

 

위와 같은 종류의 테스트 들을 수행하는 데 있어 제일 중요한 부분 중 하나는 모니터링과 리포팅이라고 할 수 있겠다.

관련해서 경험과 같이 포스팅으로 정리할 예정이다.

 

 

 

구글 Protobuf 는 강력한 데이터구조이며 환경간 이식성이 뛰어나고 패킷으로 사용할 때, 자체의 성능(통신 속도 및 작은 패킷 크기)이 뛰어나지만, 정해진 형식이 있다보니 사용에 있어서 알아두어야할 점들이 몇가지 있다.

 

실무에서 사용하면서 알아두어야 했던 점들 및 특징적인 점들 몇가지를 정리해보았다.


1. protobuf 는 패킷간 상속과 추상화를 지원하지않는다.

프로토버프를 도입하기 가장 꺼려지는 이유인데, protobuf 의 Data Structure는 상속과 추상화와 같은 생산성을 위한 개발자들의 구조를 부정한다.

Protobuf 는 애초에 범용(?) 목적으로 설계되었다기 보다는 확실한 단일 목적에 대해 Compact 하게 설계된 Data Structure 이다.

즉, 확실히 의미를 갖는 필요한 데이터만 저장할 수 있게 되어있으며, 그렇기 때문에 Stream 으로 사용할 시 필요한 정보만 주고받는 효율성을 이점으로 취할 수 있는 반면, 데이터를 담는 데 있어서 유연하지 못하다.

 

그렇기 때문에 프로토콜 버퍼를 패킷으로 통신을 해야 한다면 모든 패킷에 필요한 정보들을 분류해서 정의해주는 것이 필요하다.

 


2. protobuf 의 패킷 넘버링은 생각보다 엄격하지않다. 하지만 패킷의 순서는 매우 중요하다.

다음과 같은 에러 처리를 위한 프로토콜 버퍼 Data Structure가 있다고 가정해보자.

위의 데이터 구조에서 time_stamp 패킷의 넘버링을 4로 바꿔도 무방하다. 프로토콜 버퍼에서 구조 내에서 중요한 것은 패킷들의 순서를 지키는 일이다.

다만, Protocol Buffer 를 enum 형태로 정의해서 쓰는 경우라면 조금 얘기가 다른데,

위와 같은 Enum 패킷에서는 numbering 이 의미를 갖는다. enum 의 경우 넘버링이 Enum 클래스의 ordinal 과 동치되기 때문에, Compile 해서 사용할 경우 패킷이 다르다면 예기치 못한 에러가 발생할 수 있다.

 

 

3. Protobuf 구조가 프로토콜로 정해졌다면 데이터 구조는 변경하지 않는 것이 좋다.

통신에 있어서 Protobuf 를 사용하는데, 기존 데이터 구조가 삭제 또는 변경된다면 그 구조를 삭제 & 변경하는 것이 아니라 새로 패킷을 추가하는 편이 좋다.

그 이유는 하위호환성 때문으로, 통신하는 양쪽의 Protobuf 빌드가 항상 동기화가 완벽하다면 문제가 없지만, 개발을 하다보면 버전이 달라질 수밖에 없다.

이 때 문제를 방지하기 위해 데이터 구조의 크기를 늘리는 방법을 선택해야 한다.

하지만 패킷의 크기가 커질지는 염려하지 않아도 된다. Protobuf 의 특징 상 입력되지않는 패킷은 보내지않도록 퍼포먼스 측면에서 최적화가 지원된다.

 


4. 구글이 지원해주는 protobuf 라이브러리가 있으며, 이를 사용하면 프로토버프의 사용이 매우 편리해진다. 

다음 링크를 참조하자.

https://github.com/protocolbuffers/protobuf

 

protocolbuffers/protobuf

Protocol Buffers - Google's data interchange format - protocolbuffers/protobuf

github.com

protobuf 라이브러리는 언어별로 필요한 기능들을 유틸리티 형태로 지원한다.

아주 방대하니 전체를 쓸 생각보다는 환경에 맞게 필요한 만큼만 모듈을 가져다 쓰는 것이 좋다.

또한 protobuf 라이브러리에는 proto 파일 내에서 사용할 수 있는 공통 protobuf 형식들도 정의되어 있으므로 참고하는 것이 좋다. 
가령 proto 파일 내에서 Collection 이나 timestamp 등의 기능을 사용할 수 있도록 정리되어있다. 

 

 

5. 당연한 얘기지만 proto 파일의 주석조차 compile 결과로 빌드된 소스에 포함 된다. 

만약 소스 자체로 어떤 스크립트가 실행되어야 한다면 환경에 주의하자.

주석에 한글 특수문자가 포함된 proto 파일을 자동화 스크립트로 돌리다가 문제가 생기는 일이 더러 있었다.

 

 

프로토콜 버퍼는 구글이 지원하고 있는 직렬화방식이고 강력하며 쓰임새도 다양하다.

하지만 현재로써는 아는만큼 사용할 수 있는 도구임이 분명하다. 사용에 있어 유의하고 항상 공식 지원을 참고하는 것이 좋겠다.



메시지 지향 미들웨어(Message Oriented Middleware)란 독립될 수 있는 서비스간에 데이터를 주고받을 수 있는 형태의 미들웨어를 말한다.


구성요소간 통신을 하는 방법에 있어서 네트워크(Network) 를 이용하거나 Process 간 통신 등의 중계를 해주는 미들웨어의 경우에도 Message Oriented Middleware 라고 부를 수 있지만, 일반적으로 Server to Server 로 전달되는 Message 서비스를 말한다.


메시지 지향 미들웨어의 사용은 통신을 통해 Service 들 간의 분리와 독립, 그럼에도 불구하고 안정적인 기능을 제공해줌으로써 Software Architecture 를 설계하는 데 큰 유연성을 제공해준다.


메시지 지향 미들웨어는 대부분 메시지큐(Message Queue) 의 형태로 구현되어 있으며 줄여서 MQ라고 부른다.


MQ의 종류에는 흔히 알려진 Rabbit MQ 가 있으며 이런 종류의 메시지큐들은 AMQP(Advanced Message Queueing Protocol) 를 이용하여 구현되어 있다.


MQ 시스템을 인프라에 구축했을 때에 아키텍처상으로 이를 브로커(Broker)라고 부르기도 한다.


Message Queue 를 이용할 때 얻을 수 있는 장점으로는 다음과 같은 것들이 있다.


(1) Redundancy : 메시지의 실패는 보관 및 관리가 되며 메시지가 실패한다고 하더라도 시스템에 영향을 주지 않는다.


(2) Traffic : 메시지 큐에 메시지를 쌓아두면 무분별하게 증가할 수 있는 트래픽에 대해 느리더라도 안정된 처리가 가능하다.


(3) Batch : 메시지 큐는 메시지에 대한 일괄처리를 수행하며 특히 부하가 심한 Database 에 대한 처리에 대한 효율성을 기대할 수 있다..


(4) Decouple : 소프트웨어와 MQ는 분리되며, 이는 소프트웨어 설계에 큰 이점을 준다.


(5) Asynchronous : 큐에 넣기 때문에 영향을 받지않고 비즈니스 로직을 처리할 수 있다.


(6) Ordering : 트랜잭션의 순서가 보장되며 동시성 문제에서도 안전하다.


(7) Scalable : 다수의 서비스들이 접근하여 원하는 서비스를 이용하는데 용이하다. 이는 다수의 서비스를 분리하는 데에도 큰 도움을 준다.


(8) Resiliency : 연결된 서비스에 이상이 생기더라도 다른 서비스에 영향을 미치지 않는다.


(9) Guarantee : MQ 는 트랜잭션방식으로 설계되어 있으며 메시지 처리의 트랜잭션을 보장한다.


이러한 장점들을 갖고 있기 때문에 주로 API 서버를 구축할 때 중요하거나 그 자체로 무거울 수 있는 기능들을 별도의 서버로 분리하고, MQ 를 통해 통신하는 방식이 선호된다.


이는 프로젝트의 규모가 커짐에도 유연성을 확보할 수 있는 좋은 수단이며 서비스 자체에서 처리하기 부담스러운 요구사항들을 만족시킬 수 있는 옵션을 제공한다.



다음 링크들을 참조했습니다.


https://docs.oracle.com/cd/E19435-01/819-0069/intro.html

https://stackify.com/message-queues-12-reasons/

https://heowc.tistory.com/35



+ Recent posts