NET최선단 기술에
의한 고성능 웹 애
플리케이션
티쓰리엔터테인먼트
모바일 1팀
공통 기술 개발팀
최흥배 과장
http://www.slideshare.net/neuecc/net-22662425

지옥의 발할라 게이트
소셜 게임의 규모감
 보통 웹 애플리케이션
유저 1 클릭 마다 DB 갱신을 하는 등 부하가 높다
1유저 PV 수, 채존 시간도 통상의 웹에 비해서 길다
 픽크 시 5000 리퀘스트/sec 이상
 데일리 1억 리퀘스트 이상

 아주 높은 부하가 걸리는 웹 애플리케이션
 지옥의 발할라 게이트는 PHP로 동작하고 있음....
PHP -> C#
 왜 PHP?
여러 사정이 있음....
 현재 C#으로 전명 이행 작업 중
왜 C# ?
 퍼포먼스 상 문제
압도적인 피하지방률
대수 증가율에 의한 영향
단 CakePHP라는 엿같은 프레임워크 탓도 있음
 개발 효율의 문제
컴파일러 에러로 발견할 수 있는 문제로 서버 다운
IntelliSense가 너무 좋음
빈약한 컬력션 처리(LINQ가 없음)
그 외에도 아주 많음
AWS를 이요한 현재(PHP) 구성
C#으로 바꿀 때의 구성
기본 구성
 Windows Server 2012(EC2)
AWS의 Windows Server 인스턴스
IIS8 + .NET Framework 4.5
 RDS(MySQL)
AWS의 매니지드한 데이터 베이스 서비스
RDS에는 SQL Server도 있음
처음부터 다신 만든다면 SQL Server를 선택
 Redis(EC2 Amazon Linux)
인메모리형 KVS
캐시 세션, 그 외 NoSQL적인 사용
왜 RDBMS를 사용하는가?
 NoSQL 로 좋을까?
Azure Table, Riak, Dynamo 등 무한으로 스케일 할 수 있어서?
기능면으로는 만족할지 모르지만 그냥 선택하지 않음
적어도 '발할라 게이트' 규모에서는 RDBMS로 괜찮음
수평적 분할을 해봤지만 골치 아파서 수직적 분할만 한 상태
 이점
SQL을 사용하여 쉽게 해석
데이터 다루기가 쉽다
주변 툴이 많다
DB측의 성능 문제를 위한 수직 분할
 1대로는 부하를 견딜 수 없으므로 기능 단위로 수직 분할
유저 정보/ 길드 정보/ 배틀 정보 등으로 나눈다
 현재 6개로 분할
 테이블이 물리적으로 다른 DB로 나누어졌기 때문에 외부 Key는 안됨
따라서 일절 외부 Key를 사용하지 않는다

 쿼리에 제한( DB를 넘어선 조인은 불가능)
 수평 분할은 무한으로 스케일 할 수 있지만 최종 수단으로 최대한 피한다
기술 가능한 쿼리에 꽤 제한이 걸린다
애플리케이션 측에서 분할 제어를 위한 구현이 필요하다
애드훅 쿼리로 집계가 불가능 하다
MySQL 툴
 HeidiSQL
쿼리 작성, 테이블 정의, Export
MySQL Workbench 보다도 사용하기 쉬움
 JetProfiler
리얼타임 프로 파일러
Linux, Mac, Windows
C#에서 DB 다루기
 EntityFramework 등의 중량급 O/R 맵은 사용하지 않는다
DB의 수직 분할에 의해 외부 key에 의한 릴레이션이 존재하지 않으므로, ORM
퀄리 생성을 못한다.
마스터를 적극적으로 캐시 하는 등 인 메모리 결합이 중심이기 때문에 ORM 죠
인 추상화를 전혀 살리지 못한다.
그 때문에 단순한 쿼리가 많아서 생 SQL도 그다지 힘들지 않다.
디자인 등 ORM 유지보수의 비용이 높다.
느리다.
Micro-ORM
 DataRow => Object로 변환만을 담당
 우리는 Dapper를 채용
문자열로 생 SQL을 적어서 <T>로 맵핑, 그것만으로
아주 빠르다.
Dapper만으로 간단한 것들은 준비 되어 있다.
성능 비교
 HandCoded는 ADO.NET의 DataReader를 사용하여 데이터 읽기
 Dapper는 HandCoded와 거의 비슷
EF는 예전 버전이라서 최신 버전에서는 조금 더 성능 개선이 있다
컨넥션 형 붙이기
 물리적으로 기계가 다르므로 각각의 기계에 대해서 형으로 나눈다

 단순하지만 실수 방지나 문서적인 의미로 효과가 있다
(MySQL이므로) Master, Slave를 속박하는 것도 피하고 있다
캐시의 단위
직렬화
 Redis에서 캐시할 때 오브젝트 직렬화 형식은 Protobuf-net을 채용
 Protocol Buffers는 사이즈, 속도 모두 우수
성능은 포맷과 구현으로 결정.
그래서 C++이나 Ruby에서의 성능 비교는 .NET에 적용할 수 없다.
.NET 구현의 protobuf-net은 경험도 있어서 안정감이 있다.
세션 저장소
 애플리케이션 서버가 복수대가 되면 인 메모리의 기본 세션은 사용할 수 없다
 세션 저장소로 Redis를 채용
 단 ASP.NET의 세션 프로바이더로서 구현하지 않는다
Protobuf-net에 의한 직렬화와 직렬화 풀기가 필요하기 때문
할려고 하면 할 수는 있지만....
구현에 꽤 시간이 걸린다
 따라서 간단하게 우리가 사용할 Redis 세션 스토어를 만든다
파이프라인
 Redis의 특징으로서 파이프라인 기능이 있다
 예를 들면 3회 데이터를 가져올 때
커맨드 통신(GET) -> 결과 수신(RES),
커맨드 통신(GET) -> 결과 수신(RES),
커맨드 통신(GET) -> 결과 수신(RES)
 파이프라인 이라면
커맨드 통시(GET,GET,GET) -> 수신 결과(RES,RES,RES)
송수신 통신 비용이 한번으로 끝난다
BookSleeve
 C#제의 Redis 라이브러리
특징은 모든 것이 비동기, 모든 것이 파이프라인
 모든 리퀘스트가 컨넥션을 공유한다
다양한 리퀘스트 커맨드가 자동적으로 파이프라인 화 되어 비동기 통신 하므로
동시 접근이 있다면 어느 정도 효율적
 다루기 쉽도록 상위 라이브러리 작성, 이용
BookSleeve는 모두 byte[]이므로, 직렬화 하는등
https://github.com/neuecc/CloudStructures
동기적 상황

 GetA로 Redis나 DB 접근 등이 있고 각각 10ms 걸린다면
 3회 접근으로 30ms가 걸린다
비동기적 상황

 GetAAsync 등으로 비동기로 접근한다
결과로서 각각 10ms 걸리는 것은 변하지 않는다
 3회 접근으로 30ms가 걸린다
비동기적 상황 2

 Task.WhenAll로 기다리는 것으로 비동기가 동시에 진행한다
 결과로서 1회만 접근하여 10ms로 끝난다
 BookSleeve는 이와 같은 대기가 쉽다는 것이 큰 장점
Lazy Revisited
 예전부터의 Lazy 스타일
 프로퍼티에 처음 접근할 때에 생성
 Pros
사용하는 것이 간단
 Cons
그것이 연기되는 것인지 알 수 없다
그냥 호출하니까 DB 접근! 이랄까
Lazy Revisited
 Lazy<T> 스타일
 Pros
Lazy가 명시적
 Cons
사용하는 것이 귀찮음(매번 .value...)
AsyncLazy
 Awaitable한 Lazy
 오리지널은 MS의 Pfx팀에서
http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx
 조금 커스텀마이즈 했음
AsyncLazy + Redis/DB
AsyncLazy + Redis/DB
AsyncLazy
 Pros
BookSleeve 자동 파이프라인과 합쳐서 만든 모델 클래스가 있어도 MGET적인
효율적인 취득을 할 수 있다.
DB의 멀티 스레드에 의한 동시 초기화를 자연스럽게 기술할 수 있다.
 Cons
귀찮음....
비동기에서 조심해야 할 점
 TransactionScope 내에서 await 할 수 없다
다른 스레드가 되므로 실행 시 예외가 된다.
수동으로 BeginTransaction으로 회피
 데드락
.Result/Wait로 하면 데드락 하는 경우가 있다.
모두 async로 통하면 데드락 하지 않는다.....
TransactionScope 사용하고 싶다면 그 중에서 동기적으로 기다려야 한다.
필터가 비동기 미대응이므로 필터 내에서 쓰는 경우는 기다릴 수 밖에 없다.
신경 써서 데드락 하지 않도록 기술 한다.
 ThreadPool 증가가 느리다
ThreadPool이 늘어나는 타이밍은 즉각적이지 않다.
IO 대기를 예상할 수 있다면 ThreadPool.SetMinThread로 처음부터 늘리는 것이
효과적이다
Parallel.ForEach
 Web 애플리에서는 Task+WhenAll 중심으로 사용할 기회가 적음
 배치 처리로 DB에 대량의 insert/update에 이용
 싱글 스레드에서 1시간 -> 병렬로 5분
스레드 세이프 컨넥션
 (My)SqlConnection는 스레드 세이프가 아닌다
 Parallel 중에서 열거나 or 외측에서 ThreadLocal로 감싼 스레드 세이프로 하기

 자세한 것은 Web에서
병렬 실행과 SqlConnection http://neue.cc/2013/03/09_400.html
주의점
 수 백만 건의 처리 정도에서도 Max로 100 병렬은 충분히 넘으므로(I/O 대기 동
안 새로운 Task가 자동적으로 실행한다) 컨넥션 풀 상한 설정에 신경 써야 한다
기본으로는 100이므로, 크게 예상하는 것이 좋다.
무섭다면 연채로 두지말고 확실하게 Open/Close 한다
 어디를 아끼고, 어디를 크게 써야 하는지 잘 판단해야 한다
Async ?
 ExecuteReaderAsync
사실 C#+MySQL에서는 의미가 없다.
MySQL 라이브러리가 Delegate.BeginInvoke로 ExecuteReader를 감싸고 있으므
로.....
 사용하는 Driver이 올바르게 대응하고 있는지 확인을.
단순하게 Task로 포장할 뿐, Delegate.BeginInvoke로 포장하는 등 그럴 가능성이
충분히 있다
모니너터링 - MiniProfiler
 .NET/Ruby용 심플한 화면의 통합 프로 파일러
NuGet으로 설치 가능
 응답 시간과 DB 프로파일링을 할 수 있음
모니터링 - 로그 출력
 로거는 NLog를 이용
 화면 하부에도 로그 출력
HttpContext 단위로 보존하는
커스텀로거를 작성.
 Redis 발행은 Key와 응답 시간을
모두 로그로
모니터링 - 숫자는 보이는 곳으로
 무엇이 어느 정도로 빠른지, 느린지를 쉽게 인식하도록
병목을 사전에 알아채도록
 개발 환경도 실전과 비슷한 네트워크 구성을 한다
네트워크에 따라서 Redis가 DB 보다 느릴 때가 있다.
예를 들면 Redis가 10ms로 DB가 1ms가 되던가
이러면 Redis에서 캐시하지 않는 쪽이 빠르잖아...라고 된다
의미 없음
 이런 구축을 하기 쉬운 것이 클라우드 이다
New Relic
 PHP, Ruby, .NET, java, Python에 대응한 성능 감시 서비스
 인스톨도 초 간단
 에러 율이나 응답 시간 저하 등을 설정하여 iPhone 앱에서 Push 통지
New Relic
 슬로우 리퀘스 때 완전한 스택 트레이스가 보인다
 미처리 예외도 아주 쉽게 보인다
그래프화, 상세 화면
건 당 정렬, 필터
 SQL 슬로 쿼리나 분합 등도
NET 최선단 기술에 의한 고성능 웹 애플리케이션

NET 최선단 기술에 의한 고성능 웹 애플리케이션

  • 1.
    NET최선단 기술에 의한 고성능웹 애 플리케이션 티쓰리엔터테인먼트 모바일 1팀 공통 기술 개발팀 최흥배 과장
  • 2.
  • 3.
    소셜 게임의 규모감 보통 웹 애플리케이션 유저 1 클릭 마다 DB 갱신을 하는 등 부하가 높다 1유저 PV 수, 채존 시간도 통상의 웹에 비해서 길다  픽크 시 5000 리퀘스트/sec 이상  데일리 1억 리퀘스트 이상  아주 높은 부하가 걸리는 웹 애플리케이션  지옥의 발할라 게이트는 PHP로 동작하고 있음....
  • 4.
    PHP -> C# 왜 PHP? 여러 사정이 있음....  현재 C#으로 전명 이행 작업 중
  • 5.
    왜 C# ? 퍼포먼스 상 문제 압도적인 피하지방률 대수 증가율에 의한 영향 단 CakePHP라는 엿같은 프레임워크 탓도 있음  개발 효율의 문제 컴파일러 에러로 발견할 수 있는 문제로 서버 다운 IntelliSense가 너무 좋음 빈약한 컬력션 처리(LINQ가 없음) 그 외에도 아주 많음
  • 6.
  • 7.
  • 8.
    기본 구성  WindowsServer 2012(EC2) AWS의 Windows Server 인스턴스 IIS8 + .NET Framework 4.5  RDS(MySQL) AWS의 매니지드한 데이터 베이스 서비스 RDS에는 SQL Server도 있음 처음부터 다신 만든다면 SQL Server를 선택  Redis(EC2 Amazon Linux) 인메모리형 KVS 캐시 세션, 그 외 NoSQL적인 사용
  • 9.
    왜 RDBMS를 사용하는가? NoSQL 로 좋을까? Azure Table, Riak, Dynamo 등 무한으로 스케일 할 수 있어서? 기능면으로는 만족할지 모르지만 그냥 선택하지 않음 적어도 '발할라 게이트' 규모에서는 RDBMS로 괜찮음 수평적 분할을 해봤지만 골치 아파서 수직적 분할만 한 상태  이점 SQL을 사용하여 쉽게 해석 데이터 다루기가 쉽다 주변 툴이 많다
  • 10.
    DB측의 성능 문제를위한 수직 분할  1대로는 부하를 견딜 수 없으므로 기능 단위로 수직 분할 유저 정보/ 길드 정보/ 배틀 정보 등으로 나눈다  현재 6개로 분할  테이블이 물리적으로 다른 DB로 나누어졌기 때문에 외부 Key는 안됨 따라서 일절 외부 Key를 사용하지 않는다  쿼리에 제한( DB를 넘어선 조인은 불가능)  수평 분할은 무한으로 스케일 할 수 있지만 최종 수단으로 최대한 피한다 기술 가능한 쿼리에 꽤 제한이 걸린다 애플리케이션 측에서 분할 제어를 위한 구현이 필요하다 애드훅 쿼리로 집계가 불가능 하다
  • 11.
    MySQL 툴  HeidiSQL 쿼리작성, 테이블 정의, Export MySQL Workbench 보다도 사용하기 쉬움  JetProfiler 리얼타임 프로 파일러 Linux, Mac, Windows
  • 12.
    C#에서 DB 다루기 EntityFramework 등의 중량급 O/R 맵은 사용하지 않는다 DB의 수직 분할에 의해 외부 key에 의한 릴레이션이 존재하지 않으므로, ORM 퀄리 생성을 못한다. 마스터를 적극적으로 캐시 하는 등 인 메모리 결합이 중심이기 때문에 ORM 죠 인 추상화를 전혀 살리지 못한다. 그 때문에 단순한 쿼리가 많아서 생 SQL도 그다지 힘들지 않다. 디자인 등 ORM 유지보수의 비용이 높다. 느리다.
  • 13.
    Micro-ORM  DataRow =>Object로 변환만을 담당  우리는 Dapper를 채용 문자열로 생 SQL을 적어서 <T>로 맵핑, 그것만으로 아주 빠르다. Dapper만으로 간단한 것들은 준비 되어 있다.
  • 14.
    성능 비교  HandCoded는ADO.NET의 DataReader를 사용하여 데이터 읽기  Dapper는 HandCoded와 거의 비슷 EF는 예전 버전이라서 최신 버전에서는 조금 더 성능 개선이 있다
  • 15.
    컨넥션 형 붙이기 물리적으로 기계가 다르므로 각각의 기계에 대해서 형으로 나눈다  단순하지만 실수 방지나 문서적인 의미로 효과가 있다 (MySQL이므로) Master, Slave를 속박하는 것도 피하고 있다
  • 16.
  • 17.
    직렬화  Redis에서 캐시할때 오브젝트 직렬화 형식은 Protobuf-net을 채용  Protocol Buffers는 사이즈, 속도 모두 우수 성능은 포맷과 구현으로 결정. 그래서 C++이나 Ruby에서의 성능 비교는 .NET에 적용할 수 없다. .NET 구현의 protobuf-net은 경험도 있어서 안정감이 있다.
  • 18.
    세션 저장소  애플리케이션서버가 복수대가 되면 인 메모리의 기본 세션은 사용할 수 없다  세션 저장소로 Redis를 채용  단 ASP.NET의 세션 프로바이더로서 구현하지 않는다 Protobuf-net에 의한 직렬화와 직렬화 풀기가 필요하기 때문 할려고 하면 할 수는 있지만.... 구현에 꽤 시간이 걸린다  따라서 간단하게 우리가 사용할 Redis 세션 스토어를 만든다
  • 19.
    파이프라인  Redis의 특징으로서파이프라인 기능이 있다  예를 들면 3회 데이터를 가져올 때 커맨드 통신(GET) -> 결과 수신(RES), 커맨드 통신(GET) -> 결과 수신(RES), 커맨드 통신(GET) -> 결과 수신(RES)  파이프라인 이라면 커맨드 통시(GET,GET,GET) -> 수신 결과(RES,RES,RES) 송수신 통신 비용이 한번으로 끝난다
  • 20.
    BookSleeve  C#제의 Redis라이브러리 특징은 모든 것이 비동기, 모든 것이 파이프라인  모든 리퀘스트가 컨넥션을 공유한다 다양한 리퀘스트 커맨드가 자동적으로 파이프라인 화 되어 비동기 통신 하므로 동시 접근이 있다면 어느 정도 효율적  다루기 쉽도록 상위 라이브러리 작성, 이용 BookSleeve는 모두 byte[]이므로, 직렬화 하는등 https://github.com/neuecc/CloudStructures
  • 21.
    동기적 상황  GetA로Redis나 DB 접근 등이 있고 각각 10ms 걸린다면  3회 접근으로 30ms가 걸린다
  • 22.
    비동기적 상황  GetAAsync등으로 비동기로 접근한다 결과로서 각각 10ms 걸리는 것은 변하지 않는다  3회 접근으로 30ms가 걸린다
  • 23.
    비동기적 상황 2 Task.WhenAll로 기다리는 것으로 비동기가 동시에 진행한다  결과로서 1회만 접근하여 10ms로 끝난다  BookSleeve는 이와 같은 대기가 쉽다는 것이 큰 장점
  • 24.
    Lazy Revisited  예전부터의Lazy 스타일  프로퍼티에 처음 접근할 때에 생성  Pros 사용하는 것이 간단  Cons 그것이 연기되는 것인지 알 수 없다 그냥 호출하니까 DB 접근! 이랄까
  • 25.
    Lazy Revisited  Lazy<T>스타일  Pros Lazy가 명시적  Cons 사용하는 것이 귀찮음(매번 .value...)
  • 26.
    AsyncLazy  Awaitable한 Lazy 오리지널은 MS의 Pfx팀에서 http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx  조금 커스텀마이즈 했음
  • 27.
  • 28.
  • 29.
    AsyncLazy  Pros BookSleeve 자동파이프라인과 합쳐서 만든 모델 클래스가 있어도 MGET적인 효율적인 취득을 할 수 있다. DB의 멀티 스레드에 의한 동시 초기화를 자연스럽게 기술할 수 있다.  Cons 귀찮음....
  • 30.
    비동기에서 조심해야 할점  TransactionScope 내에서 await 할 수 없다 다른 스레드가 되므로 실행 시 예외가 된다. 수동으로 BeginTransaction으로 회피  데드락 .Result/Wait로 하면 데드락 하는 경우가 있다. 모두 async로 통하면 데드락 하지 않는다..... TransactionScope 사용하고 싶다면 그 중에서 동기적으로 기다려야 한다. 필터가 비동기 미대응이므로 필터 내에서 쓰는 경우는 기다릴 수 밖에 없다. 신경 써서 데드락 하지 않도록 기술 한다.  ThreadPool 증가가 느리다 ThreadPool이 늘어나는 타이밍은 즉각적이지 않다. IO 대기를 예상할 수 있다면 ThreadPool.SetMinThread로 처음부터 늘리는 것이 효과적이다
  • 31.
    Parallel.ForEach  Web 애플리에서는Task+WhenAll 중심으로 사용할 기회가 적음  배치 처리로 DB에 대량의 insert/update에 이용  싱글 스레드에서 1시간 -> 병렬로 5분
  • 32.
    스레드 세이프 컨넥션 (My)SqlConnection는 스레드 세이프가 아닌다  Parallel 중에서 열거나 or 외측에서 ThreadLocal로 감싼 스레드 세이프로 하기  자세한 것은 Web에서 병렬 실행과 SqlConnection http://neue.cc/2013/03/09_400.html
  • 33.
    주의점  수 백만건의 처리 정도에서도 Max로 100 병렬은 충분히 넘으므로(I/O 대기 동 안 새로운 Task가 자동적으로 실행한다) 컨넥션 풀 상한 설정에 신경 써야 한다 기본으로는 100이므로, 크게 예상하는 것이 좋다. 무섭다면 연채로 두지말고 확실하게 Open/Close 한다  어디를 아끼고, 어디를 크게 써야 하는지 잘 판단해야 한다
  • 34.
    Async ?  ExecuteReaderAsync 사실C#+MySQL에서는 의미가 없다. MySQL 라이브러리가 Delegate.BeginInvoke로 ExecuteReader를 감싸고 있으므 로.....  사용하는 Driver이 올바르게 대응하고 있는지 확인을. 단순하게 Task로 포장할 뿐, Delegate.BeginInvoke로 포장하는 등 그럴 가능성이 충분히 있다
  • 35.
    모니너터링 - MiniProfiler .NET/Ruby용 심플한 화면의 통합 프로 파일러 NuGet으로 설치 가능  응답 시간과 DB 프로파일링을 할 수 있음
  • 36.
    모니터링 - 로그출력  로거는 NLog를 이용  화면 하부에도 로그 출력 HttpContext 단위로 보존하는 커스텀로거를 작성.  Redis 발행은 Key와 응답 시간을 모두 로그로
  • 37.
    모니터링 - 숫자는보이는 곳으로  무엇이 어느 정도로 빠른지, 느린지를 쉽게 인식하도록 병목을 사전에 알아채도록  개발 환경도 실전과 비슷한 네트워크 구성을 한다 네트워크에 따라서 Redis가 DB 보다 느릴 때가 있다. 예를 들면 Redis가 10ms로 DB가 1ms가 되던가 이러면 Redis에서 캐시하지 않는 쪽이 빠르잖아...라고 된다 의미 없음  이런 구축을 하기 쉬운 것이 클라우드 이다
  • 38.
    New Relic  PHP,Ruby, .NET, java, Python에 대응한 성능 감시 서비스  인스톨도 초 간단  에러 율이나 응답 시간 저하 등을 설정하여 iPhone 앱에서 Push 통지
  • 39.
    New Relic  슬로우리퀘스 때 완전한 스택 트레이스가 보인다  미처리 예외도 아주 쉽게 보인다 그래프화, 상세 화면 건 당 정렬, 필터  SQL 슬로 쿼리나 분합 등도