Raft 코드 뜯어보기 4)

Tasker_Jang·2024년 8월 17일
0

1. codec.rs

이 코드는 Heed 라이브러리와 관련된 데이터 인코딩 및 디코딩 기능을 구현하고 있습니다. Heed는 Rust에서 사용되는 고성능의 키-값 저장소 라이브러리로, 이 코드는 로그 엔트리(Entry)와 로그 엔트리 키(String)를 Heed에 저장하거나 불러올 때 사용할 수 있도록 바이트 배열로 변환하는 기능을 제공합니다.

주요 구성 요소

1. format_entry_key_string 함수

pub fn format_entry_key_string(entry_key: &str) -> String {
    let entry_key: u64 = entry_key.parse().unwrap();

    let mut result = String::new();
    write!(result, "{:0width$}", entry_key, width = ENTRY_KEY_LENGTH).unwrap();
    result
}
  • 역할: 이 함수는 로그 엔트리 키를 고정된 길이(ENTRY_KEY_LENGTH)의 문자열로 포맷팅합니다. 키 값을 u64로 파싱한 후, 지정된 너비만큼 0으로 채워진 문자열로 변환합니다.
  • 사용처: 이 함수는 로그 엔트리 키를 Heed에 저장할 때 일정한 길이를 유지하기 위해 사용됩니다. 일정한 길이의 키는 데이터 정렬 및 검색 성능을 높이는 데 유용합니다.

2. HeedEntryKeyString 구조체

#[derive(Eq, PartialEq)]
pub struct HeedEntryKeyString(String);
  • 역할: HeedEntryKeyString 구조체는 String 타입을 감싸며, Heed 데이터베이스에서 키로 사용될 문자열을 표현합니다.

  • BytesEncode 구현:

    impl<'a> BytesEncode<'a> for HeedEntryKeyString {
        type EItem = String;
    
        fn bytes_encode(item: &'a Self::EItem) -> std::result::Result<Cow<'a, [u8]>, BoxedError> {
            Ok(Cow::Owned(
                format_entry_key_string(item).as_bytes().to_vec(),
            ))
        }
    }
    • 역할: 이 구현은 HeedEntryKeyString을 바이트 배열로 인코딩합니다. format_entry_key_string 함수를 사용하여 문자열을 고정된 길이의 문자열로 변환한 후, 바이트 배열로 변환하여 반환합니다.
  • BytesDecode 구현:

    impl<'a> BytesDecode<'a> for HeedEntryKeyString {
        type DItem = String;
    
        fn bytes_decode(bytes: &'a [u8]) -> std::result::Result<Self::DItem, BoxedError> {
            Ok(String::from_utf8_lossy(bytes).into_owned())
        }
    }
    • 역할: 이 구현은 바이트 배열을 String으로 디코딩합니다. 바이트 배열을 UTF-8 문자열로 변환하고 이를 반환합니다.

3. HeedEntry 열거형

pub enum HeedEntry {}
  • 역할: HeedEntry는 로그 엔트리(Entry)를 인코딩 및 디코딩하기 위한 열거형입니다. 실제 데이터는 없고, 주로 타입 시스템에서 사용하기 위한 목적으로 존재합니다.

  • BytesEncode 구현:

    impl BytesEncode<'_> for HeedEntry {
        type EItem = Entry;
    
        fn bytes_encode(item: &Self::EItem) -> std::result::Result<Cow<'_, [u8]>, BoxedError> {
            let mut bytes = vec![];
            item.encode(&mut bytes)?;
            Ok(Cow::Owned(bytes))
        }
    }
    • 역할: 이 구현은 Entry를 바이트 배열로 인코딩합니다. prost::Message 트레이트의 encode 메서드를 사용하여 Entry를 바이트 벡터로 변환하고 반환합니다.
  • BytesDecode 구현:

    impl BytesDecode<'_> for HeedEntry {
        type DItem = Entry;
    
        fn bytes_decode(bytes: &[u8]) -> std::result::Result<Self::DItem, BoxedError> {
            Ok(Entry::decode(bytes)?)
        }
    }
    • 역할: 이 구현은 바이트 배열을 Entry로 디코딩합니다. prost::Message 트레이트의 decode 메서드를 사용하여 바이트 배열을 Entry 구조체로 복원합니다.

