시스템 설계를 잘하는 방법

시스템 설계를 잘하는 방법
It SharingPosted On Jul 20, 202412 min read

기초

네트워크 프로토콜 (IP, DNS, HTTP, TCP, UDP)

  • IP (Internet Protocol): 인터넷 상에서 모든 데이터 패킷의 송수신을 관리합니다.
  • DNS (Domain Name System): 도메인 이름을 IP 주소로 변환하여 브라우저가 인터넷 자원을 로드할 수 있습니다.
  • HTTP (HyperText Transfer Protocol): 인터넷 상에서 웹 페이지를 전송하는 데 사용되는 프로토콜입니다.
  • TCP (Transmission Control Protocol): 데이터를 신뢰성 있게, 순서대로, 오류를 확인하여 전달합니다.
  • UDP (User Datagram Protocol): 동영상 재생이나 DNS 조회와 같이 시간에 민감한 전송에 사용됩니다.

저장, 지연 및 처리량

  • 지연 시간: 시스템에서 경험하는 시간 지연, 데이터가 한 지점에서 다른 지점으로 이동하는 데 걸리는 시간입니다.
  • 처리량: 주어진 기간 내 시스템에서 처리되는 데이터의 양을 나타내며, 시스템의 용량을 나타냅니다.

폴링, 스트리밍, 소켓

  • 폴링: 입력 장치의 상태를 지속적으로 확인하는 것입니다.
  • 스트리밍: 주로 오디오 또는 비디오 데이터의 연속 전송입니다.
  • 소켓: 네트워크를 통해 데이터를 송수신하기 위한 엔드포인트입니다.

데이터베이스

SQL vs NoSQL

  • SQL: ACID 준수와 같은 구조화된 데이터에 사용됩니다. MySQL, Oracle, SQL Server 및 PostgreSQL 등이 있습니다.
  • NoSQL: 구조화되지 않거나 반구조화된 데이터에 사용됩니다. 예시로는 MongoDB (문서 기반)와 Cassandra (열 기반) 등이 있습니다.

어떤 것을 사용해야 할까요:

  • ACID를 필요로 하는 구조화된 데이터: MySQL, Oracle, SQL Server 및 PostgreSQL과 같은 SQL 데이터베이스를 사용하세요.
  • ACID 없이 데이터 유형과 쿼리: MongoDB나 CouchBase와 같은 문서 DB를 사용하세요.
  • 유한한 쿼리를 포함하는 대용량 데이터: Cassandra와 HBase와 같은 열 지향 DB를 사용하세요.

ACID 속성

  • 원자성은 모든 트랜잭션이 완전히 성공하거나 실패해야 한다는 것을 의미합니다. 시스템 실패의 경우라도 부분적으로 완료될 수 없습니다.
  • 일관성은 각 단계에서 데이터베이스가 불변식을 따르는 것을 의미합니다: 유효성을 검증하고 손상을 예방하는 규칙입니다.
  • 고립성은 동시에 실행되는 트랜잭션이 서로에게 영향을 미치지 못하도록 합니다. 트랜잭션은 병렬로 실행되더라도 순차적으로 실행된 것과 같은 최종 상태로 결과를 내야 합니다.
  • 지속성은 트랜잭션을 최종적으로 만듭니다. 심지어 시스템 실패도 성공적인 트랜잭션의 효과를 롤백할 수 없습니다.

인덱스

데이터 검색 속도를 높이지만 추가 저장 공간 및 유지보수 오버헤드가 필요합니다.

주요 사항:

  • 더 빠른 검색을 위해 특정 열이 있는 테이블의 재료 복사본.
  • 저장 공간과 쿼리 시간 사이의 절충안.
  • 모든 사용된 열에 적합한 인덱싱을 통해 전체 테이블 스캔을 피함.
  • 누락된 및 사용되지 않는 인덱스를 최적화해야 함.
  • PostgreSQL 인덱스는 테이블 스캔을 가속화하기 위해 행 식별자/주소를 저장함.

