카테고리 없음
boost pool db 처리
denny
2025. 4. 3. 10:50
Boost.Asio를 활용한 비동기 DB 요청 처리: Thread Pool + Strand 활용
MMORPG나 대규모 온라인 서비스에서는 데이터베이스(DB) 처리가 중요한 요소 중 하나입니다. 특히, 한 유저의 요청이 여러 스레드에서 동시 실행되지 않도록 보장하면서도, 전체 시스템의 성능을 극대화해야 하는 경우가 많습니다.
이 글에서는 Boost.Asio의 thread_pool과 strand를 활용하여, 한 유저의 모든 DB 요청이 같은 스레드에서 순차적으로 실행되도록 하는 방법을 소개합니다.
1. 코드 개요
이 코드는 Boost.Asio의 thread_pool을 이용하여 DB 요청을 처리하는 시스템을 구현한 것입니다.
- 여러 유저의 DB 요청을 비동기적으로 실행합니다.
- 특정 유저의 요청은 항상 같은 스레드에서 실행되도록 보장합니다.
- strand를 사용하여 스레드 간 동기화 비용을 줄이고, 락(lock) 없이 순차적 실행을 보장합니다.
2. 주요 개념 설명
이제 코드를 하나씩 분석하면서 주요 개념을 설명해보겠습니다.
2.1. 스레드 풀과 IO 컨텍스트 생성
io_context db_io_context;
boost::asio::thread_pool db_thread_pool(4);
- db_io_context: Boost.Asio의 비동기 이벤트 루프 역할을 합니다.
- db_thread_pool(4): 4개의 스레드로 구성된 스레드 풀을 생성합니다.
- 스레드 개수를 조정하면 성능을 최적화할 수 있습니다.
2.2. 유저별 strand 관리
std::unordered_map<int, std::shared_ptr<boost::asio::strand<io_context::executor_type>>> user_strands;
- std::unordered_map<int, std::shared_ptr<boost::asio::strand<>>를 이용해 유저별 strand를 저장합니다.
- strand는 Boost.Asio에서 동기화 없이 하나의 실행 흐름을 유지하는 도구입니다.
- 즉, 특정 유저의 모든 요청이 같은 스레드에서 순서대로 실행되도록 보장해줍니다.
2.3. DB 요청 처리 함수
void handle_db_request(int user_id, int request_id) {
std::cout << "Processing request " << request_id << " for user " << user_id
<< " on thread " << std::this_thread::get_id() << std::endl;
}
- handle_db_request: 실제 DB 요청을 처리하는 함수입니다.
- 실행되는 스레드의 ID를 출력하여, 특정 유저의 요청이 같은 스레드에서 실행되는지 확인할 수 있습니다.
2.4. 특정 유저의 요청을 처리하는 post_user_request 함수
void post_user_request(int user_id, int request_id) {
auto& user_strand = user_strands[user_id];
if (!user_strand) {
user_strand = std::make_shared<boost::asio::strand<io_context::executor_type>>(db_io_context.get_executor());
}
post(*user_strand, [user_id, request_id]() {
handle_db_request(user_id, request_id);
});
}
- 유저 ID를 기반으로 strand를 가져옵니다.
- 해당 유저의 strand가 없으면 새로 생성합니다.
- post(*user_strand, ...)을 사용하여 이 유저의 모든 요청이 같은 스레드에서 순차적으로 실행되도록 보장합니다.
💡 post 함수란?
- post(*user_strand, lambda)를 호출하면, 지정된 strand에서 작업이 예약됩니다.
- Boost.Asio의 strand는 멀티스레드 환경에서 동기화 없이 순차적 실행을 보장하는 도구입니다.
2.5. 메인 함수: 요청 등록 및 실행
int main() {
// 여러 유저의 요청을 비동기 처리
for (int i = 0; i < 10; ++i) {
post_user_request(1, i); // 유저 ID: 1
post_user_request(2, i); // 유저 ID: 2
}
// 스레드 풀에서 io_context 실행
db_io_context.run();
// 스레드 풀 종료
db_thread_pool.join();
return 0;
}
- 10개의 요청을 유저 ID 1, 2에게 각각 전달합니다.
- post_user_request(1, i) → 유저 1의 요청을 strand를 통해 실행
- post_user_request(2, i) → 유저 2의 요청을 strand를 통해 실행
- db_io_context.run(); → 비동기 작업을 실행합니다.
- db_thread_pool.join(); → 스레드 풀을 종료합니다.
3. 실행 결과 분석
예제 코드 실행 시, 출력은 다음과 같은 형태가 됩니다.
Processing request 0 for user 1 on thread 140200005318400
Processing request 1 for user 1 on thread 140200005318400
Processing request 2 for user 1 on thread 140200005318400
Processing request 3 for user 1 on thread 140200005318400
...
Processing request 0 for user 2 on thread 140200004924800
Processing request 1 for user 2 on thread 140200004924800
Processing request 2 for user 2 on thread 140200004924800
...
💡 관찰할 점
- 유저 1의 모든 요청은 같은 스레드에서 실행됩니다.
- 유저 2의 모든 요청은 또 다른 스레드에서 실행됩니다.
- 하지만 서로 다른 유저의 요청은 병렬로 실행될 수 있습니다.
즉, 각 유저의 요청은 서로 다른 스레드에서 독립적으로 실행되지만, 한 유저의 요청은 같은 스레드에서 순서대로 실행됩니다.
4. strand를 이용한 동기화 없는 순차 실행의 이점
일반적으로 멀티스레드 환경에서는 락(lock)으로 동기화가 필요합니다. 하지만 strand를 이용하면 다음과 같은 이점을 얻을 수 있습니다.
✅ 락 없이 안전한 실행 보장
✅ 성능 최적화 (락의 오버헤드 감소)
✅ 유저별 동기화 문제 해결 (하나의 유저의 요청이 여러 스레드에서 동시에 실행되는 문제 방지)
✅ 코드 가독성 증가 (직관적인 흐름 제어 가능)
5. 정리
- Boost.Asio의 thread_pool을 이용해 멀티스레드로 DB 요청을 처리한다.
- 유저별 strand를 사용하여 특정 유저의 요청이 같은 스레드에서 실행되도록 보장한다.
- post(*user_strand, ...)를 사용해 strand 내부에서 비동기적으로 실행한다.
- 동기화(lock)를 사용하지 않고도 안전하게 실행할 수 있어 성능과 코드 가독성이 향상된다.
6. 확장 가능성
- DB 쿼리를 실행하는 로직 추가
- 실제 DB 엔진(MySQL, PostgreSQL)과 연동하여 실행할 수 있습니다.
- 유저 ID 대신 세션 기반 strand 관리
- std::unordered_map<session_id, strand> 형태로 구현하면 세션 기반으로 요청을 처리할 수 있습니다.
- 멀티 IO 컨텍스트를 활용한 부하 분산
- 여러 개의 io_context를 사용하여 더 큰 규모의 트래픽을 처리할 수 있습니다.
이제 이 방식을 활용하여 MMORPG 서버나 대규모 네트워크 애플리케이션에서 안전하고 효율적인 DB 처리 시스템을 구축해보세요! 🚀