Skip to content

Refactored and enhanced the consolidation algorithm - #1101

Merged
stavrospapadopoulos merged 1 commit into
devfrom
sp/52
Dec 31, 2018
Merged

stavrospapadopoulos merged 1 commit into
devfrom
sp/52

Conversation

@stavrospapadopoulos

Copy link
Copy Markdown
Member

Closes #52

``[1,2], [1,4]``. Observe that, if those four fragments were to
be consolidated, the cells of the second and third fragment
would be completely overwritten from the cells of the
fourth fragment. Therefore, the existence of those two fragments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two therefores in a row in copy

be considered for consolidation.

- The third criterion is the fragment *amplification factor*, applicable to
the case where the fragment subset fo be consolidated contains at least

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"fo" -> "to"

during consolidation.
``"sm.consolidation.step_max_frags"`` ``"4294967295"`` The maximum number of fragments to consolidate in
a single step.
``"sm.consolidation.step_min_frags"`` ``"4294967295"`` The minimum number of fragments to consolidate in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't the default be 2?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is up for discussion. There are no defaults that can simulate the current consolidation algorithm (i.e., force-consolidate all), as there may be scenarios that fragments are not "consolidatable". In that case, a default of 2 will attempt to consolidate at least 2 fragments. Is that desirable?

Comment thread doc/source/tutorials/config.rst Outdated
a single step.
``"sm.consolidation.step_min_frags"`` ``"4294967295"`` The minimum number of fragments to consolidate in
a single step.
``"sm.consolidation.step_size_ratio"`` ``"0"`` The size ratio that two ("adjacent") fragments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Doesn't this mean the default config params cause consolidation to immediately terminate doing nothing?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The algorithm permits two fragments to be consolidated if their ratio (always min / max) is larger than sm.consolidation.step_size_ratio. I'll clarify in the docs.

``"sm.consolidation.step_size_ratio"`` ``"0"`` The size ratio that two ("adjacent") fragments
must satisfy to be considered for consolidation in
a single step.
``"sm.consolidation.steps"`` ``"4294967295"`` The number of consolidation steps to be performed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't this be 1 or a small number?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Again, this is up for discussion. A huge number means that consolidation will proceed recursively until there is a single fragment, or if there is no next fragment subset that is "consolidatable".

// Delete old fragment metadata. This makes the old fragments invisible
st = delete_old_fragment_metadata(old_fragment_uris);
st = delete_fragment_metadata(to_delete);
if (!st.ok()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If deleting the fragment metadata fails, shouldn't the behavior be to not delete the fragment data? It seems like the array could quickly get in an inconsistent state if this operation fails.

It might be cleaner to delete fragment metadata + fragments at a time, that way the errors are correlated with data loss.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This one is tricky. Regardless of whether or not you delete the fragment data (after the fragment metadata deletion fails), the fragments whose metadata you did delete will not be visible to the read algorithm. The best idea would be to copy the fragment metadata in a temporary location, such that they can be recovered if this function fails. I would suggest we merge this PR and open a separate issue for this (i.e., on robustness/resilience), as testing will add complexity and more development time for this issue (which is already pretty large).

return Status::Ok();
}

