목차

네트워크 분기점

1 Gbps의 한계 - PC 라우터의 한계

 

리눅스 커널을 사용하면 대략 30만 패킷/초가 한계

평큔 패킷 길이가 300 바이트면 대략 1 Gbps 

 

한계

  • 커널의 성능  = 30만 패킷/초
  • Gigabit Ethernet 레벨 = 1 Gbps

대책

  • PC 라우터를 여러 대 병렬화
  • 박스형 라우터(Cisco 라우터) 사용 - 비싼 가격

 

500 호스트의 한계 - 1 서브넷, ARP 테이블에서의 한계

500 호스트의 한계는 스위치의 ARP 테이블 (Address Resolution Protocol table)과 관련된 한계

*ARP 테이블?  IP 주소(호스트 주소와 서브넷 주소)와 MAC 주소 간 관계를 나타내는 테이블로 스위치가 이 테이블을 지니고 있음

 

1 서브넷에 배치 할 수 있는 호스트수 = 약 500

 

브로드캐스팅 통신에 의존한 처리가 많아지면(트래픽) → CPU 부하가 수십%까지 상승  →  패킷 손실 발생

*브로드캐스팅: 네트워크 상의 모든 장치에 데이터 패킷을 전송하는 통신 방법

  • 브로드캐스트 트래픽이 많아지면 네트워크 장비나 서버의 CPU에 상당한 부하를 줄 수 있고, 이는 처리 능력의 한계로 인해 CPU 사용률이 상승하게 됩니다. CPU 부하가 과도하게 높아지면 패킷 처리가 지연되거나 제대로 이루어지지 않아 패킷 손실이 발생

네트워크 구조 계층화

앞에서 언급된 한계에 대한 대책으로 네트워크를 3단계로 구성하고 있다.

  1. Access 계층(액세스 영역): 서버의 엔드포인트에 접근을 제공하는 계층 
  2. Distrution 계층 : 트래픽을 각 서브넷에 전송하는 계층
  3. Core 계층 혹은 OSPF(Open Shortest Path First) 영역 : 트래픽의 큰 흐름을 담당하는 계층

이 3단 구조로, 가장 작은 서브넷 Access 계층에서 100대 200대 로 억제하고 디스트리뷰션을 1000대 정도 코어 전체로는 10,000대 단위를 다룰 수 있다는 계층구조를 설계한다.

 

글로벌화, CDN

 

세계 각지에서 HTTP로 데이터를 가져가려고 한다면 하나의 나라에 위치하고 있는 데이터 센터에서 전송을 한다는 것은 비현실적이다.

 

이에 대한 대안으로 CDN(Content Delivery Network) 가 있다.

CDN 전문 업체 중 하나가 Amazon Cloudfront 이다.

 

CDN 의 작동원리

세계 각지에 서버를 두고 데이터를 캐싱해서 사용자가 가지고 가려고 할때 사용자와 가장 가까운 서버로 엑세스해서 접근할 수 있게 해주는 원리

 

Amazon Cloudfront 작동 원리

  1. 사용자가 웹사이트나 어플리케이션을 요청할 때 가장 가까운 CloudFront 엣지 로케이션으로 요청이 전송됨.
  2. 요청받은 콘텐츠가 해당 엣지 로케이션에 캐시되어 있지 않은 경우, CloudFront는 원본 서버(예: Amazon S3 버킷 또는 EC2 인스턴스)로부터 콘텐츠를 가져와 사용자에게 전달.
  3. 콘텐츠는 최적화된 네트워크 경로를 통해 사용자에게 전달( 로딩 시간 단축)
  4. 콘텐츠가 업데이트 되었을 경우, 새 콘텐츠로 캐시를 갱신하고, 필요시 무효화 처리를 통해 최신 콘텐츠가 사용자에게 제공됨.

 

 

한층 높은 단계로

10Gbps 이상의 세계

  • AS(Autonomous system) 번호 보유
  • IX(Internet exchange) 에 접속해서 트래픽 교환
  • BGP(Boader Gateway Protoclo)로 라우팅 제어

 

목차

