Rust 프로젝트에서 Raft 관련 코드 실행 중 Result::unwrap() 호출 시 Err로 인해 패닉이 발생했습니다. 오류는 아래와 같은 메시지를 나타냅니다.
백트레이스(Backtrace)는 프로그램이 패닉이나 예외 상황에서 중단되었을 때, 그 중단 시점까지의 함수 호출 경로를 추적해 보여주는 디버깅 정보입니다. 이를 통해 어디에서 문제가 발생했는지, 그리고 그 문제에 이르기까지 어떤 함수들이 호출되었는지 알 수 있습니다. Rust에서는 패닉이 발생할 때 자동으로 백트레이스를 생성해주며, 이를 이용해 코드를 디버깅할 수 있습니다.
Rust에서 백트레이스를 활성화하려면 환경 변수 RUST_BACKTRACE
를 설정한 후 실행해야 합니다.
짧은 백트레이스 출력 (간단한 호출 스택 정보만 표시):
RUST_BACKTRACE=1 ./target/debug/memstore-static-members.exe --raft-addr=127.0.0.1:60061 --web-server=127.0.0.1:8001
전체 백트레이스 출력 (호출된 모든 함수와 각 소스 코드 라인까지 표시):
RUST_BACKTRACE=full ./target/debug/memstore-static-members.exe --raft-addr=127.0.0.1:60061 --web-server=127.0.0.1:8001
백트레이스는 프로그램의 충돌 원인을 찾는 데 유용합니다. 호출 경로를 따라가며 어떤 함수에서 문제가 발생했는지, 구체적인 에러가 어디에서 유발되었는지를 알 수 있습니다. 백트레이스 출력이 활성화되면, 패닉이 발생한 지점뿐 아니라 그 문제를 발생시키는 함수 호출 체인을 확인할 수 있습니다.
백트레이스를 분석하여, 어디에서 unwrap()
이 호출되어 에러가 발생했는지, 그리고 그 전의 함수 호출 흐름을 파악하면 문제를 디버깅하는 데 큰 도움이 됩니다.
Windows 환경에서 RUST_BACKTRACE=1
환경 변수를 설정하는 방식이 Linux나 macOS와는 조금 다릅니다. PowerShell 또는 명령 프롬프트에서 환경 변수를 설정하고 프로그램을 실행하려면 다음과 같은 방식으로 해야 합니다.
PowerShell을 사용하는 경우에는 환경 변수를 다음과 같이 설정할 수 있습니다:
$env:RUST_BACKTRACE = 1
./target/debug/memstore-static-members.exe --raft-addr=127.0.0.1:60061 --web-server=127.0.0.1:8001
이 명령은 PowerShell에서 일시적으로 RUST_BACKTRACE
변수를 설정하고, 그 뒤에 프로그램을 실행합니다.
명령 프롬프트에서는 아래와 같은 방식으로 환경 변수를 설정할 수 있습니다:
set RUST_BACKTRACE=1
./target/debug/memstore-static-members.exe --raft-addr=127.0.0.1:60061 --web-server=127.0.0.1:8001
이 방법으로도 동일하게 백트레이스를 활성화한 상태에서 프로그램을 실행할 수 있습니다.
이 중 하나를 선택하여 시도해 보시면 백트레이스 정보와 함께 프로그램의 충돌 원인을 확인할 수 있습니다.
Error reading key from DB
debug_rocksdb_keys
함수를 통해 RocksDB에 저장된 키를 출력하여 잘못된 키가 있는지 확인해야 합니다. 또한, 로그 인덱스 생성 로직을 다시 검토해 인덱스가 연속적으로 생성되고 있는지 확인해야 합니다.Error decoding entry for key
prost::Message
를 사용해 엔트리를 디코딩하는데, 잘못된 포맷으로 저장되었거나, 디코딩을 시도한 값이 유효하지 않을 때 발생할 수 있습니다. 주로 데이터 타입 간의 불일치나, 인덱스 또는 포맷 오류로 인해 발생합니다.Error fetching snapshot
RocksDB는 키-값 저장소로 작동하며, Raft의 로그 항목을 저장하는 데 사용됩니다. 하지만 특정 로그 항목이 제대로 저장되지 않거나, 데이터베이스 내부 상태가 의심스러울 때, RocksDB 내부의 키와 값을 출력하여 현재 상태를 파악할 수 있습니다. 이를 위해 debug_rocksdb_keys
라는 디버깅 함수를 추가하여 RocksDB에 저장된 모든 키와 값을 출력하도록 하였습니다.
pub fn debug_rocksdb_keys(&self) {
let iter = self.db.iterator(rocksdb::IteratorMode::Start);
for item in iter {
match item {
Ok((key, value)) => {
println!("Key: {:?}, Value: {:?}", key, value);
}
Err(e) => {
println!("Error reading key from DB: {:?}", e);
}
}
}
}
Raft에서 로그 항목을 RocksDB로부터 조회하는 과정에서 디버깅을 위해 추가적인 println!
출력문을 사용하여 로그 항목의 상태를 실시간으로 확인할 수 있습니다. 이를 통해 어떤 단계에서 문제가 발생하는지 확인할 수 있습니다.
fn entries(
&self,
low: u64,
high: u64,
max_size: impl Into<Option<u64>>,
context: GetEntriesContext,
) -> crate::raft::Result<Vec<Entry>> {
let mut entries = Vec::new();
for i in low..high {
let key = format!("{:020}", i);
match self.db.get(key).unwrap() {
Some(value) => {
println!("Entry found for index {}: {:?}", i, value);
let entry = Entry::decode(&*value).unwrap();
entries.push(entry);
}
None => continue,
}
}
println!("Total entries retrieved: {:?}", entries.len());
Ok(entries)
}
first_index
와 last_index
함수는 RocksDB에서 첫 번째와 마지막 로그 항목을 조회하는 데 사용됩니다. 하지만 RocksDB에는 로그 인덱스 외에도 다양한 메타데이터(예: conf_state
, hard_state
, snapshot
)가 저장되기 때문에, 이 메타데이터를 로그 인덱스와 혼동하지 않도록 필터링이 필요합니다.
fn first_index(&self) -> crate::raft::Result<u64> {
let mut iter = self.db.iterator(rocksdb::IteratorMode::Start);
for item in iter {
match item {
Ok((key, _)) => {
if key.starts_with(b"last_") || key.starts_with(b"conf_") || key.starts_with(b"hard_") || key.starts_with(b"snapshot") {
continue;
}
if key.len() == 8 {
let idx = u64::from_be_bytes(key[..8].try_into().unwrap());
return Ok(idx);
}
}
Err(_) => return Err(crate::raft::Error::Store(crate::raft::StorageError::Unavailable)),
}
}
Ok(1) // No valid log entries found
}
스냅샷은 Raft에서 중요한 역할을 하며, 특정 시점의 상태를 저장하고 복구하는 데 사용됩니다. 하지만 스냅샷 조회 과정에서 스냅샷이 없거나, 읽기 오류가 발생하는 경우가 있을 수 있습니다. 이를 해결하기 위해 스냅샷 조회 시 오류 메시지를 추가하여 문제를 명확히 파악할 수 있도록 개선했습니다.
fn snapshot(&self, request_index: u64, to: u64) -> crate::raft::Result<Snapshot> {
match self.db.get("snapshot") {
Ok(Some(value)) => {
let snapshot = Snapshot::decode(&*value).unwrap();
Ok(snapshot)
}
Ok(None) => {
println!("Warning: No snapshot found. Returning default snapshot for initial state.");
Ok(Snapshot::default())
}
Err(e) => {
println!("Error fetching snapshot: {:?}", e);
Err(crate::raft::Error::Store(crate::raft::StorageError::SnapshotTemporarilyUnavailable))
}
}
}
RocksDB와 Raft를 연동하는 과정에서 발생할 수 있는 다양한 문제를 해결하기 위해, 디버깅용 출력 추가, 메타데이터 필터링, 그리고 오류 메시지 출력 강화와 같은 개선을 통해 문제를 보다 명확히 파악하고 해결할 수 있었습니다. 이러한 접근법들은 데이터 저장과 관리에서 발생하는 불확실성을 줄이고, 시스템의 안정성을 높이는 데 중요한 역할을 합니다.
Raft와 같은 분산 시스템 알고리즘을 RocksDB와 결합하여 사용하려면, 데이터베이스 상태를 명확하게 확인할 수 있는 디버깅 기능과 오류 처리가 매우 중요합니다. 이를 통해 복잡한 시스템에서도 신뢰성을 확보할 수 있습니다.