2. constant.rs

이 코드는 Raft 저장소에서 사용될 중요한 키와 상수들을 정의하고 있습니다. 이러한 상수들은 Raft 알고리즘에서 데이터를 저장하고 조회할 때 사용되며, 일관된 키를 사용함으로써 코드의 가독성을 높이고 실수를 방지하는 데 도움을 줍니다.

주요 상수들

  1. SNAPSHOT_KEY:

    • 정의: pub const SNAPSHOT_KEY: &str = "snapshot";
    • 역할: Raft의 스냅샷 데이터를 저장할 때 사용하는 키입니다.
    • 설명: Raft는 로그가 너무 길어지는 것을 방지하기 위해 주기적으로 스냅샷을 생성합니다. 이 스냅샷은 현재 상태 머신의 상태를 저장한 것입니다. SNAPSHOT_KEY는 이 스냅샷 데이터를 저장할 때 사용되는 키입니다.
  2. LAST_INDEX_KEY:

    • 정의: pub const LAST_INDEX_KEY: &str = "last_index";
    • 역할: 마지막으로 커밋된 로그 인덱스를 저장하는 키입니다.
    • 설명: Raft 알고리즘에서는 마지막으로 커밋된 로그 엔트리의 인덱스를 관리해야 합니다. 이 인덱스는 리더 선출이나 스냅샷 생성 시 중요한 역할을 합니다. LAST_INDEX_KEY는 이 인덱스를 저장하는 데 사용됩니다.
  3. HARD_STATE_KEY:

    • 정의: pub const HARD_STATE_KEY: &str = "hard_state";
    • 역할: Raft의 하드 스테이트(Hard State)를 저장하는 키입니다.
    • 설명: 하드 스테이트는 Raft 노드의 현재 임기(term), 투표(vote) 정보 및 커밋 인덱스(commit index)를 포함합니다. 이 상태는 노드가 재시작될 때 복구해야 하는 중요한 정보입니다. HARD_STATE_KEY는 이 데이터를 저장하는 데 사용됩니다.
  4. CONF_STATE_KEY:

    • 정의: pub const CONF_STATE_KEY: &str = "conf_state";
    • 역할: Raft의 구성 상태(Conf State)를 저장하는 키입니다.
    • 설명: 구성 상태는 현재 클러스터의 구성원(노드들)의 상태를 나타냅니다. 예를 들어, 현재 투표권을 가진 노드(voters)와 리더(leader)가 누구인지 등의 정보를 포함합니다. CONF_STATE_KEY는 이 데이터를 저장하는 데 사용됩니다.
  5. ENTRY_KEY_LENGTH:

    • 정의: pub const ENTRY_KEY_LENGTH: usize = 10;
    • 역할: 로그 엔트리 키의 길이를 정의하는 상수입니다.
    • 설명: 로그 엔트리 키는 고정된 길이의 문자열로 포맷팅되며, 이 상수는 그 길이를 정의합니다. 이를 통해 모든 로그 엔트리 키가 동일한 길이를 가지도록 보장하며, 이는 데이터 정렬과 검색에서 일관성을 유지하는 데 도움이 됩니다.

이 코드에서 정의된 상수들은 Raft 알고리즘의 중요한 부분을 구성합니다. 이러한 상수들은 데이터 저장소에서 일관된 키와 길이를 사용함으로써 코드의 가독성, 유지보수성, 그리고 오류 방지에 기여합니다. SNAPSHOT_KEY, LAST_INDEX_KEY, HARD_STATE_KEY, CONF_STATE_KEY와 같은 키는 각각의 데이터를 명확하게 식별하는 데 사용되며, ENTRY_KEY_LENGTH는 로그 엔트리 키의 일관된 길이를 보장합니다.

