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

Reqeust : tsc is so slow #137

Closed
unchaptered opened this issue Sep 14, 2022 · 6 comments
Closed

Reqeust : tsc is so slow #137

unchaptered opened this issue Sep 14, 2022 · 6 comments
Assignees
Labels
feature 기능 추가 및 업데이트

Comments

@unchaptered
Copy link
Member

unchaptered commented Sep 14, 2022

1. 제안사항

현재 점점 build 속도 저하가 되고 있습니다.

tsc 의 성능 저하가 너무 심각해져서 작업에 지장이 오고 있습니다.
평균적으로 Ctrl + S 한 번에 5~20 초가 소요되고 있습니다. 해당 과정을 하루에 100 번 이상 대기하게 될 수도 있으며, 결과적으로 500~2000초 에 해당하는 큰 시간을 소요하게 됩니다.

이 문제를 vitetsc 를 동시에 사용하는 것으로 해결하려고 했으나, 해당 방식 은 다음과 같은 우려되는 부분이 있었습니다.

  • vite-plugin-node 의 종속성 문제가 발생한다.
  • 해당 라이브러리가 typescript 의 모든 기능을 정기적으로 지원한다는 보장이 없다.

이에 따라서 tsc 가 실행하는 tsconfig.json 튜닝 이나 코드 튜닝 등을 통해서 성능 향상을 기대해야 할 것 같다는 생각이 들었습니다.

tsc 튜닝을 하면서 사용한 커맨드와 그 목적은 다음과 같습니다.

명령어 설명
tsc --diagnostics 실행 중인 tsc 의 세부적인 시간 소요량을 볼 수 있습니다.
tsc --extendedDiagnostics 위와 유사하지만, 더욱 자세한 내용을 볼 수 있습니다.
tsc --showConfig 현재 실행 중인 tsconfig.json 에 대한 자세한 내용을 볼 수 있습니다.

1.1. 환경 설정

OS: Windows 10
Node:  v16.15.1 (@unchaptered), v16.13.1(@axisotherwise)
npm : v8.11.0 (@unchaptered), v8.2.2 (@axisotherwise)
@unchaptered unchaptered added the feature 기능 추가 및 업데이트 label Sep 14, 2022
@unchaptered
Copy link
Member Author

unchaptered commented Sep 14, 2022

2. tsc 성능 튜닝

2.1. include 및 exclude

tsconfig.json 의 include 구문에 정규표현식의 ***와일드 카드, * *** 를 사용할 경우 심각한 성능 저하가 발생한다 라는 내용을 발견하였습니다.

해당 글의 논리는 모든 소스 파일이 어차피 src 에 있는 경우, src/* 라는 구문은 src 안의 모든 디렉토리를 중복하여 탐색하게 된다는 것이었습니다.

이 중, 이미 include 는 사용 중이어서 exclude 를 사용하였으나 큰 성능 상승은 없었습니다.

  "include": ["src"],
  "exclude": [
    "**/node_module",
    ".github",
    ".husky",
    "coverage",
    "dist",
    "key",
    "sql"
  ]

@unchaptered
Copy link
Member Author

unchaptered commented Sep 14, 2022

2.2. incremental: true

tsconfig.json 에서 Incremental Project Emit 을 이용하여 tsconfig 의 반복실행에 대한 성능 튜닝이 가능합니다.

다만, 해당 구문은 이미 적용 중인 상태이기 떄문에 해결책이 될 수 없었습니다.

이로 인해서, 최초실행은 15 초 정도 걸리지만 반복실행은 2~3 초의 시간이 소요됩니다.

@unchaptered
Copy link
Member Author

unchaptered commented Sep 14, 2022

2.3. Union 타입 보다 기본 타입 선호

역시나 다음과 같이 Union 타입이 심각한 성능 저하를 일으킬 수도 있다는 부분을 찾았습니다.

 type CustomTokenType =
        | "AccessToken"
        | "RefreshToken"
        | "EmailVerifyToken"
        | "NicknameVerifyToken"
        | "ResetPasswordToken";

해당 구문은 컴파일 시 마다, 조건문을 비교해야 하며 이에 따라 성능 저하가 발생할 수 있는 가능성 에 대한 우려가 있었습니다. 단, 해당 조건이 12개 이상을 포함하는 Union 에 해당하는 경우라고 명시되어 있었습니다.

이를 피하기 위한 방법으로는 하위 Union 타입을 사용하는 것이 나와있었습니다.
이 부분은 차후 리펙토링에서 포함하여 수정하는 것이 좋을 것 같습니다.

 type AuthTokenType = "AccessToken" | "RefreshToken";
 type VerifyTokenType = "EmailVerifyToken" |  "NicknameVerifyToken";
 type ResetPasswordTokenType = "ResetPasswordToken";

 type CustomTokenType = AuthTokenType | VerifyTokenType | ResetPasswordTokenType ;

@unchaptered
Copy link
Member Author

unchaptered commented Sep 14, 2022

2.4. 결론

공식 문서 상의 모든 솔루션을 적용해보았음에도 성능 향상이 일어나지 않았습니다.
도저히 이해가 되지 않아서 tsc-watch --showConfig* 을 실행해 보았을 떄, tsc 와 다른 내용과 동시에 에러가 발생했습니다.
따라서, 두 프로그램이 다른 존재임을 알게 되었고 같은 tsconfig.json 을 읽지 않을 수도 있다고 생각했습니다.

@unchaptered unchaptered changed the title Reqeust : tsc 가 너무 느려요 Reqeust : tsc is so slow Sep 14, 2022
@unchaptered unchaptered changed the title Reqeust : tsc is so slow Reqeust : tsc is so slow Sep 14, 2022
@unchaptered unchaptered self-assigned this Sep 14, 2022
This was referenced Sep 14, 2022
@unchaptered unchaptered reopened this Sep 19, 2022
@unchaptered
Copy link
Member Author

unchaptered commented Sep 19, 2022

3. tsc 안정성 튜닝

tsc 튜닝 결과 2.6 초 대까지 낮추면서 rollup 방식의 빌더 를 쓰지 않는 선에서 최고의 성능이었다고 생각했습니다.
하지만, 이 방식은 성능적으로는 괜찮았으나 다음과 같은 치명적인 문제점 이 남아있습니다.

3.1. 문제점

  • ~/src/**/*.ts 파일이 사라지더라도 ~/dist/**/*.js 파일은 사라지지 않습니다.

실제로 ~/src/server.ts 를 지우더라도 ~/dist/server.js 는 사라지지 않습니다.
따라서 배포 버전에서는 성능에서 다소간의 손해 를 보더라도 확실한 안정성을 갖춰야 한다고 판단했습니다.

이 부분에서는 개인적인 판단에 의존하기 보다는 nest new app 으로 만들어진 파일들을 다소 간에 참고했습니다.

3.2. nest-cli 로 생성한 프로젝트 분석 (shell script)

  • Nest.JS 는 현업에서 많이 쓰이고 있는 엔터프라이즈급 서비스 이기 떄문에, 참고할 부분이 많을 것
"prebuild": "rimraf dist",                    // dist 폴더를 삭제
"build": "nest build",                        // node_modules/.bid/nest 를 실행

(프로젝트에 설치된) node 생태계의 cli 명령어의 경우, node_modules/.bin/cli-이름 를 실행하게 됩니다.

#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
  exec "$basedir/node"  "$basedir/../@nestjs/cli/bin/nest.js" "$@"
else 
  exec node  "$basedir/../@nestjs/cli/bin/nest.js" "$@"
fi

위 코드에서 $basedir 은 shell script 가 실행되는 폴더를 의미하므로, $basedir/../@nestjs/**node_modules/@nestjs/** 를 의미합니다. 따라서 해당 파일을 열어보면, 다음과 같은 파일이 나오게 됩니다.

#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander = require("commander");
const commands_1 = require("../commands");
const local_binaries_1 = require("../lib/utils/local-binaries");
const bootstrap = () => {
    const program = commander;
    program
        .version(require('../package.json').version, '-v, --version', 'Output the current version.')
        .usage('<command> [options]')
        .helpOption('-h, --help', 'Output usage information.');
    if ((0, local_binaries_1.localBinExists)()) {
        const localCommandLoader = (0, local_binaries_1.loadLocalBinCommandLoader)();
        localCommandLoader.load(program);
    }
    else {
        commands_1.CommandLoader.load(program);
    }
    commander.parse(process.argv);
    if (!process.argv.slice(2).length) {
        program.outputHelp();
    }
};
bootstrap();

저희는 commander 라던가 내장 객체에 대한 이해도가 높지 않습니다.
다만, 추론하건대, 일반적으로 package.json 안에 프로젝트의 스크립트가 등록되어 있는 경우가 많았습니다.
따라서 nest 라는 친구의 shell script 또한 ~/node_modules/@nest/cli/package.json 에 있을 것이라고 판단했습니다.

즉, nest build 라는 명령어는 다음과 같을 것입니다.

  • cd ${pwd}/node_modules/@nest/cli/package.json & npm run build

그리고 이에 해당하는 명령어는 바로 tsc 입니다.
따라서, nest 에서 실행하는 명령어는 다음과 같을 것입니다.

rimraf dist
tsc

물론, 최초 호출 위치가 프로젝트 루트 경로 이므로, 실제로 같은 명령어를 입력해도 거의 유사 하게 작동합니다.

3.3. nest-cli 로 생성한 프로젝트 카피

nest-cli 로 생성한 프로젝트에서 다음의 파일들을 cupick 에 맞게 밴치마킹하였습니다.

  • tsconfig.json, tsconfig.build.json >> tsconfig.build.json 단일 파일로 병합
  • "pacakge.json"."script" 의 일부 >> "pacakge.json"."script" 의 일부
"prebuild": "rimraf dist",
"build": "tsc -p tsconfig.build.json",
"prestart": "npm run build",
"start": "pm2 start ecosystem.config.js --env production",

@unchaptered
Copy link
Member Author

3.4. 결론

결과적으로 tsconfig.json 의 튜닝은 성공했으나, 불안정성이 존재 함을 인지했습니다.
이에, production 빌드 파일에서는 안정성 측면으로 성능 저하를 감수해야 했음을 알게 되었습니다.

참고 문헌은 다음과 같습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 기능 추가 및 업데이트
Projects
None yet
Development

No branches or pull requests

1 participant