Community

개발자 99% 커뮤니티에서 수다 떨어요!

← Go back
Assignment 10 - TIL (2022. 5. 25.)
by pksl
#pragmatic
2년 전
725
1

오늘의 TIL 3줄 요약

  1. 개발에 앞서 작업흐름을 분석해서 동시성을 최대한 개선해보도록 설계하자.

  2. 공유 상태를 두는 순간 동시성 문제가 발생할 수 밖에 없음을 다시금 확인하게 됐다.

  3. 액터 모델과 마이크로 서비스 아키텍쳐는 동시성 문제를 해결하는데 적용하기 좋은 설계 방식이다.

TIL (Today I Learned) 날짜

2022-05-25

오늘 읽은 범위

  • 6장. 동시성 (p.241 ~ p.272)

책갈피

  • 동시성이 겉으로 드러날 때도 있지만 라이브러리 안에 묻혀 있는 경우도 있다. (p.242)

  • 작업 흐름 분석으로 동시성을 개선하라. (p.244)

  • 동시성은 소프트웨어 동작 방식이고, 병렬성은 하드웨어가 하는 것이다. (p.247)

  • 공유 상태는 틀린 상태다. (p.249)

  • 불규칙한 실패는 동시성 문제인 경우가 많다. (p.257)

  • 액터 모델에서는 동시성을 다루는 코드를 쓸 필요가 없다. 공유된 상태가 없기 때문이다. (p.265)

  • 데이터의 도착 순서는 이제 상관없다. 어떤 사실이 칠판에 올라가면 적절한 규칙이 발동되도록 하면 된다. (p.270)

  • 특정한 비즈니스 작업 처리를 시작할 때 고유한 ‘추적 아이디’를 만들어서 붙이는 것이다. 그리고 해당 작업에 관여하는 모든 액터로 아이디를 전파하면, 나중에 로그 파일을 뒤져서 어떤 일이 일어났는지 재구성해 볼 수 있을 것이다. (p.270 ~ p.271)

오늘 읽은 소감

나에게 있어 가장 약한 주제가 나왔다. 이슈도 특히 ‘동시성’에 관련된 이슈가 처리하기 까다롭다. 이런 이슈들은 대개 완벽하게 해결되지도 않기 때문이다…

가장 처음 다뤄지는 주제는 단순한 내용인 [Topic 33. 시간적 결합 깨트리기]인데, 병렬 작업의 중요성에 대해 다뤄주고 있었다. 병렬처리에 관해 가장 이해하기 쉽게 다뤄진 칵테일 소프트웨어 예시가 특히 맘에 들었다. 읽다 보니 다른 예시를 하나 꺼내 보고 싶어졌다.

앱의 Splash Screen(로딩화면)은 그냥 아무런 작업 없이 단순히 로고만을 보여줄 수도 있고, 많은 일을 미리 처리해둘 수도 있다. Firebase 서비스들과 사용자에 대한 데이터를 가지고 있는 백엔드 서버로 구성된, 가장 심플한 앱을 예시로 들어보자면 Splash Screen에서 다음과 같은 절차들을 수행해 볼 수 있다. (권한 동의나 추가 리소스 다운로드와 같은 부분은 제외하고, 이미 로그인되어있다고 가정)

  1. Firebase RemoteConfig를 통해 설정 값들 불러오기

  2. 원격 구성에서 서버가 점검중이라고 한다면 팝업 띄우기

  3. 원격 구성에서 업데이트가 필요한 새로운 앱 버전이 있다면 팝업 띄우기

  4. Firebase Authentication에 로그인 된 계정의 토큰 가져오기

  5. 4번의 토큰을 백엔드 서버로 전송해서 사용자 정보 및 상태 불러오기

  6. 앱 Push 알림을 수신하기위해 Firebase Cloud Messaging 키 발급

  7. 발급 된 FCM 키의 정보를 포함한 기기 정보(앱버전, OS버전)를 백엔드 서버로 전송

가장 좋아보이는 흐름은 [1번, 4번, 6번]을 병렬로 각각 수행하고, 1의 후속과정인 [2번]의 과정에서 서버가 문제가 없다면 [3번, 5번]을 병렬로 수행하고, 마지막으로 [7번]을 수행할 것이다. 아니면 7번의 기기 정보를 5번의 요청안에 같이 넣어 전송하는 방법도 있다. 이 절차들을 직렬로 처리할 때와 병렬로 처리할 때의 시간 차이는 체감이 될 정도이다.

또 다른 예로 단순한 앱은 각각의 IDE를 통해 직접 빌드하는 경우가 많지만, 앱 내의 리소스가 많아지고 커지면 빌드 시간이 늘어날뿐더러 컴퓨터의 리소스를 너무 많이 점유하기 때문에 빌드하는 동안 아무것도 할 수가 없어진다. 이때는 빌드만 수행하는 빌드 서버를 따로 두는 게 일반적인 방법이며, 이 또한 프로그래밍 환경과 빌드 환경을 분리한 병렬 작업으로 볼 수 있다.