요약: Layer 테스트는 빠른 피드백, 문제의 원인 파악, 코드 안정성, 테스트 범위 확장 등을 제공하여 E2E 테스트와 상호 보완적으로 동작합니다. Layer 테스트는 기능과 성능을 빠르게 검증하는 데 유리하고, E2E 테스트는 최종 사용자 경험을 보장하는 데 필수적입니다.

1. 탄생 배경

Layer 테스트는 소프트웨어가 복잡해지고 규모가 커지면서 등장했습니다. 기존의 E2E 테스트만으로는 충분한 검증이 어려웠고, 특히 애플리케이션이 여러 계층(컨트롤러, 서비스, 리포지토리 등)으로 나뉘면서 각 계층을 개별적으로 테스트할 필요성이 제기되었습니다. 더불어 빠른 피드백을 제공하고 문제를 효율적으로 디버깅하기 위해 Layer 테스트가 도입되었습니다.

2. 사용하는 이유

  1. 복잡성 증가에 따른 필요성
    • 현대 소프트웨어는 점점 복잡해지면서 여러 계층(예: 컨트롤러, 서비스, 리포지토리)으로 나뉘어 설계됩니다. 각 계층의 기능을 개별적으로 검증해야 각 계층이 올바르게 작동하는지를 보장할 수 있기 때문에 Layer 테스트가 필요합니다.
  2. 빠른 피드백 제공
    • Layer 테스트는 특정 계층만 테스트하기 때문에 훨씬 빠르게 완료됩니다. 이를 통해 빠른 피드백을 받고, 수정해야 할 부분을 신속히 개선할 수 있습니다. 
  3. 문제의 원인 파악이 용이함
    • E2E 테스트에서 에러가 발생할 경우, 문제의 정확한 원인을 찾기 어렵습니다.
    • Layer 테스트는 각 계층을 독립적으로 검증하기 때문에, 특정 계층에서 문제가 발생했을 때 이를 신속하게 식별할 수 있습니다.
    • 이러한 방식은 디버깅을 효율적으로 만들어 문제 해결 시간을 단축시킵니다.
  4. 세부적인 기능 검증
    • Layer 테스트는 서비스나 비즈니스 로직 같은 개별 기능을 세밀하게 검증합니다.
    • E2E 테스트에서는 놓칠 수 있는 세부적인 로직을 더 정확하게 확인할 수 있습니다.
    • 각 계층의 로직이 기대대로 동작하는지 보장하여 시스템의 일관성을 높입니다.
  5. 유지보수성 향상
    • 코드가 자주 수정되고 기능이 추가되는 상황에서도 안정적으로 시스템을 유지하기 위해 Layer 테스트는 중요한 역할을 합니다.
    • 특정 계층의 변경이 다른 계층에 영향을 미치지 않도록 보장하여 리팩토링 시에도 안정성을 유지할 수 있습니다.
  6. 테스트 범위 확장
    • E2E 테스트만으로는 모든 시나리오를 포괄하기 어렵습니다.
    • Layer 테스트를 통해 특정 시나리오나 예외 상황까지 세밀히 검증할 수 있습니다.
    • 이로써 다양한 케이스를 커버하여 테스트의 신뢰성을 높입니다.
  7.  테스트 비용 절감
    • E2E 테스트는 리소스를 많이 소모하지만, Layer 테스트는 가볍고 빠르게 실행됩니다. 이를 통해 자주 테스트를 실행할 수 있어 비용이 절감됩니다.
    • 개발 주기를 최적화하고 리소스를 효율적으로 사용할 수 있습니다.
  8. 디자인 품질 향상
    • Layer 테스트를 염두에 두고 코드를 작성하면 더 모듈화되고 테스트 가능한 구조가 됩니다.
    • 코드가 더 응집력 있게 설계되며, 가독성과 유지보수성이 향상됩니다.
    • 설계 품질이 높아지면 향후 확장성에도 유리합니다.

3. 장점과 단점

