728x90
- 복제
- 네트워크로 연결된 여러 장비에 데이터의 복사본을 유지한다는 의미
- 이게 필요한 이유는 여러개가 있는데
- 지연시간이 줄어듬(사용자와 가까운 곳에 데이터 유지)
- 시스템 고가용성(하나 죽어도 다른게 잘 됨)
- 읽기 처리량 늘림(여러곳에서 부하 분산)
- 근데 데이터를 어떻게 똑같이 유지할 것인가
- 그냥 저장만 하는거면 복제하면 된다.
- 그러면 변경해야 한다면?
- 단일리더, 다중리더, 리더없는 복제 3가지 알고리즘이 있다.
- 거의 대부분이 이 3가지중 하나를 쓴다.
- 복제는 많은 트레이드오프가 있다.
- 동기식/비동기식 복제 중 어떤것을 써야할지
- 잘못된 복제를 어떻게 처리할지
리더와 팔로워
- DB복사본을 저장하는 각 노드를 복제서버(replica)라고 한다.
- 이 복제 서버에 모든 데이터가 잘 있는지를 어떻게 보장할 수 있을까
- 리더 기반 복제(능동/수동, 마스터슬레이브 복제라고도 한다)
- 복제 서버중 하나를 리더로 지정
- 쓰기 요청을 하면 리더로 보내면 리더 저장소에 데이터 기록
- 다른 복제서버를 팔로워라고 하며, 리더가 로컬 저장소에 데이터를 기록할 때 마다 변경을 복제로그나 변경스트림의 일부로 팔로워에 전달
- 그래서 동일한 순서로 모든 쓰기 적용해서 로컬 복사본 갱신
- 리더 기반 복제(능동/수동, 마스터슬레이브 복제라고도 한다)
동기, 비동기 복제
- RDB에서는 동기/비동기 설정 가능, 다른 시스템은 하드코딩하는 경우가 많음
- 동기식 : 리더에게 데이터 변경 요청이 들어오면 팔로워 응답을 기다린다.
- 장점은 믿을 수 있지만 속도가 느린 단점
- 비동기식 : 데이터 변경 요청시 일단 변경하라고 하고 응답해줌
- 이건 빠르지만 요청 일부가 유실될 수 있음
- 반동기식 : 리더와 연결된 하나의 팔로워간은 동기, 나머지는 비동기로 적용
- 리더 죽어도 하나의 팔로워는 데이터가 동일하게 남아있지
- 보통 리더 기반 복제는 완전히 비동기식으로 구성한다.
- 즉, 리더가 잘못되고 복구 불가능하면 복제되지 않은 쓰기가 유실된다는것.
새로운 팔로워 설정
- 새로운 팔로워가 설정되면 데이터 복제본을 정확히 가지고 있는지 어떻게 보장할까?
- 일단은 하나의 노드에서 데이터 파일을 복사하는건 충분치 않음. 데이터가 유동적이기 때문에 표준 파일 복사본은 다른 시점에 데이터베이스의 다른 부분을 보게 된다.
- 방법은
- 리더의 데이터베이스 스냅숏을 일정 시점에 가져옴
- 스냅숏을 새로운 팔로워 노드에 복사
- 팔로워를 리더에 연결해 스냅숏 이후 발생한 모든 데이터 변경 요청. 이거는 스냅숏이 리더의 복제 로그의 정확한 위치와 연관돼야 한다.
- 그 이후 미처리분을 모두 따라잡으면 그 때부터 따라잡았다고 하고, 리더의 데이터 변화를 이어 처리 가능함.
- 참고로 이거 DB에 따라 완전 자동도 있고 수동으로 해야하는 경우도 있고 함
노드 중단 처리
- 노드는 장애가 아니어도 수동으로 중단될 수도 있다.
- 노드 중단시 고가용성 달성 방법은?
- 팔로워 장애 : 따라잡기 복구
- 각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관
- 사실 팔로워가 중단되면 쉽게 복구 가능하다.
- 보관된 로그에서 결함이 발생하기 전에 마지막으로 처리한 트랜잭션을 알아낸다.
- 그러면 팔로워는 리더에 연결해 그동안의 데이터 변경을 모두 요청가능
- 리더 장애 : 장애 복구
- 팔로워중 하나를 리더로 승격
- 클라이언트는 새로운 리더로 쓰기를 전송하기 위한 재설정 필요
- 다른 팔로워는 새로운 리더로부터 데이터 변경을 소비하기 시작해야한다.
- 이런 과정을 장애복구(failover)라고 한다.
- 리더가 장애인지 판단. -> 대부분은 타임아웃을 사용하여 일정 시간동안 노드가 응답하지 않으면 죽은 것으로 판단한다(계획된 유지보수를 위해 의도적으로 중단한다면 타임아웃을 적용하지 않음)
- 새로운 리더 선택 -> 선출과정(나머지 복제 서버 대다수에 의해 리더가 선택된다)을 통해 이뤄지거나 이전에 선출된 제어노드에 의해 새로운 리더 임명 가능. 가장 리더에 적합한 후보는 이전 리더의 최신 데이터 변경사항을 가진 복제서버임.
- 이 리더 사용을 위해 시스템 재설정. -> 클라이언트는 새로운 쓰기를 새 리더에게 보내야한다. 그리고 시스템은 이전의 리더가 복귀되면 얘한테 너는 이제 리더가 아니다~ 라고 해줘야한다.
- 이 장애복구 과정은 잘못될 여지가 많은데
- 비동기식 복제를 쓴다면 새로운 리더가 이전 리더 실패전의 쓰기를 일부 수신하지 못했을수도 있는데 새로운 리더 선출 후에 이전리더가 복구된다면 이 쓰기를 어떻게 할까?
- 그냥 이전 리더 데이터를 폐기한다면 내구성 이슈
- 그리고 또 한가지 DB가 예전꺼로 변경되면 PK Auto Increment를 하면 redis에 캐싱된 잘못된 것을 보낼수도 있다.
- 스플릿 브레인
- 두 노드가 자신이 리더라고 생각하는것. 굉장히 위험한 상항인데 데이터가 유실되거나 오염될 수 있다. 그래서 일부 시스템은 걍 하나를 종료해버리는 메커니즘이 있음
- 그냥 이전 리더 데이터를 폐기한다면 내구성 이슈
- 그리고 리더가 죽었다고 판단 가능한 타임아웃이 얼마일지?
- 사실 안죽었는데 죽었다고 판단해서 걍 바꿔버릴수도 있는것..
- 위의 이슈들 때문에 그냥 수동장애복구 선호하는것도 있음.
- 비동기식 복제를 쓴다면 새로운 리더가 이전 리더 실패전의 쓰기를 일부 수신하지 못했을수도 있는데 새로운 리더 선출 후에 이전리더가 복구된다면 이 쓰기를 어떻게 할까?
복제 로그 구현
- 리더 기반 복제 내부 동작에 관하여
- 구문 기반 복제
- 리더는 모든 쓰기요청(구문)을 기록하고 쓰기 실행 후 구문 로그를 팔로워에 전송
- RDB는 모든 CUD구문을 팔로워에 전달. -> 팔로워는 클라이언트에게 받은것처럼 SQL구문 파싱하고 실행.
- 이 방식은 복제가 깨질 수 있는데
- NOW()나 RAND()같은걸 쓰면 깨짐
- AI나 DB데이터에 의존한다면 정확히 같은 순서로 실행해야 하는데, 이러면 여러 트랜잭션 수행이 어려움
- 부수효과를 가진 구문(트리거, 스토어드 프로시저 등)은 부수 효과가 완벽하게 결정적이지 않으면 각 복제 서버에서 다른 부수효과가 발생할 수 있다.
- 위의 방법들은 대안법이 있기는 한데... 요즘은 구문에 비결정성이 있다면 기본적으로 로우 기반 복제를 사용한다.
- 쓰기 전 로그 배송
- 일반적으로 모든 쓰기는 로그에 기록된다.
- 로그 구조화 저장소 엔진의 경우 로그 자체가 저장소의 주요 부분이다.
- 개별 디스크 블록에 덮어쓰는 B트리는 모든 변경은 쓰기 전 로그에 쓰기 때문에 고장 이후 일관성 있는 상태로 색인 복원 가능
- 위의 두경우 모두 로그는 추가전용 바이트열이다.
- 그래서 완전히 동일한 로그를 사용해 다른 노드에서 복제 서버 구축이 가능하다.
- 리더는 디스크에 로그를 기록하는 것 외에 팔로워에게 네트워크로 로그를 전송하기도 한다.
- 이제 팔로워가 요 로그를 처리하면 리더랑 동일한 데이터 구조의 복제본이 만들어진다.
- 요거는 포스트그레나 오라클에서 많이 쓰이는데 단점은 로그가 제일 저수준의 데이터를 기술한다는것.
- 쓰기전로그같은 경우는 어떤 디스크 블록에서 어떤 바이트를 변경했는지같은 상세정보를 포함하는데, 이렇게 하면 복제가 저장소 엔진이랑 밀접하게 엮임.
- 즉 DB가 저장소 형식을 변경하면 리더-팔로워의 소프트웨어 버전을 다르게 실행할 수 없다.
- 팔로워들을 하나씩 업그레이드 가능하면 따로 리더 선정을 위한 중단시간이 필요없음(만들고 리더를 바꿔버리면 되니까) -> 그동안 잘 입력이 된다면 그런데 이게 안되는게 문제
- 즉 DB가 저장소 형식을 변경하면 리더-팔로워의 소프트웨어 버전을 다르게 실행할 수 없다.
- 쓰기전로그같은 경우는 어떤 디스크 블록에서 어떤 바이트를 변경했는지같은 상세정보를 포함하는데, 이렇게 하면 복제가 저장소 엔진이랑 밀접하게 엮임.
- 일반적으로 모든 쓰기는 로그에 기록된다.
- 논리적 로그 복제
- 복제 로그를 저장소 엔진 내부와 분리하기 위한 대안중 하나
- 족제와 저장소 엔진을 위해 다른 로그형식을 사용하는것
- 위에서의 로그 배송은 물리적으로 저장소 데이터를 전달하는데, 이와 구분시키기 위해서 이를 논리적 로그라고 부르는것
- RDB의 논리적 로그는 대개 로우 단위로 DB테이블에 쓰기 기술하는 레코드열이다.
- 삽입된 로우의 로그는 모든 컬럼의 새로운 값을 포함한다.
- 삭제된 로우의 로그는 로우를 고유하게 식별하는데 필요한 정보를 포함한다. 보통은 PK지만 테이블에 PK가 없다면 모든 컬럼의 예전값을 로깅해야한다.
- 갱신된 로우의 로그는 로우를 고유하게 식별하는데 필요한 정보와 모든 컬럼의 새로운 값(적어도 변경된 모든 컴럼의 새로운 값)을 포함한다.
- 얘네들은 결국 말하자면 로우단위로 이런걸 어떻게 할것이다 라는것을 표현한 내용
- 만약에 여러 로우를 변경하는 경우는 [트랜잭션] 하나씩 로그 레코드 생성 [트랜잭션커밋] 요렇게 하면 됨
- 이렇게하면 저장소 엔진 내부랑 논리적 로그를 분리했기 때문에 하위호환성을 더 쉽게 유지할 수 있고 리더-팔로워에서 다른 버전의 DB스프트웨어나 엔진 실행 가능하다.
- 외부 애플리케이션이 파싱하기 더 쉬운 장점도 있다.
- 변경 데이터 캡쳐
- 여기서 CDC가 나올줄은 몰랐다.. 충격
- 근데 생각해보니 진짜 논리적 로그처럼 어떤 곳이 어떻게 되었다~ 를 표현해야 다른 애플리케이션에서도 CDC가 가능할 것 같다.(매번 DB에 질의할수도 없고 물리적로그면 해석할수도 없고..)
- 트리거 기반 복제
- 지금까지의 내용은 DB시스템에 의해 구현된다(애플리케이션 코드 사용 없이)
- 조금 더 유연성이 필요한 상황이 있는데, 예를들어 서브셋만 복제하거나 다른 종류의 DB로 복제하거나 충돌해소로직이 필요한 경우 복제를 애플리케이션 층으로 옮겨야한다.
- 트리거나 스토어드 프로시저를 사용하는데, 사용자 정의 애플리케이션 코드를 등록해서 DB에서 쓰기 트랜잭션이 동작하면 자동으로 실행된다.
- 트리거는 데이터 변경을 분리된 테이블에 로깅할 수 있는 기회를 가진다.
- 이 테이블로부터 데이터 변경을 외부 프로세스가 읽어서 필요한 애플리케이션 로직을 적용해 데이터 변경 복제
- 트리거는 데이터 변경을 분리된 테이블에 로깅할 수 있는 기회를 가진다.
- 다른것보다 많은 오버헤드가 있고 버그나 제한사항이 많음
- 그럼에도 유연성 떄문에 매우 유용하다.
복제 지연 문제
- 복제는 내결함성뿐 아니라 확장성이나 지연시간 때문에도 쓰임.
- 대부분이 읽기요청이고 쓰기가 아주 작은 비율로 구성된다면 많은 팔로워를 만드는게 좋다.
- 근데 이러면 동기식으로 하면 시간이 아주 늘어나니 비동기로 한다.
- 문제는 팔로워가 뒤쳐진다면 데이터 불일치 발생 가능하다.
- 이거는 어쩔수 없기는 한데 결국 팔로워랑 리더의 데이터가 일치하고 이를 최종적 일관성이라 한다.
- 이런 최종적 일관성을 빨리 맞추기 위해서는 결국 지연시간을 줄여야한다.
- 이거는 어쩔수 없기는 한데 결국 팔로워랑 리더의 데이터가 일치하고 이를 최종적 일관성이라 한다.
지연시간이 있을 때 발생 가능한 사례와 해결방법
- 자신이 쓴 내용 읽기
- 보통 애플리케이션은 사용자가 뭘 하면 그 데이터를 확인할 수 있게 해준다(글쓰기 등)
- 유저가 글을 썼는데 자기 글이 잘 쓰여졌는지 못볼 가능성도 있을것이다.
- 쓰기 후 읽기 일관성이 필요하다.
- 사용자가 수정한 내용을 읽을 때에는 리더에서 읽도록 한다.
- 그 밖에는 팔로워에서 읽기.
- 이거는 실제로 질의하지 않고 무엇이 수정되었는지를 알 방법이 필요하다.
- 애플리케이션 내 대부분의 내용을 사용자가 편집할 수 있다면 비효율적이다.
- 갱신 시점 1분동안은 리더에서 읽기 수행
- 이러면 여러 사람이 수정해도 ㄱㅊ
- 클라이언트는 가장 최근 쓰기의 타임스탬프를 기억할 수 있다. 그러면 시스템은 사용자 읽기를 위한 복제 서버가 최소한 해당 타임스탬프까지 갱신을 반영하게 할 수 있다.(만약 아직 최신이 아니면 기다리게 하거나 다른 복제서버를 통해 제공)
- 복제 서버가 여러 데이터센터에 분산됐다면 복잡도가 증가한다. 리더가 제공해야 하는 모든 요청은 리더가 포함된 데이터센터로 라우팅돼야 한다.
- 동일 사용자가 여러 디바이스로 접근한다면?
- 디바이스 간 쓰기 후 읽기 일관성이 제공돼야한다.
- 클라에 기억해도 다른 디바이스면 알수 없으니까.
- 복제 서버가 여러 데이터센터에 분산돼있다면 동일 데이터센터로의 라우팅 보장이 되지 않음.
- 보통 애플리케이션은 사용자가 뭘 하면 그 데이터를 확인할 수 있게 해준다(글쓰기 등)
- 단조 읽기
- 사용자가 시간이 거꾸로 흐르는 현상을 목격할 수 있음.
- 사용자가 각기 다른 복제 서버에서 여러 읽기를 수행한다면?
- 단조읽기는 강한일관성보다는 덜하지만, 최종적일관성보다는 강한 보장이다.
- 데이터를 읽을 때에 이전 값을 볼 수 있는 것으로, 새로운 데이터를 읽은 후에는 예전 데이터를 읽지 않는것.
- 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 방법으로 적용
- 얘를들어 ID를 통한 해싱
- 근데 복제서버가 고장나면 바로 재라우팅 해야한다.
- 얘를들어 ID를 통한 해싱
- 각 사용자의 읽기가 항상 동일한 복제 서버에서 수행되게끔 하는 방법으로 적용
- 일관된 순서로 읽기
- A가 B보다 먼저 입력했는데, A의 내용이 팔로워에 더 늦게 저장되는 상황이라면?
- 비동기인데 이걸 동기식으로 동작하는척 설계해야한다.
- 이거는 후술, 트랜잭션과 관련있음.
다중 리더 복제
- 리더 기반에서 모든 쓰기는 리더를 거쳐야 하는데, 어떤 이유로 리더에 연결할 수 없다면 쓰기가 불가능하다.
- 리더 기반 복제는 쓰기 허용 노드를 하나 이상 두는 것으로 자연스럽게 확장
- 액티브/액티브 혹은 마스터마스터라고 부름
다중 리더 복제 사용사례
- 다중 데이터센터 운영
- 리더 여러개 두고 각 리더들의 쓰기 충돌 해소하고 하는거
- 성능 : 근데 이게 로컬 데이터센터에서 먼저 처리되고 해서 사용자 인지 성능은 나쁘지 않음
- 중단 내성 : "카"
- 네트워크 문제 내성 : 데이터센터간 트래픽은 공개 인터넷을 통해 처리돼서 안정감이 떨어진다. 단일 리더가 데이터센터 내 연결 쓰기가 동기식이라 연결 문제에 민감하다. 비동기를 쓰는 다중 리더 설정에서는 네트워크 문제에 더 잘 견딘다.
- 오프라인 작업을 하는 클라이언트
- 인터넨 연결이 끊어져도 애플리케이션이 동작해야 하는 경우
- 예를들어 오프라인에서 뭘 쓴다음에 인터넷 연결되면 저장되게 한다면 로컬 쓰기를 받는 DB가 있고 비동기로 동기화하는 프로세스인것
- 협업 편집
- 여러 사람이 실시간 협업편집하는경우
- 보통 DB복제 문제로 생각하지는 않는데 앞에서의 오프라인 편집이랑 공통점이 많다.
- 한명이 편집할 때 변경내용을 즉시 로컬복제에 적용하고 동일한 문서를 편집하는 다른 사용자와 서버에 비동기로 복제
- 이 때 편집 충돌이 없음을 보장하려면 그냥 한명이 편집동안 잠궈버리는거고, 아니면 변경 단위를 매우 작게해서 잠금을 피할수도 있다.
- 근데 변경단위가 작아도 혹시나 충돌 해소가 필요한 경우 등 문제가 발생할 수 있음
- 이 때 편집 충돌이 없음을 보장하려면 그냥 한명이 편집동안 잠궈버리는거고, 아니면 변경 단위를 매우 작게해서 잠금을 피할수도 있다.
- 한명이 편집할 때 변경내용을 즉시 로컬복제에 적용하고 동일한 문서를 편집하는 다른 사용자와 서버에 비동기로 복제
쓰기 충돌 다루기
- 결국 다중 리더 복제는 쓰기 충돌이 발생한다.
- 즉 충돌 해소가 필요하다는 것이다.
- 동기 대 비동기 충돌 감지
- 단일 리더 데이터베이스에서 첫 번째 쓰기가 완료될 때까지 두번째를 차단해 기다리거나 쓰기 트랜잭션을 중단해 재시도하게 한다.
- 다중의 경우는 둘다 성공하고, 충돌은 이후 특정 시점에 비동기로 감지한다.
- 이거는 이론상 동기식으로 만들수 있기는 한데, 그럴거면 단일 리더만 사용해야 할수도 있다.
- 충돌 회피
- 가장 간단한건 충돌을 피하는것.
- 특정 레코드의 모든 쓰기가 동일한 리더를 거치도록 하면 충돌은 발생하지 않는다.
- 자주 쓰이는 방식이기는 하지만(우리도 이런식으로 쓴다.) 한 데이터센터가 고장나서 다른 데이터센터로 옮기거나 사용자 지역이 달라지는 경우 리더 변경의 필요성이 있고 이럴때는 동시 기록 가능성을 고려해야한다.
- 일관된 상태 수렴
- 다중 리더는 쓰기 순서가 정해지지 않아 최종값이 무엇인지 명확하지 않다.
- 최종 쓰기 승리
- 각 쓰기에 고유아이디를 부여하고 높은 ID를 가진 쓰기를 고른다.
- 데이터 유실 위험이 있다.
- 각 복제 서버에 고유아이디를 부여하고 높은 숫자의 복제 서버에서 생긴 쓰기가 낮은 숫자의 복제 서버에서 생긴 쓰기보다 항상 우선적으로 적용되게(데이터 유실 가능성 있음)
- 어떻게든 값을 병합(예를들어 사전순 정렬 후 연결)
- 명시적 데이터 구조에 충돌을 기록해 모든 정보 보존. 나중에 충돌을 해소하는 애플리케이션 코드를 작성(git의 conflict랑 비슷하네?)
- 최종 쓰기 승리
- 다중 리더는 쓰기 순서가 정해지지 않아 최종값이 무엇인지 명확하지 않다.
사용자 정의 충돌 해소 로직
- 대부분의 다중 리더 복제도구는 애플리케이션 코드를 통해 충돌 해소 로직을 작성한다.
- 쓰기 수행 중
- 복제된 변경 로그에서 데이터베이스 시스템이 충돌을 감지하자마자 충돌 핸들러를 호출 -> 이거는 사용자에게 충돌 내용을 표시하지 않고 백그라운드에서 빠르게 실행되어야 한다.
- 읽기 수행 중
- 모든 충돌 쓰기를 저장. 다음번에 데이터를 읽을 때에 여러 버전의 데이터가 애플리케이션에 반환된다.
- 사용자에게 충돌 내용을 보여주거나 자동으로 해소 가능
- 쓰기 수행 중
- 이 충돌 해서는 보통 전체 트랜잭션이 아니라 개별로우나 문서 수준에서 적용된다.
다중 리더 복제 토폴로지
- 복제 토폴로지 : 쓰기를 한 노드에서 다른 노드로 전달하는 통신 경로
- 즉 리더가 두개라면 복제 토폴로지는 하나뿐이다. 둘 이상이면 다양한 토폴로지가 있음
- 다중 리더일 때에(2개 이상)
- 전체 연결
- 가장 일반적이다. 모든 리더가 각자의 쓰기를 다른 모든 리더에 전송하는것.
- 원형 토폴로지
- 각 노드는 하나의 노드로부터 쓰기를 받고, 이 쓰기와 자신의 쓰기를 다른 한 노드로 전달 -> 말하자면 옆으로 넘기기
- 별모양 토폴로지
- 루트노드가 다른 모든 노드에 쓰기 전달
- 전체 연결
- 원형, 별모양 토폴로지는 모든 복제서버에의 도달을 위해 여러 노드를 거쳐야 한다.
- 즉, 다른 노드로부터의 데이터 변경 사항도 전달해야 한다는것.
- 무한복제루프를 방지하기위해 각 노드의 식별자가 있고 이게 복제시에 태깅됨
- 자기의 식별자가 있다면 데이터 변경사항 무시(그게 있다는건 저 변경에 내가 한번은 관여한거니까)
- 이거의 문제는 하나의 노드에 장애가 나면 복제 흐름에 방해를 준다는것이다.(즉 그게 복구될때까지 통신 불가)
- 즉, 다른 노드로부터의 데이터 변경 사항도 전달해야 한다는것.
- 빽뺵한 연결의 토폴로지가 내결함성이 훨씬 좋다.
- 다만 문제는 일부 네트워크 연결이 다른 연결보다 빠르면 일부 복제 메시지가 다른 메시지를 추월할 수 있음
- 예를들어 A가 리더1을 바꾸고 B가 리더3을 바꿨을 때 리더2는 무엇을 더 빨리 받을까?
- 타임스탬프를 쓴다고 해도 얼마나 기다려야 제대로 적용됐는지 알기 너무 힘들기 때문
- 버전 벡터라는게 있고, 보통 많은 다중 리더 복제 시스템에서 충돌 감지 기법은 제대로 구현되지 않았다.
- 타임스탬프를 쓴다고 해도 얼마나 기다려야 제대로 적용됐는지 알기 너무 힘들기 때문
- 예를들어 A가 리더1을 바꾸고 B가 리더3을 바꿨을 때 리더2는 무엇을 더 빨리 받을까?
- 다만 문제는 일부 네트워크 연결이 다른 연결보다 빠르면 일부 복제 메시지가 다른 메시지를 추월할 수 있음
리더 없는 복제
- 그냥 모든 곳에서 쓰기를 직접 받을 수 있는 방식
- 다이나모 스타일이라고도 한다.
- 클라가 직접 여러 복제 서버에 쓰기를 전송할수도 있고 코디네이터 노드가 클라 역할을 해줄수도 있다.
- 이 코디네이터 노드는 특정 순서로 쓰기를 수행하지 않는다.
- 리더없는 복제는 노드가 하나 다운돼도 쓰기가 가능
- 그래서 다운된게 복구되면 outdated값이 응답으로 올것이다.
- 클라는 데이터베이스에서 읽을때 읽기 요청을 병렬로 여러 노드에 전송
- 그러면 여러 노드에서 받을 때 무엇이 최신인지 결정하면 된다.
- 클라는 데이터베이스에서 읽을때 읽기 요청을 병렬로 여러 노드에 전송
- 그러면 이제 다운된애가 어떻게 누락된 쓰기를 따라잡을까?
- 읽기 복구
- 여러 노드에서 읽어왔을 때 오래된 값이랑 최신 값을 아니까 오래된걸 최신으로 대체
- 안티 엔트로피 처리
- 백그라운드 프로세스가 지속적으로 데이터간 차이를 찾아 누락된걸 변경
- 근데 이거는 상당한 지연이 발생할 수 있음
- 읽기 복구
- 그래서 위에서 쓰기를 3개중 2개만 돼도 잘 성공한 것으로 간주한다고 했는데, 이거를 몇개가 기준인지는 어떻게 정할까?
- 일반적으로는
쓰기 노드 확정수 = 읽기의 최소 질의 갯수 = (전체 복제서버수+1)/2
로 설정한다.- 참고로 여기서 n은 홀수(보통 3이나 5)로 한다.
- 근데 이게 한계가 있는데
- 쓰기 노드랑 읽기 질의 갯수가 꼭 겹치지 않음 -> 즉 쓴곳과 다른 노드에서 읽어올수 있음
- 두 개의 쓰기가 동시에 발생하면 뭐가 먼저 일어났는지 모름
- 쓰기와 읽기가 동시에 발생하면 쓰기는 일부 복제 서버에만 반영될 수 있다. -> 읽기가 예전 값 또는 최신값을 반환하는지 여부가 분명하지 않음
- 쓰기가 어디서는 성공하고 어디서는 실패했을때 성공한 곳에서 롤백하지 않는다 -> 쓰기 실패로 보고되어도 읽기에 어떤 값이 반환될지 모름
- 새 값을 전달하는 노드가 고장나면 정족수 조건이 깨짐
- 등등...
- 복제 지연 문제(자신의 쓰기 읽기, 단조 읽기, 일관된 순서로 읽기) 보장을 받을 수 없다.
- 일반적으로는
- 최신성 모니터링
- 데이터베이스가 최신 결과를 반환하는지 여부
- 오래된 값 읽기를 허용한다더라도 복제 상태에 대해 알아야한다.
- 리더 없는 복제에서는 쓰기가 적용된 순서를 고정할 수 없어 모니터링이 조금 어렵다.
- 그리고 읽기 복구만 사용한다면(안티 엔트로피 없이) 언제 바뀐건지 제한도 없이 오래됐을수도 있고
- 느슨한 정족수
- 내트워크 장애때문에 클라이언트와 노드가 끊어져 있다면 클라 입장에서 이 노드는 죽은것이다.
- 그래서 이런 상황에서 적은 갯수의 DB노드와 연결되는 경우 정족수를 만족하지 못할 수 있는데 여기서
- 정족수 만족 못하면 오류 반환
- 일단 받아들이고 일단 연결 가능한 노드에 저장
- 에서 후자 방식을 쓰면 그걸 느슨한 정족수라고 한다.
- 다중 데이터센터 운영
- 리더없는 복제도 동시쓰기 충돌, 네트워크 중단, 지연시간 급증을 허용하기 때문에 다중 데이터센터 운영에 적합하다.
- 동시 쓰기 감지
- 얘는 여러 클라이언트가 동시에 같은 키에 쓰는것을 허용한다.
- 그래서 충돌이 발생한다. -> 다중 리더 복제랑 유사함
- 예를 들어 두 클라가 동시에 값을 바뀔 때 노드에 저장되는 속도가 다를텐데 무엇이 더 빨리 되었는지를 모르는 상황
- 복제본들은 동일한 값이 되어야 하는데, 이걸 어떻게 알 수 있을까?(노드마다 다르게 볼텐데)
- 최종 쓰기 승리
- 아까 나왔던거다.
- 결국 이거는 예전값을 버리고 최신으로 덮는건데... 뭐가 먼저인지는 모른다 사실
- 임의로 타임스탬프를 붙여서 큰거를 선택하는것이다. 그래서 최종쓰기승리(LWW)이다.
- 이거는 최종적 수렴은 하는데 지속성을 희생한다.
- 동일한 키에 여러번 동시 쓰기가 있다면 클라에게는 성공으로 보고돼도 하나 말고는 무시된다.
- 그리고 동시 쓰기가 아니라도 쓰기가 삭제될 수 있다는데 이거는 나중에...
- 손실 데이터를 허용하지 않으면 LWW는 충돌해소에 적합하지 않는데, 이걸 안전하게 사용하는 방법은 키를 한번만 쓰고 이후에는 불변값으로 다루는 것이다.
- 애초에 같은 키를 동시에 갱신하는 상황을 방지하는것
- 이전 발생 관계와 동시성
- 그럼 두가지 작업이 동시에 수행됐는지 여부를 어떻게 결정할까?
- B가 A에 대해 알거나, 어떻게든 A를 기반으로 하면 A는 B의 이전발생이다.
- 그래서 사실 둘이 뭐가 먼저 발생했는지 모르면 동시작업이라고 한다.
- 이 이전 관계 파악을 위해서는
- 이전 발생에서 어떤 작업이 다른 작업 이전에 발생했는지, 나중 작업이 이전에 수행된 작업을 알거나 의존했는지를 통해 알 수 있다
- 모든 키에 대한 버전 번호를 유지하고 기록시마다 버전번호 증가.
- 클라에서 키를 읽을 때에 서버는 최신뿐 아니라 덮어쓰지 않은 모든 값을 반환한다.
- 클라이언트가 키를 기록할 떄는 이전 읽기의 버전 번호가 포함되어야 하고 이전 읽기의 모든 값을 함꼐 합쳐야한다.
- 서버가 특정 버전 번호를 가진 쓰기를 받을 때 해당 버전 이하 모든 값을 덮어쓸 수 있다.
- 그럼 두가지 작업이 동시에 수행됐는지 여부를 어떻게 결정할까?
- 동시에 쓴 값 병합
- 이전 발생 관계의 경우 자동삭제를 시키지는 않지만 클라이언트 측에서 추가적인 작업을 수행해야한다.
- 결국 여러 작업이 동시발행하면 클라가 그 값을 합쳐 정리해서 충돌을 해소해야하는데 데이터 손실이 발생 가능
- 값을 병합하면서 삭제된 데이터는 툼스톤을 두면 보이지 않게 할 수 있다.
- 결국 여러 작업이 동시발행하면 클라가 그 값을 합쳐 정리해서 충돌을 해소해야하는데 데이터 손실이 발생 가능
- 이전 발생 관계의 경우 자동삭제를 시키지는 않지만 클라이언트 측에서 추가적인 작업을 수행해야한다.
- 버전 벡터
- 모든 복제본의 버전 번호 모음
- 그니까 키당 버전번호뿐 아니라 복제본당 버전 번호도 사용하는것이다.
- 이걸 통해 위의 동시에 쓴 값 병합에서 여러 리더없는 DB사용이 가능해짐.
- 최종 쓰기 승리
'이론 정리' 카테고리의 다른 글
데이터 중심 애플리케이션 설계 4장 정리 (1) | 2024.11.20 |
---|---|
데이터 중심 애플리케이션 설계 2장 정리 (0) | 2024.11.18 |
확장성과 성능에 관한 간단한 정리 (3) | 2024.11.10 |
RAG 이론 정리와 활용 (0) | 2024.08.11 |
AI 기본 이론정리 (0) | 2024.08.10 |