본문 바로가기
Tech Interview

CS w/Backend 세미나

by Thinking 2024. 11. 5.

최근 알고리즘과 자바 언어를 위주로 공부하고 있지만, 깊게 들어갈수록 CS의 중요성을 깨닫고 있다. 그래서 kocw 강의 중 컴퓨터구조, 네트워크, 운영체제를 복기하며 정리하고 있다. 해당 글은 세미나를 참여하며 좋다고 느낀 지식을 기억하고자 개인적으로 정리한 것이다.

1. 서버 아키텍처와 성능 개념 정리

대규모 트래픽을 처리할 수 있는 서버 아키텍처를 설계하는 데에는 여러 개념과 성능 지표를 이해하는 것이 중요합니다.
서버가 초당 처리할 수 있는 트랜잭션 수(TPS), 부하 처리 방식, 그리고 확장성을 고려해 서버의 성능을 최대화하는 방법에 대해 알아보겠습니다.


2. 트래픽과 TPS (Transaction Per Second)

- TPS (Transaction Per Second): 서버가 초당 처리할 수 있는 사용자 요청의 수를 의미하며, 서비스의 규모나 성능을 평가하는 중요한 지표입니다.
서버의 최대 TPS를 측정함으로써 서버가 동시에 처리할 수 있는 요청의 한계를 파악할 수 있습니다.

예시: 서버의 최대 TPS가 500이라면, 서버는 1초에 500개의 요청까지 안정적으로 처리할 수 있다는 의미입니다.

사용자 종류
- 동시 사용자 (Concurrent User): 시스템에 접속해 현재 사용 중인 모든 사용자.
- 부하 사용자 (Active User): 동시 사용자 중 트랜잭션을 발생시키고 응답을 기다리는 사용자로, 서버 부하의 핵심이 되는 사용자들입니다.


3. 포화점 (Saturation Point)
: 사용자 수가 증가함에 따라 TPS 역시 증가하지만, 어느 순간부터는 서버가 더 이상 추가 트랜잭션을 처리하지 못하는 시점이 옵니다. 이 Saturation Point 이후로는 TPS가 증가하지 않으며, 대신 요청의 대기 시간이 길어져 서버 응답 속도가 느려집니다.
포화점을 넘어서지 않도록 서버를 안정적으로 운영하는 것이 중요합니다.


4. 리틀의 법칙 (Little’s Law)
: 리틀의 법칙에 따르면, 서버 부하가 증가하면 어느 수준까지는 TPS가 선형적으로 증가하지만, 한계에 도달하게 됩니다. 이 법칙을 통해 트래픽 증가에 따른 시스템 확장성을 예측하고,
최적의 부하 수준을 계산하는 데 도움을 받을 수 있습니다.

> 리틀의 법칙: 대기열 시스템에서 평균 시스템 내에 있는 사용자 수 = 평균 도착율 × 평균 응답 시간


5. 안정적 자원 사용
자원을 효율적으로 사용하는 것도 중요하지만, 서비스의 안정성을 유지하는 것이 우선입니다. 트래픽 급증 상황에서 서버가 안정적으로 운영될 수 있도록 설계하고, TPS 한계점을 넘지 않도록 부하를 조절하는 것이 중요합니다.


6. 서버 구조의 유연성 문제

서버 구조와 데이터베이스 설계는 운영 중에 변경하기 어렵습니다. 구조가 변경될 경우 기존 코드와 호환성 문제가 발생할 수 있고, 데이터베이스 구조가 바뀌면 기존 데이터에 대한 마이그레이션 작업이 필요합니다.
이를 고려해 유연한 아키텍처를 설계하는 것이 필요합니다.


7. 서버 아키텍처의 기본 구성 요소

서버 아키텍처는 일반적으로 세 가지 주요 구성 요소로 나뉩니다.

1) 컴포넌트 아키텍처
: 시스템의 구성 요소를 독립적으로 배포하고 서로 상호작용할 수 있는 가장 작은 단위인 컴포넌트의 상호작용을 정의합니다.
예를 들어, 인증, 데이터 처리, API 라우팅 등의 기능을 모듈로 분리하여 확장성과 유지보수를 쉽게 합니다.

2) 데이터 아키텍처
: 데이터의 수집, 변환, 분배, 소비 방식에 대한 구조를 정의합니다. 데이터 아키텍처는 데이터를 저장하고 관리하며, 필요한 경우 실시간으로 데이터를 분석하여 활용할 수 있는 방안을 포함합니다.

3) 네트워크 아키텍처
: 네트워크의 물리적 요소 및 구성과 통신 프로토콜을 정의하며, 서버가 효율적으로 데이터를 교환할 수 있도록 설계합니다.



8. 네트워크 아키텍처의 주요 고려 사항

1) 확장성 (Scalability)
트래픽이 증가할 때 시스템을 어떻게 확장할 수 있을지에 대한 계획입니다. 서버를 수평적으로 늘리거나 데이터베이스를 분산하여 성능을 유지하는 방안을 고려합니다.

2) 장애 허용성 (Fault-Tolerance)
장애가 발생했을 때 시스템이 어떻게 대응할지에 대한 부분으로, 장애 상황에서도 서비스가 지속될 수 있도록 하는 설계가 필요합니다. 예를 들어, 주요 
컴포넌트의 중복 구성을 통해 장애 발생 시 다른 서버가 자동으로 대응하도록 할 수 있습니다.

3) 부하 분산 (Load-Balancing)
여러 서버가 트래픽을 나누어 처리하여 특정 서버에 과부하가 걸리지 않도록 합니다. 이를 위해 로드 밸런서를 사용해 요청을 여러 서버로 분산시키고, 각 서버가 적절한 양의 작업을 담당하도록 합니다.

> 요약: 서버 아키텍처는 트래픽 관리, 성능 한계, 자원 안정성 등의 다양한 요소를 고려하여 설계해야 합니다. 적절한 아키텍처 설계와 함께 확장성, 장애 허용성, 부하 분산을 확보함으로써 안정적인 서버 환경을 구축할 수 있습니다.

 

 

1.서버의 동시 접속 처리와 IO Multiplexing

1) 웹 서버(Web Server)의 역할
웹 서버는 주로 정적 페이지(HTML, CSS, 이미지 등)를 클라이언트에게 반환하는 역할을 수행합니다. 클라이언트가 웹 서버에 요청을 보내면, 웹 서버는 해당 정적 파일을 찾아서 응답으로 반환합니다. 
이때 서버는 클라이언트와의 연결을 관리하는 소켓(socket)을 통해 통신을 주고받습니다.

2) 원격 프로세스와 소켓 통신
원격 프로세스는 직접적으로 통신할 수 없습니다. 대신, 다음과 같은 단계로 통신이 이루어집니다:

- 클라이언트가 요청을 보낼 때 클라이언트 소켓을 생성합니다.
- 클라이언트 소켓은 서버 소켓과 연결됩니다.
- 서버 소켓이 요청을 수신하고 서버 프로세스에 전달합니다.

여기서 중요한 점은 소켓도 파일로 취급된다는 것입니다. 즉, 소켓 또한 파일 디스크립터를 통해 관리됩니다.


2. 프로세스의 자원 제한 (`ulimit`)

리눅스에서는 `ulimit -a` 명령어를 통해 각 프로세스가 사용할 수 있는 자원의 제한을 확인할 수 있습니다. 중요한 항목 중 하나는 파일 디스크립터(file descriptors)의 수입니다.
파일 디스크립터는 파일이나 소켓을 열 수 있는 최대 수를 결정하는데, 서버가 동시에 처리할 수 있는 연결 수에 영향을 미칩니다.

파일 디스크립터 제한에는 Soft Limit와 Hard Limit이 있습니다:
- Soft Limit: 기본적으로 적용되는 제한으로, 필요에 따라 사용자나 프로세스가 일시적으로 늘릴 수 있습니다.
- Hard Limit: 시스템 설정에서 허용된 최대치로, 관리자만이 이 값을 변경할 수 있습니다.

* 프로세스는 자신의 정책에 따라 soft limit를 넘어서 hard limit 까지 자체적으로 제한을 늘릴 수 있습니다.
Java - soft limit를 뚫고 올라갑니다. 
Python - soft limit 에서 일반적으로 더 올리지 않습니다.
node.js - soft limit을 자체적으로 늘리도록 설정되어 있습니다.

예시: 대량의 동시 요청 제한
서버에 몇천 개의 동시 요청을 보내고 싶다고 가정해봅시다. 기본 설정대로라면 클라이언트와 서버 모두 각 프로세스가 사용할 수 있는 소켓의 수가 제한되어 있으므로, 
예를 들어 256개 이상의 연결을 생성하기 어렵습니다. 이 한계를 해결하려면 Soft Limit을 높이거나 시스템 설정을 조정해야 합니다.


3. MySQL과 커넥션 제한

- MySQL도 클라이언트와의 연결을 각각 소켓을 통해 관리하며, 동시에 열 수 있는 최대 연결 수가 설정되어 있습니다. 이 값은 MySQL의 max_connections 설정에 따라 달라지며, 
프로세스의 소프트 리밋(soft limit)에 의해 제약될 수 있습니다. 따라서, 대규모 접속을 처리할 때는 MySQL의 연결 제한을 확인하고 필요에 따라 설정을 조정해야 합니다.


4. 파일 관리와 소켓 관리

프로그래밍 언어에서는 파일을 열고 관리하는 기능이 비교적 간단히 제공됩니다. 마찬가지로, 소켓도 파일로 취급되기 때문에 파일 디스크립터로 관리할 수 있습니다. 이와 같은 원리를 이용하면, 서버는 단일 스레드로도 여러 요청을 처리할 수 있습니다.


5. IO Multiplexing (입출력 다중화)

하나의 요청에 하나의 스레드를 할당하는 방식이 아니라, 하나의 스레드로 여러 요청을 동시에 처리할 수 있을까요? 이를 가능하게 하는 것이 IO Multiplexing입니다.
IO Multiplexing은 하나의 스레드(혹은 프로세스)가 여러 개의 입출력 작업을 처리할 수 있도록 해주는 기법입니다. 대표적인 예로 Redis가 있습니다. Redis는 하나의 스레드로도 다수의 요청을 처리할 수 있는 이유가 IO Multiplexing 덕분입니다. 
IO Multiplexing은 동시에 여러 연결을 모니터링하여 입출력 준비가 완료된 소켓에 대해서만 처리를 수행하기 때문에, CPU 및 메모리 자원을 효율적으로 사용하면서도 높은 성능을 낼 수 있습니다.



요약
- 웹 서버는 정적 페이지를 반환하는 역할을 하며, 클라이언트와 서버 간 통신은 소켓을 통해 이루어집니다.
- 리눅스의 ulimit 명령어를 통해 각 프로세스의 자원 제한을 확인할 수 있으며, 파일 디스크립터의 제한이 소켓의 최대 수를 결정합니다.
- MySQL은 각 커넥션을 소켓으로 관리하고, 최대 연결 수 제한이 있으며 이를 조정할 수 있습니다.

 

 

 

1.네트워크 통신과 서버 아키텍처의 이해
- 네트워크와 서버 아키텍처에서 자주 언급되는 Port, Socket, 그리고 다양한 서버 구조 모델에 대해 정리해보겠습니다. 특히 Apache와 Nginx 같은 웹 서버의 차이점, 
그리고 Spring Boot의 요청 처리 방식 등을 이해하는 데 도움이 될 것입니다.

1) Port
포트(Port)는 네트워크 통신에서 애플리케이션 간의 상호 구분을 위해 사용하는 번호입니다. 하나의 IP 주소 내에서 여러 애플리케이션이 운영될 때, 
특정 애플리케이션으로 데이터를 전송하려면 해당 애플리케이션의 포트 번호를 알아야 합니다. 예를 들어, 같은 IP 주소를 가진 서버에서 HTTP 요청은 기본적으로 포트 80을 사용하고, 
HTTPS는 포트 443을 사용하여 통신합니다.

2) Socket
소켓(Socket)은 네트워크 상의 두 프로그램 간의 양방향 통신을 가능하게 하는 엔드포인트입니다. 동일한 서버로 서로 다른 요청이 들어오는 경우, 소켓을 통해 요청이 구분됩니다. 
각 소켓은 특정 IP와 포트 번호의 조합으로 고유하게 식별되어, 여러 애플리케이션 간의 통신을 원활하게 해줍니다.


2. 서버 요청 처리 방식
대부분의 서버는 클라이언트의 요청이 들어올 때 이를 처리하기 위해 별도의 프로세스나 스레드가 필요합니다. 요청을 어떻게 처리하느냐에 따라 서버의 성능이 크게 달라집니다.

1) 구형 방식: fork()
초기 서버 구조에서는 요청마다 새로운 프로세스를 생성하기 위해 fork()를 사용했습니다. 이는 요청을 받을 때마다 자식 프로세스를 생성하는 
방식으로, 안정성은 확보되지만 성능이 떨어집니다. fork() 방식의 주요 단점은 부모 프로세스의 모든 로직이 자식 프로세스에 복제되어 메모리 사용량이 증가하고, 응답 속도가 느리다는 점입니다.

2) 쓰레드(Thread) 사용
스레드를 이용하면 프로세스보다 메모리 효율적이지만, 초기에는 스레드 대신 프로세스를 선호했습니다. 그 이유는 하나의 스레드가 오류로 종료될 경우 전체 서버가 다운될 위험이 있기 때문입니다.
따라서 요청 처리의 안정성을 높이기 위해 프로세스를 사용하는 것이 일반적이었습니다.

