Skip to content

Commit

Permalink
bin: clean up toydb binary
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed Jul 21, 2024
1 parent be44cf6 commit 703bcb2
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 74 deletions.
34 changes: 19 additions & 15 deletions config/toydb.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
# The node ID, peer ID/address map (empty for single node), and log level.
# The node ID (must be unique in the cluster), and map of peer IDs and Raft
# addresses (empty for single node).
id: 1
peers: {}
log_level: INFO

# Network addresses to bind the SQL and Raft servers to.
listen_sql: 0.0.0.0:9605
listen_raft: 0.0.0.0:9705
# Addresses to listen for SQL and Raft connections on.
listen_sql: localhost:9605
listen_raft: localhost:9705

# The log level. Valid values are DEBUG, INFO, WARN, and ERROR.
log_level: INFO

# Node data directory, and the garbage fraction threshold at which to trigger
# database compaction when opening the database (Bitcask only).
# Node data directory. The Raft log is stored in the file "raft", and the SQL
# database in "sql".
data_dir: data
compact_threshold: 0.2

# Raft log storage engine
# - bitcask (default): an append-only log-structured store.
# - memory: an in-memory store using the Rust standard library's BTreeMap.
# Storage engine to use for the Raft log and SQL database.
#
# * bitcask (default): an append-only log-structured store.
# * memory: an in-memory store using the Rust standard library's BTreeMap.
storage_raft: bitcask

# SQL key-value storage engine
# - bitcask (default): an append-only log-structured store.
# - memory: an in-memory store using the Rust standard library's BTreeMap.
storage_sql: bitcask

# The minimum garbage fraction and bytes to trigger Bitcask log compaction on
# node startup.
compact_threshold: 0.2
compact_min_bytes: 1000000
155 changes: 96 additions & 59 deletions src/bin/toydb.rs
Original file line number Diff line number Diff line change
@@ -1,97 +1,134 @@
/*
* toydb is the toyDB server. It takes configuration via a configuration file, command-line
* parameters, and environment variables, then starts up a toyDB TCP server that communicates with
* SQL clients (port 9605) and Raft peers (port 9705).
*/
//! The toyDB server. Takes configuration from a config file (default
//! config/toydb.yaml) or corresponding TOYDB_ environment variables. Listens
//! for SQL clients (default port 9605) and Raft connections from other toyDB
//! peers (default port 9705). The Raft log and SQL database are stored at
//! data/raft and data/sql by default.
//!
//! Use the toysql command-line client to connect to the server.
#![warn(clippy::all)]

use serde::Deserialize;
use std::collections::HashMap;
use toydb::errinput;
use toydb::error::Result;
use toydb::raft;
use toydb::sql;
use toydb::storage;
use toydb::Server;

const COMPACT_MIN_BYTES: u64 = 1024 * 1024;