3. heed_storage 폴더의 mod.rs

이 코드는 Heed라는 데이터베이스 라이브러리를 사용하여 Raft 알고리즘의 상태 및 로그 엔트리를 저장하고 관리하는 기능을 구현하고 있습니다. HeedStorage 구조체는 StableStorage 트레이트를 구현하여 Raft 알고리즘에서 사용하는 스토리지로 활용되며, 로그 엔트리의 추가, 삭제, 조회 및 스냅샷 관리 등의 작업을 처리합니다.

주요 구성 요소

1. HeedStorage 구조체

  • HeedStorageHeedStorageCore라는 내부 구조체를 감싼 Arc<RwLock<HeedStorageCore>> 타입의 구조체입니다. 이 구조체는 Raft 알고리즘의 상태와 로그를 안전하게 관리할 수 있도록 동시성을 제어합니다.

  • 주요 메서드:

    • create: 새로운 HeedStorage 인스턴스를 생성합니다. log_dir_path, config, 그리고 logger를 인자로 받아 스토리지를 초기화합니다.
    • wl, rl: 내부 RwLock을 사용하여 HeedStorageCore에 대한 쓰기 및 읽기 접근을 제공합니다.

2. StableStorage 트레이트 구현

  • 이 트레이트는 Raft 알고리즘에서 스토리지를 사용하기 위해 필요한 다양한 메서드를 정의합니다. HeedStorage는 이 트레이트를 구현하여 로그 엔트리의 추가, 압축, 하드 스테이트 및 구성 상태 관리, 스냅샷 생성 및 복구 기능을 제공합니다.

  • 주요 메서드:

    • compact: 주어진 인덱스 이전의 로그 엔트리를 삭제하여 공간을 절약합니다.
    • append: 새로운 로그 엔트리를 추가합니다.
    • hard_state, set_hard_state: Raft의 하드 스테이트를 가져오거나 설정합니다.
    • conf_state, set_conf_state: Raft의 구성 상태를 가져오거나 설정합니다.
    • create_snapshot, apply_snapshot: 스냅샷을 생성하거나 스냅샷을 적용합니다.
    • all_entries: 저장된 모든 로그 엔트리를 반환합니다.

3. Storage 트레이트 구현

  • HeedStorageraft::Storage 트레이트도 구현하여 Raft 알고리즘의 상태 관리와 로그 접근을 위한 다양한 기능을 제공합니다.

  • 주요 메서드:

    • initial_state: 초기 Raft 상태를 반환합니다.
    • entries: 주어진 범위 내에서 로그 엔트리를 가져옵니다.
    • term: 주어진 인덱스의 로그 엔트리의 term 값을 반환합니다.
    • first_index, last_index: 첫 번째 및 마지막 로그 엔트리의 인덱스를 반환합니다.
    • snapshot: 요청된 인덱스의 스냅샷을 반환합니다.

4. HeedStorageCore 구조체

  • HeedStorageCore는 실제 데이터베이스(Env, Database) 인스턴스를 관리하며, HeedStorage의 대부분의 기능을 내부적으로 처리합니다.

  • 주요 메서드:

    • create: Heed 데이터베이스 환경 및 관련 데이터베이스를 초기화합니다.
    • compact: 로그 엔트리 압축을 수행합니다.
    • set_hard_state, hard_state: 하드 스테이트를 설정하거나 가져옵니다.
    • set_conf_state, conf_state: 구성 상태를 설정하거나 가져옵니다.
    • set_snapshot, snapshot: 스냅샷을 설정하거나 가져옵니다.
    • last_index, first_index: 마지막 및 첫 번째 로그 인덱스를 반환합니다.
    • entry, entries: 특정 인덱스의 로그 엔트리 또는 주어진 범위의 로그 엔트리를 반환합니다.
    • append: 로그 엔트리를 데이터베이스에 추가합니다.

