TPS 측정치가 낮은데 이유를 모르겠습니다.

1,473 views
Skip to first unread message

김경송

unread,
Jan 8, 2016, 4:51:23 AM1/8/16
to Netty Korean User Group
기존 C++로 만들던 서버의 TPS는 약 1500 정도였습니다. (HTTP,RTSP 서버)
이중 Netty 로 구현하면 훨씬 좋은 성능을 낼 수 있을것이란 기대를 가지고 HTTP 서버부분만 분리하여 구현을 하였습니다.
(example로 제공되는 snoop http 를 참고해서 구현했습니다.)

버전은 4.0.33 Final 버전입니다.

서버 Bootstrap은 특별한것 없이
// HTTP socket
ServerBootstrap httpBootstrap = new ServerBootstrap();
httpBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
httpBootstrap.group(httpBossGroup, httpWorkerGroup).channel(NioServerSocketChannel.class).childHandler(new HTTPInitializer(sslCtx));

Channel httpChannel;
httpChannel = httpBootstrap.bind(new InetSocketAddress(testIpAddress, testPort)).sync().channel();
Logger.info("Started in IP({}) port({})", SDMServerVersion, testIpAddress, testPort);
위와 같습니다.

initializer 부분도 특별한것 없이 HTTP decoding을 위한 부분만 있습니다. (body parsing은 필요해서 aggregator는 사용합니다.)
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
}
pipeline.addLast("idleStateHandler", new IdleStateHandler(5, 5, 0));
pipeline.addLast(new HttpServerCodec());
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(1048576));
pipeline.addLast(new HTTPHandler());
위와 같습니다.
idlStatsHandler는 유휴소켓 정리를 위해 사용했습니다.

HTTPHandler는 메모리에서 저장되어있는 값을 json형태로 변경해서 return하고..
각각 요청에 대한 spend time을 보면 0ms 에서 길어야 2ms 입니다.

코어는 8개짜리 코어라서 thread dump해보면 group-3-16 까지 16개가 생깁니다.
그래서 쉴새없이 요청을 받고 보내긴 하는데...
TPS가 1,800~1,900 정도에서 유지가 되더라구요. (저는 jmeter와 ngrinder 2개로 테스트 해보았습니다.)

다른 분들 TPS를 보면 훨씬 큰 수치가 나오던데.. 
제가 뭔가 기본적인걸 크게 잘못 생각하는게 아닌가 합니다.

구현이 잘못된 것인지
아니면 혹 network 환경 설정을 달리 해봐야할지
부하를 주는 테스트 방법이 잘못 된 것인지..
감을 못잡겠습니다.

도움 주실 수 있으신분들은 조금이라도 알려주시면 감사하겠습니다.

제가 테스트한 환경은 아래와 같습니다. (그냥 평범한 데스트탑 개발용 PC입니다.)
CPU : Intel I7-4790  (8 core)
Mem : 8기가
OS : Windows ( cent os 에서도 비슷한 tps 수치 확인)

이희승 (Trustin Lee)

unread,
Jan 11, 2016, 11:04:09 PM1/11/16
to nett...@googlegroups.com
HTTP keep-alive 와 pipelining 을 사용하지 않으셔서 그런 것은 아닌지요?
 
--
이 메일은 Google 그룹스 'Netty Korean User Group' 그룹에 가입한 분들에게 전송되는 메시지입니다.
이 그룹에서 탈퇴하고 더 이상 이메일을 받지 않으려면 netty-ko+u...@googlegroups.com에 이메일을 보내세요.
더 많은 옵션을 보려면 https://groups.google.com/d/optout을(를) 방문하세요.
 

김경송

unread,
Jan 11, 2016, 11:32:59 PM1/11/16
to Netty Korean User Group
많이 바쁘실텐데 답변 주셔서 감사합니다! 저도 나름대로 말씀주신것에 대해 얘기해보겠습니다.

1) keep-alive 이용
HTTP keep-alive는 서비스 특성상 필요가 없는 상황입니다.

조금더 자세히 만들고 있는 서버 성격을 설명드리면,
Client의 수가 몇십만이상이 될 수 있고, 
Client가 이서버에 접속하여 서비스 받을 서버의 주소만 HTTP 응답으로 획득하면 됩니다. 
그것으로 새로 만드려는 HTTP 서버의 역할은 끝납니다. (획득한 주소로 client는 이후 서비스를 제공 받음)

즉, 아주 빠른 TPS반응성이 필요한 상황입니다. (이전 서버가 이점이 부족해서 바꾸려는 시도 중입니다.)

즉 다수의 client에서 계속 연결을 유지하며 client들의 요청을 처리하는 형상은 아니고,
한번 연결을 통해 redirect 주소를 client에 전달하고 해당 연결은 바로 해제되는 형상입니다. (그리하여 keep-alive는 이용하지 않아도 된다고 생각했습니다.)

2) pipelining 이용
pipelining은 따로 제가 구현한 것은 없습니다.
initialize 부분에 채널 pipeline에 예제대로 pipeline에 등록한것이 전부인데요. (소스는 이전 글에 첨부)
따로 pipelining을 이용하는 옵션이나 신경쓸 부분이 있는지는 몰랐습니다.
혹시 관련 예제나 참조할만한 4.0.x 대 ref. 소스를 알려주실 수 있으실까요?


