이론 정리

데이터 중심 애플리케이션 설계 5장 정리

철매존 2024. 11. 22. 19:07
728x90
  • 복제
    • 네트워크로 연결된 여러 장비에 데이터의 복사본을 유지한다는 의미
    • 이게 필요한 이유는 여러개가 있는데
      • 지연시간이 줄어듬(사용자와 가까운 곳에 데이터 유지)
      • 시스템 고가용성(하나 죽어도 다른게 잘 됨)
      • 읽기 처리량 늘림(여러곳에서 부하 분산)
    • 근데 데이터를 어떻게 똑같이 유지할 것인가
      • 그냥 저장만 하는거면 복제하면 된다.
      • 그러면 변경해야 한다면?
        • 단일리더, 다중리더, 리더없는 복제 3가지 알고리즘이 있다.
        • 거의 대부분이 이 3가지중 하나를 쓴다.
    • 복제는 많은 트레이드오프가 있다.
      • 동기식/비동기식 복제 중 어떤것을 써야할지
      • 잘못된 복제를 어떻게 처리할지

리더와 팔로워

  • DB복사본을 저장하는 각 노드를 복제서버(replica)라고 한다.
  • 이 복제 서버에 모든 데이터가 잘 있는지를 어떻게 보장할 수 있을까
    • 리더 기반 복제(능동/수동, 마스터슬레이브 복제라고도 한다)
      • 복제 서버중 하나를 리더로 지정
      • 쓰기 요청을 하면 리더로 보내면 리더 저장소에 데이터 기록
      • 다른 복제서버를 팔로워라고 하며, 리더가 로컬 저장소에 데이터를 기록할 때 마다 변경을 복제로그나 변경스트림의 일부로 팔로워에 전달
      • 그래서 동일한 순서로 모든 쓰기 적용해서 로컬 복사본 갱신

동기, 비동기 복제

  • RDB에서는 동기/비동기 설정 가능, 다른 시스템은 하드코딩하는 경우가 많음
  • 동기식 : 리더에게 데이터 변경 요청이 들어오면 팔로워 응답을 기다린다.
    • 장점은 믿을 수 있지만 속도가 느린 단점
  • 비동기식 : 데이터 변경 요청시 일단 변경하라고 하고 응답해줌
    • 이건 빠르지만 요청 일부가 유실될 수 있음
  • 반동기식 : 리더와 연결된 하나의 팔로워간은 동기, 나머지는 비동기로 적용
    • 리더 죽어도 하나의 팔로워는 데이터가 동일하게 남아있지
  • 보통 리더 기반 복제는 완전히 비동기식으로 구성한다.
    • 즉, 리더가 잘못되고 복구 불가능하면 복제되지 않은 쓰기가 유실된다는것.

새로운 팔로워 설정

  • 새로운 팔로워가 설정되면 데이터 복제본을 정확히 가지고 있는지 어떻게 보장할까?
    • 일단은 하나의 노드에서 데이터 파일을 복사하는건 충분치 않음. 데이터가 유동적이기 때문에 표준 파일 복사본은 다른 시점에 데이터베이스의 다른 부분을 보게 된다.
  • 방법은
    • 리더의 데이터베이스 스냅숏을 일정 시점에 가져옴
    • 스냅숏을 새로운 팔로워 노드에 복사
    • 팔로워를 리더에 연결해 스냅숏 이후 발생한 모든 데이터 변경 요청. 이거는 스냅숏이 리더의 복제 로그의 정확한 위치와 연관돼야 한다.
    • 그 이후 미처리분을 모두 따라잡으면 그 때부터 따라잡았다고 하고, 리더의 데이터 변화를 이어 처리 가능함.
      • 참고로 이거 DB에 따라 완전 자동도 있고 수동으로 해야하는 경우도 있고 함