테스트 코드

  • 다양한 시나리오에 대해 HeedStorage의 기능을 테스트합니다.
    • test_storage_term: 특정 인덱스의 로그 엔트리의 term 값을 정확히 반환하는지 테스트합니다.
    • test_storage_entries: 주어진 범위의 로그 엔트리를 올바르게 가져오는지 테스트합니다.
    • test_storage_last_index: 마지막 로그 인덱스를 정확히 반환하는지 테스트합니다.
    • test_storage_first_index: 첫 번째 로그 인덱스를 정확히 반환하는지 테스트합니다.
    • test_storage_compact: 로그 엔트리의 압축 기능을 테스트합니다.
    • test_storage_append: 로그 엔트리 추가 기능을 테스트합니다.
    • test_storage_apply_snapshot: 스냅샷 적용 기능을 테스트합니다.

HeedStorage는 Raft 알고리즘에서 로그와 상태를 효율적으로 관리하기 위해 설계된 스토리지 구현체로, 로그 엔트리와 스냅샷의 저장, 조회, 압축, 복구 등의 기능을 제공합니다. Heed 데이터베이스를 사용하여 높은 성능과 신뢰성을 보장하며, StableStorageStorage 트레이트를 구현하여 Raft의 핵심 기능을 지원합니다. 이 구현은 특히 로그 복제와 일관성 유지가 중요한 분산 시스템에서 유용하게 사용될 수 있습니다.

4. storage 폴더의 mod.rs

이 코드는 StableStorage라는 트레이트를 정의하고 있으며, 이는 Raft 알고리즘에서 사용되는 안정적인 스토리지 인터페이스를 제공하기 위한 것입니다. StableStorage 트레이트는 Storage 트레이트를 상속하며, 로그 엔트리와 상태(State)를 저장하고 관리하는 다양한 기능을 제공합니다.

주요 구성 요소

1. StableStorage 트레이트

  • 이 트레이트는 Storage 트레이트를 상속하며, Raft 알고리즘에서 로그와 상태를 안정적으로 저장하고 관리하기 위해 필요한 다양한 메서드를 정의합니다.

  • 주요 메서드:

    • append:

      • 설명: 새로운 로그 엔트리를 저장소에 추가합니다.
      • 매개변수:
        • entries: 저장할 Entry 객체들의 슬라이스.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • hard_state:

      • 설명: 저장된 하드 스테이트를 가져옵니다.
      • 반환값: Result<HardState> - 현재 저장된 하드 스테이트.
    • set_hard_state:

      • 설명: 새로운 하드 스테이트를 저장소에 저장합니다.
      • 매개변수:
        • hard_state: 저장할 HardState 객체.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • set_hard_state_commit:

      • 설명: 하드 스테이트의 커밋 인덱스를 설정합니다.
      • 매개변수:
        • commit: 새로운 커밋 인덱스.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • conf_state:

      • 설명: 저장된 구성 상태(Conf State)를 가져옵니다.
      • 반환값: Result<ConfState> - 현재 저장된 구성 상태.
    • set_conf_state:

      • 설명: 새로운 구성 상태를 저장소에 저장합니다.
      • 매개변수:
        • conf_state: 저장할 ConfState 객체.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • create_snapshot:

      • 설명: 스냅샷을 생성하여 저장소에 저장합니다.
      • 매개변수:
        • data: 스냅샷 데이터.
        • index: 스냅샷의 로그 인덱스.
        • term: 스냅샷의 로그 용어(term).
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • apply_snapshot:

      • 설명: 스냅샷을 저장소에 적용합니다.
      • 매개변수:
        • snapshot: 적용할 Snapshot 객체.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • compact:

      • 설명: 주어진 인덱스 이전의 로그를 압축하여 저장소에서 제거합니다.
      • 매개변수:
        • index: 압축을 수행할 인덱스.
      • 반환값: Result<()> - 성공 또는 실패 여부를 나타냅니다.
    • all_entries:

      • 설명: 저장소에 저장된 모든 로그 엔트리를 반환합니다.
      • 반환값: raft::Result<Vec<Entry>> - 모든 로그 엔트리의 벡터.

