발표 전달력이 매우 좋지 않았음. 그에 따른 자기 피드백
- 한 챕터만 자세히 하기(3챕터를 너무 얕게 설명함)
- 코드를 주로 설명하기 (도식보다 예제 코드가 더 이해시키기 쉬움)
- 파이썬 내용을 빼야겠다. 너무 아는게 다름. (소공 관점의 얘기만 담기.)
발표 전달력이 매우 좋지 않았음. 그에 따른 자기 피드백
이 책의 영문 버전은 무료로 읽을 수 있습니다.
http://www.cosmicpython.com/book/preface.html
책의 실습 코드들
https://github.com/cosmicpython
이번 장에선 시스템의 유즈 케이스를 정의하는 서비스 계층 패턴을 소개하고 이전에 배운 저장소 추상화와 연계해서 테스트를 작성하는 법을 배운다.
그리고 서비스 계층을 만드는 데 가장 헷갈리는 오케스트레이션 로직, 비즈니스 로직, 인터페이스 사이의 차이를 설명한다.
실제 데이터베이스와 API 엔드포인트를 사용해서 데이터베이스부터 API 까지 테스트하는 걸 엔드투엔드 테스트라고 한다.
이 테스트를 통과하기 위해선 도메인 모델과 추상화된 저장소 이외에도 데이터를 저장소로부터 가져와서 입력을 검증하고, 성공일 경우 데이터를 커밋하는 과정도 필요하다. 이런 코드들은 웹 API에 종속되지 않는다. 웹 대신 CLI로 구현한다고 해도 똑같이 존재한다. 따라서 이 코드들이 커진다면 웹 API 코드가 복잡해지기 때문에 구분해줄 필요가 있다.
이런 코드들은 외부에서 온 요청을 받아서 내부를 제어하기 위해 있다. 그래서 오케스트레이션 계층 또는 유즈 케이스 계층, 서비스 계층이라고 한다. 이들이 하는 역할의 예는 다음과 같다.
서비스 계층으로 묶을 때의 장점은 다음과 같다.
API에서 오케스트레이션 로직을 신경 쓰지 않아도 되므로 코드가 줄어들고 작성하기 쉬워진다. 오케스트레이션 부분이 복잡해지더라도 API에선 특정 유즈 케이스에 맞는 서비스 계층을 호출하고 결과에 대한 예외 처리만 하면 된다.
오케스트레이션 부분에 대해서만 테스트 할 수 있다. 웹 API는 실제 HTTP 통신을 사용해서 E2E 테스트를 해야하지만, 오케스트레이션 부분만 테스트 할 땐 통신으로 인한 지연 없이 더 빠른 테스트가 가능하다. 여기서 추상화된 저장소에 의존한다면, 가짜 저장소를 만들어서 데이터베이스에 대한 지연도 배제할 수 있다.
또한 같은 유즈 케이스에 대해서 정상인 경우와 비정상인 경우를 테스트하더라도 반복되는 코드의 양을 줄일 수 있다.
서비스 계층 덕분에 API와 도메인 모델 사이의 의존도 사라졌다. 서비스 계층에서 API가 요청한 결과로 가공해서 반환하기 때문이다.
서비스 계층은 유즈 케이스 내에서 도메인 모델, 저장소, 웹 API 등의 복잡한 상호작용에 추상화를 적용하는 것에 불과하다. 그래서 반대로 복잡하지 않다면 서비스 계층을 넣는 것이 불필요하거나 오히려 안티 패턴이 될 수 있다. 예를 들어 정해진 웹 페이지만 띄우는 순수한 웹 앱이거나 도메인 모델이 컨트롤러가 요구하는 대부분의 로직을 처리할 수 있는 구조를 들 수 있다. 그러므로 서비스 계층은 기존의 컨트롤러-도메인 구조가 점점 복잡해질 때에 고민하는 게 좋다.
이후 5장에선 서비스 계층과 도메인 모델 간의 커플링을 해결하는 것을 다루고 6장에선 저장소와 서비스 계층 사이에 활용하는 작업 패턴을 다룬다.
출처: 랜각코 스터디 – 책 “읽기 좋은 코드가 좋은 코드다” 1장 일부
가독성의 기본 정리: 이해하는데 들이는 시간을 최소화. 이해한다는 건 디버깅과 수정이 가능한 수준을 말함.
시각적으로 좋은 코드가 읽기도 좋다.
정리되지 않음.
VM: 하이퍼바이저 위에서 게스트 OS 실행
Containers: 호스트 OS의 커널에서 실행하는 가상 프로세스
이번 주제: 그래서 뭔 기술을 써서 이렇게 주어진 컴퓨터 자원을 가상으로 쪼개고 그룹화 하고 할 수 있는가?
컨테이너를 쉽게 배보할 수 있는 프로그램
컨테이너: 호스트 커널을 공유하고 프로세스의 실행영역을 격리
namespace: 프로세스 실행 환경 격리
Cgroups: 리소스 분할 사용
루트 디렉토리를 바꾸는 명령어
원격 유저에게 특정 루트 디렉토리로 격리하기 위해 사용함. 컨테이너의 시작.
단순히 새 디렉토리에 chroot를 실행하면 안됨.
오류 메세지를 보면 bash가 없어서 그럼. -> 그래서 새 루트로 bash를 옮겨줘야 함. bin도 옮기고 lib도 옮겨줘야 함.
그러면 bash로 새 루트로 접속이 됨.
하지만 ls같은 기본 명령어도 안됨. -> 역시 새 루트에선 ls를 실행할 수 없기 때문에 bash처럼 bin을 옮겨줘야 함.
chroot 후에 새 루트로 들어간 후에는 아무리 새 루트 위에 상위 디렉토리가 있다 해도 올라갈 수 없음.
-> 새 루트를 위한 파일들을 미리 묶어두고 복붙하면 될 것 같아
-> 그래서 tarball로 압축해둔게 image
그래서 docker의 이미지를 tar로 압축 해제하면 chroot가 가능한 디렉토리가 세팅되어 있음.
chroot의 치명적인 문제점
루트 파일시스템을 변경함.
-> 원래 chroot는 pid 1의 정보를 상속받기 때문에 파일 시스템에 남은 상위 디렉토리로 이동 가능(탈옥)
-> pivot_root는 프로세스가 보고 있는 파일 시스템을 바꿔서 탈옥이 불가능함.
이러면 fake root path에 대한 문제가 완전히 해결됨. 이제 격리와 리소스 관련 문제를 해결해야 함.
네임스페이스는 프로세스에 격리된 환경과 리소스를 제공함. 7개가 있음.
time, syslog는 아직 전부 구현되지 않아서 docker같은 컨테이너 구현체마다 다르게 구현되어 있음.
프로세스에 할당하는 리소스를 제어하는 파일시스템
그룹별로 여러 장치들을 묶어서 각각 어떻게 자원을 사용할 지 파일로 기록해둠. 그리고 이 그룹에 프로세스를 바인딩함.
실습: stress 명령어로 CGroup 테스트하기
컨테이너는 프로세스다.
그래서 왜 VM보다 컨테이너가 용량이 적음?
ubuntu + nginx + mysql + …
ubuntu + nginx + tomcat + …
이러면 중복된 이미지가 많아져서 공간 문제가 발생하는거 아닌가?
복수의 filesystem을 하나로 마운트하는 기능
두 파일시스템에 동일한 파일이 있는 경우, 나중에 마운트되는 파일시스템의 파일을 오버레이함.
하위 파일시스템에 대한 쓰기 작업 진행시 Copy on Write. 복사본을 생성하여 수행
일명 상속 파일시스템이라고 불림.
현재는 OverlayFS2를 사용.
이미지 -> 병합된 이미지 -> 컨테이너 내의 수정본 으로 계층으로 쌓여있음.
Docker에선 layer DB에서 관리함.
그래서 VM은 위에서처럼 약간의 변화가 있는 각각의 가상머신을 구분해서 저장하기 때문에 차지하는 공간이 크지만, 컨테이너는 중복된 부분을 union filesystem으로 관리하기 때문에 공간을 작게 차지한다.
이 책의 영문 버전은 무료로 읽을 수 있습니다.
http://www.cosmicpython.com/book/preface.html
책의 실습 코드들
https://github.com/cosmicpython
이 장에선 도메인 모델링이라는 핵심 주제에서 벗어나서 ‘추상화’에 대해 집중한다.
대규모 시스템에서는 시스템의 다른 부분에서 이루어진 결정에 의해 의사결정이 제한될 수 있다. 그래서 만약 A 컴포넌트가 B 컴포넌트를 변경할 수 없는 경우, 서로 ‘결합’되었다고 한다.
작은 범위에서 결합된 코드들이 서로 잘 맞물려서 돌아가는 거는 ‘응집’되었다고 표현하고 이는 바람직하다.
하지만 넓은 범위에서 결합이 생기면 코드를 변경하는 비용이 크고, 아예 변경이 불가능할 수도 있다. 그래서 추상화를 통해 세부 사항을 감추면 시스템 내 결합 정도를 줄일 수 있다. B를 변경하면서 A도 변경하는 것 대신 사이에 놓인 추상화가 B에 의존하는 부분만 수정하면 A는 변경하지 않아도 되기 때문이다.

