Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#159] WAL 로그 저장 포맷 선택 및 구현 #169

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ uuid = "1.1.2"
itertools = "0.10.5"
anyhow = "1.0.86"
mockall = "0.12.1"
bitcode = "0.6.3"

[target.'cfg(windows)'.dependencies]
winreg = "0.10.1"
Expand Down
6 changes: 6 additions & 0 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ pub const DEFAULT_CONFIG_FILENAME: &str = "rrdb.config";
// 기본 Data 디렉터리 이름
pub const DEFAULT_DATA_DIRNAME: &str = "data";

// 기본 WAL 디렉터리 이름
pub const DEFAULT_WAL_DIRNAME: &str = "wal";

// 기본 WAL 확장자
pub const DEFAULT_WAL_EXTENSION: &str = "log";

// 운영체제별 기본 저장 경로를 반환합니다.
#[cfg(target_os = "linux")]
pub const DEFAULT_CONFIG_BASEPATH: &str = "/var/lib/rrdb";
Expand Down
8 changes: 7 additions & 1 deletion src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod parsing_error;
pub mod predule;
pub mod server_error;
pub mod type_error;
pub mod wal_errors;

#[derive(Debug, PartialEq)]
pub enum RRDBError {
Expand All @@ -14,6 +15,7 @@ pub enum RRDBError {
ParsingError(parsing_error::ParsingError),
ServerError(server_error::ServerError),
TypeError(type_error::TypeError),
WALError(wal_errors::WALError),
}

impl ToString for RRDBError {
Expand All @@ -25,13 +27,14 @@ impl ToString for RRDBError {
RRDBError::ParsingError(e) => e.to_string(),
RRDBError::ServerError(e) => e.to_string(),
RRDBError::TypeError(e) => e.to_string(),
RRDBError::WALError(e) => e.to_string(),
}
}
}

#[cfg(test)]
mod tests {
use predule::{ExecuteError, IntoError, LexingError, ParsingError, ServerError, TypeError};
use predule::{ExecuteError, IntoError, LexingError, ParsingError, ServerError, TypeError, WALError};

use super::*;

Expand All @@ -54,5 +57,8 @@ mod tests {

let error = TypeError::wrap("test");
assert!(error.to_string().contains("test"));

let error = WALError::wrap("test");
assert!(error.to_string().contains("test"));
}
}
1 change: 1 addition & 0 deletions src/errors/predule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ pub use super::lexing_error::*;
pub use super::parsing_error::*;
pub use super::server_error::*;
pub use super::type_error::*;
pub use super::wal_errors::*;
55 changes: 55 additions & 0 deletions src/errors/wal_errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use super::RRDBError;

#[derive(Debug)]
pub struct WALError {
pub message: String,
pub backtrace: std::backtrace::Backtrace,
}

impl PartialEq for WALError {
fn eq(&self, other: &Self) -> bool {
self.message == other.message
}
}

impl WALError {
pub fn wrap<T: ToString>(message: T) -> RRDBError {
RRDBError::WALError(Self {
message: message.to_string(),
backtrace: std::backtrace::Backtrace::capture(),
})
}
}

impl std::error::Error for WALError {}

impl std::fmt::Display for WALError {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "wal error: {}", self.message)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_wal_error_eq() {
let error1 = WALError::wrap("test");
let error2 = WALError::wrap("test");
assert_eq!(error1, error2);
}

#[test]
fn test_wal_error_display() {
let error = WALError::wrap("test");

assert_eq!(error.to_string(), "wal error: test");
}

#[test]
fn test_wal_error_wrap() {
let error = WALError::wrap("test");
assert_eq!(error.to_string(), "wal error: test");
}
}
15 changes: 14 additions & 1 deletion src/executor/config/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::constants::{DEFAULT_CONFIG_BASEPATH, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIRNAME};
use crate::constants::{DEFAULT_CONFIG_BASEPATH, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIRNAME, DEFAULT_WAL_DIRNAME, DEFAULT_WAL_EXTENSION};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct GlobalConfig {
pub port: u32,
pub host: String,
pub data_directory: String,

pub wal_enabled: bool,
pub wal_directory: String,
pub wal_segment_size: u32,
pub wal_extension: String,
}

