Performance Optimization @ Push Planet

스마트폰의 보급이 보편화 되면서 사용자들은 PC 앱에서 수행하던 작업들이 Android나 iOS기반 스마트폰 앱을 통해 Game, mCommerce, SNS, Multimedia 서비스 등으로 이동해 가고 있습니다. 최근 스마트폰의 하드웨어 및 소프트웨어 기술은 급격히 발전해 가고 있는 반면, 배터리 성능은 그에 따라가고 있지 못합니다. 스마트 폰은 데스크탑 PC와 달리 ‘Always Online’ 기기로 제한적인 배터리 용량으로 힘겹게 수명 연장의 꿈을 안고 살아갑니다. 이러한 환경에서 스마트폰의 다양한 앱들은 배터리 소모를 줄이기 위한 기술로 항상 실행하고 있는 것이 아니라 잠자고 있다가 메시지를 받으면 깨어나 실행하는 Push Notification 기술을 사용하게 됩니다. 스마트폰의 수와 앱 개수 만큼 사용자들은 수 많은 메시지들이 전달 되게 되는데, 이러한 Push Notification 서비스 서버는 늘어나는 앱 수와 폭증하는 스마트폰의 숫자로 인해서 시스템의 메시지 전송 성능은 큰 이슈입니다.

SK planet의 ‘Push Planet’은 Push Notification 서비스를 제공하는 시스템으로 현재 당사 T cloud, T ad, T shopping 등 8개의 서비스에서 이용 중이고 약 5백만 사용자가 사용 중에 있습니다. 이번 포스팅에서는 Push Planet에서 메시지 전송 성능을 높이기 위해서 진행했던 작업들을 공유하려고 합니다.

Push Planet은 Google GCM (Google Cloud Messaging)이나 Apple APNS (Apple Push Notification System)과 같은 당사의 push notification 시스템입니다. Push Planet 서버는 아래 그림과 같이 Message Queue를 중심으로 크게 front-end 서버와 back-end 서버로 나눌 수 있는데, 우선 front-end 서버는 Web/WAS로 구성된API 서버인 Dispatcher와 메시지 발송을 위한 인증서 관리 및 통계 기능을 제공하는 Dashboard, 그리고 RDBMS로 MySQL로 구성되어 있습니다. 또한, back-end 서버로는 메시지 발송 Gateway 서버와 단말의 접속을 관리하는 MQTT (Message Queue Telemetry Transport)기반에 CM (Connection Manager)으로 구성되어 있습니다.

이번 포스팅에서는 Push Planet의 현재 시스템 성능 분석을 기반으로 전체 시스템에서 각 구간별 프로파일링을 통해 최적화 할 수 있는 방안에 대해서 기술하고자 합니다.

Push Planet 시스템 성능 분석

그 동안 Push Planet의 성능 관련되어 수행했던 엑티비티들 리스트와 성능 benchmark 관련 작업 내용들을 공유하고자 합니다. 우선 Push Planet의 초기 성능 분석 결과를 통해서 성능을 최적화 할 수 있는 목표 값에 대해서 기술하고자 합니다. 일반적으로, SNS (Social Network Service)와 같은 메시지기반 시스템들의 메시지 전송 성능은 2만5천 TPS를 기준으로 글로벌 서비스와 로컬 서비스를 구분하는데, 글로벌 서비스를 지향하는 우리 시스템의 목표도 다르지 않습니다. Push Planet이 글로벌 서비스 플랫폼으로 발돋움하기 위해서 각 서버 컴포넌트들이 scale-out 되면 성능이 배수로 증가될 수 있도록 Linear Scalability를 보장하기 위해 각 구간별 프로파일링을 수행하여 bottleneck 구간을 제거해 나가야 합니다.

우선 Push Planet의 초기 메시지 전송 성능을 분석하기 위하여 LoadRunner (500 Vuser License)에서 API 서버로 메시지 전송 API를 호출하는 방식으로 테스트를 진행했습니다. API 서버는 1대부터 3대까지 늘려가면서 테스트했고, 제한된 시간 동안 API 서버가 최대로 처리할 수 있는 트래픽 양을 측정하여 아래와 같은 결과를 얻었습니다.