노드 중단 처리

  • 노드는 장애가 아니어도 수동으로 중단될 수도 있다.
  • 노드 중단시 고가용성 달성 방법은?
  • 팔로워 장애 : 따라잡기 복구
    • 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관
    • 사실 팔로워가 중단되면 쉽게 복구 가능하다.
    • 보관된 로그에서 결함이 발생하기 전에 마지막으로 처리한 트랜잭션을 알아낸다.
    • 그러면 팔로워는 리더에 연결해 그동안의 데이터 변경을 모두 요청가능
  • 리더 장애 : 장애 복구
    • 팔로워중 하나를 리더로 승격
    • 클라이언트는 새로운 리더로 쓰기를 전송하기 위한 재설정 필요
    • 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야한다.
    • 이런 과정을 장애복구(failover)라고 한다.
      1. 리더가 장애인지 판단. -> 대부분은 타임아웃을 사용하여 일정 시간동안 노드가 응답하지 않으면 죽은 것으로 판단한다(계획된 유지보수를 위해 의도적으로 중단한다면 타임아웃을 적용하지 않음)
      2. 새로운 리더 선택 -> 선출과정(나머지 복제 서버 대다수에 의해 리더가 선택된다)을 통해 이뤄지거나 이전에 선출된 제어노드에 의해 새로운 리더 임명 가능. 가장 리더에 적합한 후보는 이전 리더의 최신 데이터 변경사항을 가진 복제서버임.
      3. 이 리더 사용을 위해 시스템 재설정. -> 클라이언트는 새로운 쓰기를 새 리더에게 보내야한다. 그리고 시스템은 이전의 리더가 복귀되면 얘한테 너는 이제 리더가 아니다~ 라고 해줘야한다.
    • 이 장애복구 과정은 잘못될 여지가 많은데
      • 비동기식 복제를 쓴다면 새로운 리더가 이전 리더 실패전의 쓰기를 일부 수신하지 못했을수도 있는데 새로운 리더 선출 후에 이전리더가 복구된다면 이 쓰기를 어떻게 할까?
        • 그냥 이전 리더 데이터를 폐기한다면 내구성 이슈
          • 그리고 또 한가지 DB가 예전꺼로 변경되면 PK Auto Increment를 하면 redis에 캐싱된 잘못된 것을 보낼수도 있다.
        • 스플릿 브레인
          • 두 노드가 자신이 리더라고 생각하는것. 굉장히 위험한 상항인데 데이터가 유실되거나 오염될 수 있다. 그래서 일부 시스템은 걍 하나를 종료해버리는 메커니즘이 있음
      • 그리고 리더가 죽었다고 판단 가능한 타임아웃이 얼마일지?
        • 사실 안죽었는데 죽었다고 판단해서 걍 바꿔버릴수도 있는것..
      • 위의 이슈들 때문에 그냥 수동장애복구 선호하는것도 있음.

