2023-12-21-TIL
Today I Studied
동시성
동시성을 제어하는 가장 간단하고 확실한 방법은 ‘줄 세우기’이다. 유명 맛집에 가보면 여러 사람들이 동시에 입장하려고 할 때 항상 가게 사장님이 말씀하신다. “줄 서주세요”, “번호표 뽑고 순서대로 대기해주세요” 만약에 줄 세우는 시스템이 제대로 갖춰지지 않은 가게였다면 손님들의 불만이 많을 것이다. “왜 내가 먼저 왔는데, 다른 손님이 먼저 들어가지?”라는 식으로 말이다. 늦게 왔더라도 예약한 손님은 먼저 입장시키기도 한다. 즉, 예약 손님은 우선 순위가 높은 것이다.
이와 같이, 동시성 처리는 큐(Queue)로 가장 쉽게 제어할 수 있다.
비유를 시스템으로 옮기면 이렇다. 여러 요청이 같은 자원을 동시에 고치려 할 때 문제가 생기는 이유는 “읽고, 판단하고, 쓰는” 단계 사이에 다른 요청이 끼어들기 때문이다. 요청을 큐에 넣고 처리하는 쪽이 하나씩 꺼내 실행하면 끼어들 틈이 사라진다. 번호표가 도착 순서를 보장하는 것처럼 큐는 들어온 순서를 보장하고, 예약 손님처럼 먼저 처리할 요청이 있으면 우선순위 큐를 쓴다.
대가는 처리량이다. 한 줄로 세우면 한 번에 하나씩만 처리하므로, 같은 자원을 다투지 않는 요청까지 기다리게 하면 손해다. 그래서 줄을 자원 단위로 나눈다. 맛집마다 줄이 따로 있는 것처럼, 상품이나 좌석마다 큐를 나누면 서로 다른 자원에 대한 요청은 동시에 처리된다.
메모로 남긴 질문
같이 적어 둔 메모는 다음과 같다.
- select for update
- insert 2개 되면 안되나?
- snow flake..
- 음악 듣기 lock???
- 로컬캐시, 글로벌캐시, 분산캐시
- 일관성 관리
- 인퍼런스 서버 -> ㅇㅅㅇ
- Optional.get 쓰지말자
각 항목을 확인한 내용을 덧붙인다. “음악 듣기 lock???”은 무엇을 염두에 둔 것인지 남아 있지 않아서 질문으로 남겨 둔다.
select for update는 DB에서 줄 세우기를 하는 방법이다. MySQL 문서에 따르면 SELECT ... FOR UPDATE는 읽은 행을 UPDATE할 때처럼 잠그고, 잠금은 트랜잭션이 커밋되거나 롤백될 때 풀린다. 다른 트랜잭션은 그 행을 고치려면 기다려야 한다. MySQL 8.0에는 기다리지 않고 바로 실패하는 NOWAIT, 잠긴 행을 건너뛰는 SKIP LOCKED도 있다.
“insert 2개 되면 안되나?”는 같은 데이터가 두 번 저장되는 문제다. 존재 여부를 먼저 조회하고 없으면 insert하는 코드는, 두 요청이 동시에 조회하면 둘 다 “없다”고 보고 둘 다 insert한다. 이 경우에는 잠금보다 유니크 제약이 확실하다. 두 번째 insert를 DB가 거부하므로 애플리케이션은 그 오류만 처리하면 된다.
Snowflake는 Twitter가 만든 분산 ID 생성 방식이다. 원래 저장소의 README에 따르면 64비트 ID를 타임스탬프 41비트, 기계 번호 10비트, 순번 12비트로 나눈다. 기계마다 번호가 다르니 서버끼리 줄을 서지 않아도 ID가 겹치지 않고, 앞쪽이 시간이라 대략 시간순으로 정렬된다.
로컬 캐시는 애플리케이션 프로세스 메모리 안의 캐시이고, Redis 같은 별도 서버에 두는 캐시는 모든 인스턴스가 함께 보는 글로벌 캐시가 된다. 그 캐시 서버를 여러 노드에 나눠 두면 분산 캐시다. 로컬 캐시는 가장 빠르지만 인스턴스마다 값이 달라질 수 있다. 한 서버에서 값을 바꿔도 다른 서버의 로컬 캐시는 예전 값을 들고 있으니, 만료 시간을 짧게 두거나 변경 이벤트를 전파해 지우는 식으로 일관성을 관리해야 한다.
인퍼런스 서버는 학습이 끝난 모델을 올려 두고 예측 요청을 받아 처리하는 서버다. 예를 들어 NVIDIA Triton은 여러 프레임워크의 모델을 HTTP/REST와 gRPC로 서빙하고, 들어온 요청을 묶어 처리하는 동적 배칭을 지원한다.
Optional.get()은 값이 없으면 NoSuchElementException을 던진다. Java 21 Javadoc은 get()보다 이름에서 예외 가능성이 드러나는 orElseThrow()(Java 10부터)를 쓰라고 권한다. 값이 없을 때의 동작을 정해야 한다면 orElse, orElseGet, orElseThrow(Supplier)로 의도를 드러내는 편이 낫다.
댓글
아직 댓글이 없습니다