쓰기 작업은 삽입, 업데이트 또는 삭제를 수행해야 하는 행 그룹을 의미합니다. Kudu는 기본 키의 고유성을 강제하며, 기본 키는 행을 식별할 수 있는 유일한 식별자입니다. 이 제약 조건을 강제하기 위해 Kudu는 삽입과 업데이트 작업을 다르게 처리해야 하며, 이는 Tablet 서버가 쓰기 작업을 처리하는 방식에 영향을 미칩니다.
Kudu의 각 Tablet은 사전 쓰기 로그(WAL)와 여러 행 집합(RowSet)으로 구성됩니다. 행 집합은 메모리와 디스크에 저장(플러시될 때)된 서로 분리된 행 집합입니다.
쓰기 작업은 먼저 Tablet의 사전 쓰기 로그(WAL)에 제출되고, Raft 합의 알고리즘을 통해 팔로워 노드의 동의를 얻은 후에야 Tablet의 메모리 중 하나에 추가됩니다.
삽입은 Tablet의 MemRowSet에 추가됩니다. MemRowSet에서 MVCC(다중 버전 동시성 제어)를 지원하기 위해 최근에 삽입된 행(즉, 아직 디스크에 플러시되지 않은 새 행)에 대한 업데이트 및 삭제 작업은 MemRowSet의 원래 행 뒤에 추가되어 REDO(재실행) 레코드 목록을 생성합니다. (읽기 작업은 스캐너가 제공한 타임스탬프에 따라 올바른 행 스냅샷을 구성하기 위해 관련 REDO 레코드를 적용해야 합니다.)
MemRowSet이 가득 차면 디스크로 플러시되어 DiskRowSet이 됩니다. 디스크에 저장된 데이터의 MVCC 기능을 지원하기 위해 DiskRowSet은 두 가지 다른 파일 유형으로 나뉩니다:
MemRowSet에 있는 행의 최신 버전(즉, 모든 REDO 레코드가 적용된 원래 삽입)은 기본 데이터 파일에 기록됩니다. 이는 Parquet과 유사한 컬럼 지향 파일 형식으로, 빠르고 효율적인 읽기 액세스를 제공하여 Kudu가 분석 액세스 패턴을 지원할 수 있게 합니다. MemRowSet에 있는 행의 이전 버전(즉, REDO 레코드의 역순)은 UNDO(취소) 레코드 집합으로 델타 파일에 기록됩니다. 시간 여행 읽기는 관련 UNDO 레코드를 적용하여 이전 시점에서 올바른 행 스냅샷을 구성할 수 있습니다.
이미 인코딩 및 압축된 컬럼 지향 형식의 데이터 파일을 업데이트하려면 전체 파일을 다시 작성해야 하므로, 기본 데이터 파일은 한 번 플러시되면 불변으로 간주됩니다.
또한 행 키 고유성 제약 조건으로 인해 기본 레코드에 대한 업데이트 및 삭제는 Tablet의 MemRowSet에 추가될 수 없으며, 대신 DeltaMemStore라는 별도의 메모리 저장소에 추가됩니다.
MemRowSet과 마찬가지로 모든 변경 사항은 REDO 레코드 집합으로 DeltaMemStore에 추가됩니다. DeltaMemStore가 가득 차면 REDO 레코드는 디스크에 저장된 델타 파일로 플러시됩니다. 각 DiskRowSet에는 별도의 DeltaMemStore가 존재합니다. 올바른 행 스냅샷을 구성하려면 읽기 작업이 관련 UNDO 또는 REDO 레코드를 적용하기 전에 먼저 행의 기본 레코드를 찾아야 합니다.
요약하면 다음과 같습니다:
Kudu에서 새 데이터 삽입 과정
- 1. 클라이언트가 TMaster에 연결하여 테이블 관련 정보(파티션 정보, 테이블의 모든 Tablet 정보)를 가져옵니다.
- 2. 클라이언트는 읽기/쓰기 요청을 처리할 Tablet을 담당하는 TServer를 찾습니다. Kudu는 클라이언트 요청을 수신하고 요청이 테이블 구조에 적합한지 확인합니다.
- 3. Kudu는 Tablet의 모든 RowSet(MemRowSet, DiskRowSet)에서 삽입할 데이터와 동일한 기본 키를 가진 데이터가 있는지 검색합니다. 존재하면 오류를 반환하고, 그렇지 않으면 계속 진행합니다.
- 4. 쓰기 작업은 먼저 Tablet의 WAL에 제출되고, Raft 합의 알고리즘을 통해 팔로워 노드의 동의를 얻은 후에야 Tablet 메모리에 추가됩니다. 삽입은 MemRowSet에 추가됩니다. MVCC를 지원하기 위해 최근에 삽입된 행(아직 디스크에 플러시되지 않은 새 행)에 대한 업데이트 및 삭제는 MemRowSet의 원래 행 뒤에 추가되어 REDO 레코드 목록을 생성합니다.
- 5. Kudu는 MemRowSet에 새 행을 작성합니다. MemRowSet(1GB 또는 120초)이 특정 크기(또는 시간)에 도달하면 데이터를 디스크로 플러시하여 DiskRowSet을 생성하여 데이터를 지속화하고, 새로운 MemRowSet을 생성하여 새 데이터 요청을 계속 수신합니다.