#[allow(clippy::derivable_impls)]
Expand All @@ -24,6 +29,14 @@ impl std::default::Default for GlobalConfig {
.to_str()
.unwrap()
.to_string(),
wal_enabled: true,
wal_directory: base_path
.join(DEFAULT_WAL_DIRNAME)
.to_str()
.unwrap()
.to_string(),
wal_segment_size: 1024 * 1024 * 16, // 16MB 세그먼트 사이즈
wal_extension: DEFAULT_WAL_EXTENSION.to_string(),
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/executor/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::errors::execute_error::ExecuteError;
use crate::errors::RRDBError;
use crate::executor::predule::ExecuteResult;
use crate::logger::predule::Logger;
use crate::wal::endec::BitcodeEncoder;
use crate::wal::manager::WALManager;

use super::config::global::GlobalConfig;
use super::mocking::{CommandRunner, FileSystem, RealCommandRunner, RealFileSystem};
Expand All @@ -28,10 +30,13 @@ impl Executor {
pub async fn process_query(
&self,
statement: SQLStatement,
wal_manager: Arc<WALManager<BitcodeEncoder>>,
_connection_id: String,
) -> Result<ExecuteResult, RRDBError> {
Logger::info(format!("AST echo: {:?}", statement));

// TODO: WAL 로깅 추가

// 쿼리 실행
let result = match statement {
SQLStatement::DDL(DDLStatement::CreateDatabaseQuery(query)) => {
Expand Down
102 changes: 92 additions & 10 deletions src/executor/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ impl Executor {
// 3. 데이터 디렉터리 생성 (없다면)
self.create_data_directory_if_not_exists().await?;

// 4. 데몬 설정파일 생성 (없다면)
// 4. WAL 디렉터리 생성 (없다면)
self.create_wal_directory_if_not_exists().await?;

// 5. 데몬 설정파일 생성 (없다면)
self.create_daemon_config_if_not_exists().await?;

// 5. 데몬 실행
// 6. 데몬 실행
self.start_daemon().await?;

Ok(())
Expand Down Expand Up @@ -96,6 +99,18 @@ impl Executor {
Ok(())
}

async fn create_wal_directory_if_not_exists(&self) -> Result<(), RRDBError> {
let wal_path = self.config.wal_directory.clone();

if let Err(error) = self.file_system.create_dir(&wal_path).await {
if error.kind() != std::io::ErrorKind::AlreadyExists {
return Err(ExecuteError::wrap(error.to_string()));
}
}

Ok(())
}

#[cfg(target_os = "linux")]
async fn create_daemon_config_if_not_exists(&self) -> Result<(), RRDBError> {
use crate::constants::SYSTEMD_DAEMON_SCRIPT;
Expand Down Expand Up @@ -188,16 +203,20 @@ mod tests {
async fn test_init_config() {
use mockall::predicate::eq;

use crate::executor::mocking::{
use crate::{constants::{DEFAULT_DATA_DIRNAME, DEFAULT_WAL_DIRNAME}, executor::mocking::{
CommandRunner, FileSystem, MockCommandRunner, MockFileSystem,
};
}};

use super::*;
use std::sync::Arc;

const CONFIG: &[u8] = br##"port = 22208
host = "0.0.0.0"
data_directory = "/var/lib/rrdb/data"
wal_enabled = true
wal_directory = "/var/lib/rrdb/wal"
wal_segment_size = 16777216
wal_extension = "log"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: 추후에는 yaml 이든, 자체 포맷 구현이든 포맷 하나 골라서 구조체 형태로 기본값 정의하기

"##;

use crate::constants::SYSTEMD_DAEMON_SCRIPT;
Expand Down Expand Up @@ -240,10 +259,16 @@ data_directory = "/var/lib/rrdb/data"
// 3. 데이터 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/data"))
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_DATA_DIRNAME))
.returning(|_| Ok(()));

// 4. WAL 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_WAL_DIRNAME))
.returning(|_| Ok(()));

// 4. 데몬 설정파일 생성
// 5. 데몬 설정파일 생성
mock.expect_write_file()
.times(1)
.with(
Expand Down Expand Up @@ -294,10 +319,15 @@ data_directory = "/var/lib/rrdb/data"

// 3. 데이터 디렉터리 생성
mock.expect_create_dir()
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/data"))
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_DATA_DIRNAME))
.returning(|_| Ok(()));

// 4. 데몬 설정파일 생성
// 4. WAL 디렉터리 생성
mock.expect_create_dir()
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_WAL_DIRNAME))
.returning(|_| Ok(()));

// 5. 데몬 설정파일 생성
mock.expect_write_file()
.with(
eq("/etc/systemd/system/rrdb.service"),
Expand All @@ -316,6 +346,52 @@ data_directory = "/var/lib/rrdb/data"
Arc::new(mock)
}),
},
TestCase {
name: "WAL 디렉터리 생성 실패",
want_error: true,
mock_config: Box::new(|| {
let config = GlobalConfig::default();

Arc::new(config)
}),
mock_file_system: Box::new(move || {
let mut mock = MockFileSystem::new();

// 1. 최상위 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH))
.returning(|_| Ok(()));

// 2. 전역 설정파일 생성
mock.expect_write_file()
.times(1)
.with(
eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_CONFIG_FILENAME),
eq(CONFIG),
)
.returning(|_, _| Ok(()));

// 3. 데이터 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_DATA_DIRNAME))
.returning(|_| Ok(()));

// 4. WAL 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_WAL_DIRNAME))
.returning(|_| Err(Error::from_raw_os_error(1)));

Arc::new(mock)
}),
mock_command_runner: Box::new(|| {
let mock = MockCommandRunner::new();

Arc::new(mock)
}),
},
TestCase {
name: "데몬 설정파일 생성 실패",
want_error: true,
Expand Down Expand Up @@ -348,7 +424,13 @@ data_directory = "/var/lib/rrdb/data"
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/data"))
.returning(|_| Ok(()));

// 4. 데몬 설정파일 생성
// 4. WAL 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_WAL_DIRNAME))
.returning(|_| Ok(()));

// 5. 데몬 설정파일 생성
mock.expect_write_file()
.times(1)
.with(
Expand Down Expand Up @@ -397,7 +479,7 @@ data_directory = "/var/lib/rrdb/data"
// 3. 데이터 디렉터리 생성
mock.expect_create_dir()
.times(1)
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/data"))
.with(eq(DEFAULT_CONFIG_BASEPATH.to_owned() + "/" + DEFAULT_DATA_DIRNAME))
.returning(|_| Err(Error::from_raw_os_error(1)));

Arc::new(mock)
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod parser;
pub mod pgwire;
pub mod server;
pub mod utils;
pub mod wal;

use std::sync::Arc;

Expand Down
Loading
Loading