Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Add support for block log splitting #9184

Merged
merged 22 commits into from
Jun 23, 2020
Merged

Add support for block log splitting #9184

merged 22 commits into from
Jun 23, 2020

Conversation

huangminghuang
Copy link
Contributor

@huangminghuang huangminghuang commented Jun 4, 2020

Change Description

This PR support splitting block logs automatically based on block numbers. It also allows user to specify how many split block log files to retains. Once the limit is reached, the older block files can be move to another directory or deleted.

This PR also support recovering from the head block is not completely written to disk automatically which would eliminate most use cases for using eosio-blocklog to manually trim the end of block log file.

Change Type

Select ONE

  • Documentation
  • Stability bug fix
  • Other
  • Other - special case

Consensus Changes

  • Consensus Changes

API Changes

  • API Changes

Documentation Additions

  • Documentation Additions

Some new options for chain plugin are added:

  • blocks-log-stride: split the block log file when the head block number is the multiple of the stride. When the stride is reached, the current blog log and index will be renamed 'blocks-<start num>-<end num>.log/index' and a new current block log and index will be created with the most recent block. All files following this format will be used to construct an extended block log.
  • max-retained-block-files: the maximum number of blocks files to retain so that the blocks in those files can be queried. When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty. The retained block log files should not be manipulated by users.
  • blocks-archive-dir: the location of the blocks archive directory (absolute path or relative to blocks dir). If the value is empty, blocks files beyond the retained limit will be deleted. All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore.
  • fix-irreversible-blocks: When the existing block log is inconsistent with the index, allows fixing the log file automatically based on the index - that is, it will take the highest indexed block if it is valid; otherwise it will repair the block log and reconstruct the index.

Copy link
Contributor

@b1bart b1bart left a comment

Choose a reason for hiding this comment

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

Please note the additional template specializations for fc::datastream in the PR Description.

If there is documentation on the file format changes introduced by this split please link them as well. If not please talk to @nksanthosh about a task to document the blocks.log format in light of these (and any other changes)

unittests/restart_chain_tests.cpp Show resolved Hide resolved
plugins/chain_plugin/chain_plugin.cpp Outdated Show resolved Hide resolved

auto [itr, _] = collection.emplace(log.first_block_num(),
mapped_type{ log.last_block_num(), path_without_extension });
this->active_index = collection.index_of(itr);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only safe if the files are opened in-order, right? Otherwise the index of this entry may change as new entries are added.

Furthermore, it seems like the ordering from for_each_file_in_dir_matches is based on boost::filesystem::directory_iterator which does not guaranteed an order:

from: https://www.boost.org/doc/libs/1_70_0/libs/filesystem/doc/reference.html#Class-directory_iterator

The order of directory entries obtained by dereferencing successive increments of a directory_iterator is unspecified.

Even if it did so lexicographically, the filenames do not contain leading zeros, so you would expect blocks-101-110.log to come before blocks-21-30.log in the test cases below.

Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm not off base about this we should probably extend the test cases to cover situations where lexicographically ordered filenames are not properly ordered by block number.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

test case modified to cover it

Copy link
Contributor

Choose a reason for hiding this comment

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

The test cases include the new 3 digit names but, they do not close and re-open the block catalog and therefore wouldn't trigger the behavior I'm concerned with. Can we add a test that writes out the segmented blocks log (2 and 3 digit spans), then closes that controller and re-opens a new tester with the same block log directory and ensure that the expected blocks can be read AND have the proper block number after they are read?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

test case added in test_split_log_replay

Copy link
Contributor

Choose a reason for hiding this comment

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

@b1bart We cannot ensure we test this optimization directly, since we don't know the order the file system will decide to give it to us. The iterator will be invalidated by subsequent file additions if they are earlier, but then active_index will be reset and we will drop the invalid iterator without using it.

1. fix bug for command line options type inconsistency
2. rename block-split-factor to block-log-strides
3. modify test to prove that the lexicological order of block log filenames is irrelevant
@brianjohnson5972
Copy link
Contributor

@huangminghuang I think the documentation should indicate that retained block log files should not be manipulated by the user and that all files in the archive directory are completely in the users control

@huangminghuang
Copy link
Contributor Author

Please note the additional template specializations for fc::datastream in the PR Description.

the fc::datastream was copied from the PR 9104 which has been merge to develop.

If there is documentation on the file format changes introduced by this split please link them as well. If not please talk to @nksanthosh about a task to document the blocks.log format in light of these (and any other changes)

There is no block file format change for the PR. Only the additional options to control how to split the log files.