API

# WAS

Throughput

(Tran./sec.)

Response

Time (sec)

Resource Utilization (CPU 사용량, %)

WAS

DB

MQ

Proxy

CM

Unicast Messaging API (HTTP)

1 EA

2700

0.036

84

35

15

34

14

3 EA

6700

0.013

45

53

21

20

23

우리는 위에 결과를 토대로 WAS 서버의 개수에 따른 성능 추이를 예측해 볼 수 있습니다. 시스템의 성능을 예측하기 위한 방법은 여러 가지가 있지만, 시스템의 일부를 개선할 때 전체적으로 얼마만큼의 최대 성능 향상이 있는지 계산하는데 일반적으로 Amdahl’s Law라고 하는 법칙을 이용해서 추측이 가능합니다. 그래서 위 성능 결과값을 기반으로 우리 시스템에서 WAS 서버를 늘리면 어느 정도 성능이 향상이 있을지 계산해 보겠습니다. Amdahl’s Law에 의하면 시스템의 성능 증가분 S(N)P 만큼의 부분적 증가로 인해 N 만큼의 노드 수 증가가 있을 때 전체 성능 향상은 아래와 같은 수식으로 구해질 수 있습니다.

여기에서, 우리는 위에 실험 결과에 따라 Throughput의 증가분을 기반으로 P (Parallel Proportion)을 구할 수 있는데, WAS 서버 1대 대비 3대 일 때의 성능 증가분 값을 가지고 Amdahl’s Law에 대입해서 구하면 P = 0.89인걸 알 수 있습니다. 위의 수식에 WAS 노드 수에 따른 성능 증가 추이를 구해보면 아래와 같은 그래프를 얻을 수 있습니다. 결과적으로 초기 Push Planet은 아무리 노드 수를 증가시켜도 거의 2만 TPS에 수렴하는 양상으로 2만 5천 TPS에 도달하는 것은 불가능하다는 결론을 내릴 수 있습니다. 하지만, 그래프에서 볼 수 있듯이 1만 TPS에 도달하기 위해서는 약 6대의 WAS 서버만 가지고 도달할 수 있다는 것을 알 수 있습니다. 물론 이와 같은 결과값은 이론적인 수치지만 현재 우리 시스템이 어느 정도 한계를 가지고 있는지 예측은 가능합니다. 지금부터 우리는 Parallel Proportion을 높여, 즉 bottleneck을 줄여 적은 노드로 높은 성능을 얻고, 노드의 scale-out을 통해서 성능이 거의 배수로 증가될 수 있는 방안에 대해서 지속적으로 고민할 것입니다. 다음부터는 이러한 목표를 위해 몇 가지 수행한 작업들에 대해서 공유하고자 합니다.

성능 최적화 아이템

지금부터, Push Planet의 현재 시스템 성능 분석 결과를 기반으로 최적화할 수 있는 부분에 대해서 논의하고자 합니다. 첫째 front-end 서버 성능 향상을 위해서 Apache/Tomcat의 최적의 시스템 설정 값들을 찾았고, 디바이스들의 세션 정보 및 인증서 등을 캐싱함으로써 응답시간을 높일 수 있었고, 또한 HTTPS 요청에 대한 최적화 작업들에 대해서 논의하겠습니다. 둘째로, back-end 서버의 경우, 외부 push notification 시스템 및 자체 CM 서버와 연결 관리하는 메커니즘을 보완하였습니다. 마지막으로, 전체 데이터베이스 쿼리를 최적화함으로써 성능에 얼마나 영향을 미치는지 분석하였습니다. 이전에 초기 성능 분석 데이터는 서비스가 시작되기 전이라 Push Planet의 Live 서버에서 수행되었지만, 앞으로 기술된 성능 최적화 아이템들은 Push Planet의 Staging 서버에서 수행되었습니다.

  • Optimal Apache/Tomcat configuration