조인

  • Inner Join: 두 테이블 모두에서 일치하는 값을 가진 행을 반환.
  • Left Outer Join: 왼쪽 테이블의 모든 행과 오른쪽 테이블에서 일치하는 행을 반환.
  • Right Outer Join: 오른쪽 테이블의 모든 행과 왼쪽 테이블에서 일치하는 행을 반환.
  • Full Join: 두 테이블 중 하나에서 일치하는 경우 모든 행을 반환.

최적의 방법:

  • 긴 쿼리 대신 뷰와 저장 프로시저를 사용하세요.
  • 트리거 대신 제약 조건을 사용하세요.
  • 성능을 더 높이기 위해 UNION 대신 UNION ALL을 사용하세요.

데이터 분할

테이블이나 데이터베이스 간에 데이터를 나누는 것을 말합니다.

  • 수평 분할 (Sharding): 샤드 키를 사용하여 데이터를 여러 데이터베이스에 나누는 것입니다.
  • 수직 분할: 테이블을 작은 구분된 부분으로 분할하는 것입니다.

샤딩(Sharding)

데이터베이스를 샤딩하는 것은 서버측 시스템을 설계할 때 사용되는 일반적인 확장성 전략입니다. 서버측 시스템 아키텍처는 시스템을 더 확장 가능하고 신뢰할 수 있으며 성능이 우수하도록 만들기 위해 샤딩과 같은 개념을 사용합니다. 샤딩은 샤드 키에 따라 데이터를 수평으로 분할하는 것입니다. 이 샤드 키는 영속화해야 하는 항목을 보낼 데이터베이스를 결정합니다. 이를 위한 일반적인 전략 중 일부는 리버스 프록시입니다.

데이터 복제 전략

여러 기기 간에 데이터를 복사하여 가용성과 신뢰성을 보장합니다.

  • 동기 복제: 기본 및 보조 서버에 동시에 쓰기가 발생합니다.
  • 비동기 복제: 먼저 기본 서버에 쓰기가 발생하고 그 후에 보조 서버로 전파됩니다.

CAP 이론

"CAP" 이론은 분산 데이터베이스에서 한 번에 두 가지 속성만을 보장할 수 있다고 말합니다.

일관성: 모든 노드가 동시에 동일한 데이터를 볼 수 있습니다.

가용성: 모든 요청에 응답이 옵니다.

분할 허용성: 네트워크 분할이 발생해도 시스템이 작동합니다.

CA (일관성과 가용성): SQL 데이터베이스.

CP (일관성과 분할 내구성): DocumentDB.

AP (가용성과 분할 내구성): ColumnarDB.

최종 일관성

충분한 시간이 지나면 모든 복제본이 동일한 상태로 수렴되도록 보장하여 분산 시스템에서 높은 가용성을 달성하는 데 사용됩니다.

GraphQL

API를 위한 쿼리 언어로, REST에 대안으로 더 효율적이고 강력하며 유연한 기능을 제공합니다.

Map Reduce

대규모 데이터 집합을 처리하기 위한 프로그래밍 모델로, 클러스터 상에서 분산 알고리즘으로 작동합니다.

캐싱, LRU 캐시

  • 캐싱: 데이터를 임시로 저장하여 후속 액세스를 빠르게 합니다.
  • LRU 캐시: 최근에 사용되지 않은 항목을 먼저 제거하는 가장 최근에 사용된 캐시 방침입니다.

분산 시스템에서의 캐싱:

  • 목적: 성능 및 확장성 향상.
  • 주요 특징: 높은 캐시 히트 비율, 낮은 메모리 풋프린트.
  • 제거 방침: LRU, FIFO, 슬라이딩 윈도우.
  • 쓰기 방침: 쓰기 스루 (캐시 및 백업 저장소에 즉시 쓰기) 및 쓰기 백 (먼저 캐시에 쓰고 나중에 백업 저장소에 씁니다).
  • 구현: 빠른 조회를 위해 HashMap 또는 HashTable 사용, 마지막 항목 제거를 위해 이진 검색 트리 또는 배열 사용, 빠른 head/tail 액세스를 위해 이중 연결 리스트 사용.
  • 배치: 서버나 데이터베이스에 가까운 위치에 배치하며, 데이터베이스에 가까운 옵션으로 지속적인 저장소인 Redis를 사용합니다.

분산 트랜잭션, 2PC 및 Sage

  • 분산 트랜잭션: 여러 노드 또는 데이터베이스에 걸친 트랜잭션입니다.
  • 2PC(2단계 커밋): 모든 노드가 트랜잭션을 조정된 방식으로 커밋 또는 롤백하도록 보장합니다.
  • Sage 패턴: 각각의 트랜잭션이 단일 서비스 내의 데이터를 업데이트하는 지역 트랜잭션의 일련의 패턴입니다.

성능 튜닝

쿼리 최적화, 인덱싱 및 캐싱 전략을 포함하여 시스템 성능을 최적화하기 위해 구성 및 코드를 조정하는 작업입니다.

배포

가용성 및 확장성

가용성: 시스템이 필요할 때 운영 및 접근 가능한 정도입니다.

확장성: 더 많은 리소스를 추가하여 시스템 용량을 증가시키는 것(수평 확장) 또는 기존 리소스를 강화하는 것(수직 확장)입니다.

로드 밸런싱

여러 서버 사이에 들어오는 네트워크 트래픽을 분산하여 어느 한 서버도 과부하가 걸리지 않도록 하는 작업.

API 게이트웨이

요청 처리, 라우팅, 결합 및 변환을 담당하는 API 프론트엔드로 작동하는 서버.

프록시

클라이언트로부터 서버로 요청을 전달하는 중개자로, 종종 로드 밸런싱 및 캐싱에 사용됩니다.

서버 선택 전략

요청에 대한 최적의 서버를 선택하는 방법으로, 라운드로빈, 최소 연결 또는 리소스 기반 선택과 같은 방법이 있습니다.

일관된 해싱

클러스터 간 데이터를 분산시키는 방법으로, 노드가 추가되거나 제거될 때 재구성을 최소화합니다.

  • 표준 해싱: 객체를 검색 공간으로 매핑하고, 해당 컴퓨터로 부하를 전달합니다.
  • 일관된 해싱: 서버를 키 공간에 매핑하고, 다음 시계방향 서버로 요청을 할당하여 유연성과 확장성을 제공합니다.

VMs 및 Dockers/컨테이너

  • 가상 머신 (Virtual Machines): 완전한 운영 체제를 갖춘 물리적 컴퓨터를 모방합니다.
  • 컨테이너 (Docker): 가볍고 휴대 가능하며 소프트웨어와 해당 의존성을 포장하는 자체 포괄적 단위입니다.

쿠버네티스, 인그레스, 라이브니스 및 레디니스 프로브

  • 쿠버네티스: 컨테이너화된 응용 프로그램의 배포, 확장 및 관리를 자동화하는 오케스트레이션 도구입니다.
  • 인그레스: 쿠버네티스 클러스터에서 서비스에 대한 외부 액세스를 관리합니다.
  • 라이브니스 프로브: 응용 프로그램이 실행 중인지 확인합니다.
  • 레디니스 프로브: 응용 프로그램이 트래픽을 수용할 준비가 되었는지 확인합니다.

리더 선출

분산된 프로세스나 시스템 사이에서 리더를 선택하는 과정으로, 작업을 조정합니다.

카나리아 배포

  • 카나리아 배포: 잠재적 문제의 영향을 최소화하기 위해 작은 일부 사용자에게 변경 사항을 배포하는 것입니다.

통합

메시징 시스템 (Point to point, Pub-Sub)

  • Point to Point: 두 시스템 간의 직접 통신.
  • Pub-Sub (Publish-Subscribe): 메시지는 주제에 발행되며 구독자는 해당 주제에서 메시지를 수신합니다.

메시지 직렬화

메시지를 네트워크로 전송할 수 있는 형식으로 변환하고 나중에 재구성하는 과정입니다.

