• 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 리뷰 :: 마이구미
    책 리뷰 2024. 10. 13. 14:32
    반응형
    이 책은 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 를 리뷰한다.
    이 책은 1편에 이어 2편이 나오게 되었다.

    본인은 평범한 프론트엔드 개발자이다. 개인적인 생각과 해석이 들어가 있을 수 있다.
    1편 책 리뷰 - https://mygumi.tistory.com/403
    책 링크 -
    https://m.yes24.com/Goods/Detail/124138645

     

     

    21년 1편을 읽던 당시를 회상해보면, 굉장히 좋았던 책으로 기억한다.

    그래서 2편도 나오자마자 구매를 하였었다. (구매하고 한참 뒤에 읽게 되었지만...)

     

    개인적으로는 2편도 흥미롭게 읽었다.

    하지만 1편보다는 조금 더 어려웠고, 너무 깊은 내용들은 훅훅 넘어가는 식으로 읽긴 했다.

    중간중간 관련 내용에 대해 GPT 와 다른 글들을 참고하면서 읽어서 좀 더 읽는 시간이 길긴했다.

     

    그래도 많은 것들을 좀 더 공부해보게 되었다.

    그 중 개인적인 기준으로 몇가지를 기록하려고 한다.

     


     

    레디스 펍/섭은 채널을 만드는 비용이 아주 저렴하다.

     

    (페이스북의 주변 친구 기능에서 모든 온라인 친구에게 보내는 위치 변경 내역 메시지의 라우팅 계층으로 활용하는 과정에서)

    채널로 전송된 메시지가 그냥 버려지더라도, 그 과정에서 서버에 가해지는 부하는 거의 없다.

    채널 하나를 유지하기 위해서는 구독자 관계를 추적하기 위한 해시 테이블과 연결 리스트가 필요한데 아주 소량의 메모리만을 사용한다.

    오프라인 사용자라 어떤 변경도 없는 채널의 경우에는 생성 된 이후에 CPU 자원은 전혀 사용하지 않는다.

     

     

    경로 안내 서비스에서 경로 내에서 특정 위치에서 교통사고가 난다면?

     

    경로 안내 서비스를 받고 있는 사용자와 그 경로 정보를 데이터베이스에 저장한다면, 다음과 같다.

     

    user_1: r_1, r_2, r_3 ….
    user_2: r_2, r_8, r_9 ….
    ...
    user_n: r_2, r_10 …

     

    그리고 경로 안내 타일 r_2 에서 교통사고가 발생한다면?

    어떤 사용자가 영향을 받는지 알아내려면 모든 레코드를 전수 조사해야한다.

     

    user_1: r_1, r_2, r_3 ….
    user_2: r_2, r_8, r_9 ….
    ...
    user_n: r_2, r_10 …

     

    그렇다면, 레코드 수가 n 이고 경로 평균 길이가 m 일때 시간 복잡도는 O(n*m) 이 된다.

    개선할 수 있는 다른 접근법은 다음과 같다.

    경로 안내 타일, 그 타일을 포함하는 상위 타일, 그 상위 타일의 상위 타일을 출발지와 목적지가 모두 포함된 타일을 찾을 때까지 재귀적으로 더하여 보관한다.

     

    user_1: r_1, super(r_1), super(super(r_1)) …

     

    어떤 타일의 교통 상황이 변했을 때, 영향이 있는 사용자는 해당 사용자의 레코드 마지막 타일에 그 타일이 속하는 사용자이다.

    시간복잡도는 O(n) 으로 줄어들게 된다.

     

     

    메시지 큐(푸시 vs 풀)

     

    브로커가 데이터를 소비자에게 보낼 것이냐 아니면 소비자가 브로커에서 가져갈 것이냐?

     

    [푸시 모델]

    • 낮은 지연 - 브로커가 메시지를 받는 즉시 소비자에게 보낼 수 있다.
    • 소비자가 메시지를 처리하는 속도가 생산자가 메시지를 만드는 속도보다 느릴 경우 소비자에게 큰 부하가 걸릴 가능성

     

    [풀 모델]

    • 메시지를 소비하는 속도는 소비자가 결정
    • 일괄 처리 적합
    • 메시지가 없어도 끌고 가려고 해서 컴퓨팅 자원 낭비. 이를 극복하기 위해 롱 폴링 모드 지원.

    대부분의 메시지 큐는 푸시 모델 대신 풀 모델을 지원하고 있다.

     

    메시지 전달 방식

     

    [최대 한 번]

    메시지가 소실되더라도 다시 전달되는 일은 없다.

    지표 모니터링에 적합

     

    [최소 한 번]

    메시지 소실은 발생하지 않는 전달 방식(중복 전송 될 수 있음)

     

    [정확히 한 번]

    지불, 매매, 회계 등 금융 관련 적합

    성능 및 구현 복잡도 측면에서 많은 비용 지불

     

    지표 모니터링 풀 vs 푸시

     

    풀 - 지표 수집기가 각 서버에 접근하여 수집

    푸시 - 각 서버가 직접 지표를 수집기에 전송하는 모델

     

    로그 수집을 위해 서버에 보내거나 특정 위치에 로그를 기록하여 수집해가도록 하곤 했었는데 풀, 푸시 모델을 딱히 생각해본 적은 없다.

    이미 다양한 곳에서 요구사항에 따라 각 모델을 적용하고 있었구나 라고 되짚어보게 되었다.

     

    이벤트 발생 시각 vs 처리 시각

     

    [이벤트 발생 시각]

    클라이언트에 설정된 시각이 잘못되었거나 악성 사용자가 타임스탬프를 고의로 조작하는 문제에서 자유로울 수 없음

     

    [처리 시각]

    이벤트가 시스템에 도착한 시각이 한참 뒤인 경우에는 집계 결과가 부정확해짐

     

    대부분 "이벤트 발생 시각" 을 사용하겠지만, 단점에 대해 고민하거나 인지하기 좋은 단점으로 보인다.

     

    ACID

    from GPT

     

    Atomicity (원자성): 트랜잭션의 작업은 모두 실행되거나 또는 모두 실패해야 합니다.

    Consistency (일관성): 트랜잭션이 성공한 후에도 데이터베이스는 일관된 상태를 유지해야 합니다.

    Isolation (고립성): 트랜잭션은 서로 간섭하지 않고 독립적으로 실행되어야 합니다.

    Durability (지속성): 트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 저장되어야 합니다.

     

    Atomicity: 계좌에서 돈을 차감하는 작업과 입금하는 작업이 동시에 성공하거나 실패해야 함.

    Consistency: 트랜잭션이 완료된 후에도 각 계좌의 잔고는 음수가 되는 등의 데이터 무결성이 깨지지 않아야 함.

    Isolation: 두 명의 사용자가 동시에 같은 계좌에서 출금을 시도하더라도, 각 트랜잭션은 독립적으로 처리되어야 함.

    Durability: 계좌 이체가 완료된 후, 시스템 오류가 발생해도 이체 결과는 안전하게 저장되어 있어야 함.

     

    관계형 데이터베이스는 ACID 속성을 보장한다.

     

     락 메커니즘 - 중복 예약

     

    1. 예약 가능 객실 현황 확인 (SELECT)
    2. 객실 예약 (UPDATE)

    [비관적 락]

    트랜잭션이 데이터를 읽을 떄 해당 데이터에 락을 걸고 다른 트랜잭션이 해당 데이터에 접근하려고 하면 대기 or 거부

    잔여 객실 확인하는 쿼리에 락 걸기 - SELECT … FOR UPDATE

     

    [낙관적 락]

    데이터 접근에는 락을 걸지 않고 데이터 수정하는 시 타임스탬프나 버전을 비교하여 충돌 유무를 판단.

    잔여 객실 확인한 후, 이후 실제 업데이트 시점에서 검사 실행.

     

    UPDATE inventory
    SET reserved = reserved + 1, version = version + 1
    WHERE roomtype_id = 123
      AND version = 1
    
    -- 만약 0개의 레코드가 업데이트되었을 경우 롤백 또는 재시도 
    IF @@ROWCOUNT = 0 
    BEGIN -- 충돌이 발생했으므로, 트랜잭션을 롤백하거나 재시도 로직을 구현 
    RAISERROR('Update failed due to optimistic lock conflict.', 16, 1); 
    ROLLBACK; END;

     

     

    숫자 필드의 데이터 유형이 string 인 이유

     

    프로토콜, 소프트웨어, 하드웨어에 따라 직렬화/역직렬화에 사용하는 숫자 정밀도가 다를 수 있다.

    이러한 차이가 의도치 않은 반올림 오류를 유발할 수 있다.

     

    지수적 백오프(exponential backoff)

     

    재시도 전에 기다리는 시간을 직전 재시도 대비 두 배씩 늘려 나가는 방안.

    지나치게 공격적인 재시도 전략은 컴퓨팅 자원을 낭비하고 서비스 과부하를 유발한다.

     

    프론트엔드 입장에서도 서버 요청에 대한 retry 기능을 사용하는 것은 항상 부담스럽다.

    retry 가 발생하는 경우에는 대부분 이슈가 발생한 상황이라는 것이다.

    그러한 상황에서 retry 는 문제를 더 크게 만들 위험이 더 크기 떄문이다.

    retry 사용을 피할 수 없다면, 개선할 수 있는 방안으로 위와 같은 방안도 고민해보면 좋아보인다.

     

    Retry-After 헤더

     

    Retry-After 헤더는 클라이언트가 다시 요청을 언제 시도할 수 있는지를 알려주는 헤더입니다.

    **503 (Service Unavailable)**나 429 (Too Many Requests) 상태 코드와 함께 자주 사용됩니다.

    초 단위로 명시하거나, 특정 날짜와 시간을 지정하는 방식으로 사용할 수 있습니다.

    주로 서버의 일시적 과부하요청 제한을 관리하기 위해 사용됩니다.

     

    아직까지는 retry 를 의도적으로 서버에서 응답하는 상황을 겪어보지는 못했다.

    이런 상황이 생긴다면, 해당 헤더와 상태 코드를 인지하면 좋을 것 같다.

    반응형

    댓글

Designed by Tistory.