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

add sst file snapshot impl #1618

Merged
merged 97 commits into from
Mar 27, 2017
Merged

add sst file snapshot impl #1618

merged 97 commits into from
Mar 27, 2017

Conversation

hhkbp2
Copy link
Contributor

@hhkbp2 hhkbp2 commented Feb 15, 2017

Hi,

This PR tries to add impl for sst file snapshot. It's split from PR #1561

Cargo.toml Outdated
@@ -62,6 +62,7 @@ signal = "0.2"

[dependencies.rocksdb]
git = "https://github.com/pingcap/rust-rocksdb.git"
branch = "hhkbp2/sst-file-snapshot"
Copy link
Contributor

Choose a reason for hiding this comment

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

can use master now

let mut buf = vec![0; DIGEST_BUFFER_SIZE];
loop {
let n = try!(f.read(&mut buf[..]));
if n == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

digest.write(&but[..n])
if n < buf.len() {
    return sum 
}

}
}

fn parse_cf_size_checksums(kvs: &[KeyValue]) -> RaftStoreResult<Vec<(String, u64, u32)>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer define a struct to contain the meta and use json to do the codec directly, no need to encode/decode manually.

Btw, we can also define a protobuf type too. But json is simple here.

try!(fs::create_dir_all(dir_path.as_path()));
}
let prefix = format!("{}_{}", SNAP_GEN_PREFIX, key);
let display_path = Snap::get_display_path(&dir_path, &prefix);
Copy link
Contributor

Choose a reason for hiding this comment

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

using a unique folder to save all CF SST files and meta may be better? like /dir/gen_{key}/

Copy link
Contributor

Choose a reason for hiding this comment

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

so we can rename, delete the folder directly later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's commonly seen that a single directory is used for raw data files, e.g. RocksDB use a single db dir to store sst files of all LSM levels.

if !need_to_pack(cf) {
continue;
}
try!(self.next_file(cf.to_owned()));
Copy link
Contributor

Choose a reason for hiding this comment

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

seem that it is not for next file, but to get the correct writer index with the CF.

if buf.len() == 0 {
return Ok(0);
}
while self.cf_index < self.cf_files.len() {
Copy link
Contributor

Choose a reason for hiding this comment

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

why not using enumerate directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to record the current index to self.cf_index.

let mut f = self.meta_file.file.as_mut().unwrap();
try!(f.write_all(&v[..]));
try!(f.flush());
try!(fs::rename(&self.meta_file.tmp_path, &self.meta_file.path));
Copy link
Contributor

Choose a reason for hiding this comment

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

the file is still opened, can it be renamed?

@siddontang
Copy link
Contributor

PTAL @BusyJay @zhangjinpeng1987

@hhkbp2 hhkbp2 changed the title [WIP] add sst file snapshot impl add sst file snapshot impl Feb 21, 2017
@hhkbp2
Copy link
Contributor Author

hhkbp2 commented Feb 22, 2017

meta.get_size(),
meta.get_checksum()));
}
cf_file.size = meta.get_size();
Copy link
Member

Choose a reason for hiding this comment

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

Is it a bug that meta exists but the cf file doesn't?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean exactly "is it a bug"?
The data corruption could cause this exception: the cf files are deleted/gone, but the meta file remains.

Copy link
Member

Choose a reason for hiding this comment

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