책에선 두 파일 디렉터리를 동기화하는 코드를 작성하면서 이를 설명한다.
제시된 요구사항에 맞춰서 한 함수로 구현하면, 파일을 읽고, 같은 파일인지 확인한 후, 동기화를 수행하는 코드를 작성한다. 이러면 함수 내에 도메인 로직과 IO 코드가 결합되어 있다. 단순히 함수를 테스트하고 싶어도 테스트 코드에도 임시 파일을 생성하는 IO 로직이 들어가야 되며, 실제 IO가 발생하지 않고 실행 과정만 출력 시키고 싶다거나, 클라우드 같은 원격 서버와의 동기화로 변경하려고 한다면, 결합된 도메인 로직도 변경해야 한다.
(실패할 수 있는 구간에서 실행 과정을 확인하는 테스트를 dry run이라고 부른다. 소방서에서 실제로 물을 뿌리지 않고 화재 훈련하는 거에서 가져왔다.)
테스트 하기 쉽게 코드를 다시 작성하려면 어떻게 해야 할까?
먼저 코드에서 뚜렷한 책임을 구분한다. 여기선 다음과 같이 구분한다.
그 다음, 이 책임들을 각각 추상화를 통해 단순하게 만든다. 어떻게 파일을 읽을지 동기화 시킬 지는 감추고, 비즈니스 로직은 동기화하는 과정에만 초점을 맞출 수 있게 한다. 그러기 위해선 각각의 책임에서 “무엇을 하길 원하고, 어떻게 달성할지”를 구분한다. ‘무엇’이 추상화되어야 하고, ‘어떻게’는 세부 사항으로써 감춰져야 한다. 예로 들어 2번의 경우, 파일마다 각각 동기화 방식을 선택하길 원하고, 어떻게 동기화 방식을 결정할 지는 감춰지게 된다.
이 과정의 목표는 실제 파일 시스템 없이도 테스트를 할 수 있게 하는 것이다. 그러기 위해서 외부에 아무런 의존성이 없는 코드의 ‘핵’을 만들고 외부에서 이 핵에 어떻게 반응하는지 살펴볼 수 있게 해야 한다.
(게리 번하트는 Functional Core, Imperative Shell 라고 이 접근 방법을 설명했다.)
그래서 핵을 먼저 분리한다. 코드에서 IO 없이 데이터만 주어지면 로직으로 처리 가능한 부분을 찾아서 분리한다. 예를 들어 위에 2번은 이렇게 분리 가능한 핵에 해당되고, 하나의 함수로 분리하면 실제 파일 시스템 없이 2번에 대해서만 테스트가 가능해진다.
가짜 IO에 대해서도 테스트 할 수 있도록, 1번과 3번을 구현할 때, IO 대상을 외부에서 받아오도록 수정할 수 있다. 1번 과정의 코드가 실제 파일 시스템이 아닌 함수의 매개변수를 통해 외부에서 파일 시스템 객체를 받아서 파일을 읽게 하는 것이다. 외부의 파일 시스템은 파일을 읽고 쓰는 기능이 인터페이스로 열려 있어야 한다.
이렇게 하면 IO가 테스트를 위해 바뀌더라도 우리가 지금까지 구현한 동기화 코드가 수정되지 않는다. 단, 이제부터 매번 명시적으로 파일 시스템을 주입 시켜줘야 된다. 이를 “테스트가 야기한 설계 손상”이라고 부르기도 한다.
Python에선 mock.patch를 쓰면 코드를 수정하지 않고 단위 테스트를 수행할 수 있다. 하지만 책의 저자들은 이 방법이 코드 냄새를 유발한다고 생각하고, 위의 과정처럼 코드의 책임을 명확하게 구분하고, 테스트 더블로 대체하기 쉽게 책임들을 작은 크기로 만드는 게 도움이 된다고 생각한다.
(테스트 더블은 테스트 시 복잡한 실제 시스템을 고려하지 않도록 만들어주는 객체들이다. stub이나 dummy 같은 것들을 말한다.)
여기선 정확히는 세 가지 이유를 들었다.
3번과 같은 이유를 든 건, 저자들은 TDD가 테스트 이전에 설계를 위한 기법이라고 생각하기 때문에 테스트에서 설계를 알아볼 수 없으면 문제가 있다고 보기 때문이다.
휴리스틱한 방법이지만 책에 적혀 있는 것. 이건 연습을 통해 나아진다고 한다.
/ 연산자를 창의적으로 활용했다고 생각되어서 소개하고 싶다.
Python에서 경로를 합치는 방법은 os.path.join()을 생각하는데, Pathlib에선 연산자 오버라이드로 이렇게 쓴다.
file_path = Path(folder_path) / file_name
C++도 연산자 오버라이드를 지원하는 언어라서 찾아봤는데, 범용적으로 많이 쓰는 boost 라이브러리의 하위의 Boost.Filesystem 에선 /= 연산자를 오버라이드 해둬서 경로 추가를 할 수 있다.
https://www.boost.org/doc/libs/1_78_0/libs/filesystem/doc/reference.html#path-appends
0장에서 1장까지의 내용 요약
이 책의 영문 버전은 무료로 읽을 수 있습니다.
http://www.cosmicpython.com/book/preface.html
책의 실습 코드들
https://github.com/cosmicpython
저장소 패턴은 데이터 저장소를 간단한 형태로 추상화한 것이다. 이를 통해 모델과 저장소를 분리할 수 있고, 저장소에 대한 테스트를 용이하게 해준다.
저장소 패턴을 적용하기 전에 먼저 저장소를 사용해야 되는지 확인한다. 우리가 구현할 시스템에 저장소가 필요하다면, 최소 기능 제품(minimum viable product)을 만들기 위해 추상화된 저장소를 구상해야 한다.
예로 들어 웹 서비스라면 웹 API가 최소 기능 제품이 된다. 책에선 할당 기능의 엔드포인트를 보여준다. 여기서 batch를 저장소에서 가져와서 새로 할당한 후에 저장하는 걸로 구상했기 때문에, 추상화된 저장소 또한 batch를 가져오고 저장하는 기능이 필요하다.
일반적으로 UI-로직-데이터베이스 가 필요한 시스템을 구상할 때, 계층 아키텍쳐를 적용한다. 하지만 실제로 로직 부분인 도메인 모델에는 어떤 의존성이 생기지 않는게 좋다. 정확히는 어떤 상태가 있는 의존성은 없어야 된다는 말이다. 예로 들어 helper 라이브러리는 괜찮지만 ORM이나 프레임워크에 의존해선 안된다는 말이다. 따라서 UI와 데이터베이스에서 비즈니스 로직을 일방적으로 의존하는 계층 구조를 취한다. 이를 양파 아키텍쳐라 부른다.

