ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 9장 - 일관성과 합의
    CS/데이터 중심 애플리케이션 설계 요약 2023. 1. 9. 00:01

    내결함성

    분산 시스템에서는 많은 것들이 잘못될 수 있습니다.

    가장 간단하게는 전체 서비스가 실패하도록 두고 사용자에게 오류 메시지를 보여주는 것입니다.

    하지만 이런 결정을 받아들이기 어렵다면 결함을 견뎌낼 수 있도록 구성해야 합니다.

    이번 장에서는  내결함성을 지닌 분산 시스템을 구축하는데 쓰이는 알고리즘과 프로토콜의 몇 가지를 예를 이야기합니다.

     

    추상화

    트랜잭션을 사용함으로써 애플리케이션이 걱정하지 않도록 합니다.

    이는 트랜잭션의 추상화인데 이와 같은 방식을 분산 시스템에서는 합의라고 합니다.

    합의는 모든 노드가 어떤 것에 동의하도록 만듭니다.

    예를 들어 리더가 죽었다면 다른 노드들이 합의하여 새 리더를 뽑을 수 있습니다.

    두 노드가 자신이 리더라고 생각하는 스플릿 브레인 상황에서도 올바르게 구현된 합의는 이 문제를 피하게 해 줍니다.

     

    일관성 보장

    복제 데이터베이스는 최종적 일관성을 제공하지만 특정 시간에는 서로 다른 데이터를 볼 수 있습니다.

    하지만 이는 일관성이 낮으며 더 강한 일관성을 보장하기 위하여 성능, 내결함성을 희생해야 합니다.

     

     

    선형성(최신성 보장)

    선형성의 기본 아이디어는 시스템에 데이터 복사본이 하나만 있으며, 그 데이터를 대상으로 수행하는 모든 연산은 원자적인 것처럼 보이게 만듭니다.

    만약 여러 복제본이 존재하더라도 애플리케이션은 신경 쓰지 않습니다.

     

    클라이언트가 쓰기를 성공적으로 완료하자마자 그 데이터베이스를 읽는 모든 클라이언트는 방금 쓰인 값을 볼 수 있어야 합니다.

    이는 읽는 값이 최근에 갱신된 값이며 뒤처진 캐시나 복제본에서 나온 값이 아님을 보장합니다.

     

    선형성은 어떻게 보장될까?

    읽기 쓰기 이외에 cas(compare and set) 연산을 활용합니다.

    새로운 값이 쓰이거나 읽힌 이후 실행되는 모든 읽기는 값이 다시 덮여 쓰일 때까지 쓰인 값을 읽게 됩니다.

     

    선형성이 유용한 환경

    스포츠 시합의 최종 점수 조회는 시시한 예입니다.

    결과가 몇 초 뒤처진다고 해도 실질적인 손해를 유발할 가능성은 거의 없습니다.

     

    단일 리더 복제를 사용하는 시스템에서 시스템은 리더가 여러 개가 아니라 진짜로 하나만 존재하도록 보장해야 하며 이때 잠금을 사용할 수 있습니다.

     

    분산 잠금과 리더 선출을 구현하기 위해 아파치 주키퍼나 etcd 같은 코디네이션 서비스가 종종 사용됩니다.

    이런 작업의 기반은 선형성 저장소 서비스입니다.

     

    선형성 시스템 구현하기

    데이터를 복사본을 진짜로 하나만 가지고 있으면 됩니다.

    하지만 해당 노드에 장애가 나면 데이터는 손실되거나 적어도 다시 접근할 수 없게 됩니다.

     

    가장 흔한 방법으로는 복제를 이용합니다.

    엄격한 정족수를 사용한 읽기 쓰기는 선형적인 것처럼 보이지만 네트워크 지연의 변동이 심하면 경쟁 조건이 생길 수 있습니다.

    성능을 포기한다면 선형적으로 만드는 게 가능하지만 추천하진 않습니다.

     

    만약 다중 데이터베이스 센터라면 다중 리더를 채택해야 선형적인 시스템을 구현할 수 있습니다.

    단일 리더의 경우 네트워크 단절이 발생하면 선형성이 사라져 버림(데이터가 과거에 머무르기 때문)

     

    선형성과 CAP 정리

    애플리케이션이 선형성을 요구한다면 일부 복제 서버는 연결이 끊긴 동안 요청을 처리할 수 없습니다.

    하지만 선형성을 요구하지 않는다면 연결이 끊기더라도 독립적으로 요청을 처리하는 방식으로 쓰기를 처리할 수 있습니다.

    따라서 선형성이 필요 없다면 네트워크 문제에 더 강인합니다.

     

    CAP는 때때로 일관성(Consistency), 가용성(Availability), 분단 내성(Partition tolerance) 세 개 중 두 개를 고르라는 것으로 표현됩니다.

    하지만 네트워크 분단은 선택할 수 있는 선택지가 아닙니다. (항상 발생)

     

    따라서 네트워크 단절이 발생하였을 때 일관성 또는 가용성 사이에서 선택해야 한다고 보는 것이 합리적입니다.

     

    선형성과 네트워크 지연

    선형성은 유용하지만 현실에서는 최신 다중 코어 CPU의 램조차 선형적이지 않습니다.(주로 성능 향상을 위해)

    많은 경우의 선형성이 필요한 것처럼 보이는 시스템에 사실 진짜로 필요한 것은 일관적 일관성이며 이는 더 효율적으로 구현될 수 있습니다.

     

    원자적 커밋과 2단계 커밋(2PC)

    트랜잭션의 결과는 커밋 성공이나 어보트입니다.

    이것은 다중 객체 트랜잭션과 보조 색인을 유지하는 데이터베이스에서 특히 중요합니다.

    데이터가 변경되면 보조 색인에도 반영되어야 하며 원자성은 데이터와 보조 색인이 일관성을 유지하도록 보장합니다.


    단일 노드에서 분산 원자적 커밋으로

    단일 데이터베이스 노드에서 실행되는 트랜잭션에게 원자성은 흔히 저장소 엔진에서 구현됩니다.

    클라이언트가 데이터베이스에게 커밋을 요청하면 데이터베이스는 트랜잭션의 쓰기가 지속성 있게 합니다.

    그 후 디스크에 있는 로그에 커밋 레코드를 추가합니다.

    이 과정에서 데이터베이스가 죽으면 트랜잭션은 노드가 재시작될 때 로그로부터 복구됩니다.

    죽기 전에 디스크에 쓰이는 데 성공했다면 트랜잭션은 성공된 것으로 간주됩니다.

    커밋을 원자적으로 만들어주는 것은 단일 장치입니다. (특정 디스크 드라이브의 컨트롤러)

     

    트랜잭션에 여러 노드가 관여한다면 어떻게 될까요?

    단일 노드와 같이 한다면 어떤 노드는 성공하고 어떤 노드는 실패할 수 있습니다.

    이렇게 되면 일관성이 사라집니다.

     

    2단계 커밋 소개

    여러 노드에 걸친 원자적 트랜잭션 커밋을 달성하도록 하는 알고리즘입니다.

    단일 노드 트랜잭션처럼 하나의 커밋 요청 대신 커밋/오보트 과정은 두 단계로 나뉩니다.

     

    이때 2PC(2단계 커밋)와 2PL(2단계 잠금)은 다릅니다.

    2PC는 코디네이터를 활용합니다.

    데이터베이스 노드를 트랜잭션의 참여자로 보고 모든 참여자가 준비되면 1단계 커밋이 완료된 후, 모든 참여자가 1단계 커밋이 완료된다면 2단계 커밋을 하는 순서입니다.

     

    이때 참여자 중 누구라도 어보트를 요청하면 모든 노드에 어보트 요청이 갑니다.

     

    사실 1단계 커밋과 특별한 차별점이 존재하지 않는 것처럼 보이지만 코디네이터가 존재하기 때문에 원자성을 보장합니다.

    코디네이터는 모든 참여자에게 커밋이나 어보트 요청을 전송하고, 이 요청이 실패하거나 타임 아웃되면 코디네이터는 영원히 재시도해야 합니다.

     

    이때 코디네이터가 죽을 수도 있고, 커밋이나 오보트 요청이 실패하면 코디네이터는 무한히 재시도합니다.

    이론상으로는 참여자들끼리 통신하여 각 참여자가 어떻게 투표했는지 알아낼 수 있지만 이것은 2PC 프로토콜의 일부는 아닙니다.

     

    2PC가 완료될 수 있는 유일한 방법은 코디네이터가 복구되어야 합니다.

    커밋/어보트 요청을 보내기 전 디스크에 있는 트랜잭션 로그에 자신의 커밋/어보트 내역을 기록해야 하는 이유입니다.

    이를 통해 의심스러운 트랜잭션들의 상태를 결정합니다.

     

    3PC라는 개념도 존재하지만 응답 시간과 지연에 제한이 존재한다는 가정하에 동작합니다.

    하지만 기약 없는 지연이 있는 네트워크들이 존재하기 때문에 2PC가 계속 사용됩니다.

     

    현실의 분산 트랜잭션

    현실에서는 성능을 떨어뜨린다고 비판받으며 여러 클라우드 서비스들은 분산 트랜잭션을 구현하지 않는 선택을 합니다.

    이를테면 MySQL의 분산 트랜잭션은 단일 노드 트랜잭션보다 10배 이상 느리다고 보고됩니다.

     

    보통 분산 트랜잭션은 두 가지로 나눕니다.

    1. 데이터베이스 내부 분산 트랜잭션

    2. 이종 분산 트랜잭션

     

    이종 분산 트랜잭션에서 메시지 큐에서 나온 메시지는 그 메시지를 처리하는 데이터베이스 트랜잭션이 커밋에 성공했을 때만 처리된 것으로 확인받을 수 있습니다.

     

    메시지 브로커와 데이터베이스가 서로 다른 장비에서 실행되더라도 이것이 가능해집니다.

    이를 통해 메시지가 결과적으로 정확히 한 번 처리되도록 보장할 수 있습니다.

     

    XA 트랜잭션

    이종 기술에 걸친 2단계 커밋을 구현하는 표준이며 여러 전통적인 RDB와 메시지 브로커에서 지원됩니다.

    XA는 인터페이스를 제공하는 CAPI일 뿐이다.

     

    의심스러운 상태를 지속해야 하는 이유 :  잠금

    트랜잭션이 의심스럽다면 그냥 나중에 처리하면 안 될까?

    트랜잭션은 보통 더티 쓰기를 막기 위해 독점적인 잠금을 처리하기 때문에 최대한 빠르게 처리되어야 해당 락을 풀 수 있습니다.

     

    만약 코디네이터가 재시작하는데 20분 걸린다면 이런 잠금은 20분 동안 유지됩니다.

     

    코디네이터 장애에서 복구하기

    만약 코디네이터의 트랜잭션 로그가 손실된 경우가 존재할 수 있습니다.

    이런 트랜잭션은 자동으로 해소될 수 없어 관리자가 수동으로 커밋/롤백을 결정해야 합니다.

     

    XA 구현에는 참여자가 코디네이터 확정적 결정을 얻지 않고 의심스러운 트랜잭션을 커밋/롤백할지 결정하는 경험적 결정이 존재합니다.

     

    정리

    일관성 모델인 선형성에 대해 깊이 알아보았습니다.

     

     

    댓글

Designed by Tistory.