2016년 1월 12일 화요일 오후 1시 4분 9초 UTC+9, 이희승 (Trustin Lee) 님의 말:

이희승 (Trustin Lee)

unread,
Jan 12, 2016, 12:37:14 AM1/12/16
to nett...@googlegroups.com
아, 그렇군요. 그렇다면 다음과 같은 방법을 사용해 볼 수 있습니다:
 
우선 epoll transport를 사용합니다.
N개 스레드의 EpollEventLoopGroup을 만들지 않고, 1개 스레드의 EpollEventLoopGroup을 N개 만듭니다.
그리고, SO_REUSEPORT옵션을 이용하여 같은 TCP/IP 포트를 리슨하는 N개의 서버 소켓을 만듭니다. (N개의 ServerBootstrap이 나오겠죠)
ServerBootstrap 설정시 boss와 worker 그룹을 동일하게 설정합니다.
 
대충 이런 식이겠죠:
 
for (int i = 0; i < N; i++) {
  ServerBootstrap b = new ServerBootstrap();
  EventLoopGroup g = new EpollEventLoopGroup(1);
  b.group(g);
  b.channel(EpollServerSocketChannel.class);
  b.option(EpollChannelOption.SO_REUSEPORT);
  b.childHandler(...);
  b.bind(PORT) ....
}
 
이렇게 하여 boss thread 에서 worker thread 로 hand-off 하는 과정에서 발생하는 레이턴시가 줄어들고, epoll transport 의 도움을 받아 전체적으로 스루풋이 다소 향상될 것 같네요.
 

김남철

unread,
Jan 12, 2016, 12:40:28 AM1/12/16
to nett...@googlegroups.com
아 이런 방법이...
좋은 팁이네요...

2016년 1월 12일 오후 2:37, 이희승 (Trustin Lee) <t...@motd.kr>님이 작성:

이희승 (Trustin Lee)

unread,
Jan 12, 2016, 12:42:44 AM1/12/16
to nett...@googlegroups.com
다시 읽어 보니 윈도우즈를 사용하고 계시군요. 윈도우즈에서는 epoll transport 와 SO_REUSEPORT 를 사용할 수 없네요.
 
마찬가지 테크닉을 사용하되 SO_REUSEPORT 대신 SO_REUSEADDR을 사용하여 accept시 약간의 오버헤드를 감수하는 방법도 있습니다만 실제로 해 보지는 않았습니다. 아예 포트를 여러 개 잡아서 하는 방법도 있습니다만 이 부분은 요구사항에 따라 달라지겠네요.

김경송

unread,
Jan 12, 2016, 2:03:01 AM1/12/16
to Netty Korean User Group
앗 테스트의 편의를 위해서 윈도우에서 테스트 했을뿐 실제 운용시에는 윈도우 운영체제는 아닙니다. 
실제 서버가 운영될 곳은 Linux 서버입니다. (CentOS)

말씀해주신 부분을 시도해 보도록 하겠습니다. 
정말 감사드립니다!

2016년 1월 12일 화요일 오후 2시 42분 44초 UTC+9, 이희승 (Trustin Lee) 님의 말:

산하

unread,
Jan 21, 2016, 8:28:52 PM1/21/16
to Netty Korean User Group
운영체제 설정을 확인해 보십시오.

  • 프로세스상 사용할 수 있는 최대 Open File Descriptor 갯수 늘이기
    • 시스템에서 소켓은 하나의 File Descriptor 에 대응된다. 따라서 다수의 동접을 처리할 수 있기 위해선 프로세스당 오픈할 수 있는 최대 FD 갯수가 충분해야 한다. 하기는 CentOS 예
      • /etc/security/limits.conf 에서 hard nofile 과 soft nofile 갯수를 충분히 (최대 65535) 늘인다.
  • 소켓 Close 시의 TIME_WAIT 시간 줄이기
    • 소켓 종료를 시작하는 쪽에서는 마지막 ACK 전송 이후에 일정시간 대기후에 소켓을 정리한다.
    • 따라서, 다수의 동접을 처리하기 위해서는 해당 TIME_WAIT 시간이 최소화되어야 한다.
    • 하기는 CentOS 예
      • /etc/sysctl.conf 에서 net.ipv4.tcp_fin_timeout 을 작게 (최소 1) 설정한다.
  • Client 소켓 Ephemeral Port 범위 늘이기
    • 클라이언트는 서버에 접속시 포트 번호를 커널로부터 랜덤하게 할당받게 된다.
    • 따라서 다수의 동접을 처리하기 위해서는 클라이언트 랜덤 포트 범위가 충분해야한다.
    • 하기는 CentOS 예
      • /etc/sysctl.conf 에서 net.ipv4.ip_local_port_range 범위를 충분히 넓게 (최대 65535) 설정한다.

2016년 1월 8일 금요일 오후 6시 51분 23초 UTC+9, 김경송 님의 말:

김경송

unread,
Jan 22, 2016, 6:25:44 AM1/22/16
to Netty Korean User Group
산하님 감사합니다.
안그래도 말씀하신 부분에 대해서도 의심이 있었는데,
실제 설정 방법까지 알려주시니 정말 감사합니다.
관련 설정도 변경해 가면서 테스트 해보도록 하겠습니다. ^^

2016년 1월 22일 금요일 오전 10시 28분 52초 UTC+9, 산하 님의 말:
Reply all
Reply to author
Forward
0 new messages