프롤로그
어느 날 기술 면접에서 "캐시 도입 이후 데이터베이스의 QPS는 얼마나 나왔나요?"라는 질문이 나왔다고 가정해 볼게요.
만약 이 순간, "TPS랑 비슷한 것 같은데.."라며 유추해보지만 QPS가 정확히 무엇을 의미하는지, 캐시와 QPS 그리고 TPS가 어떤 관계가 있는지 바로 떠오르지 않는다면 당황스러운 상황이 될 수 있습니다.
QPS(Query Per Second)와 TPS(Transaction Per Second)는 단순한 숫자가 아닙니다.
이들은 시스템의 상태를 진단하고, 성능을 개선하며, 안정성을 확보하는 데 필요한 중요한 지표에요. 대규모 트래픽을 다룰 일이 없는 환경이라도, 이 지표를 이해하면 더 나은 시스템 설계와 효율적인 문제 해결 방법을 찾을 수 있죠.
이 글에서는 QPS와 TPS의 개념과 실무를 가정한 시나리오를 설명하고, MySQL을 사용해 QPS를 직접 측정하는 방법을 보여드립니다.
QPS? TPS?
QPS (Query Per Second)
1초 동안 처리된 쿼리의 개수를 나타내는 성능 지표입니다. 시스템이 얼마나 많은 요청을 처리할 수 있는지 보여주며, 일반적으로 응답 속도와 처리량의 척도로 활용됩니다.
- 예시로, 초당 50명의 사용자가 "운동화"를 검색했다면, QPS는 50입니다.
TPS (Transaction Per Second)
데이터 무결성과 ACID 원칙을 준수하는 트랜잭션이 초당 얼마나 성공적으로 처리되었는지를 나타냅니다. 트랜잭션은 더 복잡하고 무거운 작업 단위를 포함하며, 모두 성공하거나 하나라도 실패하면 전체 요청이 실패합니다.
트랜잭션은 보통 여러 작업(예: 재고 감소, 주문 생성, 결제 처리)을 포함해요.
QPS와 TPS는 무엇을 기준으로 측정할까?
이 단위는 무엇을 기준으로 측정하는지에 따라 그 의미가 달라질 수 있습니다. 예를 들어, QPS와 TPS를 데이터베이스를 기준으로 측정할 수도 있고, Redis 같은 캐싱 시스템 또는 애플리케이션 레벨에서 측정할 수도 있습니다.
이 글에서는 데이터베이스를 기준으로 QPS와 TPS를 설명합니다. 애플리케이션 수준에서도 비슷한 지표를 사용할 수 있지만, 실무에서는 데이터베이스와 캐싱 계층에서의 성능 지표가 더 실질적이고 유용하게 사용되기 때문입니다.
쇼핑몰 검색 사례로 QPS 이해하기
시나리오: 쇼핑몰의 "운동화" 검색
한 쇼핑몰에서 운동화를 검색했다고 가정해 볼게요. 이 과정에서 어떤 일이 벌어질까요?
1. 사용자가 "운동화"라고 입력하고 검색 버튼을 클릭합니다.
2. 쇼핑몰 서버는 이 요청을 처리하기 위해, 상품 데이터베이스에 쿼리를 보냅니다
SELECT * FROM products WHERE category = 'shoes';
3. 데이터베이스가 쿼리를 실행하고, "운동화"에 해당하는 상품 목록을 반환합니다.
4. 쇼핑몰 서버는 받은 결과를 사용자 화면에 보여줍니다.
이 과정은 하나의 검색 요청이며, 서버는 이를 처리하기 위해 데이터베이스 쿼리를 실행했습니다.
초당 50명이 검색하면?
이제 1초 동안 50명이 동시에 "운동화"를 검색한다고 상상해 보세요.
- 서버는 초당 50개의 쿼리를 처리해야 합니다.
- 즉, 데이터베이스는 1초 동안 50개의 검색 요청을 처리합니다.
이렇게 1초 동안 처리된 쿼리의 개수를 바로 QPS(Query Per Second)라고 부릅니다.
여기선 50 QPS가 되겠죠.
QPS를 MySQL에서 측정해보기
실습환경: MySQL 8.x
MySQL에서 QPS(Query Per Second)를 정확히 측정하는 방법
MySQL은 "SHOW GLOBAL STATUS" 명령어를 통해 서버 상태를 확인하고, QPS를 계산하는 데 필요한 데이터를 제공합니다.
이를 활용해 QPS를 계산할 수 있습니다.
QPS 계산 공식
MySQL에서 QPS는 다음 공식으로 계산할 수 있습니다
QPS = (Queries2 - Queries1) / (Uptime2 - Uptime1)
- Queries: MySQL 서버가 처리한 총 쿼리 수
- Uptime: MySQL 서버가 시작된 이후 경과한 총 시간(초)
MySQL 환경 세팅 (선택)
이 세팅은 MySQL으로 즉시 테스트가 불가능한 경우 도커 컴포즈를 활용해 테스트 할 수 있도록 가이드 하기 위함입니다.
docker-compose.yml 파일
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-container
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: shop
MYSQL_USER: user
MYSQL_PASSWORD: password
volumes:
- mysql_data:/var/lib/mysql
- schema.sql:/docker-entrypoint-initdb.d/schema.sql
command: --default-authentication-plugin=mysql_native_password
volumes:
mysql_data:
docker-compose와 동일한 경로에 schema.sql을 두면 자동으로 생성 쿼리가 실행됩니다.
-- 데이터베이스와 테이블 생성
CREATE DATABASE IF NOT EXISTS shop;
USE shop;
CREATE TABLE IF NOT EXISTS products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL
);
-- 초기 데이터 삽입
INSERT INTO products (name, category, price) VALUES
('Running Shoes', 'shoes', 79.99),
('Basketball Shoes', 'shoes', 99.99),
('Casual Sneakers', 'shoes', 59.99),
('Formal Shoes', 'shoes', 120.00),
('Winter Boots', 'shoes', 140.00),
('Yoga Mat', 'fitness', 25.00),
('Dumbbells', 'fitness', 50.00),
('Treadmill', 'fitness', 499.99),
('Mountain Bike', 'sports', 899.99),
('Tennis Racket', 'sports', 150.00);
docker-compose가 설치되어 있다면, 위 파일을 아래 명령으로 실행하여 mysql을 구동시켜 줍니다.
docker-compose up
1. MySQL 명령어를 통한 QPS 확인
MySQL 접속
docker exec -it mysql-container mysql -u root -prootpassword
현재 Queries 값과 Uptime 확인
SHOW GLOBAL STATUS LIKE 'Queries';
SHOW GLOBAL STATUS LIKE 'Uptime';
쿼리를 실제 호출한 수에 따라 결과가 달라지게 되겠지만 아래처럼 계산할 수 있습니다.
첫 번째 조회
Queries: 10500
Uptime: 200
두 번째 조회 (1초 후)
Queries: 10550
Uptime: 201
계산
QPS = (10550 - 10500) / (201 - 200) = 50
TPS와 QPS의 차이
TPS 간단히 이해하기: 쇼핑몰 주문 처리 사례
사용자가 "운동화"를 주문한다고 가정해 봅시다.
- 재고를 확인합니다.
- 재고를 감소합니다.
- 주문을 저장합니다.
이 3가지 행위가 하나의 트랜잭션으로 작동해야 하며, 모두 성공하거나, 하나라도 실패하면 이 요청 자체는 실패해야 합니다.
이를 코드로 표현해보면 아래와 같습니다.
@Transactional
public void placeOrder(Long productId, int quantity) {
// 재고 확인
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() < quantity) {
throw new RuntimeException("Not enough stock");
}
// 재고 감소
product.setStock(product.getStock() - quantity);
// 주문 저장
Order order = new Order(productId, quantity);
orderRepository.save(order);
}
이 모든 작업이 성공적으로 완료되었을 때, 우리는 이를 하나의 트랜잭션으로 보고 TPS(Transaction Per Second)로 관리합니다.
TPS란? 데이터 무결성과 ACID 원칙을 보장하는 단위 작업이 초당 얼마나 성공적으로 완료되었는지 나타냅니다. 트랜잭션은 성공적 완료와 실패 사이의 이분법적 결과를 가지며, 이 점에서 단순 요청 수인 QPS와 차별화됩니다.
실무에서 성능 지표는 어떻게 활용될까?
성능 지표는 단순히 "얼마나 많은 요청을 처리했는지"를 알려주는 것에 그치지 않습니다. 오히려 문제가 발생할 지점을 예측하고, 이를 해결하기 위한 방향성을 제시하는 중요한 도구입니다.
다음은 실무에서 성능 지표가 어떻게 사용되는지 보여주는 사례입니다. 이를 통해 자연스럽게 여러분이 더 배워야 할 개념에 대해서도 알 수 있을 거예요.
설명을 위한 극단적인 시나리오이며 실제 사례가 아닙니다.
사례1: 쇼핑몰 할인 이벤트 트래픽 급증
대형 쇼핑몰에서 대규모 할인 이벤트를 시작한다고 가정해 볼게요. 평소에는 초당 500건의 검색 요청을 처리하던 시스템이, 이벤트 시작과 함께 초당 5,000건 이상의 트래픽을 처리해야 하는 상황에 놓였습니다.
처음에는 서버가 요청을 잘 처리하는 듯했지만, 몇 분 후부터 응답 시간이 길어지고, 일부 요청이 실패하기 시작했습니다. 운영팀은 실시간으로 모니터링하던 QPS와 TPS 지표를 통해 문제가 발생했음을 빠르게 파악했습니다.
- 최대 QPS: 평소보다 10배 증가한 5,000.
- TPS: 주문 성공률이 평소 대비 50% 감소.
문제를 분석해 보니, 데이터베이스가 쏟아지는 요청을 감당하지 못하고 있었습니다. 특히, 인기 상품을 반복적으로 조회하는 요청이 데이터베이스에 과도한 부하를 주고 있었던 것이죠.
문제 해결
- 캐싱 도입
운영팀은 Redis와 같은 메모리 기반 캐싱 시스템을 사용해 인기 상품 목록 데이터를 캐싱했습니다.- 캐싱 데이터는 실시간성을 유지하기 위해 TTL(Time-to-Live)을 설정하여 일정 시간마다 갱신했습니다.
- 이를 통해 데이터베이스에 불필요한 쿼리를 보내지 않고도 동일한 요청을 빠르게 처리할 수 있었습니다.
- 트래픽 분산
부하가 특정 서버에 집중되지 않도록 라운드 로빈 방식의 부하 분산을 적용했습니다.- 로드 밸런서는 서버 간 요청을 균등하게 분배하여 과부하가 발생하지 않도록 조정했습니다.
- 이 과정에서 Nginx와 같은 로드 밸런서를 활용해 설정을 최적화했습니다.
결과
- 데이터베이스 부하가 80% 감소하며, 시스템이 안정적으로 운영되었습니다.
- TPS는 평소 수준으로 회복되었고, 평균 응답 시간이 약 30% 개선되었습니다.
- 사용자는 더 이상 느린 응답이나 오류 메시지를 보지 않아도 됐습니다.
사례2: 금융 서비스에서의 장애 모니터링
한 금융 서비스는 특정 시간대에 거래량이 급증하는 특징이 있었습니다. 특히 월말이나 월초에는 트래픽이 예측 불가능하게 증가해 시스템이 종종 과부하에 걸리곤 했습니다.
운영팀은 Prometheus와 Grafana를 사용해 시스템의 QPS와 TPS를 실시간으로 모니터링하기 시작했습니다.
- 특히, TPS 실패율이 3%를 초과하면 알람이 발생하도록 설정해 장애를 사전에 감지할 수 있도록 준비했습니다.
해결방법
- 트래픽 분석과 병목 파악
운영팀은 Prometheus의 데이터와 Grafana 대시보드를 통해 특정 API(/transactions)가 트래픽의 병목 지점이라는 사실을 확인했습니다.- QPS가 비정상적으로 높은 경향을 보였고, TPS 실패율도 특정 시간대에 집중되어 있었습니다.
- 트래픽 분산 및 스케일링 조치
- 로드 밸런서는 트래픽을 동적으로 조정하여, 특정 서버에 과부하가 걸리지 않도록 설정했습니다.
- 동시에, 클라우드 환경에서 Auto Scaling을 통해 추가 서버를 증설하여, 급증하는 트래픽을 처리할 수 있는 환경을 마련했습니다.
- 알람 시스템 개선
- TPS 실패율이 특정 임계치를 초과하면 알람이 발생해, 클라우드 환경에서 스케일링이 자동으로 진행되도록 설정했습니다.
결과
- TPS 실패율이 3%를 넘지 않으며 안정적으로 거래를 처리했습니다.
- 클라우드 서버 추가와 트래픽 분산 덕분에 서비스가 중단 없이 운영되었고, 장애를 사전에 예방할 수 있었습니다.
시나리오를 통해 알 수 있는 점
이 사례들이 말해주는 메시지는 단순합니다. 성능 지표는 단순히 "얼마나 많은 요청을 처리했는지"를 알려주는 것이 아니라, 문제가 발생할 지점을 예측하고, 이를 해결하기 위한 방향성을 제시하는 도구라는 것이죠.
Redis와 같은 캐싱 도입, 로드 밸런서 최적화, 그리고 Auto Scaling과 같은 스케일링 전략은 모두 QPS와 TPS라는 지표를 활용해 설계된 솔루션입니다.
에필로그
이제 다시 프롤로그의 질문으로 돌아가 볼게요.
캐시 도입 이후 데이터베이스의 QPS가 얼마나 나왔나요?
이 질문에 어떻게 답변할 수 있을까요?
이 글을 읽기 전이었다면, QPS가 정확히 무엇을 의미하는지조차 선뜻 떠올리지 못했을지도 몰라요. 하지만 이제는 다를 겁니다.
이 질문은 면접관이 단순히 "얼마나 많은 쿼리를 처리했는지"를 묻는 것이 아닙니다. 캐시 도입이 데이터베이스 성능에 어떤 영향을 주었는지, 그리고 그 영향을 구체적인 성능 지표(QPS)를 통해 설명할 수 있는지를 확인하려는 의도가 담겨 있을 가능성이 큽니다.
"캐시를 도입한 이후 데이터베이스의 QPS는 눈에 띄게 감소했습니다. 캐시 도입 이전에는 데이터베이스가 초당 약 5,000개의 쿼리를 처리해야 했지만, 캐시를 적용한 후에는 이 수치가 약 1,500 QPS로 감소했습니다. 이는 약 70%의 감소로, 데이터베이스에 가해지는 부하를 크게 줄일 수 있었습니다. 결과적으로 데이터베이스의 CPU 및 메모리 사용률이 감소했고, 응답 시간이 개선되어 전체 시스템의 성능과 안정성이 향상되었습니다."
상황에 따라 더 구체적인 수치나 캐시 히트율, TPS 같은 꼬리 질문이 등장할 수 있겠지만, 이 답변에서는 QPS의 정의와 캐시 도입이 시스템 성능에 미치는 영향을 논리적으로 설명하면서, 실무 경험을 통해 이를 뒷받침하는 방식이 핵심입니다.
그동안 막연하게 느껴졌던 지표 단위들이 이제는 보다 명확하게 다가올 거에요. 그리고 이러한 지식을 바탕으로 실제 문제를 해결하고, 시스템을 개선하는 데 적극적으로 활용하시길 바랍니다.
🎉 "서류합격률 4%를 탈출하는 개발자 이력서 작성법" 강의를 출시했습니다. 신입, 경력 모두를 대상으로 강의가 제작되었으니 이력서 작성이 막막하거나 잦은 서류 탈락을 하고 계시다면 이 강의 수강을 추천드립니다.
'Tech > Server' 카테고리의 다른 글
리더가 MSA 전환을 하자고 했다. 그리고 처참히 실패했다. (2) | 2024.10.27 |
---|---|
서버는 어떻게 늘려야할까? (2) | 2021.12.19 |
인프런 지식공유자로 활동하고 있으며 MSA 전환이 취미입니다. 개발과 관련된 다양한 정보를 몰입감있게 전달합니다.