@brianjohnson5972
Copy link
Contributor

Please note the additional template specializations for fc::datastream in the PR Description.

the fc::datastream was copied from the PR 9104 which has been merge to develop.

If there is documentation on the file format changes introduced by this split please link them as well. If not please talk to @nksanthosh about a task to document the blocks.log format in light of these (and any other changes)

There is no block file format change for the PR. Only the additional options to control how to split the log files.

I would say that this is an extension to the block log format, in that now instead of it just being a single file representing a block log, we have daisy chained files, with an understanding of the ordering based on the file name. This does need to be added to the documentation, which will include things like: if the file name must match the range of blocks in the file, that a more recent range of blocks obfuscates the same blocks from a lower range. These are assumptions about functionality, I have not looked at code yet.


auto [itr, _] = collection.emplace(log.first_block_num(),
mapped_type{ log.last_block_num(), path_without_extension });
this->active_index = collection.index_of(itr);
Copy link
Contributor

Choose a reason for hiding this comment

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

The test cases include the new 3 digit names but, they do not close and re-open the block catalog and therefore wouldn't trigger the behavior I'm concerned with. Can we add a test that writes out the segmented blocks log (2 and 3 digit spans), then closes that controller and re-opens a new tester with the same block log directory and ensure that the expected blocks can be read AND have the proper block number after they are read?

Copy link
Contributor

@brianjohnson5972 brianjohnson5972 left a comment

Choose a reason for hiding this comment

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

Will this work with starting with a block log version == 1 (starting at block 1) and then will append to it? I think it will, but in looking through the code I see that we only care about the version if we are calling reset. I am wondering if we need a check for starting at block num 1 if version ==1 and stride is set. Would also be nice to have a test for older version, but obviously we are not setup for doing that.

if (!fc::is_directory(data_dir))
fc::create_directories(data_dir);
else
catalog.open(data_dir);
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we retro-actively adding multi-file support for all post-version 1 block log formats? (haven't thought if that is possible, just know that it definitely couldn't work if format requires first block to be block num 1) Even if we are, wouldn't we need a check here for version > 1, or will that happen later on in the logic?

Choose a reason for hiding this comment

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

Possibly unpopular thought: discontinue v1 block log format support in nodeos. Instead provide a utility to convert v1 format to v2 format which can be run manually prior to upgrading to this version of nodeos.

Otherwise need continue to maintain backwards support (forever)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

test case added to split log from v1 to the latest version; i.e. if the system starts with v1 block log, it will continue using the v1 log until it reaches the block number to split. After splitting, the newer block log would use the latest block log version regardless of the original block log version.

libraries/chain/block_log.cpp Show resolved Hide resolved
}