ORM은 SQL 대신 OOP의 방식으로 데이터베이스에 접근해줄 수 있게 해주는 프레임워크이다. 따라서 가장 중요한 기능이 ‘영속성 무지'(persistence ignorance)다. 도메인 모델이 어떻게 영속화되는지 알 필요가 없게 해준다. 이를 통해 특정 데이터베이스 기술에 도메인이 직접 의존하지 않도록 유지할 수 있다.
하지만 ORM의 튜토리얼을 보면 도메인 모델을 ORM의 프로퍼티에 기반해서 도메인 모델을 만들게 된다. 그러면 오히려 ORM에 의존하는 모델을 만들게 된다. 그에 따른 문제점으로 도메인 모델이 객체 지향이 아닌 다른 게 필요한다면 ORM 때문에 벗어나기 어려워진다는 게 있다. (이것도 Python을 사용하기 때문에 고려한 문제라고 본다.) 그리고 도메인 모델을 테이블로 매핑하는 방법을 원하는대로 제어하기 힘들다.
책에선 ORM이 아닌 조금 예전 방식인 mapper를 사용한다. 이럴 경우, mapper는 도메인 모델에 종속되지만 반대로 도메인 모델에선 mapper를 알지 못한다. 덕분에 만약 우리가 다른 ORM을 사용한다고 하더라도 도메인 모델은 수정되지 않는다.
저장소를 추상화 하기 전에 먼저 데이터베이스에 접근하기 위해 사용하는 도구들이 정상 작동하는지 테스트한다. 예로 들어 mapper가 제대로 작동하는지 SQL 구문을 같이 호출해서 검증하는 테스트 코드를 짤 수 있다.
그 후에 어떻게 추상화할 건지 생각해보자. 위에서 본 batch의 경우, 저장을 위한 add()와 가져오기 위한 save()가 필요하다. 그렇다면 이에 맞춰서 저장소의 인터페이스를 만들면 된다.
Python에선 ABC(추상 기반 클래스)와 duck typing, 프로토콜 로 추상화된 저장소를 구현할 수 있다. ABC는 무시하고 코딩하기 쉬워서 보통은 duck typing에 의존해서 추상화를 구현한다. 프로토콜은 상속을 사용하지 않고 타입을 지정할 수 있는 대안이다. 특히 상속보다 구성(composition)이라는 규칙을 선호한다면 좋은 대안이다.
추상화된 저장소를 만들었다면, 다음은 저장소 구현체에 대한 테스트를 먼저 짠다. 정말로 구현체를 통해 DB와 연결되었는지 확인하기 위해 SQL문의 실행 결과와 구현체의 결과가 같은지 검증한다.
테스트를 만들었다면 이 테스트를 통과하기 위한 구현체를 만든다.
책에서는 ‘포트’와 ‘어댑터’라는 용어를 쓴다. ‘포트’는 애플리케이션과 추상화 대상 사이의 인터페이스를 말하고, ‘어댑터’는 이 인터페이스 뒤에 있는 구현체를 말한다.
아키텍쳐 패턴을 적용할 때 반드시 뒤따라오는 대가도 고려해야 한다.
위에서 ORM 의존 모델 대신 저장소를 추상화하겠다는 패턴을 적용할 때는 전체적인 복잡도는 줄어들 수 있다. 그 덕분에 여러 종류의 저장소를 쓸 때나 테스트를 위한 가짜 저장소를 만들 때, 도메인 모델이 영향받지 않을 수 있다.
하지만 이 패턴도 단점이 있다. ORM 의존 모델의 경우, 수동으로 매핑한 코드를 만들고 유지보수하는 비용이 발생하지 않는다. 마지막으로 ORM이 지원하는 데이터베이스 범위 내에서의 변경이라면 ORM도 도메인 모델과 저장소 사이의 의존성 문제를 이미 해결해주고 있다.
책에선 그 트레이드오프를 다음과 같은 그래프로 보여준다.

