일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- typeORM
- mongoose
- class
- dfs
- MySQL
- flask
- Bull
- TypeScript
- OCR
- AWS
- 게임
- Python
- cookie
- react
- 자료구조
- jest
- Express
- MongoDB
- nodejs
- 공룡게임
- game
- Queue
- GIT
- Sequelize
- 정렬
- JavaScript
- Nest.js
- nestjs
- Dinosaur
- Today
- Total
포시코딩
[Nest.js][동시성 문제] Transaction 사용과 통 Lock 걸어버리기 본문
코드
Back-End: Nest.js - boards.service.ts
async joinGroup(boardId: number, userId: number) {
const board = await this.getBoard(boardId);
const boardJoinInfo = await this.joinRepository.find({
where: { boardId },
});
if (board.joinLimit <= boardJoinInfo.length) {
throw new ForbiddenException('자리 부족');
}
boardJoinInfo.forEach((join) => {
if (join.userId === userId) {
throw new ConflictException('이미 join 했습니다.');
}
});
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
// await queryRunner.startTransaction('SERIALIZABLE');
try {
// this.joinRepository.insert({ boardId, userId });
await queryRunner.manager.getRepository(Join).insert({
boardId, userId
});
const afterJoin = await this.joinRepository.count({
where: { boardId },
});
if (board.joinLimit <= afterJoin) {
throw new Error('동시성 문제 발생');
}
await queryRunner.commitTransaction();
} catch (e) {
await queryRunner.rollbackTransaction();
throw new BadGatewayException(e.message);
} finally {
await queryRunner.release();
}
}
Front-End: React - List.js
function test() {
test_form(9, 0);
test_form(9, 1);
test_form(9, 2);
test_form(9, 3);
test_form(9, 4);
}
function test_form(boardId, userId) {
axios
.post(
`http://localhost:8080/boards/${boardId}/join2`,
{userId},
{ withCredentials: true },
)
.then((response) => {
const statusCode = response.status;
// console.log('status code: ' + statusCode);
if (statusCode === 201) {
console.log(`id: ${userId} 성공 - status code: ` + statusCode);
} else {
console.log('status code: ' + statusCode);
}
})
.catch((e) => {
// console.log('axios 통신실패');
console.log(e.response.data.message);
});
}
동시성 문제 구현
joinLimit이 2인 board에 대해 동시에 5개의 요청을 진행
원래라면 두 개만 들어간 다음 나머지는 transaction에 의해 502 에러를 내며
롤백 됐어야 했는데 4개나 들어간 모습.
2개나 초과되어 들어갔다.
다시 시도해보면 시도할 때마다 잘못 들어가는 개수도 달라지는데
어차피 의도했던바라 성공이라고 볼 수 있다.
이제 이걸 어떻게 해결하면 좋을지 찾아내는건데
高.. 쉽지 않다.
1차 피드백
위 내용 관련해서 튜터님과 상담을 진행했고
그 결과 아래와 같은 내용이 종합되었다.
1.
현재 동시에 테스트를 진행한건 완전한 '동시'가 아니다.
물론 원하는 결과를 얻고 있긴 하지만
진짜 '동시'가 되려면 더 빡세게 해야 되는데
JavaScript는 싱글 쓰레드기 때문에
Python의 Gevent나
Java에서 쓰레드를 나눠 진행하는 방법을 생각해보면 좋겠다.
2. 아니면 아예 중간에서 해당 요청들을 받는
Python 또는 Java로 이루어진 서버를 구성한다던가 해서
다시 본래 백엔드 Nest.js 서버에 보내는 방법도 괜찮을듯
이거와 관련해서 SaaS 인프라 구성에 대해 찾아보기
3. 그게 아니면 스트레스 테스트 툴을 이용하는 것도 방법이 될 것이다.
4. 통으로 Lock을 거는 방법을 사용할 수도 있는데
let isLock = false;
function test() {
if (isLock) {
return;
}
isLock = true;
// .. do something
isLock = false;
}
이런 느낌으로 로직을 만드는 것도 어느정도 해결방법이 될 수 있을 것이다.
물론 완벽히 막지는 못한다는걸 알고 있어라!
1차 정리
boards.service.ts
@Injectable()
export class BoardsService {
private isLock: boolean;
constructor(
// ...생략
) {
this.isLock = false;
}
async joinGroup(boardId: number, userId: number) {
if (this.isLock) {
throw new BadGatewayException('진행중인 상태가 있으므로 다시 시도해주시기 바랍니다.');
}
this.isLock = true;
// ...생략
try {
// ...생략
await queryRunner.commitTransaction();
} catch (e) {
await queryRunner.rollbackTransaction();
throw new BadGatewayException(e.message);
} finally {
await queryRunner.release();
this.isLock = false;
}
}
마지막 피드백 내용의 통 Lock을 거는 방법으로 로직을 수정해봤다.
아까 넣었던 DB 데이터를 지우고 다시 똑같이 시도해봤는데
효과가 있다!
처음 들어간 userId = 0의 저장이 진행중일 때
isLock = true 상태가 되어 다른 userId들이 접근하지 못하고 502 에러를 리턴하는
BadGatewayException으로 인해 팅겨 나왔다.
당장은 이렇게 사용할 수도 있지만 사용자로 하여금 본인이 시도했는데
자꾸 이미 진행중인 상태가 있어서 다시 시도하라는 말이 나오면 짜증이 날 것이다.
더군다나 아직 빈 자리가 있으면 더더욱
그래도 이와 관련해서 스터디를 진행한 결과 TypeORM을 쓰면서
bull, queue와 같은 키워드 힌트를 얻었는데 한 번 찾아보며 관련해서도 구현해봐야겠다.
추가 작성
https://velog.io/@soyeon207/DB-Lock-총-정리-2-낙관적-락과-비관적-락-분산락-데드락
위에서 했던 통 Lock을 거는 행위를 존재하는 용어로 표현하자면
비관적 락(Perssimistic Lock)이라고 하는듯 하다.
위 포스팅을 읽어보면 내가 원하는 결과를 얻기 위해서는
낙관적 락(Optimistic Lock)을 해야하는것으로 보이는데
TypeORM 에서 사용하는 예제를 좀처럼 찾아보기 힘들었다.
좀 찾아본 결과 이정도..?
일단 이 다음 포스팅에서 얘기하는 Bull Queue를 사용하는 방식에서 진전이 있어
해당 방법으로 어느정도 결과를 보고나서 낙관적 락, 비관적 락에 대해 알아보고 테스트해봐야 할 것 같다.
해당 포스팅은 아래 포스팅으로 이어진다.
'Node.js' 카테고리의 다른 글
[동시성 문제] Apache JMeter를 이용한 테스트 (0) | 2023.02.20 |
---|---|
[Nest.js][동시성 문제] Bull Queue (0) | 2023.02.18 |
[Nest.js][CORS] cookie를 전달받지 못하는 문제 (0) | 2023.02.17 |
[TypeORM] FK 설정 후 join해서 데이터 가져오기 (0) | 2023.02.17 |
[Nest.js] 8. TypeORM Repository 적용 (끝) (0) | 2023.02.16 |