장점

  • 빠른 테스트 속도:  E2E 테스트보다 실행 속도가 빠릅니다.
  • 효율적인 디버깅:  문제가 발생할 때 정확한 계층을 쉽게 파악할 수 있습니다.
  • 높은 테스트 커버리지: 특정 비즈니스 로직이나 예외 상황을 세밀하게 테스트할 수 있습니다.
  • 디자인 품질 향상: Layer 테스트를 염두에 둔 코드는 더 모듈화되고 테스트 가능한 구조가 됩니다.
  • 테스트 비용 절감: 더 적은 리소스와 비용으로 빈번한 테스트를 수행할 수 있습니다.

단점

  • 제약된 실제 환경 테스트: Layer 테스트는 개별 계층만 테스트하므로 실제 사용자 경험이나 전체 시스템의 통합을 충분히 검증할 수 없습니다.
  • 추가적인 코드 작성 필요: Layer 테스트를 작성하기 위해서는 더 많은 테스트 코드와 설정이 필요합니다.
  • 복잡성 증가: 각 계층에 대해 별도의 테스트를 작성하고 유지해야 하므로 프로젝트 관리가 복잡해질 수 있습니다.
  • 전체 시스템 통합을 검증하는 데 제한적: Layer 테스트만으로는 전체 애플리케이션의 흐름을 보장할 수 없으므로 E2E 테스트와 함께 사용해야 합니다.

4. E2E 테스트와의 차이점

비교 항목  Layer 테스트 E2E 테스트
테스트 범위 특정 계층(컨트롤러, 서비스, 리포지토리 등)을 독립적으로 테스트 애플리케이션의 전체 워크플로우를 검증
속도 빠름 느림
문제 분석 문제 발생 시 원인 계층을 쉽게 식별 가능 문제의 원인을 특정하기 어려움
테스트 목적 특정 비즈니스 로직이나 계층의 정확성 검증 전체 시스템의 통합과 사용자 흐름 검증
비용 리소스와 비용이 적게 듦 많은 리소스와 비용이 필요함
테스트 환경 독립적이며 모의 객체(Mock)나 가짜 서비스(Fake)를 사용 실제 환경에서 모든 구성 요소를 테스트
유지보수 테스트 코드가 많아 복잡할 수 있음 한 번 작성하면 잘 변경되지 않음

 

목차

가상화 기술

가상화 기술의 목적

  • 확장성 - 오버헤드 최소화
  • 비용대비 성능 - 리소스 사용률 향상, 운영의 유연함(환경의 단순화)
  • 고가용성 - 환경의 격리

가상화 기술의 효용

  • IPMI(Intelligent Platform Management Interface)를 대체하는 하이퍼바이저
    • IPMI : 벤더 서버에 있는 리모트 관리기능
    • 하이퍼바이저 : 호스트 OS, 서버 상에 최초로 기동하는 OS
  • 하드웨어 간 차이 흡수( → 환경 추상화)
    • 새로운 하드드웨어나 오래된 하드웨어로도 차분에 신경 쓰지 않고 사용 가능
  • 준 가상화(ParaVirtualization) 사용 - Xen 에 특화된 내용
  • 리소스 소비 제어
    • 과부하 경고
    • 부하 조정

가상화 서버 구축정책

가상화 기술을 도입하는 가장 기본적인 목적은 하드웨어의 이용효율 향상

 

방법

- 남아있는 리소스를 주로 이용하는 게스트 OS를 투입

  • CPU 리소스가 남아있다 ▶ 웹 서버 
  • I/O 리소스가 남아있다  ▶ 캐시 서버

주의할 점

  • 같이 두는 것을 피하는 형태의 조합
    • 리소스 소비경향이 비슷하고 부하가 높은 용도의 게스트 OS 끼리(각가의 웹 서버끼리 등)는 리소스를 서로 점유하려고 하기 때문
  • 중앙 스토리지는 사용하지 않는다.
    • 고가의 스토리지를 사용하지 않으면 충분한 안정성 확보 불가.

구체적인 게스트OS 구성예시 그림

가상화로 얻은 장점 

물리적인 리소스 제약에서 해방

  • 리소스를 동적으로 변경
  • VM(게스트 OS)의 마이그레이션이나 복제 용이

→ 서버 증설이 용이  → 더 나은 확장성 확보

 