3) 현대적인 접근: Hybrid Model
하이브리드 모델은 Apache 웹 서버에서 사용하는 방식으로, 여러 개의 자식 프로세스를 미리 생성하고 각 프로세스가 내부적으로 여러 스레드를 관리합니다.
이를 통해 안정성을 확보하면서도 요청 처리 속도를 높입니다. 이 방식은 프로세스 풀(Process Pool)을 형성하여 서버가 요청에 빠르게 응답할 수 있도록 합니다.

- Apache의 특징: 요청 후 응답을 기다려야 하며, 프로세스 기반이므로 요청 처리에서 메모리 부담이 큽니다.

- Nginx의 특징: 비동기적이고 논블로킹 방식으로 요청을 처리합니다. 메인 프로세스는 요청을 전달만 하고 다른 일을 수행하므로, 대규모 요청에 대응하기에 적합합니다. Apache와 달리 대기시간을 줄이고 처리 효율을 높입니다.

Spring Boot와 Tomcat
Spring Boot는 Tomcat 서버를 기본 내장 서버로 사용하여, 클라이언트 요청을 처리할 스레드를 미리 생성해둡니다. 이 방식은 요청이 들어올 때마다 미리 준비된 스레드를 사용해 처리하는 
스레드 풀(Thread Pool) 방식을 채택합니다. 이를 통해 성능과 자원 관리를 최적화할 수 있습니다.


3. 10K Problem
"10K Problem"은 1만 개의 클라이언트 요청을 동시에 처리할 수 있는 네트워크 I/O 모델을 설계할 수 있는지를 의미합니다. Apache와 Nginx의 접근 방식을 비교하면 다음과 같습니다.

- Apache: 요청마다 스레드를 생성하는 방식이므로, 메모리 부담이 커지고 스레드가 많아질수록 컨텍스트 스위칭 비용이 증가합니다.

- Nginx: 논블로킹 방식으로 요청을 큐에 넣고 순차적으로 처리하기 때문에 메모리 부담이 상대적으로 적습니다. 요청을 바로 처리하지 않고 큐에서 대기하게 하므로 동시에 많은 요청을 처리하는 데 유리합니다.

10K Problem 해결 방안
1만 개의 요청을 동시에 처리하기 위해 단순히 스레드 풀의 크기만 늘리는 것은 한계가 있습니다. 스레드가 많아지면 컨텍스트 스위칭 비용이 증가하여 오히려 성능이 저하될 수 있습니다. 
이에 대한 해결책으로는 다음과 같은 비동기 방식이나 이벤트 기반 서버 모델을 도입하는 방법이 있습니다.

 

 

1.HTTP, TCP, UDP와 HTTP/3에 대한 이해

HTTP란?
- HTTP(HyperText Transfer Protocol)는 웹 상에서 정보를 주고받는 프로토콜로, 주로 HTML 문서를 주고받기 위해 사용됩니다. HTTP는 애플리케이션 계층의 프로토콜로, 대부분 TCP(Transmission Control Protocol)를 전송 계층에서 사용하여 신뢰성 있는 통신을 보장합니다.

> 참고: HTTP는 순수한 기술로, 특정 전송 프로토콜에 의존하지 않습니다. RFC 2616 표준에 따라 HTTP는 TCP뿐만 아니라 다른 프로토콜에서도 동작할 수 있습니다.

2. HTTP는 왜 TCP를 사용할까?

TCP는 신뢰성 있는 통신을 보장하기 위한 여러 메커니즘을 제공합니다. 패킷 손실이나 순서가 뒤바뀌는 문제를 해결하고, 연결을 안정적으로 유지하기 위해 
TCP는 3-way handshake를 통해 연결을 설정하고 패킷을 전달합니다.

이 과정은 다음과 같습니다:
1) SYN: 클라이언트가 연결 요청을 서버에 보냅니다.
2) SYN-ACK: 서버가 요청을 받아들이고 클라이언트에 응답합니다.
3) ACK: 클라이언트가 응답을 확인하고 연결이 성립됩니다.

하지만 TCP는 이러한 안정성 때문에 연결 설정에 시간이 걸리며, 특히 다수의 요청을 처리할 때 성능 저하가 발생할 수 있습니다. 그래서 TCP Fast Open이나 HTTP/1.1의 
Persistent Connection과 웹 캐시 기능이 도입되어, 자원을 더 빠르게 불러올 수 있도록 개선되었습니다.


3. HTTP/1.0 vs HTTP/1.1

HTTP/1.0
- HTTP/1.0에서는 요청마다 새로운 TCP 연결을 설정했습니다. 예를 들어, 한 웹페이지에 JavaScript 파일, CSS 파일이 각각 하나씩 있다면, 
각각의 파일을 불러오기 위해 총 3번의 TCP 연결을 맺어야 했습니다.

HTTP/1.1
HTTP/1.1에서는 Persistent Connection을 통해 여러 리소스를 하나의 TCP 연결로 전송할 수 있게 되었습니다. 
따라서 위의 예시에서는 1번의 연결만으로 HTML 문서, JS 파일, CSS 파일을 모두 불러올 수 있게 되어 네트워크 자원 사용이 개선되었습니다.


4. TCP의 한계와 UDP의 필요성

TCP는 신뢰성을 보장하기 위해 연결마다 설정 및 해제 과정을 거치기 때문에 다소 무거운 프로토콜입니다. 반면, UDP(User Datagram Protocol)는 연결 설정이 필요 없는 비연결형 프로토콜로,
빠르고 간단하게 패킷을 전달할 수 있지만 신뢰성 보장이 어렵습니다. UDP는 1:1 통신이 아닌 Broadcast(브로드캐스트) 방식도 지원하여 네트워크 상의 모든 장치에 데이터를 보낼 수 있습니다. 

> 예시: DHCP(Dynamic Host Configuration Protocol)와 같은 프로토콜은 네트워크에서 IP를 자동으로 할당하기 위해 UDP를 사용합니다.
DHCP 서버는 로컬 네트워크의 모든 장치에 브로드캐스트 메시지를 보내기 때문에, 1:1 연결만 가능한 TCP로는 적합하지 않습니다.



5. HTTP/3와 UDP

HTTP/3부터는 TCP 대신 UDP를 사용하여 통신합니다. 그 이유는 TCP의 신뢰성 보장 방식이 과거 네트워크 환경에 맞춰 설계되었기 때문입니다.
TCP는 1981년에 표준화된 이후로 다양한 방식으로 개선되었지만, 근본적인 제약으로 인해 현대의 고속 네트워크에서 최적의 성능을 제공하기 어렵습니다.

이를 보완하기 위해 HTTP/3는 QUIC(Quick UDP Internet Connections) 프로토콜을 사용합니다. QUIC은 UDP 위에서 동작하면서도 자체적으로 신뢰성을 보장하는 메커니즘을 제공합니다.

QUIC의 신뢰성 보장 방식
- QUIC은 OSI 7계층 모델의 특성을 활용하여 UDP의 속도와 TCP의 신뢰성을 함께 제공합니다. 기존에 전송 계층에서 제공하던 신뢰성 기능을 애플리케이션 계층으로 가져와 독립적인 레이어로 구성한 것이 특징입니다.
이를 통해 패킷 손실에 대한 처리, 순서 보장 등 TCP의 기능을 제공하면서도 더 빠른 전송을 가능하게 합니다.


6. 브라우저는 어떤 프로토콜을 사용할까?

웹 브라우저는 HTTP/2, HTTP/3 등의 프로토콜을 자동으로 선택하여 최적의 성능을 제공합니다. 브라우저는 특정 서버와 통신할 때 사용할 프로토콜을 선택하는 몇 가지 방법이 있습니다:

1) Alt-Svc 헤더 사용: 서버는 Alt-Svc 헤더를 통해 클라이언트에 HTTP/3 프로토콜이 사용 가능하다고 알릴 수 있습니다. 이 경우, 클라이언트는 HTTP/3 프로토콜을 선택하여 통신하게 됩니다.

2) TCP와 UDP 동시 요청: 브라우저가 HTTP 요청을 보낼 때 TCP와 UDP로 동시에 연결을 시도한 후, 먼저 응답이 도착한 프로토콜을 사용하는 방식입니다.


결론
- HTTP는 현대 웹 통신의 핵심적인 역할을 수행하며, 다양한 네트워크 환경과 요구사항에 맞춰 진화해 왔습니다. HTTP/1.1의 Persistent Connection부터 HTTP/2의 멀티플렉싱,
그리고 HTTP/3의 UDP 기반 QUIC 프로토콜 사용에 이르기까지, HTTP는 최적의 성능을 위해 계속해서 개선되고 있습니다.

 

 

1. 서버 설계하기: 이미지 처리 서버

클라우드 스토리지 서비스, 예를 들어 Google Drive나 MyBox와 같은 시스템을 개발한다고 가정해봅시다. 이런 
서비스에서는 이미지 업로드 시 썸네일을 생성하거나 OCR(광학 문자 인식) 작업을 수행하는 등의 이미지 처리가 요구됩니다. 특히, 고해상도 
이미지나 OCR 처리를 위해서는 GPU 같은 고성능 자원이 필요할 수 있습니다.

아래에서는 이미지 처리 서버를 설계하는 과정에서 고려할 주요 요소와 통신 방식에 대해 정리하겠습니다.

이미지 처리 서버 설계 시 고려사항

1) 썸네일 추출 작업의 비용*: 썸네일 추출을 위한 API를 호출할 때마다 리소스가 소비됩니다. 
GPU를 활용한 이미지 처리 작업은 비용이 많이 들기 때문에, 빈도에 따라 비용 효율성을 고려해야 합니다.
   
2) 실시간성의 필요성: 썸네일이 즉시 제공될 필요가 있을까요? 대부분의 경우, 이미지 파일을 다운로드하거나 미리보기 할 때 썸네일이 필요합니다.
그러나 업로드 시 바로 썸네일이 필요하지 않을 수 있습니다.

3) API 호출 비율: 썸네일 추출 API 호출이 전체 API 요청 중에서 차지하는 비율이 높지 않다면, GPU 
자원을 항상 유지할 필요가 없을 수 있습니다. 자원이 많이 소모되는 작업을 분리하여 비동기적으로 처리하는 것이 효율적일 수 있습니다.


2. 서버 설계 아이디어: 컴포넌트 분리

API 호출 빈도가 높지 않고 실시간성이 필요하지 않다면, 컴포넌트를 분리하는 방안을 고려할 수 있습니다. 
예를 들어, 썸네일 생성과 같은 이미지 처리 작업을 별도의 프로세스로 분리하여 비동기적으로 처리하면 시스템의 부담을 줄일 수 있습니다. 
이를 통해 GPU 자원을 절약하고, 서버의 성능을 최적화할 수 있습니다.


3. 프로세스 간 통신 (IPC) 방식 선택

컴포넌트를 분리한 후에는 각 프로세스 간의 통신을 설정해야 합니다. 이미지 처리 작업과 같은 컴포넌트가 독립된 프로세스로 동작할 때 
활용할 수 있는 IPC(Inter-Process Communication) 방식은 다음과 같습니다:

1) Named Pipe: 단방향 통신을 제공하는 특수 파일을 활용한 방법입니다. 요청이 있을 때 즉각적인 처리가 필요하지 않은 경우 유용하며, 
별도의 파일 형태로 프로세스 간 데이터를 전달할 수 있습니다.


4. 과도한 분리의 단점

컴포넌트를 지나치게 분리하면 관리 포인트가 많아져 시스템이 복잡해질 수 있습니다. 분리된 컴포넌트가 많아지면 유지보수가 어려워지고,
전체 성능을 저하할 수 있는 비효율이 발생할 수 있습니다. 따라서 서버 설계 시에는 컴포넌트 분리와 통신 방식 선택이 균형을 이루도록 해야 합니다.


정리 - 이미지 처리 서버를 설계할 때는 고성능 자원을 효율적으로 관리하기 위해, 실시간 요구 사항과 호출 빈도를 고려하여 컴포넌트를 분리하는 것이 좋습니다. IPC 방식으로는 Named Pipe나 Message Queue를 활용할 수 있으며, 비동기 처리를 통해 리소스 효율성을 높일 수 있습니다. 그러나 과도한 분리는 관리와 성능 면에서 오히려 비효율을 초래할 수 있으므로, 서버 설계 시 적절한 균형을 유지하는 것이 중요합니다.

 

 

1. 메모리 계층성과 성능 최적화

효율적인 프로그램 성능을 위해 메모리 계층성과 캐싱 전략을 이해하는 것은 필수적입니다. 이 글에서는 메모리 계층성과 MySQL Buffer Pool, Java의 Boxing 타입, 캐싱 전략에 대해 다룹니다.


2. 메모리 계층성

- 컴퓨터 시스템에서 메모리 계층성(memory hierarchy)은 다양한 속도와 크기의 메모리 계층을 두어 성능을 최적화하는 구조입니다. 
계층별로 가까운 쪽일수록 빠른 접근 속도를 가지지만, 용량은 작아지고 가격은 높아집니다. 아래는 주요 메모리 계층의 개요입니다:

- Register: CPU 내에 있는 가장 빠른 메모리입니다. 연산에 사용되는 데이터와 명령어를 임시로 저장하며, CPU가 가장 빠르게 접근할 수 있습니다.
- Cache Memory: CPU에 가까운 고속 메모리로, 자주 사용하는 데이터를 일시적으로 저장하여 CPU의 메모리 접근 시간을 줄입니다. 일반적으로 L1, L2, L3 등의 레벨로 구분됩니다.
- Main Memory : 시스템 메모리로, 실행 중인 프로그램과 데이터를 저장합니다. CPU가 직접 접근할 수 있는 메모리 중 가장 용량이 크지만, 캐시보다는 느립니다.
- HDD/SDD: 주기억장치의 데이터가 사라지지 않도록 저장하는 비휘발성 저장 장치입니다. 속도는 느리지만 용량이 매우 크고, 데이터를 영구적으로 보관할 수 있습니다.


3. MySQL Buffer Pool

- MySQL InnoDB 엔진은 데이터베이스 성능을 높이기 위해 Buffer Pool이라는 캐시를 사용합니다.
이는 InnoDB의 테이블과 인덱스 데이터를 메모리에 캐싱하여 디스크 I/O를 줄이고, 쿼리 성능을 크게 개선합니다.

- Buffer Pool: 테이블 및 인덱스 페이지를 메모리에 저장하는 공간으로, 자주 사용하는 데이터를 빠르게 읽을 수 있습니다.
- InnoDB: MySQL의 스토리지 엔진 중 하나로, 트랜잭션을 지원하며, 데이터의 일관성과 무결성을 유지하는 역할을 합니다.
InnoDB는 Clustered Index 구조를 사용하여, PK 기반 쿼리 성능을 최적화합니다.

Cluster Index 단점으로는 ~ (정리중)


4. 캐싱 전략과 지역성

- 캐싱은 성능을 최적화하기 위한 중요한 전략입니다. 캐시의 주요 원리는 시간 지역성과 공간 지역성이라는 개념에 기반합니다.

1) 캐시의 시간 지역성 (Temporal Locality)
- 시간 지역성은 최근에 참조된 데이터가 다시 참조될 가능성이 높다는 원리입니다. 예를 들어, 메모리에 읽어둔 데이터를 캐시에 보관하면 이후 반복해서 조회할 때 빠르게 꺼낼 수 있습니다.
데이터베이스에서도 자주 사용되는 쿼리 결과를 캐싱하면, 동일 쿼리에 대한 응답 시간을 줄일 수 있습니다.

2) 캐시의 공간 지역성 (Spatial Locality)
- 공간 지역성은 특정 위치의 데이터가 참조될 때, 그 주변 데이터도 함께 참조될 가능성이 높다는 원리입니다. 예를 들어, 리눅스 시스템은 메모리를 페이지 단위로 관리하며,
기본 페이지 크기는 4KB입니다. 따라서 한 번에 4KB 데이터를 불러와서 캐시하면, 추가적인 조회가 있을 때 캐시된 페이지에서 빠르게 접근할 수 있습니다.

EX) Clusterd Index: MySQL InnoDB의 기본 인덱스 구조입니다. 데이터가 물리적으로 연속된 위치에 저장되므로, 
PK 기반의 쿼리를 수행할 때 해당 데이터와 인접한 데이터도 빠르게 조회할 수 있어 공간 지역성을 활용한 성능 향상이 가능합니다.


5. 캐싱 대상: 꼭 데이터여야 하는가?
- 캐싱의 대상은 데이터뿐 아니라, 다양한 형태로 확장될 수 있습니다. 예를 들어, JVM의 Warm Up 과정을 통해, 
자바 프로그램이 실행되면서 JIT(Just-In-Time) 컴파일러가 자주 실행되는 코드를 네이티브 코드로 변환하여 성능을 향상시키는 것도 일종의 캐싱입니다.


요약 - 메모리 계층성, 캐싱 전략, 그리고 데이터베이스의 구조적 최적화를 이해하고 적용하면 시스템 성능을 크게 개선할 수 있습니다. 
특히, 시간 지역성과 공간 지역성을 고려한 캐싱 전략은 메모리와 디스크 I/O를 효율적으로 관리하는 데 중요한 역할을 합니다.

 

1. 서버 부하 분산과 데이터베이스 성능 최적화 개념 정리

- 서버에 다수의 요청이 들어올 때, 시스템 안정성과 성능을 유지하기 위해 로드 밸런서를 사용하여 요청을 분산시킵니다. 로드 밸런싱 전략과 레이어에 따라 다양한 기능이 제공되며,
부하 분산을 통해 시스템의 단일 장애 지점(SPOF)을 방지할 수 있습니다. 또한, 데이터베이스 성능 최적화에서는 인덱스, 페이징 단위, 정렬 등이 중요한 역할을 합니다.


2. 로드 밸런서 (Load Balancer)
- 로드 밸런서는 사용자 요청을 여러 서버에 효율적으로 분배하여 서버의 과부하를 방지하고 성능을 최적화하는 역할을 합니다.

로드 밸런싱 방식은 여러 가지가 있으며, 대표적인 방식은 다음과 같습니다:

- Round Robin: 들어오는 요청을 순서대로 돌아가며 각 서버에 배분합니다.
- Least Connection: 현재 연결 수가 가장 적은 서버에 요청을 전송하여 부하를 분산합니다.


3. 로드 밸런서의 레이어
- 로드 밸런서는 일반적으로 L4와 L7 레이어에서 동작하며, 각 레이어에 따라 지원하는 기능과 성능에 차이가 있습니다.

- L4 (Layer 4): 네트워크 계층에서 포트와 IP 주소를 기반으로 요청을 분배합니다. 기본적인 로드 밸런싱 방식(Round Robin, Health Check 등)을 지원하며, L7보다 속도가 빠릅니다.
- L7 (Layer 7): 애플리케이션 계층에서 HTTP 헤더, 쿠키, URL 등의 정보를 바탕으로 요청을 분배합니다. 패킷의 내용을 분석하고 더 다양한 조건으로 분배할 수 있지만, L4에 비해 성능은 낮습니다.


4. SPOF (Single Point Of Failure)
- SPOF는 시스템 구성 요소 중 하나가 장애가 발생하면 전체 시스템에 영향을 미치는 단일 장애 지점을 의미합니다. 
로드 밸런서 자체가 SPOF가 되지 않도록 여러 로드 밸런서를 설정하거나 GSLB(Global Server Load Balancing)를 통해 서버 상태와 부하를 실시간으로 체크하여 IP를 분배하는 방법이 사용됩니다.


5. Health Check
- 서버가 다운되었을 때 로드 밸런서가 이를 감지하지 못하면 사용자의 요청이 오류가 발생하는 서버로 전송될 수 있습니다. 이를 방지하기 위해 로드 밸런서는
Health Check를 수행하여 서버의 상태를 확인하고, 정상 작동하지 않는 서버를 트래픽 분배 대상에서 제외합니다.


6. Master-Slave 구조
- 데이터베이스에서 Master-Slave 구조를 사용하면 SPOF 문제를 줄일 수 있습니다. Replication을 통해 데이터를 여러 노드에 복제하여,
마스터 서버에 장애가 발생하면 슬레이브 서버가 마스터로 승격되어 시스템 가용성을 유지할 수 있습니다. 예시로 MongoDB는 Master-Slave 구조를 지원합니다.


7. 데이터베이스 성능 최적화
- 성능을 높이기 위해 데이터베이스에서는 여러 기법을 사용합니다. 이 과정에서 중요한 요소는 인덱스, 페이징 단위, 정렬 방식 등입니다.

1) 인덱스 (Index)
- 인덱스는 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조입니다. 대부분 B+ Tree 형식으로 저장되며, 테이블의 필드가
삽입/수정/삭제될 때 인덱스가 자동으로 업데이트됩니다. 하지만 빈번한 수정 작업이 인덱스의 성능에 부정적인 영향을 줄 수 있습니다.

2) UNIQUE INDEX: 중복을 허용하지 않는 인덱스입니다. 중복 확인을 위해 데이터가 삽입될 때 지연 처리가 불가능해, 일반 인덱스보다 성능이 낮을 수 있습니다.


8. 데이터 페이징과 관리
- 데이터베이스의 데이터는 항상 Page 단위로 관리됩니다. 예를 들어, 페이지 크기가 4KB일 때 각 페이지에 포함될 수 있는 레코드의 수는 레코드 크기에 따라 달라집니다.

- 예시: 
  - 데이터 크기가 4바이트인 경우, 페이지에 1,024개의 레코드 저장 가능.
  - 데이터 크기가 4,096바이트(VARCHAR(4096))라면 페이지에 1개의 레코드만 저장 가능.

1) Primary Key (PK)
- PK는 작을수록 성능상 유리합니다. PK의 크기가 작을수록 비교 연산에 필요한 시간이 단축되기 때문에, 레지스터 크기에 맞추어 PK를 설정하는 것이 좋습니다.

2) InnoDB In-Memory Cache
- InnoDB는 메모리 캐시를 활용하여 성능을 높입니다.

- InnoDB Buffer Pool: 테이블과 인덱스 데이터를 캐싱하여 데이터베이스 접근 성능을 향상시킵니다.
- Change Buffer: Secondary Index Page에 대한 변경 내용을 캐싱하여 성능을 최적화합니다.


9. COUNT 함수의 최적화
- COUNT(*)와 COUNT(field)는 서로 다르게 동작합니다. COUNT(*)는 모든 레코드를 대상으로, COUNT(field)는 특정 필드에 값이 있는 레코드만을 대상으로 합니다. 성능 차이가 있으므로 상황에 맞게 선택해야 합니다.


10. ORDER BY와 인덱스

ORDER BY 절이 인덱스를 타고 실행되는 경우, 읽은 데이터를 그대로 반환하면 되므로 효율적입니다. 반면 인덱스를 사용하지 않을 경우, 임시 테이블을 생성하여 정렬하고 반환해야 하므로 성능에 영향을 미칩니다.
데이터 양이 많아 메모리에 올릴 수 없는 경우 외부 정렬 기법을 사용하게 됩니다.