지금까지 중 가장 어려운 주제가 나왔다. ‘[Topic 34. 공유 상태는 틀린 상태]

DB에 데이터를 추가하거나 갱신하는 코드를 작성할 때는 데이터가 아토믹하게(Atomicity) 관리되는지 늘 신경 써야 한다. 이는 즉, 기본적으로 데이터의 정합성을 유지해야 하고, [Topic 25. 단정적 프로그래밍]에서 다룬 것처럼 절대 발생하지 않아야 할 것 같은 예외 상황들이 이미 지나갔을 수도 있으므로 정기적으로 무결성 검사를 해줘야 한다는 의미다. (은행의 점검 시간은 무결성 검사를 위해 존재한다고 볼 수 있다)

아토믹하게 데이터를 관리하기 위해서 우리는 보통 트랜젝션(Transaction)을 사용한다. 그럼 트랜젝션을 둘둘 두르면 문제가 해결되느냐? 트랜젝션이 많아지면 반사적으로 데드락(Deadlock)이라는 고통을 주는 이슈가 트랜젝션을 견고하게 두른 만큼 발생하기 쉬워진다. 개인적으로 이 균형을 잡는 게 아직도 쉽지 않다.

[Topic 34. 공유 상태는 틀린 상태]를 한 줄로 요약하면 ‘이런 문제들을 해결하기는 어려우므로 대안을 알려줄 테니 공유 상태는 되도록 쓰지 말자’ 라는 것이다. 과연… 그게 가능할까? 여러모로 기대된다.

첫 번째 대안은 [Topic 35. 액터와 프로세스]였다. 액터에게 처리해야 할 일을 전달해서 수행하고, 결과를 다시 요청을 보낸 액터를 통해 반환받도록 한다. 요청이 동시에 들어오더라도 처리하는 액터는 하나라 한 번에 하나씩 처리하기 때문에 동시성 문제는 발생하지 않는다… 이거 어디서 많이 본 모습이다. RoR(Ruby on Rails)의 Sidekiq, Resque와 같은 ActiveJob과 유사한 구조다. 또한 Vue.js의 Vuex도 이와 유사한 점을 가지고 있다. 이와 같은 액터 모델은 실제로 많은 동시성 문제를 해결할 수 있다.

두 번째 대안으로는 [Topic 36. 칠판]이 제시되었다. 동시 협력 처리에 관한 설계 방법으로 여러 가지 조건들을 모아두는 칠판 같은 모델을 따로 두고 각각의 데이터들을 취합하는 것… 이것도 어디서 많이 본 모습이다. 마이크로 서비스 아키텍쳐! 칠판을 통해 동시성 문제는 해결될 수 있지만, 그 대가로 구축하기 어려운 환경, 구성요소 간 데이터를 직접적으로 가져오는 방식은 마이크로 서비스의 이점을 깨는 구조라 적용이 어려워지며, 여기저기 흩어져있어 관리하기 어려운 데이터 등 많은 비용들이 발생한다.

일단 각각의 대안들에 대해서 이해하고 충분히 좋은 대안이라는 걸 납득했지만, 현재 운영 중인 서비스에서 중요하게 다루는 동시성 이슈는 앞서 말한 두 대안과는 스코프가 달라 해결책으로서 적용은 불가능할 것 같다.

운영 중인 서비스의 동시성 이슈는 주로 DB를 경유함으로써 발생하는 이슈인지라, 액터 모델을 적용하게 되면 각 액터가 작업을 처리하는 지점에 병목이 발생하기 쉬워지게 된다 (기존보다 부하 발생 가능성 증가). 마이크로 서비스 아키텍쳐를 적용하기에는 규모가 좁고 작은 로직을 가진 서비스라 오버 스펙이 되어버린다. 기획상의 변경이 많이 이루어져야 할 수 있으며, 클라이언트에도 구버전과 호환이 어려운 수준의 변경이 있을 것으로 예상되고, 최종적으로 DB 관리가 힘들어지게 되기 때문이다. 따라서 현재 서비스 내에 있는 DB관련 동시성 이슈는 그냥 기존처럼 요청을 retry 하는 방법으로 감내할 수밖에 없고, 나중에 각 성격에 맞는 프로젝트나 비지니스 로직이 나온다면 적용해볼 만할 것 같다.

과제

  1. Actor 모델 구현을 도와주는 라이브러리 리서치 (Ruby, TS, Dart 등)

  2. 가장 작은 단위로 마이크로 서비스를 적용해본 실사례 혹은 오픈소스 프로젝트 리서치

오늘 읽은 다른사람의 TIL

  • Assignment 9 - MISSION 연습문제 풀이

  • 앞서 작성된 Assignment 10 - TIL

1 comment