복제 로그 구현

  • 리더 기반 복제 내부 동작에 관하여
  • 구문 기반 복제
    • 리더는 모든 쓰기요청(구문)을 기록하고 쓰기 실행 후 구문 로그를 팔로워에 전송
    • RDB는 모든 CUD구문을 팔로워에 전달. -> 팔로워는 클라이언트에게 받은것처럼 SQL구문 파싱하고 실행.
    • 이 방식은 복제가 깨질 수 있는데
      • NOW()나 RAND()같은걸 쓰면 깨짐
      • AI나 DB데이터에 의존한다면 정확히 같은 순서로 실행해야 하는데, 이러면 여러 트랜잭션 수행이 어려움
      • 부수효과를 가진 구문(트리거, 스토어드 프로시저 등)은 부수 효과가 완벽하게 결정적이지 않으면 각 복제 서버에서 다른 부수효과가 발생할 수 있다.
    • 위의 방법들은 대안법이 있기는 한데... 요즘은 구문에 비결정성이 있다면 기본적으로 로우 기반 복제를 사용한다.
  • 쓰기 전 로그 배송
    • 일반적으로 모든 쓰기는 로그에 기록된다.
      • 로그 구조화 저장소 엔진의 경우 로그 자체가 저장소의 주요 부분이다.
      • 개별 디스크 블록에 덮어쓰는 B트리는 모든 변경은 쓰기 전 로그에 쓰기 때문에 고장 이후 일관성 있는 상태로 색인 복원 가능
    • 위의 두경우 모두 로그는 추가전용 바이트열이다.
      • 그래서 완전히 동일한 로그를 사용해 다른 노드에서 복제 서버 구축이 가능하다.
    • 리더는 디스크에 로그를 기록하는 것 외에 팔로워에게 네트워크로 로그를 전송하기도 한다.
      • 이제 팔로워가 요 로그를 처리하면 리더랑 동일한 데이터 구조의 복제본이 만들어진다.
    • 요거는 포스트그레나 오라클에서 많이 쓰이는데 단점은 로그가 제일 저수준의 데이터를 기술한다는것.
      • 쓰기전로그같은 경우는 어떤 디스크 블록에서 어떤 바이트를 변경했는지같은 상세정보를 포함하는데, 이렇게 하면 복제가 저장소 엔진이랑 밀접하게 엮임.
        • 즉 DB가 저장소 형식을 변경하면 리더-팔로워의 소프트웨어 버전을 다르게 실행할 수 없다.
          • 팔로워들을 하나씩 업그레이드 가능하면 따로 리더 선정을 위한 중단시간이 필요없음(만들고 리더를 바꿔버리면 되니까) -> 그동안 잘 입력이 된다면 그런데 이게 안되는게 문제
  • 논리적 로그 복제
    • 복제 로그를 저장소 엔진 내부와 분리하기 위한 대안중 하나
    • 족제와 저장소 엔진을 위해 다른 로그형식을 사용하는것
    • 위에서의 로그 배송은 물리적으로 저장소 데이터를 전달하는데, 이와 구분시키기 위해서 이를 논리적 로그라고 부르는것
    • RDB의 논리적 로그는 대개 로우 단위로 DB테이블에 쓰기 기술하는 레코드열이다.
      • 삽입된 로우의 로그는 모든 컬럼의 새로운 값을 포함한다.
      • 삭제된 로우의 로그는 로우를 고유하게 식별하는데 필요한 정보를 포함한다. 보통은 PK지만 테이블에 PK가 없다면 모든 컬럼의 예전값을 로깅해야한다.
      • 갱신된 로우의 로그는 로우를 고유하게 식별하는데 필요한 정보와 모든 컬럼의 새로운 값(적어도 변경된 모든 컴럼의 새로운 값)을 포함한다.
      • 얘네들은 결국 말하자면 로우단위로 이런걸 어떻게 할것이다 라는것을 표현한 내용
      • 만약에 여러 로우를 변경하는 경우는 [트랜잭션] 하나씩 로그 레코드 생성 [트랜잭션커밋] 요렇게 하면 됨
    • 이렇게하면 저장소 엔진 내부랑 논리적 로그를 분리했기 때문에 하위호환성을 더 쉽게 유지할 수 있고 리더-팔로워에서 다른 버전의 DB스프트웨어나 엔진 실행 가능하다.
    • 외부 애플리케이션이 파싱하기 더 쉬운 장점도 있다.
      • 변경 데이터 캡쳐
      • 여기서 CDC가 나올줄은 몰랐다.. 충격
        • 근데 생각해보니 진짜 논리적 로그처럼 어떤 곳이 어떻게 되었다~ 를 표현해야 다른 애플리케이션에서도 CDC가 가능할 것 같다.(매번 DB에 질의할수도 없고 물리적로그면 해석할수도 없고..)
  • 트리거 기반 복제
    • 지금까지의 내용은 DB시스템에 의해 구현된다(애플리케이션 코드 사용 없이)
    • 조금 더 유연성이 필요한 상황이 있는데, 예를들어 서브셋만 복제하거나 다른 종류의 DB로 복제하거나 충돌해소로직이 필요한 경우 복제를 애플리케이션 층으로 옮겨야한다.
    • 트리거나 스토어드 프로시저를 사용하는데, 사용자 정의 애플리케이션 코드를 등록해서 DB에서 쓰기 트랜잭션이 동작하면 자동으로 실행된다.
      • 트리거는 데이터 변경을 분리된 테이블에 로깅할 수 있는 기회를 가진다.
        • 이 테이블로부터 데이터 변경을 외부 프로세스가 읽어서 필요한 애플리케이션 로직을 적용해 데이터 변경 복제
    • 다른것보다 많은 오버헤드가 있고 버그나 제한사항이 많음
    • 그럼에도 유연성 떄문에 매우 유용하다.

복제 지연 문제

  • 복제는 내결함성뿐 아니라 확장성이나 지연시간 때문에도 쓰임.
  • 대부분이 읽기요청이고 쓰기가 아주 작은 비율로 구성된다면 많은 팔로워를 만드는게 좋다.
    • 근데 이러면 동기식으로 하면 시간이 아주 늘어나니 비동기로 한다.
  • 문제는 팔로워가 뒤쳐진다면 데이터 불일치 발생 가능하다.
    • 이거는 어쩔수 없기는 한데 결국 팔로워랑 리더의 데이터가 일치하고 이를 최종적 일관성이라 한다.
      • 이런 최종적 일관성을 빨리 맞추기 위해서는 결국 지연시간을 줄여야한다.

지연시간이 있을 때 발생 가능한 사례와 해결방법

  • 자신이 쓴 내용 읽기
    • 보통 애플리케이션은 사용자가 뭘 하면 그 데이터를 확인할 수 있게 해준다(글쓰기 등)
      • 유저가 글을 썼는데 자기 글이 잘 쓰여졌는지 못볼 가능성도 있을것이다.
    • 쓰기 후 읽기 일관성이 필요하다.
    • 사용자가 수정한 내용을 읽을 때에는 리더에서 읽도록 한다.
      • 그 밖에는 팔로워에서 읽기.
      • 이거는 실제로 질의하지 않고 무엇이 수정되었는지를 알 방법이 필요하다.
      • 애플리케이션 내 대부분의 내용을 사용자가 편집할 수 있다면 비효율적이다.
    • 갱신 시점 1분동안은 리더에서 읽기 수행
      • 이러면 여러 사람이 수정해도 ㄱㅊ
      • 클라이언트는 가장 최근 쓰기의 타임스탬프를 기억할 수 있다. 그러면 시스템은 사용자 읽기를 위한 복제 서버가 최소한 해당 타임스탬프까지 갱신을 반영하게 할 수 있다.(만약 아직 최신이 아니면 기다리게 하거나 다른 복제서버를 통해 제공)
        • 복제 서버가 여러 데이터센터에 분산됐다면 복잡도가 증가한다. 리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅돼야 한다.
        • 동일 사용자가 여러 디바이스로 접근한다면?
          • 디바이스 간 쓰기 후 읽기 일관성이 제공돼야한다.
          • 클라에 기억해도 다른 디바이스면 알수 없으니까.
          • 복제 서버가 여러 데이터센터에 분산돼있다면 동일 데이터센터로의 라우팅 보장이 되지 않음.
  • 단조 읽기
    • 사용자가 시간이 거꾸로 흐르는 현상을 목격할 수 있음.
    • 사용자가 각기 다른 복제 서버에서 여러 읽기를 수행한다면?
    • 단조읽기는 강한일관성보다는 덜하지만, 최종적일관성보다는 강한 보장이다.
    • 데이터를 읽을 때에 이전 값을 볼 수 있는 것으로, 새로운 데이터를 읽은 후에는 예전 데이터를 읽지 않는것.
      • 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 방법으로 적용
        • 얘를들어 ID를 통한 해싱
          • 근데 복제서버가 고장나면 바로 재라우팅 해야한다.
  • 일관된 순서로 읽기
    • A가 B보다 먼저 입력했는데, A의 내용이 팔로워에 더 늦게 저장되는 상황이라면?
    • 비동기인데 이걸 동기식으로 동작하는척 설계해야한다.
      • 이거는 후술, 트랜잭션과 관련있음.

다중 리더 복제

  • 리더 기반에서 모든 쓰기는 리더를 거쳐야 하는데, 어떤 이유로 리더에 연결할 수 없다면 쓰기가 불가능하다.
  • 리더 기반 복제는 쓰기 허용 노드를 하나 이상 두는 것으로 자연스럽게 확장
    • 액티브/액티브 혹은 마스터마스터라고 부름

다중 리더 복제 사용사례

  • 다중 데이터센터 운영
    • 리더 여러개 두고 각 리더들의 쓰기 충돌 해소하고 하는거
    • 성능 : 근데 이게 로컬 데이터센터에서 먼저 처리되고 해서 사용자 인지 성능은 나쁘지 않음
    • 중단 내성 : "카"
    • 네트워크 문제 내성 : 데이터센터간 트래픽은 공개 인터넷을 통해 처리돼서 안정감이 떨어진다. 단일 리더가 데이터센터 내 연결 쓰기가 동기식이라 연결 문제에 민감하다. 비동기를 쓰는 다중 리더 설정에서는 네트워크 문제에 더 잘 견딘다.
  • 오프라인 작업을 하는 클라이언트
    • 인터넨 연결이 끊어져도 애플리케이션이 동작해야 하는 경우
    • 예를들어 오프라인에서 뭘 쓴다음에 인터넷 연결되면 저장되게 한다면 로컬 쓰기를 받는 DB가 있고 비동기로 동기화하는 프로세스인것
  • 협업 편집
    • 여러 사람이 실시간 협업편집하는경우
    • 보통 DB복제 문제로 생각하지는 않는데 앞에서의 오프라인 편집이랑 공통점이 많다.
      • 한명이 편집할 때 변경내용을 즉시 로컬복제에 적용하고 동일한 문서를 편집하는 다른 사용자와 서버에 비동기로 복제
        • 이 때 편집 충돌이 없음을 보장하려면 그냥 한명이 편집동안 잠궈버리는거고, 아니면 변경 단위를 매우 작게해서 잠금을 피할수도 있다.
          • 근데 변경단위가 작아도 혹시나 충돌 해소가 필요한 경우 등 문제가 발생할 수 있음