여기까지가 저장소 패턴에 대한 내용이다. 다음은 서비스 계층으로 넘어갈 것이다.
이 책의 영문 버전은 무료로 읽을 수 있습니다.
http://www.cosmicpython.com/book/preface.html
책의 실습 코드들
https://github.com/cosmicpython
값 객체는 데이터는 있지만 유일한 식별자가 없는 개념을 표현하기 위한 방법이다.
예로 들어 사람들에게 지폐는 서로 다른 지폐더라도 같은 값어치가 있으면 같다고 취급한다. 물론 실제로는 지폐마다 식별자가 있긴 하지만, 거래 시스템을 모델링할 때는 그걸 고려하지 않기 때문에 5천원 짜리 지폐 두 장은 서로 같은 것이라고 취급한다.
아무튼 값 객체로 표현되는 대상들은 식별자 대신 안에 있는 데이터가 그 대상을 나타낸다. 따라서 지폐의 경우 그 값어치가 지폐를 구분하는 기준이 되고, 값어치가 바뀌면 다른 지폐로 인식된다. 이 말이 어려우면 이름표를 예로 들어도 된다. 이름표에 이름이 한 글자 바뀌면 다른 사람의 이름표가 되기 때문이다.
그래서 값 객체는 보통 불변 객체로 정의한다. (ex. python에선 @dataclass(frozen=True)) 그리고 내부 속성들의 모든 값들이 같으면 객체가 같다고 정의한다. (이걸 ‘값 동등성’이라 부른다.)
주의점: 값 객체가 안에 있는 데이터가 중요한 객체라고 해서 메서드를 포함해선 안된다고 말한 건 아니다. OOP 언어를 쓴다면, 값 객체와 밀접하게 연관된 연산들은 같이 묶어두는게 좋다.
값 객체와 반대로 내부의 데이터가 아닌 객체 자체에 정체성이 있는 걸 엔티티라고 부른다. 예로 들어 위에서 이름은 글자가 바뀌면 다른 이름이 된다고 했는데, 사람은 개명을 하든 성별을 바꾸든 간에 바뀌기 전과 같은 사람이라고 인식한다. 따라서 사람은 이름과 다르게 ‘영속적인 정체성’이 있다고 할 수 있다.
위에서 든 예시처럼 값이 바뀌어도 엔티티는 같다고 인식하는 걸 ‘정체성 동등성’ 이라고 한다. Python의 경우, 정체성 동등성을 보장하기 위해 __eq__()와 __hash__()를 오버라이드할 필요가 있다.
도메인 서비스 함수는 비즈니스 개념이나 프로세스를 나타내긴 하지만 값 객체나 엔티티에 포함하기엔 자연스럽지 않는 개념들을 가리킨다. 책에서 든 가구 주문 시스템 예시에선 고객이 가구를 주문한 내역인 ‘주문’과 공장에서 재고를 쌓는 ‘배치’라는 엔티티가 있는데 주문에 배치를 할당하는 프로세스는 양 쪽 어디에 메서드로 포함해도 자연스럽지 않다고 판단했다.
Python의 경우엔 OOP도 되지만 FP도 되는 다중 패러다임 언어이기 때문에 이 개념을 단순히 함수로 정의해서 해결할 수 있다. 이 말은 언어에 따라서 굳이 class로 만들지 말고 가장 적합한 방식으로 구현하라는 뜻이다.
도메인 서비스는 서비스 계층 개념과 혼동될 여지가 있다. 서비스 계층에서 서비스는 애플리케이션을 사용하는 하나의 Use case를 가리키지만, 도메인 서비스는 위에서 말했듯이 비즈니스 개념이나 프로세스를 가리킨다. 하지만 대부분 서비스라고 하면 서비스 계층을 말한다는 것도 주의하자.
도메인에서 발생할 수 있는 예외도 코드로 구현해야 한다. 당연히 Exception 클래스와 try-except 구문으로 이런 예외를 처리하겠지만, 중요한 건, 이런 예외 사항도 반드시 유비쿼터스 언어로 표현되어야 된다는 것이다.
예시로 가구 주문 시스템을 만들 때, 도메인 전문가와 대화하면서 ‘품절'(out of stock)이 발생해서 주문할 수 없는 경우에 대해 들었다면, 관련된 코드에서 처리할 예외 클래스의 이름은 OutOfStock 이 되어야 된다.
그 외에 배운 것.
다음 장은 데이터베이스 활용을 위한 저장소 패턴에 대한 얘기를 할 거다.
https://en.wikipedia.org/wiki/Keepalive
https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Keep-Alive
Keepalive는 두 장치간의 연결이 계속 유지되고 있는지 주기적으로 확인하기 위해 보내는 메세지를 말한다. keepalive를 보낸 후에 응답이 돌아오지 않는다면, 링크가 끊어졌다고 추정한다. 따라서 keepalive는 연결 확인 말곤 역할이 없기 때문에 최소한의 크기를 가져서 네트워크 대역폭을 많이 차지하지 않게 해야한다.
TCP에선 기본적으로 꺼져있고, 선택적으로 켤 수 있다. keepalive는 세 가지 매개변수를 설정할 수 있다.
위의 개념과 전혀 관련없다. HTTP에서의 keepalive는 추가 메세지 전달을 위해 연결을 열어둬야 할 때에 사용한다. 두 가지 매개변수를 가진다.
Available zone (가용 영역): 하나의 데이터 센터를 말함. 네트워크가 연결되어 있고 장애 대처가 가능한 전력 공급망, 서버 랙, 라우터, 스위치, 하드웨어 방화벽 등등, 온도 제어 등이 갖춰진 물리적 시설
Multi Zone Region: 물리적으론 떨어져 있지만 서로 연결된 가용 영역들의 집단. 이 각각의 가용영역들과 전부 연결된 PoP(Network Point of presence, network hotel)를 통해 외부 사용자들이 Region에 접근한다.
위와 같이 구성하는 이유는 가용성이 높은 클라우드 네이티브 아키텍쳐를 구성하기 위해서다. 여러 가용 영역에 동일한 애플리케이션을 배포해서 가용 영역 중 하나가 장애가 발생하더라도 다른 가용 영역에 접근할 수 있게 구성할 수 있다.
https://en.wikipedia.org/wiki/Fork%E2%80%93join_model
https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
https://www.baeldung.com/java-fork-join
병렬 컴퓨팅에서 작업을 분할정복 방식으로 처리하는 걸 Fork-Join model이라고 한다. 의사코드로 다음과 같다.
solve(problem):
if problem is small enough:
solve problem directly (sequential algorithm)
else:
for part in subdivide(problem)
fork subtask to solve(part)
join all subtasks spawned in previous loop
return combined results
중요한 건 작업의 크기에 threshold를 두고 그보다 작으면 단일 쓰레드에서 처리하고, 그렇지 않으면 작업을 분할하고 모든 작업 결과를 병합할 수 있을 때까지 기다리면 된다.
https://hamait.tistory.com/612
Fork-Join 모델을 사용하는 Java의 ForkJoinPool은 쓰레드 풀에서 효율적인 작업 분배를 위해서 work-stealing 알고리즘을 사용한다. 각각의 쓰레드는 작업을 대기시키기 위한 deque를 가지고 있고 다음과 같이 동작한다.
애초에 쓰레드풀로 들어온 작업들이 전부 균등한 크기라면 ForkJoinPool이 최고의 선택은 아니다. 굳이 작업을 쪼개서 나눠줄 필요도 없고 오버헤드가 되기 때문. 하지만 만약 작업의 크기가 다르다면, 큰 작업을 받은 쓰레드가 자신의 작업을 쪼개서 deque에 넣어두면, 다른 여유가 생긴 쓰레드가 가져갈 수 있기 때문에 효율적인 분배가 가능하다. 그리고 현실에선 균등하지 않은 작업이 들어오는 경우가 많기 때문에 work-stealing 알고리즘이 유효하다.