소프트웨어 레벨의 강력한 호스트 제어

  • 비정상 동작 시 문제 국소화
  • 호스트 제어가 용이

→ 하드웨어와 운용 비용 저하 → 비용대비 성능 향상, 고가용성으로 발전

 

가상화 도입 시 주의할 점

  • 성능상 오버헤드 존재
  • 가상화 기술이 병목현상의 원인이 될 수도 있다.
  • PC 라우터에서는 가상화를 사용하기 때문에 성능이 떨어지는 경우 존재

하드웨어와 효율향상

저가 하드웨어의 유용한 이용 방침

  • 최소한의 관리기능
  • 많은 코어CPU
  • 대량의 메모리
  • flexible 한 I/O 성능
    • Diskless
    • 하드웨어 RAID-10
    • SSD RAID-10
  • 관리용 하드콘솔 불필요
    • IPMI 기능 →Intel AMT

SSD

하테네에서는 주로 다수의 DB 슬레이브 서버에 사용 I/O  때문

 

액세스 성능

  • 양호한 랜덤액세스 성능
  • 메모리 > SSD > HDD RAID-0/10 > HDD RAID-1

 

목차

다중성 확보

AP 서버

서버가 멈추면 이에 대한 대응으로 로드밸런서로 페일오버(failover, 장애극복) 페일백(failback, 정상복귀) 하여 고장난 서버를 자동적으로 분리하고 서버가 복귀 되면 원상태로 복귀시키는 작업을 수행하고 있다. 

 

페일오버(failover, 장애극복) 페일백(failback, 정상복귀) 방식 그림

 

위의 그림처럼, 서버가 10대 정도 있으면 1, 2대 서버가 정지하더라도 서버에 큰 영향을 주지 않아 운영하는데 수고를 줄일 수 있다.

DB 서버

DB 서버도 마찬가지로 서버를 여러대 나열해서 1, 2대 정지하더라도 충분한 처리능력이 있도록 해두는 것이 중요

 

다중화 ( 24시간 365일 100% 가동률) 을 위해서 멀티 마스터 방법을 사용

멀티 마스터?

- 쌍방으로 레플리케이션, 즉 서로가 서로의 슬레이브가 되는 상태로 해두고 한쪽에 쓰기작업을 하면 다른 한쪽으로 전달하고 반대쪽에 쓰더라고 다른쪽으로 전달하는 양방향 레플리케이션 방법

 

MySQL의 경우

한쪽에서 쓰기 작업을 수행하면 데이터가 반대쪽으로 전달되기까지 약간의 지연이 발생

→ 특정 시점에서는 데이터가 완전히 일치하지 않는 경우가 존재

→ 이 시점에 한쪽 서버가 분리되어 다운되면, AP 서버가 특정 데이터를 쓰려고 시도했을 때 이 데이터가 실제로는 쓰이지 않는 모순된 상황이 발생

해결방안

  • 엔터프라이즈에서는 이를 대처하기 위해 레플리케이션을 동기적으로 처리
  • 웹 서비스에서는 동기가 맞지 않는 리스크를 어느 정도 받아들임으로써 성능을 중시

 

멀티 마스터

 

MySQL 서버 구축에서 주류

 

페일오버(장애 극복)에 대한 구체적인 동작

- 상호간에 VRRP(Virtual Router Redundancy Protocol)라는 프로토콜로 감시.

- VRRP에 의해 한쪽이 분리된 것을 알게 되면 자신이 Active 마스터로 승격

 

멀티 마스터 구성  =  기본적으로 서버 2대 + Active/Standby 구성 → 항상 Active 쪽만 쓰기작업을 한다.

방식

Active 인 서버가 다운되면 Standby 였던 쪽이 Active로 승격해서 새로운 마스터가 된다.

다운된 서버는 수작업으로 복구 후

→ Active 역할을 맡았던 서버를 계속 Active로 유지하고 복구된 서버를 Standby로 설정 

문제가 있던 서버를 다시 Active로 설정하고 기존의 Standby 서버를 다시 Standby로 설정

 

이 방식은 전환 타이밍에 따라 동기가 맞지 않은 위험이 남아 있음