fn main() -> Result<()> {
let args = clap::command!()
.arg(
clap::Arg::new("config")
.short('c')
.long("config")
.help("Configuration file path")
.default_value("config/toydb.yaml"),
)
.get_matches();
let cfg = Config::new(args.get_one::<String>("config").unwrap().as_ref())?;
use clap::Parser as _;
use serde::Deserialize;
use std::collections::HashMap;

let loglevel = cfg.log_level.parse::<simplelog::LevelFilter>()?;
let mut logconfig = simplelog::ConfigBuilder::new();
if loglevel != simplelog::LevelFilter::Debug {
logconfig.add_filter_allow_str("toydb");
fn main() {
if let Err(error) = Command::parse().run() {
eprintln!("Error: {error}")
}
simplelog::SimpleLogger::init(loglevel, logconfig.build())?;

let path = std::path::Path::new(&cfg.data_dir);
let raft_log = match cfg.storage_raft.as_str() {
"bitcask" | "" => raft::Log::new(Box::new(storage::BitCask::new_compact(
path.join("log"),
cfg.compact_threshold,
COMPACT_MIN_BYTES,
)?))?,
"memory" => raft::Log::new(Box::new(storage::Memory::new()))?,
name => return errinput!("invalid Raft storage engine {name}"),
};
let raft_state: Box<dyn raft::State> = match cfg.storage_sql.as_str() {
"bitcask" | "" => {
let engine = storage::BitCask::new_compact(
path.join("state"),
cfg.compact_threshold,
COMPACT_MIN_BYTES,
)?;
Box::new(sql::engine::Raft::new_state(engine)?)
}
"memory" => {
let engine = storage::Memory::new();
Box::new(sql::engine::Raft::new_state(engine)?)
}
name => return errinput!("invalid SQL storage engine {name}"),
};

Server::new(cfg.id, cfg.peers, raft_log, raft_state)?.serve(&cfg.listen_raft, &cfg.listen_sql)
}

/// The toyDB server configuration. Can be provided via config file (default
/// config/toydb.yaml) or TOYDB_ environment variables.
#[derive(Debug, Deserialize)]
struct Config {
/// The node ID. Must be unique in the cluster.
id: raft::NodeID,
/// The other nodes in the cluster, and their Raft TCP addresses.
peers: HashMap<raft::NodeID, String>,
listen_sql: String,
/// The Raft listen address.
listen_raft: String,
/// The SQL listen address.
listen_sql: String,
/// The log level.
log_level: String,
/// The path to this node's data directory. The Raft log is stored in
/// the file "raft", and the SQL state machine in "sql".
data_dir: String,
compact_threshold: f64,
/// The Raft storage engine: bitcask or memory.
storage_raft: String,
/// The SQL storage engine: bitcask or memory.
storage_sql: String,
/// The garbage fraction threshold at which to trigger compaction.
compact_threshold: f64,
/// The minimum bytes of garbage before triggering compaction.
compact_min_bytes: u64,
}

impl Config {
fn new(file: &str) -> Result<Self> {
/// Loads the configuration from the given file.
fn load(file: &str) -> Result<Self> {
Ok(config::Config::builder()
.set_default("id", "1")?
.set_default("listen_sql", "0.0.0.0:9605")?
.set_default("listen_raft", "0.0.0.0:9705")?
.set_default("listen_sql", "localhost:9605")?
.set_default("listen_raft", "localhost:9705")?
.set_default("log_level", "info")?
.set_default("data_dir", "data")?
.set_default("compact_threshold", 0.2)?
.set_default("storage_raft", "bitcask")?
.set_default("storage_sql", "bitcask")?
.set_default("compact_threshold", 0.2)?
.set_default("compact_min_bytes", 1_000_000)?
.add_source(config::File::with_name(file))
.add_source(config::Environment::with_prefix("TOYDB"))
.build()?
.try_deserialize()?)
}
}

/// The toyDB server command.
#[derive(clap::Parser)]
#[command(about = "Starts a toyDB server.", version, propagate_version = true)]
struct Command {
/// The configuration file path.
#[arg(short = 'c', long, default_value = "config/toydb.yaml")]
config: String,
}

impl Command {
/// Runs the toyDB server.
fn run(self) -> Result<()> {
// Load the configuration.
let cfg = Config::load(&self.config)?;

// Initialize logging.
let loglevel = cfg.log_level.parse()?;
let mut logconfig = simplelog::ConfigBuilder::new();
if loglevel != simplelog::LevelFilter::Debug {
logconfig.add_filter_allow_str("toydb");
}
simplelog::SimpleLogger::init(loglevel, logconfig.build())?;

// Initialize the Raft log storage engine.
let datadir = std::path::Path::new(&cfg.data_dir);
let raft_log = match cfg.storage_raft.as_str() {
"bitcask" | "" => {
let engine = storage::BitCask::new_compact(
datadir.join("raft"),
cfg.compact_threshold,
cfg.compact_min_bytes,
)?;
raft::Log::new(Box::new(engine))?
}
"memory" => raft::Log::new(Box::new(storage::Memory::new()))?,
name => return errinput!("invalid Raft storage engine {name}"),
};

// Initialize the SQL storage engine.
let raft_state: Box<dyn raft::State> = match cfg.storage_sql.as_str() {
"bitcask" | "" => {
let engine = storage::BitCask::new_compact(
datadir.join("sql"),
cfg.compact_threshold,
cfg.compact_min_bytes,
)?;
Box::new(sql::engine::Raft::new_state(engine)?)
}
"memory" => {
let engine = storage::Memory::new();
Box::new(sql::engine::Raft::new_state(engine)?)
}
name => return errinput!("invalid SQL storage engine {name}"),
};

// Start the server.
Server::new(cfg.id, cfg.peers, raft_log, raft_state)?
.serve(&cfg.listen_raft, &cfg.listen_sql)
}
}

0 comments on commit 703bcb2

Please sign in to comment.