Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] feature : transaction 적용 및 에러 fix #182

Merged
merged 5 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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