현 상황에서는 깨긋이 받아들이고(시스템이나 네트워크에서 발생할 수 있는 어떤 이슈나 문제를 인정하고) 수동으로 복구

 

참고

DB 서버 (슬레이브방식)

  • 서버를 여러 대를 나열한다.
  • 서버 1, 2대 정지하더라도 충분히 처리할 수 있도록 해둔다.

스토리지 서버

이미지 파일과 같은 미디어 파일을 저장하기 위해서 분산 스토리지 서버로 MogileFS가 있다.

 

분산 파일 시스템을 사용

- 확장성 : 대량의 파일의 보존 가능

- 다중성 : 일부 서버가 다운되더라도 전체 장애가 되지 않음

 

MogileFS

- 파일은 파일로 저장

- 파일이 어디에 위치해 있는지를 나타내는 메타 정보를 별도로 관리하는 방법

 

시스템 안정화

시스템 안정화와 상반관계

 

* 안정성 ↔ 자원효율

  한계에 이를 때까지 CPU 사용 - 자원 효율 향상 - 비용절감

    하지만 서버 1대가 다운 → 전체 처리능력 초과 → 장애

* 안정성 ↔ 속도

  한계에 이를 때까지 메모리를 튜닝 - 속도 향상

    하지만 메모리소비가 늘어난다 → 성능 저하   장애 

시스템의 불안정 요인

전형적인 요인들

◆ 애플리케이션/서비스 레벨 → 부하 증가

  1. 기능추가, 메모리 누수
    • 새로운 기능을 추가했는데 그 기능이 예상보다 무거워서 전체적인 부하가 늘어나 서비스가 다운
    • 메모리 누수는 완전히 배제하기 어려운 문제 - 버퍼가 너무 작으면 시간이 지남에 따라 버퍼를 다 사용해서 스왑을 사용하기 시작해서 부하가 증가
  2. 지뢰
    • 특정 URL이 읽히면(지뢰를 밟으면) 아무리 시간이 지나도 응답이 오지 않아서 마치 지뢰처럼 장애의 원인이 되는 현상
  3. 사용자의 액세스 패턴 :
    • 인기많은 사이트에 링크를 걸어두면 그 사이트 사용자가 집중적으로 접속해서 다운되는 경우와 같은 사용자의 패턴
  4. 데이터량 증가
  5. 외부연계 추가
    • Amazon API 와 연결된 서비스가 있을때 Amazon이 다운되면 해당 서비스도 다운

◆ 하드웨어 → 처리능력 저하

  1. 메모리, HDD 장애, NIC(Network Interface Card) 장애

시스템 안정화 대책

적절한 여유(버퍼) 유지

  • 메모리량, CPU 부하 → 한계의 7할 운용

불안정 요인 제거

  • SQL 부하의 상한선을 미리 정함
  • 메모리 누수를 줄임
  • 이상 동작시 자율제어
    • 자동 DOS 판정
    • 자동 재시작 (AP 서버, 호스트 OS)
    • 자동 쿼리제거

목차

Test Fixture 란

소프트웨어 테스트에서 테스트 환경을 설정하고 관리하기 위한 코드 집합을 의미합니다. 테스트가 정확하고 일관성 있게 실행될 수 있도록 준비하는 과정입니다. 이는 테스트를 위해 필요한 모든 상태나 환경을 포함합니다. 예를 들어, 데이터베이스를 설정하거나, 필요한 객체를 생성하거나, 특정 상태로 초기화하는 작업이 포함됩니다.

Test Fixture를 구성하는 기본 원칙

  • 독립적인 테스트 환경 구성: 이전 테스트의 실행이 이후 테스트에 영향을 주지 않도록 하기 위해, beforeEach 또는 afterEach를 사용해 환경을 초기화하고 정리
  • Setup과 Teardown 구분 
    • SetUp : beforeAll, beforeEach 메서드를 사용하여 필요한 초기 설정
    • Teardown : afterAll, afterEach를 사용해 자원을 정리
  • 재사용 가능한 코드 작성: 공통된 설정이나 데이터 준비 작업을 별도의 헬퍼 함수나 클래스로 추출하여, 필요할 때 재사용할 수 있게 작성