Push Planet의 front-end 서버는 Apache/Tomcat으로 구성된 Dispatcher라고 하는 API 서버로 구성이 되어 있습니다. Dispatcher는 자바 기반의 시스템으로, 메모리를 JVM에서 관리하기 때문에 GC (Garbage Collection) 으로 인한 순간적인 리소스 사용량이 증가하는 현상을 볼 수 있습니다. Peak Connection 시에는 이러한 GC로 인해 클라이언트는 503 에러가 반환될 수도 있습니다. 따라서, Apache/Tomcat의 MaxClients 및 MaxThreads 등에 값들에 의해서 시스템 성능에 많은 영향을 미칠 수 있습니다.

우선 Apache에 httpd.conf 파일의 MaxClients는 허용하는 범위에서 지속적으로 새로운 httpd 프로세스를 생성하게 됩니다. httpd 프로세스는 mod_jk 모듈이 관리하는 AJP Connector의 접속 풀에서 사용가능한 연결을 사용하여 Tomcat으로 전달하고, 그렇지 않으면 새로운 연결을 생성하여 전달합니다. 만약, 지속적인 연결 요청으로 Tomcat이 멈추는 경우에는 요청을 더 이상 처리할 수 없으므로 AJP Connector에서 설정한 backlog queue 크기가 허용하는 한도에서 쌓이게 되고, 해당 queue의 크기를 초과하면, 클라이언트에게 503 에러를 반환하게 됩니다. 이러한 상황을 가정하고 시스템의 리소스 및 초당 요청 수에 기반하여 충분히 backlog 크기를 설정할 필요가 있습니다. 예를 들어, 클라이언트의 연결 요청이 초당 1000이고 GC 시간이 1초라면, 최소한 backlog 크기는 이 보다 크게 설정을 해야만 합니다. 또한, Apache에서는 MaxClients 값을 통해서 클라이언트들의 요청에 대해서 httpd 프로세스를 생성하게 되는데 이 값이 너무 커도 시스템의 리소스 한계치로 인해서 시스템이 멈추는 경우도 있어, 시스템의 리소스 한계치 대비 설정을 해야만 합니다. 예를 들어, 시스템의 전체 메모리가 10GB라면 최대(약 80%)로 잡아서 8GB 이상이 사용되지 않도록 설정하면 됩니다. 그렇다면, httpd 프로세스 크기(4M라고 가정)로 나누어 계산하면 MaxClients는 약 2048이라는 값을 설정할 수 있습니다. 하지만, 기본 값은 그렇지만 시스템 설정 값은 다양한 조합으로 트래픽을 주며 최적의 값을 찾아내는 게 중요하고, 또한 Tomcat에 MaxThreads값은 worker thread 수로서 Apache로부터 들어오는 요청들을 처리해서 어플리케이션으로 연결해주는 역할을 합니다. 따라서, 본 값은 Apache에 MaxClients 값 및 어플리케이션의 응답시간에 따라서 유동적으로 설정해야 시스템 성능 향상을 도모할 수 있습니다. 아래 그림과 같이 Push Planet에 Dispatcher 서버에 구조를 볼 수 있습니다.

마지막으로 초기 Push Planet에 HTTPS로 API 요청을 할 경우 HTTP 대비 30%의 성능을 보였는데, 몇몇 논문들의 내용을 참고로 최적화를 통하여 HTTP대비 70% 이상의 성능이 가능하다는 것을 알았습니다. HTTPS 최적화를 위해 성능과 보안 두 가지를 만족시킬 수 있는 SSL Cipher Suite 조합을 비교 분석 하였습니다. 초기 Push Planet은 AES256으로 설정이 되어 있는데, 이는 암/복호화하는데 너무 많은 자원을 사용하게 되어 성능이 떨어져 AES128로 암/복호화하는 것을 고려했습니다. 물론 AES256으로 암/복호화하는 것에 비해서 보안 정도는 떨어지지만, AES128로 암/복호화해도 큰 무리가 없다는 다수의 리포트가 있으니 큰 문제는 없다고 판단했습니다. 참고로, 보안이 중요한 은행 전상망에 front-end 서버들은 대부분 AES256 암호화를 채택 중이나, 대부분의 웹 서비스 서버들은 AES128 알고리즘을 사용하고 있다고 합니다. 결과적으로 Push Planet의 SSL Cipher Suite는 ‘RC4-SHA:AES128-SHA:HIGH:MEDIUM:!aNULL:!MD5’를 적용해서 HTTP 대비 약 70% 성능(목표 결과)에 도달하였습니다.

  • Data caching with local/global cache