BOOST_AUTO_TEST_CASE(test_trim_blocklog_front_v1) {
block_log::set_version(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't need this anymore.

@brianjohnson5972
Copy link
Contributor

#9184 (comment)
I agree, but since @huangminghuang has done such a phenomenal job refactoring the block_log and adding unit tests, it is much easier to keep the support, than to go through the documentation to remove it. Also, this feature should allow any users maintaining a complete block log to use this feature to not have to keep it as one big file. We also need to know the logic works to be able to add the feature into block log to convert the file. Right now the best it would allow you to do would be to trim block 1, but would not be complete. Hopefully we will also add a feature to eosio-blocklog to split a block log on a given stride as well, so that users can maintain a complete block_log backup, without having to have it all residing on their active server.

Copy link
Contributor

@brianjohnson5972 brianjohnson5972 left a comment

Choose a reason for hiding this comment

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

I also mentioned 2 tests I would like to add in a text.

this->active_index = this->active_index == npos ? npos : this->active_index - items_to_erase;
}
this->collection.emplace(start_block_num, mapped_type{end_block_num, filename_base});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Documentation indicates that inserting here could invalidate the active_index (unless it is a stable_vector, not sure how we know). Also, do we know that the flat_map storage is fixed size per iterator, since the filename_base is not going to be guaranteed to be the same size? I think the safest thing here is to just set active_index = npos after an add. (another corner case on top of this is that we could have an archived file that has a higher start_block_num than this start_block_num, and if we have not had any call that has changed active_index, it could be pointing at that iterator and then it would be invalid at this point)

if (!index_matches_data(index_path, log))
block_log::construct_index(log_path, index_path);

auto [itr, _] = collection.emplace(log.first_block_num(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to check if iterator is not added and if it was not, then need to log a warning and set active_index to npos, since the cached information isn't really in the collection.


block_log::~block_log() {}

bool detail::block_log_impl::recover_from_incomplete_block_head(block_log_data& log_data, block_log_index& index) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@b1bart we need to discus this, it is a change to how nodeos functions with an invalid block log.

"split the block log file when the head block number is the multiple of the split factor")
("max-retained-block-files", bpo::value<uint16_t>()->default_value(config::default_max_retained_block_files),
"the maximum number of blocks files to retain so that the blocks in those files can be queried.\n"
"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty." )
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this match the documentation in the PR.

"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty." )
("blocks-archive-dir", bpo::value<bfs::path>()->default_value(config::default_blocks_archive_dir_name),
"the location of the blocks archive directory (absolute path or relative to blocks dir).\n"
"If the value is empty, blocks files beyond the retained limit will be deleted.")
Copy link
Contributor

Choose a reason for hiding this comment

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

Make this match the documentation in the PR.

@@ -228,6 +227,14 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip
cfg.add_options()
("blocks-dir", bpo::value<bfs::path>()->default_value("blocks"),
"the location of the blocks directory (absolute path or relative to application data dir)")
("blocks-log-stride", bpo::value<uint32_t>()->default_value(config::default_blocks_log_stride),
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should add here (and also in PR API documentation) something like "When the stride is breached, the current blog log and index will be renamed 'blocks--.log/index' and a new current block log and index will be created with the most recent block. All files following this format will be used to construct an extended block log."

1. add allow-block-log-auto-fix option
2. reimplement recover_from_incomplete_block_head() to work with v3 log
3. improve some chain plugin argument descriptions
@@ -185,6 +185,12 @@ namespace eosio { namespace chain {

using log_entry = std::variant<log_entry_v4, signed_block_v0>;

const block_header& get_block_header(const log_entry& entry) {
return std::visit(overloaded{ [](const signed_block_v0& v) -> const block_header& { return v; },
[](const log_entry_v4& v) -> const block_header& { return v.block; } },
Copy link
Contributor

Choose a reason for hiding this comment

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

Indentation here is a little wierd.

@@ -8,7 +8,7 @@ namespace eosio { namespace chain {
namespace detail { class block_log_impl; }

/* The block log is an external append only log of the blocks with a header. Blocks should only
* be written to the log after they irreverisble as the log is append only. The log is a doubly
* be written to the log after they irreversible as the log is append only. The log is a doubly
Copy link
Contributor

Choose a reason for hiding this comment

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

"after they are irreversible"

("max-retained-block-files", bpo::value<uint16_t>()->default_value(config::default_max_retained_block_files),
"the maximum number of blocks files to retain so that the blocks in those files can be queried.\n"
"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty." )
"When the number is reached, the oldest block file would be move to archive dir or deleted if the archive dir is empty.\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

"would be moved to archive dir"

@@ -402,6 +404,7 @@ BOOST_FIXTURE_TEST_CASE(restart_from_block_log_with_incomplete_head,restart_from
logfile.open("ab");
const char random_data[] = "12345678901231876983271649837";
logfile.write(random_data, sizeof(random_data));
allow_block_log_auto_fix = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm confused here, why is this being set at the very end of the test?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would think that we should have a test for all paths: truncate index don't set flag and verify block log and index are at the block that block log pointed to, same setup but with flag and verify block log points to block that index pointed to, set flag and have block index point to invalid position (possibly both to far and wrong place in file) and verify that it then matches the block log's block (and index is fixed).

@@ -228,13 +228,20 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip
("blocks-dir", bpo::value<bfs::path>()->default_value("blocks"),
"the location of the blocks directory (absolute path or relative to application data dir)")
("blocks-log-stride", bpo::value<uint32_t>()->default_value(config::default_blocks_log_stride),
"split the block log file when the head block number is the multiple of the split factor")
"split the block log file when the head block number is the multiple of the split factor\n"
"When the stride is reached, the current block log and index will be renamed 'blocks-num_begin-num_end.log/index'\n"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use 'block--.log/index' to make it clearer? (here and in the PR documentation)

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry, markup removed my text
block-<start num>-<end num>.log/index

"If the value is empty, blocks files beyond the retained limit will be deleted.\n"
"All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore.")
("allow-block-log-auto-fix", bpo::value<bool>()->default_value("false"),
"When the existing block log is inconsistent with the index, allows fixing the block log and index files")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should add that it will take the highest indexed block, if it is valid, otherwise it will repair the block log and reconstruct the index.

Copy link
Contributor

Choose a reason for hiding this comment

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

also, add the same to the PR description.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants