개인 다마고치 프로젝트중 응가 기능을 구현해야 했었는데
어떤 방식이 가장 효율적일지 판단이 서지않았다
구현 전 여러가지 방법들을 생각해 봤는데
1. HTTP 통신시 제일 마지막으로 요청한 시간부터 현재 시간까지 계산해서 응가 눈 횟수 결정(요청마다 현재시간 db 에 저장)
- HTTP통신이 이루어 질때마다 db작업을 해야함
- 요청 1번에 db작업 1번이므로 사용자가 많아지면 서버에 부담이 생길수 있음
2. 요청마다 쿠키에 시간을 저장 => 제일 마지막 요청 이후 지난 시간에 따라 응가 눈 횟수 결정
- 서버가 받는 부담이 적다
- 쿠키 유효기간은 어떻게 할것인가(100년..?)
- 클라이언트가 쿠키를 조작 또는 삭제로 응가를 안누게 조작할 수 있다
3. 토큰 => 세션으로 변경 세션에 마지막 요청 저장후 지난 시간에 따라 응가 눈 횟수 결정
- 세션의 유효기간은 어떻게 할것인가(무제한으로 하면 서버 메모리도 제한적이고 사용자가 로그아웃을 하지 않는 한 세션은 사라지지 않는다)
- 클라이언트가 조작할 수 없다
4. 스케줄러로 일정 시간 마다 응가눔
- 수많은 펫의 응가눈 상태를 동시에 업뎃하므로 유저가 많아지면 서버에 부하가 생길 수 있다
나는 4가지의 방법들 중에 2,3번 방법은 제외했다 이유는 쿠키에 데이터를 저장하는건 신뢰성이 떨어지고 세션에 데이터를 저장하면 메모리 문제가 심각하다
그렇다면 1,4 번 방법들만 남았는데 2가지 방법중 서버에 부담이 적은 가장 효율적인건 어떤 방법일까?
성능 테스트
먼저 1번 방법인 요청이 올때마다 db에 현재 시간을 저장하는 방법을 테스트 해보았다
class RequestService {
private sqlTemplate;
constructor() {
this.sqlTemplate = new SqlTemplate();
}
private async updateRequestTime(userId: number) {
const Changed = await this.sqlTemplate.modifyQuery('UPDATE last_requests SET time = now() WHERE user_id = ?', [
userId,
]);
return Changed;
}
private async createRequestTime(userId: number) {
await this.sqlTemplate.modifyQuery('INSERT INTO last_requests (user_id) VALUES (?)', [userId]);
}
async createOrUpdateTime(userId: number) {
const changed = await this.updateRequestTime(userId);
if (!changed) {
await this.createRequestTime(userId);
}
}
}
//index.ts
let userId = 1;
app.post('/test', async (req, res) => {
await requestService
.createOrUpdateTime(userId++)
.then(() => res.sendStatus(200))
.catch(() => res.sendStatus(400));
console.log(userId);
});

Artillery 라이브러리를 이용해서 60초 동안 5명의 유저를 시작으로 초당 10명씩 요청하는 유저가 증가하게 셋팅했다
결과

총 45000번의 작업을 했는데 생각보다 마음에 드는 수치이다

생각보다 너무 괜찮게 나와서 초당 늘어나는 유저의 수를 10명에서 20명으로 올려봤다

총 75000번의 작업을 했는데 이번에는 p95가 120.3ms, p99가 135.7ms로 fail이 나왔다
해당 데이터를 가지고 스케줄러랑 비교해 볼것이다
동시에 업데이트 테스트(스케줄러)
45000건의 데이터 한번에 업데이트
부하테스트를 하며 db에 저장된 45000건의 데이터를 한번에 업데이트 해보았다

app.get('/test', async (req, res) => {
console.time('test');
await requestService
.updateAllPooCount()
.then(() => res.sendStatus(200))
.catch(() => res.sendStatus(400));
console.timeEnd('test');
});
한번에 업데이트하는 로직이 소요되는 시간을 측정하기 위해 console 객체를 이용했다
결과

45000건의 데이터를 한번에 업데이트 하는데는 62.291ms 가 소요됬다
이번엔 75000건의 데이터를 한번에 업데이트 해보았다


결과는 104.418ms
비교
| 요청마다 시간 저장 | 한번에 db업데이트 | |
| 45000 번의 요청 또는 데이터 업뎃 | 25.8ms( p99 ) | 13.6ms(평균) | 62.291ms |
| 75000 번의 요청 또는 데이터 업뎃 | 135.7ms( p99 ) | 48.1ms(평균) | 104.418ms |
내가 해본 테스트는 실제 상황과 다르기도 하고 요청마다 시간을 저장하는 방법과 한번에 db를 업데이트 하는 방법은 상황 자체가 다르기 때문에 테스트 결과를 100% 신뢰할 순 없지만 p99를 기준으로 판단하면
- 서버에 부하(또는 유저수)가 적을수록 요청마다 시간을 저장하는게 효율적
- 서버에 부하(또는 유저수)가 많을수록 한번에 db를 업데이트 하는게 효율적
일 것이고 평균값으로 판단하면 요청마다 시간을 저장하는것이 압승이다
결론
나는 위의 비교표 중에서 요청마다 시간을 저장하는 방법중 평균값을 기준으로 판단을 했을때 요청마다 시간을 저장하는 방법이 효율적이라고 판단했다
이유는 실제 유저들이 아무리 많다고 해도 1분간 75000건의 요청은 할 수 없다는 판단이다(디도스 공격이 아닌 이상...) 만약 정말로 유저들이 많아져서 1분에 75000건의 요청 보다 더 많은 요청(백만건,천만건...)이 들어오게된다면 서버 증설이 더 낫지 않을까 하는 생각이다
'이것저것' 카테고리의 다른 글
| AI 응답 속도 개선하기 (2) | 2024.08.21 |
|---|---|
| 동적 객체 생성 vs 정적 객체 생성 (0) | 2024.04.30 |
| 쿠키, 세션, 토큰 (0) | 2024.03.28 |
| webpack 개념과 탄생 배경 (0) | 2024.03.21 |
| tsc, babel 차이점 (0) | 2024.03.19 |