현재 Push Planet에서 가장 많은 트래픽을 유발시키는 API는 메시지 발송과 주기적으로 현재 세션 정보를 업데이트하는 API 입니다. 또한 어플리케이션 정보로 메시지를 전송할 서비스 어플리케이션의 고유 정보와 메시지 발송 시 마다 인증서를 로드해서 SSL Handshake를 수행하는데, 이는 메시지 발송이 많아 지면 많아 질수록 많은 부하가 초래됩니다. 따라서 우리는 이러한 정보들에 대해서 캐쉬를 이용하여 잦은 변동으로 인해 갱신되는 세션 정보는 Global Cache를 이용하고 거의 바뀌지 않고 재사용되는 정보는 Local Cache를 사용하는게 이득이 됨을 보일 겁니다. 왜냐하면, 예를 들어, 인증서 정보의 경우는 어플리케이션 마다 거의 바뀌지 않는 정보로 Local Cache를 사용할 수 있는데, 데이터 Consistency로 인해서 인증서 정보가 수정되면 모든 WAS에 로컬 캐쉬들 간에 동기화를 수행해야만 합니다. 인증서 정보는 잦은 수정이 발생되지 않기 때문에 동기화 주기를 크게 가져갈 수가 있어서 서버 간에 동기화를 해서 부담이 되지 않습니다. 하지만, 세션 같은 정보를 Local Cache를 사용한다면 잦은 동기화로 인해서 서버에 상당한 부담을 안겨주게 됩니다. 따라서, 이러한 세션 정보는 Global Cache를 사용함으로써 WAS 서버의 부담을 많이 줄여주면서 캐쉬로 인해 응답시간에 있어서 많은 이득을 볼 수 있게 됩니다. 최근 네트워크의 성능이 워낙 좋아져서 서브넷에서 두 노드들 간의 network latency는 전체 시스템 성능에서 무시해도 좋을 만큼 좋아졌습니다. 하지만, 원거리 통신, 즉 inter-region 간에 communication 시에는 0.01 sec. 이상만 되어도 session replication 및 database clustering 등 많은 요소들을 고려해야만 전체 성능을 보장 받을 수 있습니다.

 

위 그림과 같이 Local/Global Cache를 적용했을 때의 구조를 도식화 하였습니다. 우선 Local Cache를 적용하여 성능 분석을 진행한 결과를 공유 드리고, Global Cache를 적용한 버전으로 성능 테스트한 결과를 공유 드리겠습니다.

우선 Local Cache를 적용하였을 때, 아래 테이블 결과에서 메시지 전송 테스트 결과 응답시간 기준으로 0.014sec. 만큼 성능이 좋아 졌다는 것을 알 수 있습니다. 본 결과는 500 동시 접속자를 기준으로 메시지 전송 성능을 계산해보면, cache를 사용하지 않은 경우 (response time: 0.024) 20833 TPS이고, cache를 사용한 경우 (response time: 0.010) 5000 TPS을 성능을 보여 2.4배 이상의 성능이 향상된다는 것을 볼 수 있습니다.

API

Transaction Name

# WAS

Throughput

(Tran./Sec.)

Response

Time (sec)

Resource Utilization

(CPU 사용량, %)

WAS

DB

Proxy

Unicast Messaging API (HTTP)

Send message without cache

1 EA

1415

(목표:1400)

0.024

23.4

36

13.8

Send message with cache

1 EA

1420

(목표:1400)

0.010

22.5

6.5

11.6

다음으로, Push Planet 디바이스들의 Session 정보를 Global Cache (Plandas: SK planet에서 개발한 Redis기반 Cloud Cache 인프라 이용)를 적용하여 응답시간을 측정해 보았습니다. Global Cache 서버는 서브넷에 팜으로 구성되어있는데, 인증 및 라우팅을 수행하는 Coordinator 서버군과 Redis기반 Cache 서버군으로 구성이 되어있습니다.

아래 측정 결과는 좀 더 실제 시나리오와 가깝게 측정하기 위해서 메시지 전송 비율 60%, 세션 업데이트 비율 40%로 측정한 결과입니다.

API

Transaction Name

# WAS

Throughput

(Tran./Sec.)

Response

Time (sec)

Resource Utilization

(CPU 사용량, %)

WAS

DB

Proxy

Unicast Messaging API (HTTP)

Send message without cache

1 EA

1406

(목표:1400)

0.03

23.4

25.4

6.5

Send message with cache

1 EA

1413

(목표:1400)

0.0226

10.4

4.8

6.3

위 결과를 분석해보면, 위의 수행 환경의 공통점은 이미 Local Cache를 적용한 버전 위에서 Global Cache를 적용한 버전과의 차이를 비교하였습니다. 결과적으로 Global Cache를 적용한 버전과 그렇지 않은 버전의 응답 시간의 차이는 약 0.008 sec. 정도의 차이를 보임을 알 수 있는데 응답 시간에서는 큰 차이가 없다고 볼 수도 있지만, 서버의 자원 사용량의 차이를 보면, API 서버 2.2배, DB 서버 5.2배 정도의 자원 사용량 차이가 발생합니다. 동시 접속자 수가 증가하고 트래픽이 많아지면 잠재적인 문제가 발생될 수 있다는 것을 알 수 있습니다. 참고로 Local Cache와 Global Cache 적용 결과에서 Proxy 서버의 자원 사용량에서 Local Cache의 경우 자원 사용량이 더 많은 이유는 첫 번째의 경우 전체 트래픽에서 메시지 전송 API의 요청이 100%로 Proxy 서버의 사용량이 많아 발생되는 결과라고 보시면 됩니다.

아래에는 Plandas의 Dashboard에서 보여주는 Cache Request/Hit/Miss Rate를 보여주고 있습니다. Push Planet 디바이스에서 한 시간 단위로 올라오는 세션 업데이트 정보로, 분 당 10만건 이상의 데이터가 Global Cache를 통해서 가져가고 있다는 것을 볼 수 있습니다.

  • Connection reuse in gateway server

Push Planet은 자체적으로 MQTT 프로토콜 기반으로 모바일 디바이스의 연결을 관리하여 직접 메시지를 전송하는 Push Notification 서비스를 하고 있기도 하지만, Google GCM이나 Apple APNS를 통한 Gateway 역할도 수행하고 있습니다. Push Planet에 연결된 국내 디바이스의 경우에는 네트워크 관점에서 지연이 발생되지 않지만 GCM이나 APNS는 미국 서부에 있는 서버와 연결하여 메시지 전송 요청을 보내야 합니다. 국내에서 미국 서부의 경우 네트워크 지연이 평균 100ms ~ 200ms로 대량의 메시지 전송을 요청할 경우 매우 느린 응답시간으로 성능에 큰 문제를 안고 있습니다. 따라서 이러한 네트워크 지연을 방지하기 위해서는 Connection Pool을 통해서 연결된 네트워크 자원을 재사용해야만 합니다.

Connection을 맺는 비용을 줄이기 위해서 우리는 Apache 에서 제공되는 Object Pool을 적용하여 Connection을 재사용하도록 적용하였습니다. 아래 그림과 같이 Gateway (Proxy)의 worker thread들은 MQ에 메시지가 있으면 가져와서 파싱하고 Channel ID를 보고 해당 채널에 Connection Pool을 생성하여 메시지를 전달하고, 재 요청 시에는 해당 Connection Pool에서 Connection을 가져와 메시지를 전달하게 됩니다. 전체 채널에 대해 같은 Apache Connection Pool을 적용한 것은 기존에 Push Planet에서 사용중인 모듈로, 개발 시간 절약 및 유지보수 향상 측면에서 이득이 있기 때문에 사용하였습니다.

위와 같이 모든 채널에 Connection Pool을 적용함으로써 응답 시간 기준으로 전체 시스템 성능이 1.2배 정도의 향상을 보였습니다. 참고로, Back-end 서버의 성능을 측정하기 위해서는 Loadrunner에서 front-end 서버로 메시지 전송 API를 호출하고, back-end 서버에서 로그 분석을 통해서 초당 메시지 전송 수를 계산하였습니다.

  • Query optimization