Redis vs Kafka vs RabbitMQ

  • Redis: 인메모리 데이터 구조 저장소로 데이터베이스, 캐시 및 메시지 브로커로 사용됩니다.
  • Kafka: 분산 스트리밍 플랫폼으로, 실시간 데이터 파이프라인 및 스트리밍 애플리케이션을 구축하는 데 사용됩니다.
  • RabbitMQ: AMQP(고급 메시지 큐잉 프로토콜)를 구현한 메시지 브로커입니다.

API 아키텍처

API의 구조 및 디자인, 버전 관리, 인증 및 요청 제한과 같은 모베스트 프랙티스를 포함합니다.

API에 대한 최상의 실천 방법:

  • 해독이 쉬운 JSON을 사용하고 XML보다 선호합니다.
  • 리소스에는 명사를, HTTP 메서드에는 동사를 사용합니다 (GET, PUT, PATCH, DELETE).
  • 오류 처리에는 HTTP 상태 코드를 활용합니다.
  • 버전 관리를 구현합니다.
  • 페이로드 크기를 줄이고 제한합니다.
  • 캐싱을 활성화합니다.
  • 충분한 네트워크 속도를 보장합니다.
  • 과도한 요청을 제어하기 위해 요율 제한을 사용합니다.
  • 데이터 전송을 최소화하기 위해 업데이트에는 PUT보다는 PATCH를 선호합니다.
  • /health 및 /metrics와 같은 미리 정의된 엔드포인트와 로깅, 모니터링을 활성화합니다.

REST(Representational State Transfer)는 HTTP 메서드를 사용하여 네트워크 애플리케이션을 디자인하는 아키텍처 스타일입니다. SOAP(Simple Object Access Protocol)은 XML을 사용하여 웹 서비스에서 구조화된 정보를 교환하는 데 사용됩니다. gRPC는 RPC(Remote Procedure Calls)를 위한 고성능 오픈소스 프레임워크입니다.

회로 차단기, 오류 복구

서킷 브레이커: 장애를 감지하고 유지 보수 중에 계속해서 발생하는 장애를 방지하는 논리를 캡슐화하는 데 사용되는 디자인 패턴입니다.

보안

TLS, SSL, HTTPS 프로토콜

  • TLS (전송 계층 보안): 컴퓨터 네트워크 상의 통신을 보호하는 프로토콜입니다.
  • SSL (보안 소켓 레이어): TLS의 전신으로 암호화된 통신을 제공합니다.
  • HTTPS (HTTP Secure): TLS 암호화가 적용된 HTTP입니다.

핸드셰이킹

핸드셰이킹: 두 개체 간의 협상 및 통신 매개변수 설정 과정입니다.

인증 및 권한 부여

  • 인증: 사용자 또는 시스템의 신원을 확인하는 과정입니다.
  • 권한 부여: 인증된 사용자 또는 시스템에 부여된 권한을 결정하는 과정입니다.

암호화

데이터를 인코딩하여 무단 액세스를 방지하는 과정.

JWT

JWT (JSON Web Token): 두 당사자 간에 전송될 클레임을 나타내는 간소하고 URL 안전한 방법.

OAuth

OAuth는 토큰 기반 인증에 일반적으로 사용되는 액세스 위임을 위한 오픈 표준입니다.

ELK Stack & Monitoring

Elasticsearch, Logstash, 그리고 Kibana는 실시간으로 로그 데이터를 검색, 분석, 시각화하기 위해 사용됩니다.

OWASP Framework

OWASP (Open Web Application Security Project): 소프트웨어 보안을 개선하기 위한 프레임워크입니다.

디자인 패턴

생성, 구조 및 행위

  • Creational Patterns: 객체 생성 메커니즘을 다룹니다. 예: 싱글톤, 팩토리.
  • Structural Patterns: 객체 구성을 다룹니다. 예: 어댑터, 컴포지트.
  • Behavioral Patterns: 객체 상호작용과 책임을 다룹니다. 예: 옵서버, 전략.

싱글톤 패턴

클래스가 단 하나의 인스턴스만 가지도록 보장하고 그 인스턴스에 대한 전역 접근 지점을 제공합니다.

팩토리 패턴

객체를 생성할 때 인스턴스화 논리를 클라이언트에 노출시키지 않습니다.

옵저버 패턴

객체 간에 일대다 종속성을 정의하여 하나의 객체가 상태를 변경할 때 모든 종속 객체가 알림을 받습니다.

데코레이터 패턴

객체에 새로운 기능을 동적으로 추가합니다.

발행-구독 모델

발행자는 메시지를 주제에 보내고, 구독자는 주제로부터 메시지를 수신합니다.

이벤트 버스 패턴

어플리케이션의 서로 다른 부분이 이벤트를 방송하여 서로 통신할 수 있는 구조 패턴입니다.

아키텍처

SOLID 원칙

  • 단일 책임 원칙: 한 클래스는 변경될 이유가 하나여야 합니다.
  • 개방 폐쇄 원칙: 소프트웨어 요소는 확장에 대해서는 열려 있어야 하지만 수정에 대해서는 닫혀 있어야 합니다.
  • 리스코프 치환 원칙: 서브 타입은 기본 타입으로 대체 가능해야 합니다.
  • 인터페이스 분리 원칙: 클라이언트는 사용하지 않는 메서드에 의존하도록 강제해서는 안 됩니다.
  • 의존 역전 원칙: 구상이 아닌 추상에 의존해야 합니다.

12 Factor App

웹 서비스 앱을 만들기 위한 방법론으로, 명시적 포맷, OS와의 깔끔한 계약, 그리고 지속적인 배포를 강조합니다.

Tiered, Layered & SOA Architecture

  • Tiered Architecture: 애플리케이션을 프리젠테이션, 비즈니스, 데이터 레이어 등 논리적 계층으로 분리합니다.
  • Layered Architecture: Tiered와 유사하지만 더 분산된 구조입니다.
  • SOA (Service-Oriented Architecture): 서비스가 독립적으로 원격으로 액세스할 수 있는 기능 단위를 제공하는 디자인 스타일입니다.

마이크로서비스 및 안티패턴

마이크로서비스 아키텍처: 비즈니스 도메인 주변에 모델링된 작은 자율 서비스 모음으로 응용 프로그램을 구축하는 것.

안티패턴: 공통이지만 비효율적 또는 역생산적인 관행, 예를 들어 공유 데이터베이스, 잘못된 서비스 경계.

자바 메모리

  • 애플리케이션 전체에서 Java Heap Space를 사용합니다. 이는 객체를 할당하기 위한 대량의 메모리입니다. -Xms, -Xmx를 사용하여 최소 및 최대값을 조정할 수 있습니다.
  • Stack은 현재 실행 중인 메서드, 힙 내 객체에 대한 참조, 로컬 변수에만 사용됩니다. 스레드 당 스택이 생성됩니다. Java에서의 스택 메모리는 정적 메모리 할당 및 스레드 실행에 사용됩니다. -Xss를 사용하여 튜닝할 수 있습니다. 만약 StackOverflow 오류가 발생하면 조정하는데 사용됩니다.
  • PermGen은 메타데이터 공간 또는 메서드 영역입니다. PermGenSpace에서 OutOfMemoryError가 발생하며 기본적으로 64MB가 할당되며 -XX:MaxPermSize를 튜닝할 수 있습니다. Java 8에서는 metaspace로 불리며 모든 메모리를 점유할 수 있습니다.
  • Java 런타임을 가져오세요: Runtime runtime = Runtime.getRuntime()

Java 기본

  • 상속, 다형성, 캡슐화 및 추상화: 핵심 OOP 원칙들입니다.
  • Final 키워드: 변수, 메서드 및 클래스의 변경을 방지합니다.
  • Static 키워드: 모든 객체에 의해 공유되는 단일 복사본입니다.
  • 생성자: 정적일 수 없으며 객체를 초기화합니다.
  • 정적 메서드 오버라이딩(가려짐): 컴파일 시간에 해결됩니다.
  • 오버라이딩 방지: 비공개 및 final 수식어.
  • 추상 클래스 vs. 인터페이스: 추상 클래스에는 추상이 아닌 메서드가 있을 수 있으며, 인터페이스에는 Java 8부터 정적 및 기본 메서드가 있을 수 있습니다.
  • 불변 클래스: 생성 후 변경할 수 없습니다.
  • 불변 클래스 생성:
    • 클래스를 final로 만듭니다.
    • 필드를 비공개로 만듭니다.
    • 세터가 없습니다.
  • String, StringBuffer, StringBuilder: String은 불변입니다. StringBuffer는 동기화됩니다.
  • 문자열 풀: Java 힙에 있는 문자열 리터럴의 저장 영역입니다.
  • Fail-fast vs. Fail-safe 반복자: Fail-fast 반복자는 수정 시 오류를 throw하며, fail-safe 반복자는 복제본에서 작동합니다.
  • ArrayList vs. LinkedList: ArrayList는 동적 배열을 사용하며, LinkedList는 이중 연결 목록을 사용합니다.
  • LinkedHashMap: 순서를 위해 해시 테이블과 연결된 목록을 결합합니다.
  • Streams API: 함수형 방식으로 컬렉션 처리를 가능하게 합니다.
  • 스레드: 독립적 실행 경로이며, Thread를 확장하거나 Runnable을 구현하여 생성될 수 있습니다.
  • Callable: Runnable의 향상된 버전으로, Future 객체를 반환합니다.
  • Volatile 키워드: 변경 사항을 직접 주 메모리로 플러시합니다.
  • Transient 키워드: 직렬화에서 필드를 제외합니다.

Java에서의 Garbage Collection

  • Garbage collection은 힙 메모리를 확인하여 사용 중인 객체와 그렇지 않은 객체를 식별하고 사용하지 않는 객체를 삭제하는 과정입니다.
  • 가비지 컬렉터 스레드가 실행될 때 다른 스레드는 잠깐 멈추므로 Stop The World라고 부릅니다.
  • System.gc()를 호출하는 것은 가비지 컬렉터를 실행할 것을 제안하는 것뿐이며, JVM이 언제 실행할지 결정합니다.
  • 가비지 컬렉터에는 표시, 삭제, 압축과 같은 3단계가 있습니다.
  • Java 힙에는 젊은 세대와 구형 세대 공간이 있습니다. 젊은 세대에는 Eden 공간과 2개의 Survivor 공간이 있습니다. 캐시는 구형 세대 공간에 있을 수 있습니다. 우리는 다른 공간을 설정하고 조정할 수 있습니다.
  • Serial, Parallel과 같은 여러 유형의 GC가 있습니다. 명령줄에서 -XX:+UseSerialGC, -XX:+UseParallelGC, -XX:+UseParallelOldGC, -XX:+UseParNewGC와 같이 사용할 수 있습니다.

HashMap 구현

  • HashMap: 버킷의 배열, 각 버킷은 노드의 연결 리스트입니다.
  • 해싱: hashcode()를 사용하여 문자열을 짧은 값으로 변환합니다.
  • 충돌 처리: Java 8+에서 고충돌 시나리오에 대해 연결 리스트나 이진 트리를 사용합니다.

Spring

  • Spring Framework: Java 애플리케이션을 위한 가벼운 프레임워크로, IoC, AOP, 트랜잭션, MVC 및 예외 처리를 제공합니다.
  • Spring IoC: 객체의 제어를 컨테이너로 전환하여 라이프사이클과 의존성을 관리합니다.
  • AOP: 교차 관심사를 분리함으로써 모듈화 수준을 높입니다.
  • Spring Boot: Spring Framework 위에 구축되었으며, 자동 구성 및 내장형 애플리케이션 서버 지원이 제공됩니다.
  • Dependency Injection: 객체가 의존하는 다른 객체를 받아들이는 설계 패턴으로, 느슨한 결합을 촉진합니다.