주로 ab와 wrk 등을 이용해서 애플리케이션들에 대한 테스트를 수행했다. 단일 애플리케이션 성능 측정에는 쓸만하지만 분산 시스템 성능 측정에 써먹기는 애매모호하다. 아래와 같은 분산 시스템이 있다고 가정해 보자.
테스트를 쉽게 하기 위해서 모델을 단순화 했다. 중요한 점은 비동기 구간이 있어서, 단순히 Frontend API Server의 응답속도를 측정하는 걸로는 원하는 정보를 얻을 수 없다는 것이다. 이런 모델에서 가장 중요한 성능지표는 Frontend API server 에서 보낸 메시지가 클라이언트 애플리케이션까지 도착하는 시간이다. 이를 위해서 측정해야 하는 정보들은 아래와 같다.
단일 Frontend API Server의 초당 처리량은 얼마나 되는가.
초당 처리량이 정해질 경우 적절한 동시 접속.
동시 접속이 늘어남에 따라서 메시지 도착에 걸리는 시간.
이 외에도 각 시스템의 CPU, 메모리 자원을 측정하는 것도 중요한
분산 시스템 성능 측정 방안
시스템 구축
아래와 같은 시스템을 구축하기로 했다.
패킷 생성기 : wrk를 이용해서 HTTP 패킷을 만들기로 했다.
API Server : 성능과 생산성을 모두를 얻기 위해서 Go 언어를 선택했다. HTTP 요청을 받아서 REDIS Queue에 쌓는다.
REDIS : 메시지 큐 역할을 한다.
Message Receiver : REDIS 큐로 부터 메시지를 읽는다. 읽은 메시지는 MQTT Push Server로 보낸다. 역시 Go 언어로 개발한다.
MQTT Push server : MQTT Push server다. Mosquitto로 구성한다.
Logging server : 시간기록을 위한 로깅 서버를 만들었다. 각 요청은 MsgID를 가지고 있다. MsgID를 이용해서 메시지를 추적할 수 있다. API 서버는 MsgID를 키로 현재 시간을 로깅 서버에 저장한다. MQTT Client도 MsgID와 시간을 저장한다.
Logging
로깅 서버(Logging server)에 저장한 시간을 이용하면, 메시지가 MQTT 클라이언트(MQTT Client)에 도착한 시간을 계산 할 수 있다.
MsgID.spentTime = MsgID.endtime - MsgID.starttime
문제는 시간의 동기화다. 성능측정은 마이크로초 단위에서 이루어지기 때문에, NTP등을 이용하는 것으로는 서로 다른 서버(여기에서는 API Server와 MQTT 클라이언트)의 시간을 일치시키는 건 불가능에 가깝다. 이 문제는 로깅을 하는 서버의 시간을 기준으로 삼는 것을 해결 할 수 있다. REDIS는 TIME 명령을 제공하는데, 이 명령을 실행하면 REDIS 서버의 시간을 반환한다.
# redis-cli -h logserver
logserver:6379> TIME
1) "1425055608"
2) "756492"
MsgID 생성
MsgID를 이용해서 로그를 추적한다. 따라서 각 패킷에 서로 다른 MsgID 값을 줘야 한다. 패킷 생성기는 HTTP Body에 MsgID를 포함한 데이터를 실어 보낸다.
POST / HTTP/1.1
Host: apiserver
{
"MsgID":1234,
"source":"client-id",
"target":"target-id"
}
데이터를 받은 API server는 데이터를 unmarshal해서 MsgID를 가져와서 기록 한다.
Logging 샘플링
극한의 상황에서 밴체마크 툴을 돌린다. 이 경우 로깅 행위 자체가 성능에 영향을 줄 수 있다. 성능에 영향을 최소화 하기 위해서 샘플링 하기로 했다. "MsgID % 100 == 0" 식을 이용 1/100 으로 샘플링 하기로 했다.
Logging 저장 과정
각 소프트웨어 컴포넌트들은 요청받은 메시지의 MsgId를 이용해서, REDIS에 리스트(list)자료 구조로 저장한다. Reporting Server는 로그 정보를 취합해서 응답 시간을 계산한다.
wrk를 이용한 트래픽 생성
패킷 생성기는 MsgID를 만들 수 있어야 한다. 즉 데이터를 동적으로 만들 수 있는 툴을 선택해야 했다. 나는 wrk를 선택했는데, wrk가 내장한 lua 스크립팅을 이용해서 이용해서 HTTP 헤더와 바디를 자유롭게 제어 할 수 있었기 때문이다
MsgID는 일련번호로 하기로 했다. 일련번호로 할 경우 약간의 문제가 있긴하다. wrk는 스레드를 만들어서 대량의 패킷을 만드는데, 각 스레드가 독립적으로 작동한다. 따라서 8개의 스레드로 트래픽을 만들경우, 8개의 동일한 MsgID가 만들어진다. 뭐, 크게 상관이 없기는 한데, 기분이 꿀꿀하면 랜덤값을 사용하는 방법이 있기는 하다.
트래픽을 두 대 이상의 서버에서 분산해서 만들겠다면, MsgID에 서버 식별자를 붙이는 등의 작업을 해야 할 거다.
잘 만들어졌다. 메시지가 목적지까지 도착한 시간은 "1426087754.105100 - 1426087754.104515 == 0.000585"이다.
정식 테스트
wrk를 이용해서 정식으로 테스트를 수행한다. 1부터 20까지 동접을 늘려가면서 테스트를 수행한다. 자동으로 테스트를 수행하는 파이슨 스크립트를 만들었다. 예쁜 코딩 같은 건 신경쓰지 않았다. 결과는 표준출력 되며, 이걸 파일로 저장해서 gnuplot로 시각화 하기로 했다. (파이선용 gnuplot 패키지가 있긴 하던데..)
import os
import re
import time
import redis
import json
# 표준출력된 wrk 테스트 결과를 파싱해서 원하는 형식으로 만든다.
# 동접\tLatency\t초당처리건수를 출력한다.
def report(c, lines):
rtv = []
for str in lines:
str=re.sub("^[ \t]+",'', str)
if str.find("Latency") == 0 or str.find("Requests/sec") == 0:
token = re.split("[ \t]+", str)
value = re.sub("[^0-9\.]", '', token[1])
if re.match("[0-9\.]+us", token[1]):
rtv.append(float(value)/1000)
elif re.match("[0-9\.]+s", token[1]):
rtv.append(float(value)*1000)
else:
rtv.append(float(value))
return "%d\t%f\t%f" % (c, rtv[0], rtv[1])
def run(cmd):
stdin, stdout, stderr = os.popen3(cmd)
return stdout.readlines()
offset = 5
maxConcurrency = offset * 61
duration = 10
# redis 서버에 연결
r = redis.StrictRedis(host='redis-server', port=6379, db=0)
# 1부터 10까지 동접을 늘려가면서 테스트 한다.
for num in range(1, 11):
cmd = ("wrk -t %d -c %d -d 10s http://apiserver:8080 -s post.lua" % (num, num))
result =run(cmd)
report_str =report(num, result)
time.sleep(15)
# apiserver의 로그와 mqttclient의 로그를 이용해서, 도착까지 걸린 시간을 구한다.
# 제대로 하려면 기록된 모든 msgid에 대한 처리 시간을 구한다음, 평균을 내야 할 것이다.
# 귀찮아서 제일 마지막에 기록된 msgid로만 연산을 하기로 했다. (별차이 없다.)
start_data = json.loads(r.lrange('apiserver', 0, 0)[0])
end_data = json.loads(r.lrange('mqttclient', 0, 0)[0])
print report_str,"\t",float(end_data['LogTime']) - float(start_data['LogTime'])
r.flushall()
time.sleep(5)
Contents
분산 시스템 성능 측정의 어려움
분산 시스템 성능 측정 방안
시스템 구축
Logging
MsgID 생성
Logging 샘플링
Logging 저장 과정
wrk를 이용한 트래픽 생성
wrk 설치
기본 사용
Lua 스크립팅
개발
환경
Go 개발 환경 설정
Logging 라이브러리
API Server 개발
Push Server 개발
MQTT Client
테스트
curl을 이용한 단위 테스트
정식 테스트
Recent Posts
Archive Posts
Tags