콘솔 내용

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        1.527 s, estimated 2 s

Ran all test suites.

Jest did not exit one second after the test run has completed.

"This usually means that there are asynchronous operations that weren't stopped in your tests. 
Consider running Jest with `--detectOpenHandles` to troubleshoot this issue."

 

테스트를 한 후에 뭔가를 멈추지 않았다고 한다. 

 

콘솔에서 알려준대로 package.json 에서 "test:e2e" 에  --detectOpenHandles 추가

그 결과 해당 문구는 뜨지 않았다.

하지만 테스트가 끝나지 않아 내가 강제로 종료를 해야하는 상황이 발생.

 

이유는 Jest 공식문서: -- detectOpenHandles 를 참고해보니

이 옵션을 사용하면 성능이 저하되며 디버깅을 할때만 사용하라고 한다.

 

옵션을 추가하지 않고 해결하기 위해 다시 검색을  했다.

How To Fix: Jest Did Not Exit 에 따르면

나의 경우에는 app을 이용하여 연결을 하였지만 app 것을 끝내지 않아서 발생한 것 같다.

 

아래의 코드를 마지막에 추가하고 실행해보니 테스트가 끝나후 안전하게 종료 되었다.

afterAll(async () => {
    await app.close();
  });

 

 

e2e 테스트 중에 연결된 것 ( app, database) 은 테스트가 끝나면 모두 종료하자.

e2e 테스트를 하다가 발생한 에러

ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)... Error: DataSource with name "default" has already added.

 

검색을 하여 찾은 아래의 링크를 따라 해결

https://github.com/Aliheym/typeorm-transactional/issues/10

 

Error when running E2E tests with Nest.js · Issue #10 · Aliheym/typeorm-transactional

Hi there, thanks for the package! It really is super useful. My issue is that we can't run our E2E tests with it as of now. We get the console error Error: DataSource with name "default" has alread...

github.com

 

두가지 해결 방식

방식 1. getDataSourceByName('default') 추가

import { addTransactionalDataSource, getDataSourceByName } from 'typeorm-transactional';

// ...

TypeOrmModule.forRootAsync({
    dataSourceFactory: async (options) => {
      return getDataSourceByName('default') || addTransactionalDataSource(new DataSource(options));
    },
  });

 

방식 2. deleteDataSourceByName('default') 추가

import { addTransactionalDataSource, deleteDataSourceByName } from 'typeorm-transactional';

// ...

TypeOrmModule.forRootAsync({
    dataSourceFactory: async (options) => {
      deleteDataSourceByName('default');

      return addTransactionalDataSource(new DataSource(options));
    },
  });

 

각 방식에서 사용된 코드의 의미???

import { DataSource, EntityManager, Repository } from 'typeorm';
...
export type DataSourceName = string | 'default';
const dataSources = new Map<DataSourceName, DataSource>();

export const getDataSourceByName = (name: DataSourceName) => dataSources.get(name);
export const deleteDataSourceByName = (name: DataSourceName) => dataSources.delete(name);

 

 

방식 1 을 선택하여   getDataSourceByName('default') 코드를 추가하여 해결

//변경 전
 async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }
    return addTransactionalDataSource(new DataSource(options));
  },
  
// 변경 후
 async dataSourceFactory(options) {
    if (!options) {
      throw new Error('Invalid options passed');
    }
    return getDataSourceByName('default') || addTransactionalDataSource(new DataSource(options));
  },

 

 

이렇게 하니 귀찮은 일이 발생했다.

1. 앞의 테스트에서 내가 생성한 유저를 다시 생성하려면 이미 있는 e-mail 이라는 문구가 발생

2. 앞의 테스트에서 내가 지운 유저를 또 지우려면 내가 원하는 204가 아니라 500 반환

 