Push Planet은 세션 정보 및 인증서 정보에 대해서 캐쉬를 사용하여 응답 시간을 높일 수 있었지만 여전히 RDBMS(MySQL)를 사용해서 쿼리를 많이 수행하고 있습니다. RDBMS에서는 인덱스 기능을 통해서 이전에 수행한 결과를 메모리 영역에서 바로 결과를 되돌려주어 응답 시간을 빠르게 가져갈 수 있습니다. 그래서 대부분의 쿼리 최적화는 이러한 인덱스를 사용할 수 있는 쿼리인지 아닌지에 따라서 성능 차이가 많이 납니다. 특히 디스크에 쓰기 작업을 수행하는 Insert/Update 쿼리는 Select 오퍼레이션에 비해서 부하가 가중되어 되도록 필요할 때만 사용해야 하는데 어플리케이션 영역에서 캐쉬를 통해서 수정된 데이터에 대해서만 업데이트하는 방안도 좋은 방향입니다. 쿼리 최적화를 위해서 아래와 같이 몇 가지 수정한 내용 및 수정된 쿼리에 따른 성능 영향 평가를 수행한 결과를 공유합니다.

첫째, 메시지를 받을 수신자의 라우팅 정보를 가져오는 쿼리의 경우 세 개의 테이블을 조인하여 처리되었고 마지막 세 번째 라우팅 테이블의 경우 LEFT JOIN을 통해서 처리되었는데, 조인을 없애고 라우팅 정보는 캐쉬로 처리하고, 없는 경우에는 SELECT 쿼리를 수행하는 방식으로 수정하였습니다.

SELECT 
		route.endpoint_id ,
		token.channel_type ,
		token.channel_token ,
	        cm.cluster_id
FROM
		channelroute route  , token token
LEFT JOIN cm_mapping cm ON cm.client_id = SUBSTRING_INDEX(endpoint_id, '/', 1)
WHERE
		route.endpoint_id = token.endpoint_id
	        AND route.group_topic = #{group_topic}
	        AND token.channel_type = #{channel_type}
                AND hash_code >= #{from_cluster} AND hash_code <= #{to_cluster}

위의 쿼리를 수정하고, 이전 버전과 성능 비교 분석 결과 약 1.5 배 정도의 성능 향상을 보여줍니다. 쿼리 수정 전 버전은 0.024 sec.의 응답시간을 보였으며, 쿼리 수정 이후 버전은 0.017 sec.의 응답 시간으로, 500 동시 접속자 수를 기준으로 20833 TPS vs 29411 TPS 의 성능 차이를 보임을 알 수 있습니다. 

지금까지, Push Planet의 성능 최적화를 위해 수행했던 몇 가지 작업들을 살펴보았습니다. 이러한 작업들을 통해서 우리는 Push Planet의 메시지 전송 성능을 높일 수 있었는데, 현재의 자세한 성능은 공유하기 어렵지만, 중요한 것은 Parallel Proportion P 값을 0.9 이상으로 향상 시켰다는 것입니다. 앞으로 Push Planet을 Global Platform으로 확장해 나가기 위해서 분산 데이터베이스 구조 및 Inter-region clustering을 위한 몇 가지 작업들을 수행해 나갈 예정입니다. 또한, Internet-of-things 시대에 센서를 포함한 다양한 머신들 간에 기본 메시징 플랫폼으로 변모하기 위한 bi-directional communication channel 및 complex event processing 등에 다양한 이슈들을 풀어 나갈 예정입니다.

참고로, Push Planet에 관련된 자세한 설명은 아래 사이트를 참고하시면 됩니다.

https://push.skplanetx.com/api

김홍수 기술전략실

저는 분산시스템, P2P Grid Computing을 전공하였고, SK Planet에서 Push Notification System에 back-end 서버 개발 및 성능최적화 문제를 해결하는 일에 참여를 하였습니다. 현재 Web Performance Engineering 분야에 관심을 가지고 있습니다.

공유하기