5. utils.rs

이 코드는 Raft 시스템에서 로그 데이터와 스토리지 관련 유틸리티 기능을 제공하는 모듈입니다. 주로 파일 시스템과 관련된 작업을 수행하며, 로그 데이터를 관리하는 데 필요한 다양한 함수를 포함하고 있습니다.

주요 함수 및 기능

1. 경로 관련 함수

  • get_storage_path(log_dir: &str, node_id: u64) -> String:

    • 설명: 주어진 로그 디렉토리와 노드 ID를 기반으로 특정 노드의 스토리지 경로를 생성합니다.
    • 반환값: 노드의 스토리지 경로를 문자열로 반환합니다.
    • 예시: get_storage_path("/var/logs", 1)"/var/logs/node-1"을 반환합니다.
  • get_data_mdb_path(log_dir: &str, node_id: u64) -> String:

    • 설명: 특정 노드의 데이터베이스 파일(data.mdb) 경로를 생성합니다.
    • 반환값: data.mdb 파일의 경로를 문자열로 반환합니다.
  • clear_storage_path(log_dir_path: &str) -> Result<()>:

    • 설명: 주어진 경로의 스토리지를 모두 삭제합니다.
    • 반환값: 성공 시 Ok(())를 반환하며, 오류 발생 시 오류 정보를 반환합니다.
  • ensure_directory_exist(dir_pth: &str) -> Result<()>:

    • 설명: 주어진 경로에 디렉토리가 존재하지 않으면, 디렉토리를 생성합니다.
    • 반환값: 성공 시 Ok(())를 반환하며, 오류 발생 시 오류 정보를 반환합니다.

2. 로그 데이터 처리

  • entry_type_to_str(entry_type: i32) -> &'static str:

    • 설명: 로그 엔트리의 타입을 문자열로 변환합니다.
    • 반환값: 엔트리 타입에 따른 문자열을 반환합니다.
    • 예시: entry_type_to_str(0)"EntryNormal"을 반환합니다.
  • append_compacted_logs(dest_path: &Path, new_data: &[Entry]) -> io::Result<()>:

    • 설명: 압축된 로그 데이터를 JSON 파일에 추가합니다. 만약 파일이 없으면 새로 생성하고, 기존 데이터가 있으면 이를 읽어와 새 데이터를 추가한 후 다시 저장합니다.

    • 매개변수:

      • dest_path: 로그 데이터를 저장할 경로.
      • new_data: 추가할 새로운 로그 엔트리 데이터.
    • 반환값: 성공 시 Ok(())를 반환하며, 오류 발생 시 io::Result 타입의 오류 정보를 반환합니다.

    • 구현 과정:

      1. 파일이 존재하지 않으면 생성.
      2. 기존 데이터를 파일에서 읽어옴.
      3. 새로운 로그 데이터를 JSON 형식으로 변환하여 기존 데이터에 추가.
      4. 파일의 내용을 초기화한 후 새로운 데이터를 저장.

예제 사용 시나리오

  • 로그 경로 설정: 시스템 초기화 시 각 노드의 스토리지 경로와 데이터베이스 파일 경로를 설정하고 관리할 수 있습니다.
  • 로그 데이터 저장: Raft 시스템에서 사용되는 로그 데이터를 JSON 파일로 저장하고 관리할 수 있습니다. 이때 로그 압축과 같은 작업이 필요할 때 이 모듈을 활용할 수 있습니다.
  • 스토리지 경로 초기화: 테스트나 장애 복구 시 특정 노드의 스토리지 데이터를 초기화하거나 삭제할 때 사용할 수 있습니다.
profile
터널을 지나고 있을 뿐, 길은 여전히 열려 있다.

0개의 댓글