beforeAll 을 이용하여서 하니 

  • 옵션< getDataSourceByName('default') >을 추가하지 않아도 해당 에러가 뜨지 않았다. Error: DataSource with name "default" has already added.
  • 테스트할때마다 내가 적은 expect 부분을 수정하지 않아도 되었다.

의문점

  • beforeEach 에서는 중복이 발생했는데 어떻게 beforeAll 에서는 중복이 발생하지 않을까

-> 데이터베이스를 확인해보니 역시나 중복이 발생. 중복을 방지하기 위해  

 

최종적으로 테이블을 드랍하고 동기화 하는 코드를 추가하여 마무리하였다.

 beforeAll(async () => {
    initializeTransactionalContext();
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();
    
    dataSource = moduleFixture.get<DataSource>(DataSource);
    
    // 데이터베이스 테이블 초기화 및 동기화
    await dataSource.dropDatabase();
    await dataSource.synchronize();
    
    // 토큰 발행
    token = generateTestToken();
    
    app = moduleFixture.createNestApplication();
    await app.init();
  });

 

※ 데이테베이스 테이블 초기화를 beforeEach에 사용하면 생성한 유저가 사라져서 jwt 토큰을 발행하는데 문제가 발생한다.

대규모 서비스에만 있는 문제 혹은 어려움

  1. 확장성 확보 와 부하 분산 필요
    • 스케일 아웃  - 서버의 역할을 분담하거나 서버 대수를 늘림으로 써 시스템의 처리 능력을 높이는 방법
      • 장점 : 비용 절감
      • 단점
        • 요청 분배는 어떻게 할 것인가?
        • 데이터 동기화는 어떻게?
        • 통신 지연시간은 어떻게?
  2. 다중성 확보  - 특정 서버가 고장나거나 성능이 저하되어도 서비스를 계속 할 수 있는 구성
  3. 효율적인 운용 필요 - 대규모 시스템을 건강한 상태로 얼마나 유지할 수 있을 것인가?에 대한 운용

대규모 데이터량에 대한 대처

데이터 처리 과정

     디스크 > 메모리 > 캐시 메모리 > CPU

 

 데이터 처리량이 많아지면?

    저속의 디스크로 I/O 이 많이 발생

→ 디스크 I/O 에 대기에 들어선 프로그램은 읽기가 완료될때 까지 다음 처리 불가

 시스템 전체의 성능이 저하

 

대규모 데이터 처리시 고민해야할 것들

  • 어떻게 하면 데이터를 적게 가져갈 수 있을까?
  • 여러 서버로 분산시킬 수 있을까?
  • 필요한 데이터를 최소한의 횟수로 읽어 들일 수 있을까?
  • 등등

 

아키텍처 Architecture?

  • 사전적 의미 : 컴퓨터 시스템의 설계 및 구조
시스템 구성 및 동작 원리를 나타내는 것
구성 요소 간의 관계 및 시스템 외부 환경과의 관계를 묘사하는 것  
시스템 구성 요소에 대한 설계 및 구현을 지원하는 수준을 기술하는 것
요구 사양 및 시스템 수명 주기를 고려하는 것  
시스템의 전체적인 최적화를 목표로 하는 것
 하나의, 서비스가 어떻게 구성되며 어떻게 동작이 된다 를 표현하는 것
https://www.osckorea.com/post/bigaebaljado-swibge-ihaehaneun-akitegceoyi-gaenyeom

 

레이어드 아키텍처?

시스템을 여러 계층으로 나누어 각 계층이 특정 역할을 담당하도록 만든 구조

애플리케이션의 경우

  • 프레젠테이션(constroller), 비즈니스(serviec), 퍼시스턴스(repository) 계층으로 나눠서 관리하는 방법
  • 레이어드 아키텍처 패턴에서는 각 계층 간의 의존성이 중요한 규칙으로 작용

레이어드 아키텍처 패턴의 의존성 규칙

단방향 의존성 규칙 =  상위 계층은 하위 계층에만 의존할 수 있고, 하위 계층은 상위 계층에 의존해서는 안 된다는 원칙