Then it should return an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The checking is added in load_snapshot_meta().

}
let meta = found.unwrap();
if file_exists(&cf_file.path) {
try!(check_file_size_and_checksum(&cf_file.path,
Copy link
Member

Choose a reason for hiding this comment

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

If it's about to generate the snapshot, when either file size or checksum doesn't match, the file should be deleted; otherwise should panic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

address comment. The panic should be raised properly by upper level code.

}
let file = try!(OpenOptions::new()
.write(true)
.create_new(true)
Copy link
Member

Choose a reason for hiding this comment

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

This may fail if meta file exists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The meta file is written at last. If the meta file exists, this snapshot exists(normally, if there is no data corruption). It checks exists() at the begin of this function. So if the meta file exists, the control flow will not get to this create_new().

Copy link
Member

Choose a reason for hiding this comment

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

What if the meta file exists but some cf files are missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then it must be through some kind of data corruption, then it return an error here.

// set snapshot meta data
let total_size = try!(self.total_size());
snap_data.set_file_size(total_size);
snap_data.set_version(SNAPSHOT_VERSION);
Copy link
Member

Choose a reason for hiding this comment

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

It seems v2 or v1 share the same SNAPSHOT_VERSION.

Copy link
Member

Choose a reason for hiding this comment

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

My mistake, they don't.

stat: &mut SnapshotStatistics)
-> RaftStoreResult<()> {
if self.exists() {
match self.validate() {
Copy link
Member

Choose a reason for hiding this comment

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

The file is checked twice when it exists already. First time is in init_for_building, second time is here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the point here? Are you suggesting merging two piece of code together?

Copy link
Member

Choose a reason for hiding this comment

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

What I mean is the file only needs to be checked once. Either checks can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They are two pieces of code which are called on initialization and building. And the exists() checks the existence for different purpose. It's doable to add some flag var like exists, but it's simpler to just call a simple function exists().


pub struct Snap {
display_path: String,
cf_files: Vec<CfFile>,
Copy link
Member

Choose a reason for hiding this comment

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

Seems using HashMap is more readable than a Vec and an index.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's good to use a simple vector and index combination. The vector specifies the cf sequence, the index could be set to upper bound to indicate that there is no more capacity to read/to write. And it's simple and fast.

return Err(io::Error::new(ErrorKind::Other, e));
}
}
let size = try!(get_file_size(&cf_file.tmp_path));
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible that the size doesn't match the actual file when the file handle is dropped without flush?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function only runs when a snapshot is built, so the cf file size is first initialized as snapshot meta. And the file is always being flushed when closed.
I don't get it by "the size doesn't match the actual file"? Could you elaborate on this?

cf_file.checksum = try!(calc_crc32(&cf_file.path));
} else {
// Clean up the `tmp_path` if this cf file is empty.
delete_file_if_exist(&cf_file.tmp_path);
Copy link
Member

Choose a reason for hiding this comment

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

The clean up job should be implemented in Drop trait in case there is any missing condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't know what you mean by the missing condition. Drop::drop takes care of the situation where the snapshot file is written partly on error.

Copy link
Member

Choose a reason for hiding this comment

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

I thought Drop was not implemented when I saw the temp file was deleted explicitly here.

fs::metadata(&self.meta_file.path)
}

fn total_size(&self) -> io::Result<u64> {
Copy link
Member

Choose a reason for hiding this comment

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

Why not include the size of meta file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

total_size returns the total size appended after the snapshot meta.

snap_data.set_meta(self.meta_file.meta.clone());

// add size
let mut size_track = self.size_track.wl();
Copy link
Member

Choose a reason for hiding this comment

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

The size should be updated once the snapshot file is saved, otherwise if there are several failure, there will be a big gap between the size_track and the actual snap directory size.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's updated after the snapshot file is saved, actually. It will not get to any error after the meta file is done saving.

Copy link
Member

Choose a reason for hiding this comment

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

use util::transport::SendCh;
use util::{HandyRwLock, HashMap};

use super::engine::Snapshot as DbSnapshot;
use super::peer_storage::JOB_STATUS_CANCELLING;

// Data in CF_RAFT should be excluded for a snapshot.
const SNAPSHOT_CFS: &'static [CfName] = &[CF_DEFAULT, CF_LOCK, CF_WRITE];
Copy link
Member

Choose a reason for hiding this comment

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

This can be used in all the place related to snapshot too, like region.rs.

@BusyJay
Copy link
Member

BusyJay commented Mar 24, 2017

If there are snapshot files that have no correspond meta file, they won't be deleted by gc.

snapshot_meta: SnapshotMeta,
size_track: Arc<RwLock<u64>>)
-> RaftStoreResult<Snap> {
let cfs = try!(check_snapshot_meta(&snapshot_meta));
Copy link
Member

Choose a reason for hiding this comment

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

The check is redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why it's redundant? This check_snapshot_meta plays its role to check the validity of the snapshot meta.

Copy link
Member

Choose a reason for hiding this comment

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

set_snapshot_meta should do the job properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

set_snapshot_meta checks whether the size and checksum is correct when snapshot file exists. check_snapshot_meta checks whether the received snapshot meta has valid cf name and number. They are different.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need two function to do the check. When setting snapshot meta, the size, checksum and cfs all should be as expected.

@hhkbp2
Copy link
Contributor Author

hhkbp2 commented Mar 25, 2017

If there are snapshot files that have no correspond meta file, they won't be deleted by gc.

It will be drop during gc procedure.
@BusyJay

@BusyJay
Copy link
Member

BusyJay commented Mar 25, 2017

@hhkbp2
Copy link
Contributor Author

hhkbp2 commented Mar 26, 2017

PTAL

}
}

fn get_snapshot_meta(cf_files: &[CfFile]) -> RaftStoreResult<SnapshotMeta> {
Copy link
Member

Choose a reason for hiding this comment

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

s/get_snapshot_meta/gen_snapshot_meta

@zhangjinpeng87
Copy link
Member

LGTM

@hhkbp2
Copy link
Contributor Author

hhkbp2 commented Mar 27, 2017

PTAL @BusyJay

// load snapshot meta if meta_file exists
if file_exists(&s.meta_file.path) {
if let Err(e) = s.load_snapshot_meta() {
if !to_build {
Copy link
Member

Choose a reason for hiding this comment

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

You can load the meta lazily instead of passing a to_build explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The meta is needed in the most common interface exists(). Snap is usually used as a short-time living object in the context. It's not shared or reused. It would not benefit to delay the meta loading, but just make the code complicated.

src/util/file.rs Outdated
}

pub fn delete_file_if_exist(file: &PathBuf) {
if !file_exists(file) {
Copy link
Member

Choose a reason for hiding this comment

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

The file can be removed directly and then omit the not found error. So there will be only once sys call.

try!(self.meta_file.meta.write_to_vec(&mut v));
try!(self.meta_file.file.take().unwrap().write_all(&v[..]));
try!(fs::rename(&self.meta_file.tmp_path, &self.meta_file.path));
let mut size_track = self.size_track.wl();
Copy link
Member

Choose a reason for hiding this comment

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

Should be updated at L1235.

Ok(snapshot_meta)
}

fn set_snapshot_meta(&mut self, snapshot_meta: SnapshotMeta) -> RaftStoreResult<()> {
Copy link
Member

Choose a reason for hiding this comment

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

I think it should be checked if the cfs in snapshot_meta is the same as SNAPSHOT_CFS.

@hhkbp2
Copy link
Contributor Author

hhkbp2 commented Mar 27, 2017

Ping @BusyJay

let found = snapshot_meta.get_cf_files().iter().find(|x| x.get_cf() == cf_file.cf);
if found.is_none() {
return Err(box_err!("cf {} doesn't exist in snapshot meta", cf_file.cf));

Copy link
Member

Choose a reason for hiding this comment

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

Seems L977~L981 can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad. Forget to clean up the old code.

Copy link
Member

@BusyJay BusyJay left a comment

Choose a reason for hiding this comment

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

rest LGTM

@BusyJay
Copy link
Member

BusyJay commented Mar 27, 2017

LGTM

@ngaut ngaut merged commit 9bbb312 into master Mar 27, 2017
@ngaut ngaut deleted the hhkbp2/sst-file-snapshot-v2 branch March 27, 2017 12:15
@sre-bot sre-bot added the contribution Type: PR - From contributors label Dec 18, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution Type: PR - From contributors
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants