////
Search
8️⃣

8장. 기분 좋은 확장성

이 장에서 다룰 내용

확장성 대 성능
점진적인 확장성
데이터베이스 규칙 위반
보다 원활한 병렬화
모놀리스의 진실
과거의 웹사이트는 데이터베이스로 텍스트 파일을 사용했었다.
무언가를 쓸 때 텍스트 파일을 잠궈야 했으며 정보를 제공할 때 역시, 잠궈야 했다.
이는 심각한 성능 저하를 일으키는데, 하드웨어 때문이 아니고 데이터 구조와 병렬화에 대한 결정으로 기인한 것이었다.
이것이 확장성의 핵심이다. 성능만으로는 시스템을 확장할 수가 없다. 점점 더 많은 사용자를 만족시키기 위해서는 모든 측면을 고려한 설계가 필요하다.
시스템 관점에서 확장성은 더 많은 하드웨어를 투입하여 시스템을 더 빠르게 만들 수 있는 능력을 의미한다.
프로그래밍 관점에서 확장성 좋은 코드는 수요가 증가하는 상황에서도 응답성을 일정하기 유지할 수 있다.
확장성 또한 더 큰 목표를 향하면서도 구체적이고 작은 단계부터 점진적으로 풀어가는 것이 가장 좋다.
확장할 수 있는 코드를 만들기 위한 첫 번째 단계는 확장성을 방해하는 잘못된 코드를 제거하는 것이다.

1. 잠금을 사용하지 마라

프로그래밍에서의 잠금은 Thread-Safe한 코드를 작성하기 위한 기능이다.
Thread-Safe이란 코드 조각 2개 이상인 스레드에서 동시에 호출되어도 일관되게 작동하는 것을 의미한다.
잠금은 다른 스레드를 멈추고 특정 조건에서 기다리게 할 수 있다. 이는 일관성을 제공하기는 하지만 동시에 확장성에서는 큰 문제가 될 수 있다.

2. 불일치를 수용하라

데이터베이스는 잠금, 트랜잭션, 원자 카운터, 트랜잭션 로그, 페이지 체크섬, 스냅샷 등과 같이 불일치를 방지하는 수 많은 기능을 제공한다.
신뢰성은 흑백의 개념이 아니다. 성능과 확장성이 향상될 수 있다면 어느 정도는 희생을 감수할 수 잇다.
NoSQL은 외래 키나 트랜잭션과 같이 전통적인 관계형 데이터베이스에서 추구하는 어떠한 일관성을 무시하고 그 대가로 성능, 확장성을 얻겠다는 철학에서 출발했다.
NOLOCK이란 구문을 사용한 예시를 설명하는데, 처음봐서 알아보니 mssql에서 사용하는 쿼리힌트였다.

3. 데이터베이스 연결을 캐시하지 마라

데이터베이스에 대한 단일 커넥션을 하나 생성한 후, 이를 다른 코드에서 공유하는 것은 범하기 쉬운 잘못된 아이디어다.
쿼리를 실행하기 전에 앞서 복잡한 절차들, 커넥션 생성, 세션 연결 등의 절차를 무시할 수 있어 합리적이라고 생각할 수 있지만 데이터베이스에 대한 연결이 하나인 경우, 병렬적으로 쿼리를 수행할 수 없어 효과적이지 못하다.
현재의 프레임워크들은 대부분, 연결을 획득 후, 쿼리 수명동안만 이를 유지하고 다시 반환하는 식으로 확장성을 구현하고 있다.

1. ORM의 형태로

최신 객체 관계 매핑 도구(ORM)들은 Entity 프레임워크와 같이 완전히 다른 복잡한 추상화 집합을 제공하며 데이터베이스의 구체성에 의존하지 않게 해준다.
모든 것이 자동으로 이루어지므로 연결이 언제 열리고 반환되는지 신경쓰지 않아도 된다.

4. 스레드를 사용하지 마라

확장성은 병렬화뿐만 아니라 리소스를 절약하는 것이기도 하다. 전체 메모리 이상으로 확장할 수 없고 CPU 사용량 100% 이상으로 확장할 수 없다.
스레드 풀에는 대개 시스템의 CPU 코어 수보다 더 많은 스레드가 있는데, 이는 스레드가 종종 I/O와 같은 다른 작업이 끝날 때까지 대기해야 하기 때문이다.
위 그림에선 CPU 1은 스레드 1의 입출력 장치의 처리가 진행되는 동안 스레드 5의 작업을 처리한다.
이는 CPU 코어 수만큼의 스레드를 갖는 것보단 낫지만 CPU를 최대로 활용하지는 못한다.
운영 체제는 스레드에 짦은 실행 시간을 제공한 다음 CPU 코어를 다른 스레드에 양보하여 모든 스레드가 적당한 시간 안에 실행될 수 있도록 하는데, 이를 선점형 스케쥴링이라고 한다.
입출력 대기 시간을 활용하는 더 정확한 방법은 비동기식 입출력을 사용하는 것이다. 비동기 입출력은 명시적이기 때문에 의식적이고 정확하게 CPU가 이런 흐름을 따라 처리되겠구나를 가늠할 수 있다.

1. 비동키 코드의 주의사항

코드를 비동기로 만들 때는 특정한 사항을 염두에 두어야한다.
I/O 작업이 없다면 비동기는 필요 없다.
비동기식 프로그래밍은 I/O 바운드 작업과 함께 사용할 때만 확장성에 도움을 줄 수 있다.
CPU 바운드 작업에서 비동기를 사용하면 단일 스레드에서 병렬로 실행되는 I/O 작업과 달리 작업을 위한 별도의 스레드가 필요하기 때문에 확장성에 도움이 되지 않는다.
동기화와 비동기화를 섞지 마라
동기화된 컨텍스트에서 비동기 함수를 안전하게 호출하는 것은 어려운 기술이다.
동기 맥락의 컨텍스트에 비동기 코드를 함께 사용하지마라. 설정이 복잡하기 때문에 보통 프레임워크만이 이를 수행한다.

2. 비동기를 이용한 멀티스레딩

비동기 I/O는 리소스를 덜 소모하기 때문에 I/O가 많이 필요한 코드에서 멀티스레딩보다 더 나은 확장성을 제공한다.
그러나 멀티스레딩과 비동기는 서로 배타적이지 않기 때문에 두 가지를 동시에 가질 수 있고 멀티스레드 코드를 작성하기 위해 비동기 프로그래밍 구문을 사용할 수도 있다.

5. 모놀리스를 존중하라

마이크로서비스의 개념은 간단하다.
코드를 자체적으로 호스팅이 가능한 프로젝트별로 분산하여 향후 해당 프로젝트를 별도의 서버에 배포하는 것으로 확장성을 확보하는 것이다.
모롤리스는 마이크로서비스와 대치되는 개념이다. 그렇다고 확장성이 떨어지는 개념도 아니다. 앱을 분할해야하는 상황이 오기 전까지 모놀리스는 훌륭한 구성안이 되어준다.
마이크로서비스로 새로운 프로젝트를 복잡하게 시작하지 말아라. 단점이 장점보다 작아질 때만 사용할 것을 고려하라.

6. 요약

다단계 다이어트 프로그램처럼 점진적으로 확장성에 접근하라. 작은 개선을 통해 궁극적으로 더 나은 확장 가능한 시스템을 구축할 수 있다.
확장성의 가장 큰 블록 중 하나는 잠금이다. 이들과 함께 살 수 없고, 이들 없이는 살 수도 없다. 대로는 이것이 불필요하다는 것을 인정해야 한다.
코드 확장성을 높이기 위해 수동으로 잠금을 획득하는 것보다 잠금이 없는 데이터 구조나 동시성 데이터 구조를 선호하라.
다중 스레드 환경에서 리소스 초기화를 안전하게 수행하며 성능을 최적화하고 싶다면 Double-Checked Locking을 사용하라.
더 나은 확장성을 위해 불일치를 활용하는 법을 배워라. 비즈니스에 적합한 수준의 불일치 타입을 선택하고 확장 가능한 코드로 만들 기회로 활용하라.
ORM은 일반적으로 귀찮은 작업이지만, 여러분이 생각하지 못한 최적화로 확장성이 더 뛰어난 앱을 만들 수도 있다.
뛰어난 확장성이 필요한 모든 I/O 바인딩 코드에 비동기 I/O를 사용하여 사용 가능한 스레드를 보존하고 CPU 사용량을 최적화하라.
CPU 바운드 작업의 병렬화를 위해 멀티스레딩을 사용하라. 그러나 비동기식 프로그래밍 구조와 함께 멀티스레딩을 사용하는 경우라면 비동기식 I/O를 통한 확장성의 이점을 기대하지 마라.
마이크로서비스 아키텍처에 대한 설계 논의가 끝나기 전에 모놀리스 아키텍처는 전 세계 투어를 끝낼 것이다.