의존성 관계

[상위 계층] (constroller)  ▶▶  (serviec) ▶▶ (repository) [ 하위 계층]

 

- Constroller 는 serviec 에만 의존한다.

  • constroller 는 serviec를 호출 할 수 있다. 
  • constroller 는 repository 를 호출 할 수 없다.
  • serviec 계층의 변화는 constroller 계층에 영향을 미칠 수도 있다.

- Serviec 는 repository 에만 의존한다.

  • Serviec 는 Repository 를 호출 할 수 있다. 
  • Serviec 는 Constroller 를 호출 할 수 없다.

각 계층의 역할

Controller

  • Client 로 부터의 요청을 서비스에 전달하는 역할
  • 서비스로부터 받은 요청에 대한 응답을 client 에게 전달하는 역할
  • Client 의 요청을 이해하고 그에 대한 응답을 하는 계층 

Serviec

  • 컨틀롤러에서 받은 요청의 유효성을 검증하는 역할
  • 검증된 요청을 수행하기 위한 비즈니스 로직을 수행하는 역할
    • 받은 요청을 수행하기 위한 데이터를 Repository 에 요청 및 데이터 검증
    • 검증된 데이터를 이용하여 요청에 맞게 데이터를 가공
  • 요청을 수행하기 위한 모든 메세드가 모인 계층

Repository

  • 데이터베이스에 대한 접근을 추상화를 제공하는 역할
    • 사용자 어제 구매한 식품 목록을 조회하기 위해 sql 문을 작성하는대신 productRepository.findPrdoductByDate() 와 같은 코드를 제공하는 것이 추상화를 제공하는 것
  • 데이터베이스와의 데이터 CRUD(Create, Read, Update, Delete) 작업을 담당하는 계층

레이어드 아키텍처 패턴을 사용하는 이유

  1. 코드의 유지 보수성과 확장성이 크게 향상한다.
  2. 코드의 재사용성이 커진다.
  3. 각 레이어가 독립적으로 변경될 수 있다.

에러 메세지

ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

 

GitAction 마켓에 있는 "appleboy/ssh-action@v1.0.3" 

를 적용해서 EC2 에 ssh 로 접속하려는데

"인증 불가....."

 

EC2 보안그룹에 문제가 발생한 것인가 하여 확인

- 애플리케이션도 배포해보고 해당 ip 로 접속도 다된다.

 

Actions secret 에 입력한 값이 잘 못 되었나 하여 다시 수정하여 시도

-> 같은 에러 발생

 

구글링 해본 결과 : 패스워드 방식이 아니라 key 를 이용하는데 내가 사용하는 key 의 알고리즘이 EC2 에 있는 우분투 버전에 따라 지원하지 않을 수도 있다고 한다. 

-> appleboy/ssh-action git hub 에 있는 내용에 따라 

/etc/ssh/sshd_config 에 CASignatureAlgorithms +ssh-rsa 추가

-> 여전히 같은 에러

 

다시 CI/CD 를 위해 작성한 코드로 돌아가서 코드를 다시 확인
Actions secret  에 설정한 Repository secrets의 이름과 코드에 있는 이름이 다른 것이 있다...

 

회고

역시 에러는 에러가 발생한 위치 부근의 코드를 확인하거나

콘솔에 찍힌 로그를 잘 확인하면 해결이 가능한 것이였다.

또한 시스템 적인 에러보다 내가 몰라서 만들거나 실수가 많다....

 

'서버 & 웹 개발 노트 > CI CD' 카테고리의 다른 글

[ CI/CD] 테스트를 거친 후에 배포하는 방법  (0) 2024.08.28
[ CI/CD ] .env 파일 관리하기  (0) 2024.08.28
CI/CD 구축 툴 Github Actions  (0) 2024.08.17
CI/CD 란?  (0) 2024.08.17

+ Recent posts