-
Notifications
You must be signed in to change notification settings - Fork 3.6k
DB intrinsic implementation in RocksDB #9529
DB intrinsic implementation in RocksDB #9529
Conversation
…thod outside the class definition.
… obvious it is the chainbase implementation till it can be refactored out into db_context_chainbase.
… rocksdb_db_implementation-updated
int32_t db_idx_double_end(uint64_t code, uint64_t scope, uint64_t table) override { | ||
return -1; | ||
} | ||
const int64_t billable_size = (int64_t)(value_size + db_key_value_any_lookup::overhead); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two questions for my own information:
- Do we want to use auto in cases like this?
- Are C style or C++ style casts preferred? (static_cast probably in this case)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1)I think the only advantage of using auto versus the type is if you are saving yourself typing, and going back and replacing would be more typing. (i.e. I think auto is just for a developers convenience, it doesn't make it more maintainable)
2) yes
|
||
// copy locally, since below the key_store memory will be changed | ||
const auto old_payer = key_store.payer; | ||
if (payer == name{}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about payer.empty()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
} | ||
|
||
int32_t db_context_rocksdb::db_next_i64(int32_t itr, uint64_t& primary) { | ||
if (itr < primary_iter_store.invalid_iterator()) return primary_iter_store.invalid_iterator(); // cannot increment past end iterator of table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to make the logic here itr == primary_iter_store.invalid_iterator()
? Would seem a better fit with how std iterators work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a confusing use of iterator. It really should be called handle. But this was written to mimic the similar chainbase logic for DB, so I think it should match. "Handles" above 0 indicate a specifically cached database value, -1 is invalid, less than -1 indicates that you have a handle to the end of the table (-2 is the table[0], -3 is the table[1], etc). So in this case it is using this to indicate that db_next_i64 for "end handles" returns an invalid handle. As you can see as I talk about it, it is kind of an amalgam of handle and iterator.
} | ||
} | ||
|
||
void db_key_value_any_lookup::remove_table_if_empty(name scope, name table) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a version that removes the table if it is not empty? Could we shorten the name to remove_table
if not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer having the longer name, rather than having a short name and longer comment.
if (!view.get(parent.receiver.to_uint64_t(), slice_table_key)) { | ||
std::string event_id; | ||
apply_context& context = parent.context; | ||
if (context.control.get_deep_mind_logger() != nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we cache the result of get_deep_mind_logger
and use that for the conditional below too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
const name payer = payer_payload{first_kv->value.data(), first_kv->value.size()}.payer; | ||
std::string event_id; | ||
apply_context& context = parent.context; | ||
if (context.control.get_deep_mind_logger() != nullptr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe cache the result of get_deep_mind_logger
and use that for the conditional below too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
struct db_key_value_any_lookup { | ||
db_key_value_any_lookup(db_context& c, b1::chain_kv::view& v) : parent(c), view(v) {} | ||
virtual ~db_key_value_any_lookup() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the destructor is empty, we can let the compiler generate it for us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
libraries/chain/include/eosio/chain/backing_store/db_key_value_any_lookup.hpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at code at high level. I liked the refactor work, especially for payer related code.
@linhuang-blockone it is inside the if because we only need to construct the event_id if we are going to log it, otherwise we don't care. See the similar apply_context::dm_***_chainbase methods. This requirement comes from that code. |
if( itr < primary_iter_store.invalid_iterator() ) { // is end iterator | ||
const backing_store::unique_table* table_store = primary_iter_store.find_table_by_end_iterator(itr); | ||
EOS_ASSERT( table_store, invalid_table_iterator, "not a valid end iterator" ); | ||
if (current_session.begin() == current_session.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be an opportunity for an optimization here, but session should just provide an empty
and size
method.
|
||
void db_key_value_any_lookup::add_table_if_needed(const shared_bytes& key, account_name payer) { | ||
auto table_key = db_key_value_format::create_full_key_prefix(key, end_of_prefix::pre_type); | ||
auto session_iter = current_session.lower_bound(table_key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lower_bound is definitely the lynchpin method in all of this
auto entire_table_prefix_key = db_key_value_format::create_full_key_prefix(key, end_of_prefix::at_type); | ||
// since this prefix key is just scope and table, it will include all primary, secondary, and table keys | ||
auto session_itr = current_session.lower_bound(entire_table_prefix_key); | ||
EOS_ASSERT( session_itr != current_session.end(), db_rocksdb_invalid_operation_exception, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that you might want to see if it makes a difference in performance is checking session_itr.key()
instead. The end iterator will return an empty key and if you have an iterator that returns an empty key, it must be the end. That would bypass the needed creation of the end iterator. But I'm assuming that keys can never be empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, for any of my code the key should at the very least have the db type prefix, so empty key does mean we are not looking at anything in the db intrinsics portion of the code.
} | ||
|
||
bool db_key_value_any_lookup::match(const shared_bytes& lhs, const shared_bytes& rhs) { | ||
return lhs.size() == rhs.size() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shared_bytes has this operator.
if (iter == current_session.end()) { | ||
return false; | ||
} | ||
return match(lhs, (*iter).first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iterator also has a key()
method will only return the key instead of both the key and value.
const auto new_kt = static_cast<char>(key_type::table); | ||
const char* new_kt_ptr = &new_kt; | ||
// already have everything in char format/order, so just need to assemble it | ||
return detail::assemble<2, eosio::session::shared_bytes>({rocksdb::Slice{prefix_key.data(), full_prefix_size}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should get rid of the dependency of rocksdb in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was planning on cleaning out the old code that used Slices, but figured I would use the same type that it is using throughout the cpp and hpp and then clean them all up together.
static const char db_type_prefix = detail::get_rocksdb_contract_db_prefix_as_char(); | ||
b1::chain_kv::bytes code_as_bytes; | ||
b1::chain_kv::append_key(code_as_bytes, code.to_uint64_t()); | ||
auto ret = eosio::session::make_shared_bytes<std::string_view, 3>({std::string_view{&db_type_prefix, 1}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I removed the make_shared_bytes
function. We can add it back though since it would be much easier than converting all these over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I first tried making it one of the constructors, but was having lots of compiler problems so I just went the easy route. I'll circle back if this shows up as a performance hotspot.
eosio::session::shared_bytes as_payload() const { | ||
char payer_buf[payer_in_value_size]; | ||
memcpy(payer_buf, &payer, payer_in_value_size); | ||
return eosio::session::make_shared_bytes<std::string_view, 2>({std::string_view{payer_buf, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a very nice concise of creating one of these by passing an array of string_view
instances.
… rocksdb_db_implementation--last-merge libraries/chain/include/eosio/chain/backing_store/kv_context_rocksdb.hpp
Change Description
Providing the implemenation for db_context_rocksdb, allowing db intrinsic data to be stored in rocksdb.
(WIll merge in Timothy's session changes as the last step before commiting this PR)
Change Type
Select ONE
Consensus Changes
API Changes
Documentation Additions