AVALVE_DATASERVER
디렉토리에 모든 파일이 담겨 있다.Layered Architecture
Presentation Layer
Business Logic Layer
Data Access Layer
Infrastructure Layer
try/catch
을 통한 트랜잭션 처리현재 로직의 구조를 살펴보면,
```jsx
if(){ // 첫번째 분기점
실행
} else if {
실행
if() { // 두번째 분기점
실행
} else if {
실행
}
}
```
1.1 🤷♀️ Why is it a problem?
새로운 조건이 추가되거나 기존 로직이 변경될 때, 영향을 받는 부분을 파악하고 수정하는 것이 복잡해짐(의존성의 문제)
예상치 못한 버그
중첩된 if-else구문은 가독성 저하의 문제를 야기
테스트 코드 작성에 대한 어려움
따라서, 현업에서는 분기점을 지양한다.
분기를 최소화하여 작은 단위로 분리하고 전략 패턴을 사용하여 공통적인 부분은 추상화하여 재 사용하는 방식으로 구조화한다.
*전략 패턴?
- 알고리즘을 정의하고 캡슐화하는 방식
- execute()를 사용하여 인스턴스에 접근하고 함수를 실행할 수 있음
3.1 /upload
Flow of Control
// app.js
app.use('/upload', uploadMiddleware, uploadRouter);
// uploadRouter.js
router.head('/upload_status', (req,res) =>
uploadController.checkUploadStatusHeader(req, res, dbConnection));
return router;
// uploadController.checkUploadStatusHeader.js
checkUploadStatusHeader: async (req, res, dbConnection) => {
try {
await s3Service.checkUploadStatusHeader(req, res, dbConnection);
logger.info('upload request header status controller To service');
res.status(200).send('Upload status checked successfully')
} catch (error) {
logger.error('Error procession upload status', error);
res.status(500).send('Error processing upload');
}
}
3.2 if(1) 제거: req.headers.status의 upload_start
, upload_finish
에 대한 분기점 제거
UploadInterface
인터페이스 정의```jsx
// 인터페이스 정의
class UploadInterface {
async execute(req, dbConnection) {
throw new Error('Execute method should be implemented');
}
}
```
- 에러 메세지 일괄 적용
- 필수 구현 기능 표준화
UploadInterface
를 상속받는 class정의.UploadStart
, UploadFinis
// 'upload_start'
class UploadStart extends UploadInterface {
async execute(req, dbConnection) {
logger.info("(HEAD) upload start");
await deviceRepository.updateHttpAccessStatusToEnabled(req.headers.token, dbConnection);
}
}
// 'upload_finish'
class UploadFinish extends UploadInterface {
async execute(req, dbConnection) {
logger.info("(HEAD) upload finish");
await deviceRepository.clearHttpAccessByToken(req.headers.token, dbConnection);
...
...
(생략)
execute()
오버라이드해야함checkUploadStatusHeader()
가 controller에서 호출되면!// s3Service.js
const uploadObjectCreated = makeUploadObject[req.headers.status];
if (uploadObjectCreated) {
await uploadObjectCreated.execute(req, dbConnection);
...
...
(생략)
}
makeUploadObject[req.headers.status]; 코드가 실행되면 req.headers.status의 값에 해당하는 인스턴스 값을 선언된 변수에 할당한다.
// 인스턴스 생성
const makeUploadObject = {
'upload_start': new UploadStart(),
'upload_finish': new UploadFinish(fileListManager)
};
**new
인스턴스**이다.3.3 if(2) 제거: req.headers.device_name의 Sensorbox
, yongin_camera
에 대한 분기점 제거
Sensorbox
, yongin_camera
에 대한 Url주소만 할당하여 다소 복잡했던 로직을 단순화시켰다.```
// 디바이스 타입에 따라 다른 람다 URL 사용
const deviceName = req.headers.device_name;
let lambdaUrl = '';
switch (deviceName) {
case 'Sensorbox':
lambdaUrl = config.lambdaUrls.SENSORBOX;
break;
case 'yongin_camera':
lambdaUrl = config.lambdaUrls.YONGIN_CAMERA;
break;
default:
logger.info('Unknown device type');
return;
}
```
3.4 람다 함수 처리
if (req.headers.device_name == "Sensorbox") {
for (var item of json_list) {
logger.info("%s Lambda URL Request", item)
request.post({
headers: {"json_filename": item},
url: 'https://3hu37ei2ieqgwo5tpqqbjkrhfy0answm.lambda-url.ap-northeast-2.on.aws/'
}, function(error, response, body) {
logger.info(body)
});
}
json_list = []
} else if (req.headers.device_name == "yongin_camera") {
for (var item of json_list3) {
logger.info("%s Lambda URL Request", item)
request.post({
headers: {"json_filename": item},
url: 'https://hbbbcwsqbealfcfrmu7e5hluia0huqwu.lambda-url.ap-northeast-2.on.aws/'
}, function(error, response, body) {
logger.info(body)
});
}
json_list3 = []
}
// configs.js
lambdaUrls: {
sensorbox: process.env.SENSORBOX,
yonginCamera: process.env.YONGIN_CAMERA
}
// s3Service.js
function processLambdaRequest(jsonUploadList, lambdaUrl) {
jsonUploadList.forEach(item => {
logger.info("%s Lambda URL Request", item);
axios.post(lambdaUrl, {}, {
headers: { 'json_filename': item }
})
.then(response => {
logger.info(response.data);
})
.catch(error => {
logger.error(error.response ? error.response.data : error.message);
});
});
}
axios
라이브러리로 변경하였다.3.5 파일 리스트 관리
json_list
, json_list2
, json_list3
을 처리한 뒤 초기화하는 로직이 구현되어 있었다.var json_list = [];
var json_list2 = [];
var json_list3 = [];
addFile()
, clearFiles()
에서 Sensorbox, yongin_camera에 대한 파일을 분류한 뒤, 업로드 처리가 끝나면 리스트를 초기화 한다.// 파일 리스트를 관리하는 클래스
class FileListManager {
constructor() {
this.sensorboxJsonFiles = []; // Sensorbox 디바이스에서 업로드된 JSON 파일 목록
this.allUploadedJsonFiles = []; // 모든 디바이스에서 업로드된 JSON 파일 목록
this.yonginCameraJsonFiles = []; // yongin_camera 디바이스에서 업로드된 JSON 파일 목록
}
addFile(deviceName, fileName) {
if (deviceName === "Sensorbox") {
this.sensorboxJsonFiles.push(fileName);
} else if (deviceName === "yongin_camera") {
this.yonginCameraJsonFiles.push(fileName);
}
this.allUploadedJsonFiles.push(fileName);
}
clearFiles() {
this.sensorboxJsonFiles = [];
this.allUploadedJsonFiles = [];
this.yonginCameraJsonFiles = [];
}
}
axios
로 변경객체나 배열 내부의 값을 쉽게 추출하여 변수에 할당할 수 있게 해주는 강력한 JavaScript 문법
req.headers
객체에서 device_owner
와 device_name
속성을 추출하고, 이들을 각각 deviceOwner
와 deviceName
이라는 새로운 변수에 할당
const { device_owner: deviceOwner, device_name: deviceName } = req.headers;
본 보고서는 Node.js 애플리케이션의 리팩토링 전후 성능 분석 결과를 기반으로 작성되었습니다. 리팩토링의 목적은 코드의 가독성, 유지보수성을 높이고, 실행 성능을 개선하는 것이었습니다. 이를 위해 Node.js의 내장 프로파일러를 사용하여 성능 데이터를 수집하고 비교 분석하였습니다.
아래와 같은 데이터리스트 전송
리팩토링 과정을 거친 후, 우리의 애플리케이션 성능은 상당한 개선을 보였습니다. 특히, 전체 프로파일링 틱 수에서의 감소는 응답성의 대폭적인 향상을 의미합니다. 이 문서에서는 리팩토링 전과 후의 성능 지표를 비교 분석하여 리팩토링의 효과를 상세히 설명합니다.
ntdll.dll
실행 비율: 리팩토링을 통해 ntdll.dll
에서의 실행 비율이 2.3% 감소하였습니다. 이는 시스템 수준에서의 최적화를 반영하며, 애플리케이션의 효율성을 향상시킨 중요한 요소입니다.node.exe
실행 비율: node.exe
에서의 실행 비율이 2.2% 증가하였습니다. 이는 Node.js 런타임 내의 코드 실행이 활발해졌음을 나타내며, 리팩토링을 통해 자바스크립트 코드의 효율이 개선되었음을 의미합니다.리팩토링 과정에서 시스템 라이브러리 부하가 현저히 감소한 것은 중요한 성과입니다. 특히, ntdll.dll
같은 시스템 라이브러리에서의 시간 소비가 줄어든 것은 시스템 호출과 I/O 작업의 최적화를 의미합니다. 이는 리팩토링을 통해 시스템 레벨에서의 작업이 효율적으로 재구성되었음을 보여줍니다.
또한, Redis를 활용한 캐싱 전략의 도입은 데이터베이스 접근 최소화와 네트워크 지연 감소를 가져왔습니다. 이러한 최적화는 전체 시스템의 성능 향상에 크게 기여하였으며, 고 부하 상황에서의 응답 시간 단축과 사용자 경험 향상을 가능하게 하였습니다.
리팩토링을 통한 성능 개선은 숫자로 증명됩니다. 프로파일링 틱 수의 대폭적인 감소와 시스템 라이브러리 부하의 감소는 리팩토링이 애플리케이션의 성능에 긍정적인 영향을 미쳤음을 명확히 보여줍니다. 가비지 컬렉션 실행 비율의 증가는 더 활발한 메모리 관리를 의미하며, 이는 메모리 효율성의 개선으로 이어졌을 가능성이 높습니다.
종합적으로, 리팩토링은 애플리케이션의 성능을 크게 향상시켰으며, 이는 사용자 경험의 개선으로 직결됩니다. 따라서 리팩토링은 애플리케이션 개발 과정에서 중요한 단계로 간주되어야 합니다.