Skip to content

Commit

Permalink
merge: [BE] transaction 적용 및 에러 fix #182
Browse files Browse the repository at this point in the history
[BE] feature : transaction 적용 및 에러 fix
  • Loading branch information
Gseungmin authored Dec 4, 2023
2 parents 3adde51 + 7505deb commit f9e6f51
Show file tree
Hide file tree
Showing 17 changed files with 355 additions and 56 deletions.
6 changes: 2 additions & 4 deletions backEnd/api/.gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
# compiled output
/dist
/node_modules
/.env
/.prod.env
/.test.env

/*.env
LoadTestDocs
# Logs
logs
*.log
Expand Down
51 changes: 51 additions & 0 deletions backEnd/api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backEnd/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"cache-manager-ioredis": "^2.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cls-hooked": "^4.2.2",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
Expand Down
56 changes: 48 additions & 8 deletions backEnd/api/src/codes/codes.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Injectable, Logger } from '@nestjs/common';
import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { Code } from './schemas/code.schemas';
import { Model } from 'mongoose';
import { Connection, Model } from 'mongoose';
import { SaveCodeDto } from './dto/saveCode.dto';
import { TransactionRollback } from '../common/exception/exception';

@Injectable()
export class CodesService {
constructor(@InjectModel(Code.name) private codeModel: Model<Code>) {}
private logger = new Logger(CodesService.name);
constructor(
@InjectModel(Code.name) private codeModel: Model<Code>,
@InjectConnection() private readonly connection: Connection,
) {}

async save(saveCodeDto: SaveCodeDto): Promise<Code> {
const savedCode = new this.codeModel(saveCodeDto);
return savedCode.save();
const session = await this.connection.startSession();
session.startTransaction();
try {
const code = await this.codeModel.create(saveCodeDto);
await session.commitTransaction();
return code;
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
}

async getAll(userID: number) {
Expand All @@ -23,11 +39,35 @@ export class CodesService {

async update(userID: number, objectID: string, saveCodeDto: SaveCodeDto) {
const query = { userID: userID, _id: objectID };
return this.codeModel.updateOne(query, saveCodeDto);
const session = await this.connection.startSession();
session.startTransaction();
try {
const result = await this.codeModel.updateOne(query, saveCodeDto);
await session.commitTransaction();
return result;
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
return;
}

async delete(userID: number, objectID: string) {
const query = { userID: userID, _id: objectID };
await this.codeModel.deleteOne(query);
const session = await this.connection.startSession();
session.startTransaction();
try {
await this.codeModel.deleteOne(query);
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
this.logger.error(e);
throw new TransactionRollback();
} finally {
await session.endSession();
}
}
}
17 changes: 17 additions & 0 deletions backEnd/api/src/common/decorator/typeormTransactional.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getNamespace } from 'cls-hooked';
export const typeormTransactional = () => {
return (target: any, key: string, descriptor: PropertyDescriptor) => {
const nameSpace = getNamespace('namespace');
nameSpace.set();
// console.log("target instance:", target);

const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
// 비동기 작업을 수행하거나 비동기 함수를 호출할 수 있습니다.
const [res, req] = args;
if (!req.authorized) return res.redirect('/user/login');
// console.log(originalMethod)
await originalMethod.apply(this, args);
};
};
};
16 changes: 9 additions & 7 deletions backEnd/api/src/run/run.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ export class RunGateway implements OnGatewayConnection {

@OnEvent(EVENT.COMPLETE)
answer(data) {
this.logger.log('received running result');
this.logger.log(`received running result ${JSON.stringify(data)}`);
const socket = this.connectedSockets.get(data.socketID);
const response = new ResponseCodeBlockDto(
data.statusCode,
data.result,
data.message,
);
socket.emit(SOCKET_EVENT.DONE, response);
if (socket) {
const response = new ResponseCodeBlockDto(
data.statusCode,
data.result,
data.message,
);
socket.emit(SOCKET_EVENT.DONE, response);
}
}

@SubscribeMessage(SOCKET_EVENT.DISCONNECT)
Expand Down
7 changes: 2 additions & 5 deletions backEnd/api/src/run/run.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BadRequestException,
HttpStatus,
Injectable,
InternalServerErrorException,
Expand Down Expand Up @@ -66,11 +67,7 @@ export class RunService {
const res = e.response.data;
if (e.response.data.status === HttpStatus.INTERNAL_SERVER_ERROR)
throw new InternalServerErrorException();
return new ResponseCodeBlockDto(
res.statusCode,
res.message,
'Failed to Run Code',
);
throw new BadRequestException(res.message);
}
}

Expand Down
31 changes: 18 additions & 13 deletions backEnd/api/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UserEntity } from './entity/user.entity';
import { UserDto } from './dto/user.dto';
import { TransactionRollback } from '../common/exception/exception';
type OAUTH = 'github' | 'google';
@Injectable()
export class UsersService {
Expand All @@ -12,14 +13,23 @@ export class UsersService {
) {}

async addUser(userDTO: UserDto, oauth: OAUTH) {
const user = new UserEntity();
user.name = userDTO.name;
user.authServiceID = userDTO.authServiceID;
user.oauth = oauth;

const qr = this.getQueryRunner();
try {
const user = new UserEntity();
user.name = userDTO.name;
user.authServiceID = userDTO.authServiceID;
user.oauth = oauth;
await this.usersRepository.save<UserDto>(user);
await qr.connect();
await qr.startTransaction();
await qr.manager.save<UserEntity>(user);
await qr.commitTransaction();
} catch (e) {
console.log(e);
await qr.rollbackTransaction();
throw new TransactionRollback();
} finally {
await qr.release();
}
}

Expand All @@ -32,13 +42,8 @@ export class UsersService {
return find;
}

async delUser(userDTO: UserDto): Promise<void> {
const user = await this.usersRepository.findOne({
where: {
authServiceID: userDTO.authServiceID,
},
});
user.isActive = false;
await this.usersRepository.save<UserEntity>(user);
getQueryRunner() {
const dataSource = this.usersRepository.manager.connection;
return dataSource.createQueryRunner();
}
}
93 changes: 93 additions & 0 deletions backEnd/api/test/loadtest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const { io } = require('socket.io-client');

const DURATION = 120 * 1000; // 120초
const MAX_CLIENTS = 100;
const ERROR_RATE = 0.1;
let total_request = 0;
let errorCount = 0;
let clientCount = 0;
let concurrentClients = 10;
const 소요시간 = [];

const URL = 'ws://localhost:4000/run';
const data = {
code: 'console.log("Hello World")',
language: 'javascript',
};

const printResult = () => {
console.log('총 요청개수 : ', total_request);
console.log('성공한 요청 개수 : ', 소요시간.length);
console.log(
'평균 소요 시간 : ',
소요시간.reduce((p, c) => p + c, 0) / 소요시간.length,
);
console.log('최대 동시 요청 수 : ', concurrentClients - 10);
};

const createSingleClient = () => {
// for demonstration purposes, some clients stay stuck in HTTP long-polling
const start = Date.now();
const socket = io(URL, {
transport: ['websocket'],
});

let doneEventReceived = false;
// Set a timeout of 6 seconds
const timeId = setTimeout(() => {
if (!doneEventReceived) {
errorCount++;
// console.log('Timeout: done event not received within 6 seconds.');
socket.disconnect();
}
}, 6000);

socket.on('connect', () => {
// console.log('소켓이 연결되었습니다.');
socket.emit('request', data);
});

socket.on('done', (data) => {
// console.log(data); // 코드 실행 결과
doneEventReceived = true;
clearTimeout(timeId);
소요시간.push(Date.now() - start);
socket.disconnect();
});
};

const createClient = () => {
if (clientCount >= MAX_CLIENTS) {
setTimeout(() => {
console.log('Test Success');
printResult();
process.exit(1);
}, 6000);
}
if (total_request * ERROR_RATE < errorCount) {
setTimeout(() => {
console.log('Test Done By error');
printResult();
process.exit(1);
}, 6000);
}

console.log(`Create ${concurrentClients} VUs`);
Array.from({ length: concurrentClients }, (v, i) => i).forEach(() => {
createSingleClient();
});

total_request += concurrentClients;
concurrentClients += 10; // 동시 실행 횟수를 10씩 증가
clientCount++;

setTimeout(createClient, 3000); // 1초 뒤에 다음 그룹의 클라이언트 생성 시작
};

createClient();

setTimeout(() => {
console.log('duration done');
printResult();
process.exit(1); // Exit the process with an error code (1)
}, DURATION); // Set the timeout to 60 seconds
Loading

0 comments on commit f9e6f51

Please sign in to comment.