Test Fixture를 사용하는 이유

  1. 테스트가 항상 동일한 조건에서 실행되며, 테스트의 독립성과 신뢰성을 보장
  2. 사용하면 코드가 간결해지고, 유지보수성이 높아지며, 자동화된 테스트 실행이 용이

예제 상황

  1. 데이터베이스 테스트: 데이터베이스를 사용하는 애플리케이션의 경우, 각 테스트 전에 데이터베이스를 특정 상태로 초기화하고, 테스트 후에 정리하는 작업을 Test Fixture로 구성
  2. HTTP 요청 테스트: API 테스트에서는 각 요청에 대한 환경 설정(예: 모의 데이터 또는 인증 토큰 설정)을 Test Fixture로 구성

NestJS에서의 Test Fixture

NestJS에서 테스트를 작성할 때, beforeEach, afterEach, beforeAll, afterAll을 사용하여 테스트 픽스처를 구성할 수 있습니다. 주로 TestingModule을 사용하여 애플리케이션 모듈의 인스턴스를 생성하고, 의존성을 주입하는 작업이 포함

 

구성 요소 설명

  • TestingModule: NestJS의 테스트 모듈을 생성하여 의존성을 주입하고, 각 컴포넌트가 올바르게 동작하는지 확인합니다.
  • beforeAll: 테스트 스위트 내에서 한 번만 실행되는 초기화 코드입니다. 여기서 TestingModule을 컴파일하고 컨트롤러와 서비스를 인스턴스화합니다.
  • beforeEach: 각 테스트가 실행되기 전에 실행되어, 테스트 상태를 초기화하거나 Jest의 mock을 재설정합니다.
  • afterAll: 테스트가 모두 종료된 후 한 번 실행되어, 테스트 환경을 정리하거나 자원을 해제합니다.

NestJS와 Jest를 사용하여 테스트 픽스처를 구성하는 방법을 보여주는 예제

import { Test, TestingModule } from '@nestjs/testing';
import { MyService } from './my.service';
import { MyController } from './my.controller';

describe('MyController', () => {
  let controller: MyController;
  let service: MyService;

  beforeAll(async () => {
    // 모든 테스트 전에 실행되는 setup 작업 (한 번만 실행)
    const module: TestingModule = await Test.createTestingModule({
      controllers: [MyController],
      providers: [MyService],
    }).compile();

    controller = module.get<MyController>(MyController);
    service = module.get<MyService>(MyService);
  });

  beforeEach(() => {
    // 각 테스트 전에 필요한 초기화 작업 수행
    jest.clearAllMocks(); // Jest의 모든 mock 초기화
  });
  
  afterEach(() => {
    // 각 테스트 후 자원 해제나 초기화 작업
  });
  
  // 테스트
  it('should return expected data', async () => {
    const result = 'expected result';
    jest.spyOn(service, 'getData').mockImplementation(() => result);

    expect(await controller.getData()).toBe(result);
  });
  

  afterAll(() => {
    // 모든 테스트 후에 실행되는 teardown 작업 (한 번만 실행)
    // 테스트 모듈 정리 또는 자원 해제
  });
});

 

 

반복되는 코드를 모두 Fixture를 만들어서 해야 할까?

 

경우에 따라 다르겠지만, 테스트 상황에서 기본적으로 몰라도 되는 확인해보지 않아도 되는 코드는  Fixture를 생성해서 구현하는 것이 좋아 보인다. 예를 들어, 데이터 베이스 초기화같은 것이 있겠다.

 

하지만, 유저 생성, 토큰생성 등을 beforeAll 이나 beforeEach 에 구현해 버리면 각 테스트 마다 확인하기 위해 코드의 위 아래를 오가야하므로 오히려 비효율적이라고 생각한다.

 

차라리, 해당 기능들을 함수나 클래스를 만들어서 각 테스트에서 호출하여 사용하는 방법이 해당 테스트를 더 빨리 이해할 수 있을 것 같다.

  

 

'TDD' 카테고리의 다른 글

[ TDD ] [ 책 ] 6 장 테스트 코드의 구성  (0) 2024.10.27

+ Recent posts