Status StorageManager::get_fragment_info(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we check the on-disk format version of the array here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, I don't think so, as this depends on format agnostic parameters. However, retrieving the timestamp from the fragment URI will have to be refactored when we swap the UUID and timestamp in a separate issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the format defines the binary layout + on disk organization, the two are coupled

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Still I am not sure I understand why we would have to handle the format version here. We are not doing any parsing of the binary files in this function.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well the filename example is something that will cause a format number increment (so that we can keep reading old arrays). So @jakebolewski might be correct that some checks should be added here, even though the binary format is not touched.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Regarding the fragment folder name format, this will be revisited when we swap the UUID with the timestamp. So we will put the necessary checks then. Right now, there is no violation of backwards compatibility.

if (!open_array->fragment_metadata_exists(frag_uri) &&
frag_timestamp <= timestamp) {
auto metadata = open_array->fragment_metadata(frag_uri);
if (metadata == nullptr) { // Fragment metadata does not exist - load it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

check the on-disk format version here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think that any format-specific stuff will be handled directly by FragmentMetadata. Now, if we change completely the format to drop fragment metadata as a whole, we will need to revisit this (but let's punt for now).

return vfs_->move_dir(old_uri, new_uri);
}

Status StorageManager::get_fragment_info(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we check the on-disk format version of the array here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as above, I don't think so for now.

// fragment sets in the middle of the timeline may get consolidated,
// which will hinder the next step of consolidation (which will
// select some small and some big fragments).
if (min_size == UINT64_MAX || m[i][j] < (min_size / 1.25)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we document that the consolidation algorithm biases older fragments?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are older fragments a good metric to bias? Given no domain overlap information each fragment would assume to contribute equally to degradation in read performance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Great catch - I was sure you were going to see that. :)

This is to prevent a subtle but important case, which I encountered when I was performing some perf analysis locally. Suppose that you have 10 fragments, each of roughly equal size. I am saying roughly, because even if you write the exact same number of cells in each fragment, if you use compression the actual fragment sizes may be slightly different.

Suppose also that we consolidate 5 at a time. if the algorithm chooses to consolidate 2-6 first, then the algorithm will next choose 1, 2-6, 7, 8, 9 into 1-9 (or 2-6, 7, 8, 9,10 into 2-10), and then 1-9, 10. Observe the imbalance. The ideal scenario would have been 1-5, 6-10 and then 1-5, 6-10. This condition biases towards "alignment", rather than "old" fragments.

Comment thread tiledb/sm/misc/utils.h
class T,
typename std::enable_if<!std::is_integral<T>::value, T>::type* = nullptr>
uint64_t cell_num(const T* rect, unsigned dim_num) {
assert(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the point of implementing this function? There is no definition generated for cell_num if T is non-integral, so I believe it would be already be a compile time error if you tried e.g. cell_num<float>(...)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is called in Consolidator::are_consolidatable, which applies to all T (integral and real). It takes proper care if all fragments are sparse (the only case where the domain might be real, as this is only applicable to sparse arrays). Therefore, the definition for non-integrals is needed. In general, a real domain can only be determined at run-time, so we need to provide function definitions for non-integral T in several places, but throw asserts to make sure the code never invokes them. I am open to alternatives if any, but let's address in a separate PR as this is not specific to consolidation (and may appear in other places as well).


// Check the encryption key. Note we always pass true for cache hit by
// definition of reopening an array.
auto st = check_array_encryption_key(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This status is not checked! You can reopen an array with an arbitrary encryption key with this bug.

RETURN_NOT_OK(vfs_->dir_size(uri.second, &size));

// Get fragment non-empty domain
auto metadata =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't need new and delete for this local object -- can just do FragmentMetadata metadata(...); to stack allocate.

return Status::Ok();
}

Status StorageManager::get_fragment_info(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Well the filename example is something that will cause a format number increment (so that we can keep reading old arrays). So @jakebolewski might be correct that some checks should be added here, even though the binary format is not touched.

RETURN_NOT_OK(vfs_->is_file(coords_uri, &sparse));

// Get fragment non-empty domain
auto metadata =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should stack allocate this object as in the previous case.

return true;

// Check overlap of union with earlier fragments
for (int i = 0; i <= (int)start - 1; ++i) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can just do size_t i and i < start

auto domain_size = 2 * array_schema->coords_size();

// Allocate memory for union of non-empty domains of fragments to consolidate
auto union_non_empty_domains = (T*)std::malloc(domain_size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we start using either stack allocations or std::unique_ptr<uint8_t> for these ad-hoc allocations? We shouldn't be introducing more malloc/frees than necessary.

storage_manager_->vfs()->remove_dir(new_fragment_uri);
bool is_dir;
auto st2 = storage_manager_->vfs()->is_dir(*new_fragment_uri, &is_dir);
if (is_dir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

st2 also doesn't appear to be checked anywhere here.

storage_manager_->vfs()->remove_dir(new_fragment_uri);
clean_up(buffer_num, buffers, buffer_sizes, query_r, query_w);
bool is_dir;
auto st2 = storage_manager_->vfs()->is_dir(*new_fragment_uri, &is_dir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

st2 not checked

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These two places handle a previous error status. Since we do not provide an error stack (yet), checking st2 here is redundant, since we will report only st. However, we need to initialize is_dir to false to properly handle the if command immediately below.

} else if (i + j >= col_num) { // Non-valid entries
m[i][j] = UINT64_MAX;
} else { // Every other row is computed using the previous row
auto ratio = (float)fragments[i + (j - 1)].fragment_size_ /

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can this happen when j == 0? In that case we are doing i + (0 - 1).

@stavrospapadopoulos stavrospapadopoulos Dec 31, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In this else block, it is always i>0, since the case of i==0 is handled in the first if block above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about j? If it can be 0 then it will cause an OOB access into the fragments vector, since size_t is an unsigned type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, you are right, the parenthesis there may be problematic. I think that even with overflow this would work (as integers "wrap around"), but I will remove the parenthesis anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants