서버 성능 테스트를 해야하는 업무가 발생하여 어떤식으로 진행했는지 공유 해보려고 합니다.
하루 기준 약 300만의 유저가 1~2일 동안 저희 서비스에 유입이될 수 있는 상황이였습니다.
이에 따라 3000000 / 24 / 60 / 60 1초에 약 34명이 유입될 수 있다고 단순하게 계산을 한 후
서버 성능 테스트를 진행해보기로 했습니다.
상황에 따라 어느 언어나 툴을 사용하던 시나리오를 잘 구성하여 체크하면 된다고하여
가장 효율적인 방법을 고민하게 되었고,
가장 자신 있는 C# Conole 프로젝트로 진행 해보려고 합니다.
시나리오
필수 요소인 회원가입 & 로그인 작업 이후 유저가 실제로 행동할 Task를 분석하여
시나리오를 구성해보았습니다.
1초에 30명 ~ 50명 유저가 유입된다.
모든 유저는 각 Task 당 약 1초의 delay로 진행된다.
A (로그인) -> B Task -> C Task -> D Task -> B Task -> C Task -> D Task -> Exit
성능 테스트용 봇 개발
.NET Console 프로젝트를 활용하여 개발 하였습니다.
using log4net;
using log4net.Config;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
class PerformanceTestBot
{
private static readonly ILog log = LogManager.GetLogger(typeof(PerformanceTestBot));
private static readonly HttpClient client = new HttpClient();
private const int userCountPerSecond = 30; // 초당 생성할 유저 수
private static bool keepRunning = true;
private static int userCounter = 1; // 생성 시작할 유저 id
static async Task Main(string[] args)
{
// Log4net 초기화
XmlConfigurator.Configure(new FileInfo("log4net.config"));
log.Info("Starting performance test...");
Console.CancelKeyPress += (sender, e) =>
{
e.Cancel = true;
keepRunning = false;
log.Info("Stopping user creation...");
};
await StartUserCreationLoop(userCountPerSecond);
log.Info("Performance test completed.");
}
// 유저 생성 루프
private static async Task StartUserCreationLoop(int userCountPerSecond)
{
while (keepRunning)
{
var tasks = new List<Task>();
for (int j = 0; j < userCountPerSecond; j++)
{
tasks.Add(CreateAndRunScenario());
}
_ = Task.WhenAll(tasks);
await Task.Delay(10000); //1초
}
}
// 유저 생성 후 시나리오 실행
private static async Task CreateAndRunScenario()
{
int userId = Interlocked.Increment(ref userCounter);
// SSL 인증서 무시 설정을 추가한 HttpClient 생성
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
using (var localClient = new HttpClient(handler))
{
var loginToken = await ATask();
if (!string.IsNullOrEmpty(loginToken))
{
log.Info($"[A Task] User {userId} signed up successfully with login token: {loginToken}");
for (int i = 0; i < 3; i++)
{
await BTask();
log.Info($"[B Task] User {userId} with token {loginToken} called API B.");
await Task.Delay(2000);
await CTask();
log.Info($"[C Task] User {userId} with token {loginToken} called API C");
await Task.Delay(2000);
await DTask();
log.Info($"[D Task] User {userId} with token {loginToken} called API D");
await Task.Delay(2000);
}
log.Info($"User {userId} has completed all missions and is exiting.");
}
else
{
log.Warn($"[A Task] User {userId} failed to sign up.");
}
}
}
#region API methods
// A Task
private static async Task<string> ATask()
{
// A API 호출
}
// B Task
public static async Task BTask()
{
// B API 호출
}
// C Task
public static async Task CTask()
{
// C API 호출
}
// D Task
public static async Task DTask()
{
// D API 호출
}
#endregion
}
성능 테스트 과정
서버 모니터링 시스템을 킨 후 현재 CPU, Memory를 확인한 후 프로그램을 실행하였습니다.
초당 30명 생성은 약 5초 후에 CPU 100% 차면서 서버가 멈췄습니다😂
현재 개발 서버 스펙은 초당 15명 생성이 한계였습니다😂
개발 서버를 2배로 스펙업 후 초당 40명 생성을 확인 하였고
약 3시간 동작 결과 CPU 70%로 안정적으로 시나리오 동작 하는 것을 확인 하였습니다.
성능 테스트 완료
상용 서버와 개발 서버 스펙업을 비교 하고 유저 유입 기간에 맞춰서
상용 서버 스펙업을 진행하기로 하였습니다.
그리고 성공적으로 상용서버에서 유저 유입에 성공 하였습니다🎈 🎈 🎈
'Language > C#' 카테고리의 다른 글
[C#] DotNetty로 TCP 소켓 통신 구현: Unity와 게임 서버 간 패킷 교환하기 (0) | 2025.01.03 |
---|---|
[C#] protobuf-net 프로토콜 (0) | 2025.01.02 |
[C#] 데드락(Deadlock)과 예시 (1) | 2024.06.14 |
[C#] 이진 탐색 (Binary search) vs 균형 이진 트리 (Balanced Binary Search Tree) 속도 비교 (0) | 2024.06.14 |
[C#] 파일 생성 일자 비교 후 이전 날짜 파일 삭제하기 (0) | 2023.03.14 |