쓰기 충돌 다루기

  • 결국 다중 리더 복제는 쓰기 충돌이 발생한다.
    • 즉 충돌 해소가 필요하다는 것이다.
  • 동기 대 비동기 충돌 감지
    • 단일 리더 데이터베이스에서 첫 번째 쓰기가 완료될 때까지 두번째를 차단해 기다리거나 쓰기 트랜잭션을 중단해 재시도하게 한다.
    • 다중의 경우는 둘다 성공하고, 충돌은 이후 특정 시점에 비동기로 감지한다.
    • 이거는 이론상 동기식으로 만들수 있기는 한데, 그럴거면 단일 리더만 사용해야 할수도 있다.
  • 충돌 회피
    • 가장 간단한건 충돌을 피하는것.
    • 특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 하면 충돌은 발생하지 않는다.
    • 자주 쓰이는 방식이기는 하지만(우리도 이런식으로 쓴다.) 한 데이터센터가 고장나서 다른 데이터센터로 옮기거나 사용자 지역이 달라지는 경우 리더 변경의 필요성이 있고 이럴때는 동시 기록 가능성을 고려해야한다.
  • 일관된 상태 수렴
    • 다중 리더는 쓰기 순서가 정해지지 않아 최종값이 무엇인지 명확하지 않다.
      • 최종 쓰기 승리
        • 각 쓰기에 고유아이디를 부여하고 높은 ID를 가진 쓰기를 고른다.
        • 데이터 유실 위험이 있다.
      • 각 복제 서버에 고유아이디를 부여하고 높은 숫자의 복제 서버에서 생긴 쓰기가 낮은 숫자의 복제 서버에서 생긴 쓰기보다 항상 우선적으로 적용되게(데이터 유실 가능성 있음)
      • 어떻게든 값을 병합(예를들어 사전순 정렬 후 연결)
      • 명시적 데이터 구조에 충돌을 기록해 모든 정보 보존. 나중에 충돌을 해소하는 애플리케이션 코드를 작성(git의 conflict랑 비슷하네?)

사용자 정의 충돌 해소 로직

  • 대부분의 다중 리더 복제도구는 애플리케이션 코드를 통해 충돌 해소 로직을 작성한다.
    • 쓰기 수행 중
      • 복제된 변경 로그에서 데이터베이스 시스템이 충돌을 감지하자마자 충돌 핸들러를 호출 -> 이거는 사용자에게 충돌 내용을 표시하지 않고 백그라운드에서 빠르게 실행되어야 한다.
    • 읽기 수행 중
      • 모든 충돌 쓰기를 저장. 다음번에 데이터를 읽을 때에 여러 버전의 데이터가 애플리케이션에 반환된다.
      • 사용자에게 충돌 내용을 보여주거나 자동으로 해소 가능
  • 이 충돌 해서는 보통 전체 트랜잭션이 아니라 개별로우나 문서 수준에서 적용된다.

다중 리더 복제 토폴로지

  • 복제 토폴로지 : 쓰기를 한 노드에서 다른 노드로 전달하는 통신 경로
    • 즉 리더가 두개라면 복제 토폴로지는 하나뿐이다. 둘 이상이면 다양한 토폴로지가 있음
  • 다중 리더일 때에(2개 이상)
    • 전체 연결
      • 가장 일반적이다. 모든 리더가 각자의 쓰기를 다른 모든 리더에 전송하는것.
    • 원형 토폴로지
      • 각 노드는 하나의 노드로부터 쓰기를 받고, 이 쓰기와 자신의 쓰기를 다른 한 노드로 전달 -> 말하자면 옆으로 넘기기
    • 별모양 토폴로지
      • 루트노드가 다른 모든 노드에 쓰기 전달
  • 원형, 별모양 토폴로지는 모든 복제서버에의 도달을 위해 여러 노드를 거쳐야 한다.
    • 즉, 다른 노드로부터의 데이터 변경 사항도 전달해야 한다는것.
      • 무한복제루프를 방지하기위해 각 노드의 식별자가 있고 이게 복제시에 태깅됨
      • 자기의 식별자가 있다면 데이터 변경사항 무시(그게 있다는건 저 변경에 내가 한번은 관여한거니까)
    • 이거의 문제는 하나의 노드에 장애가 나면 복제 흐름에 방해를 준다는것이다.(즉 그게 복구될때까지 통신 불가)
  • 빽뺵한 연결의 토폴로지가 내결함성이 훨씬 좋다.
    • 다만 문제는 일부 네트워크 연결이 다른 연결보다 빠르면 일부 복제 메시지가 다른 메시지를 추월할 수 있음
      • 예를들어 A가 리더1을 바꾸고 B가 리더3을 바꿨을 때 리더2는 무엇을 더 빨리 받을까?
        • 타임스탬프를 쓴다고 해도 얼마나 기다려야 제대로 적용됐는지 알기 너무 힘들기 때문
          • 버전 벡터라는게 있고, 보통 많은 다중 리더 복제 시스템에서 충돌 감지 기법은 제대로 구현되지 않았다.

리더 없는 복제

  • 그냥 모든 곳에서 쓰기를 직접 받을 수 있는 방식
  • 다이나모 스타일이라고도 한다.
  • 클라가 직접 여러 복제 서버에 쓰기를 전송할수도 있고 코디네이터 노드가 클라 역할을 해줄수도 있다.
    • 이 코디네이터 노드는 특정 순서로 쓰기를 수행하지 않는다.
  • 리더없는 복제는 노드가 하나 다운돼도 쓰기가 가능
  • 그래서 다운된게 복구되면 outdated값이 응답으로 올것이다.
    • 클라는 데이터베이스에서 읽을때 읽기 요청을 병렬로 여러 노드에 전송
      • 그러면 여러 노드에서 받을 때 무엇이 최신인지 결정하면 된다.
  • 그러면 이제 다운된애가 어떻게 누락된 쓰기를 따라잡을까?
    • 읽기 복구
      • 여러 노드에서 읽어왔을 때 오래된 값이랑 최신 값을 아니까 오래된걸 최신으로 대체
    • 안티 엔트로피 처리
      • 백그라운드 프로세스가 지속적으로 데이터간 차이를 찾아 누락된걸 변경
      • 근데 이거는 상당한 지연이 발생할 수 있음
  • 그래서 위에서 쓰기를 3개중 2개만 돼도 잘 성공한 것으로 간주한다고 했는데, 이거를 몇개가 기준인지는 어떻게 정할까?
    • 일반적으로는 쓰기 노드 확정수 = 읽기의 최소 질의 갯수 = (전체 복제서버수+1)/2 로 설정한다.
      • 참고로 여기서 n은 홀수(보통 3이나 5)로 한다.
    • 근데 이게 한계가 있는데
      • 쓰기 노드랑 읽기 질의 갯수가 꼭 겹치지 않음 -> 즉 쓴곳과 다른 노드에서 읽어올수 있음
      • 두 개의 쓰기가 동시에 발생하면 뭐가 먼저 일어났는지 모름
      • 쓰기와 읽기가 동시에 발생하면 쓰기는 일부 복제 서버에만 반영될 수 있다. -> 읽기가 예전 값 또는 최신값을 반환하는지 여부가 분명하지 않음
      • 쓰기가 어디서는 성공하고 어디서는 실패했을때 성공한 곳에서 롤백하지 않는다 -> 쓰기 실패로 보고되어도 읽기에 어떤 값이 반환될지 모름
      • 새 값을 전달하는 노드가 고장나면 정족수 조건이 깨짐
      • 등등...
    • 복제 지연 문제(자신의 쓰기 읽기, 단조 읽기, 일관된 순서로 읽기) 보장을 받을 수 없다.
  • 최신성 모니터링
    • 데이터베이스가 최신 결과를 반환하는지 여부
    • 오래된 값 읽기를 허용한다더라도 복제 상태에 대해 알아야한다.
    • 리더 없는 복제에서는 쓰기가 적용된 순서를 고정할 수 없어 모니터링이 조금 어렵다.
      • 그리고 읽기 복구만 사용한다면(안티 엔트로피 없이) 언제 바뀐건지 제한도 없이 오래됐을수도 있고
  • 느슨한 정족수
    • 내트워크 장애때문에 클라이언트와 노드가 끊어져 있다면 클라 입장에서 이 노드는 죽은것이다.
    • 그래서 이런 상황에서 적은 갯수의 DB노드와 연결되는 경우 정족수를 만족하지 못할 수 있는데 여기서
      • 정족수 만족 못하면 오류 반환
      • 일단 받아들이고 일단 연결 가능한 노드에 저장
    • 에서 후자 방식을 쓰면 그걸 느슨한 정족수라고 한다.
  • 다중 데이터센터 운영
    • 리더없는 복제도 동시쓰기 충돌, 네트워크 중단, 지연시간 급증을 허용하기 때문에 다중 데이터센터 운영에 적합하다.
  • 동시 쓰기 감지
    • 얘는 여러 클라이언트가 동시에 같은 키에 쓰는것을 허용한다.
    • 그래서 충돌이 발생한다. -> 다중 리더 복제랑 유사함
    • 예를 들어 두 클라가 동시에 값을 바뀔 때 노드에 저장되는 속도가 다를텐데 무엇이 더 빨리 되었는지를 모르는 상황
    • 복제본들은 동일한 값이 되어야 하는데, 이걸 어떻게 알 수 있을까?(노드마다 다르게 볼텐데)
      • 최종 쓰기 승리
        • 아까 나왔던거다.
        • 결국 이거는 예전값을 버리고 최신으로 덮는건데... 뭐가 먼저인지는 모른다 사실
        • 임의로 타임스탬프를 붙여서 큰거를 선택하는것이다. 그래서 최종쓰기승리(LWW)이다.
        • 이거는 최종적 수렴은 하는데 지속성을 희생한다.
          • 동일한 키에 여러번 동시 쓰기가 있다면 클라에게는 성공으로 보고돼도 하나 말고는 무시된다.
        • 그리고 동시 쓰기가 아니라도 쓰기가 삭제될 수 있다는데 이거는 나중에...
        • 손실 데이터를 허용하지 않으면 LWW는 충돌해소에 적합하지 않는데, 이걸 안전하게 사용하는 방법은 키를 한번만 쓰고 이후에는 불변값으로 다루는 것이다.
          • 애초에 같은 키를 동시에 갱신하는 상황을 방지하는것
      • 이전 발생 관계와 동시성
        • 그럼 두가지 작업이 동시에 수행됐는지 여부를 어떻게 결정할까?
          • B가 A에 대해 알거나, 어떻게든 A를 기반으로 하면 A는 B의 이전발생이다.
          • 그래서 사실 둘이 뭐가 먼저 발생했는지 모르면 동시작업이라고 한다.
          • 이 이전 관계 파악을 위해서는
            • 이전 발생에서 어떤 작업이 다른 작업 이전에 발생했는지, 나중 작업이 이전에 수행된 작업을 알거나 의존했는지를 통해 알 수 있다
            • 모든 키에 대한 버전 번호를 유지하고 기록시마다 버전번호 증가.
            • 클라에서 키를 읽을 때에 서버는 최신뿐 아니라 덮어쓰지 않은 모든 값을 반환한다.
            • 클라이언트가 키를 기록할 떄는 이전 읽기의 버전 번호가 포함되어야 하고 이전 읽기의 모든 값을 함꼐 합쳐야한다.
            • 서버가 특정 버전 번호를 가진 쓰기를 받을 때 해당 버전 이하 모든 값을 덮어쓸 수 있다.
      • 동시에 쓴 값 병합
        • 이전 발생 관계의 경우 자동삭제를 시키지는 않지만 클라이언트 측에서 추가적인 작업을 수행해야한다.
          • 결국 여러 작업이 동시발행하면 클라가 그 값을 합쳐 정리해서 충돌을 해소해야하는데 데이터 손실이 발생 가능
            • 값을 병합하면서 삭제된 데이터는 툼스톤을 두면 보이지 않게 할 수 있다.
      • 버전 벡터
        • 모든 복제본의 버전 번호 모음
        • 그니까 키당 버전번호뿐 아니라 복제본당 버전 번호도 사용하는것이다.
          • 이걸 통해 위의 동시에 쓴 값 병합에서 여러 리더없는 DB사용이 가능해짐.