-
Notifications
You must be signed in to change notification settings - Fork 998
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
feat(server): memory defrag support - unit tests added to verify #448 #523
Conversation
b0836e1
to
df4f7f4
Compare
src/core/compact_object.cc
Outdated
// Supporting only for Strings - but not externals, or small string - they are using different | ||
// allocations. otherwise we may crash See HasAllocated function for testing other conditions | ||
const bool is_mimalloc_allocated = | ||
(HasAllocated() && taglen_ != SMALL_TAG && ObjType() == OBJ_STRING); |
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.
why logically it's correct, I suggest that you structure it differently so that when we add support for the SMALL_TAG it will be easily extended.
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 don't like it too, it was even more cumbersome. I think this is a classic case where you need a function.
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 meant separate tag_len_ test from others. and later write
if (is_mimalloc_allocated) {
if (taglen_ == ROBJ_TAG) {
...
}
// else TODO
}
return false;
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 - the next commit will have it, but since this can grow I think it would be more readable and less comments if this is in a function
src/server/engine_shard_set.cc
Outdated
CHECK(prime_table->depth() < 40) | ||
<< "the depth for the prime table is invalid: " << prime_table->depth(); | ||
PrimeTable::Cursor cur = defrag_state_.cursor; | ||
const std::size_t max_iteration = slice.DbSize(kDefaultDbIndex) / kCursorCountRelation; |
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.
Do I read correctly that the task run-time is linear with the size of the database. so it will traverse for 1000000/50 iterations for 1M records ?
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.
your whole function runs at least max_iteration
loops.
DoDefrag should be very fast. It should run several buckets and exit and then continue etc.
With this code we will have unresponsive backend with it being stuck for time linear to db size. Could be many seconds.
We need microseconds.
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.
so now I understand what you mean by const. Changing this to limit = 50 (const value), will take about 200-300 micro (this include the writing to the log, so should be even less).
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 did a measurement for the value of 50 (const value), the time it sends running the function DoDefrag is ~0us (its about 400ns), so I think this value is good.
src/server/engine_shard_set.cc
Outdated
DCHECK(slice.IsDbValid(kDefaultDbIndex)); | ||
auto [prime_table, expire_table] = slice.GetTables(kDefaultDbIndex); | ||
// otherwise we will end with negative invalid segment id - crashed at CI | ||
CHECK(prime_table->depth() < 40) |
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.
it's your assumption, right? Please add todo to remove this check.
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 check is based on "dash_internal.h line 679 (function segment_id). This can be removed however since I found the reason for the crash in CI. I left it in just in case, but I will remove this.
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 did not understand this part. Did you find the reason? what was the reason for the crash?
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.
in any case, I prefer using DCHECK_LT
if you need it for tests
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 found the reason (and why it was not consistent) - I had a bug in the shutdown loop, that was only be visible when the DoDefrag was called. I assumed that the defrag_task value is > 0 but this wasn't true, so the task was not stopped between tests. Not sure why it happened always on CI and only when running it for a very many iterations, but I was able to reproduce this locally today, and now its not happening again, so there is no real reason to leave this check (I removed it on my local branch).
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.
Very glad it was found!
510c77f
to
d3b413b
Compare
src/server/dragonfly_test.cc
Outdated
int kMaxNumKeysToDelete = 10'000; | ||
int current_step = kFactor; | ||
for (int i = 1; i < kMaxNumKeysToDelete; current_step *= kFactor) { | ||
for (int j = i - 1 + current_step; i < current_step; i++, j++) { |
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.
what does this loop do? should it be i < current_step
or j < current_step
?
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.
j is just to set the string value, it is not controlling the loop number of iterations - you can write this without the j at all it would just looking at the line that set the name of the key as something like i + current_step - 1. So j is just to make it less verbose there. But you are using i for both loops.
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, I understand now but it does break my pattern recognition.
Do you mind writing the equivalent code with inner loop like:
``for (; i < current_step; ++i) {
int j = i - 1 + current_step;
....
}
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.
NP
src/server/dragonfly_test.cc
Outdated
EXPECT_GT(GetMallocCurrentCommitted(), NUMBER_OF_KEYS); | ||
// we are expecting to have at least one try by now | ||
EXPECT_GT(shard->GetDefragStats().tries, 0); | ||
EXPECT_GT(GetMallocCurrentCommitted(), kMaxNumKeysToDelete); |
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.
you are comparing GetMallocCurrentCommitted() bytes with number of keys?
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.
should be removed - I change the test to set the max memory to be explicit, this was to make sure that the task would actually run (that is, we more memory than deleted keys, but was done when I initialy wrote the test and just want to very that didn't deleting too many keys, again before I realised that it better to just set the value for max memory).
src/server/dragonfly_test.cc
Outdated
return stats.success_count; | ||
} | ||
--tries; | ||
this_fiber::sleep_for(220ms); |
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.
there is a lot to unpack here but I am pretty sure this line is never called. Otherwise, it would crash.
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.
what do you want to test in words?
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 need to somehow make sure that the idle task was executed and that it found a memory to defrag (it should once it was running since we deleted keys at random locations). I have to say that this didn't crashed, but it makes the test a little bit unpredictable in the sense that you cannot be sure whether the task ran or not. I though of adding a "TEST" functions that would be called explicitly to execute the DoDefarg, but didn't like the idea of adding functions that are should not be part of the production code.
ce19c72
to
0460f3e
Compare
src/server/dragonfly_test.cc
Outdated
// creating a busy wait fiber here that checks the stats whether the defrag task run and | ||
// whether it did something. | ||
auto stats = shard->GetDefragStats(); | ||
while (tries > 0 && stats.tries < 3) { |
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.
DragStats stats;
for (int i = 0; i < 10; ++i) {
auto stats = shard->GetDefragStats();
if (stats.success_count > 10) {
break;
}
this_fiber::sleep_for(220ms);
}
EXPECT_GT(stats.success_count, kMinExpectedDefragCount);
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
src/server/engine_shard_set.cc
Outdated
} | ||
|
||
void EngineShard::DefragTaskState::Init() { | ||
cursor = 0u; | ||
cursor = kCursorDoneState; |
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.
Init
can be removed.
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.
removing
Signed-off-by: Boaz Sade <[email protected]>
0460f3e
to
7f12a82
Compare
fixes #448
feat(server): active memory defrag task #448
Signed-off-by: Boaz Sade [email protected]
This PR is a complementary PR for the high level implementation of the defrag task - PR 509.
In this PR we are covering the low level part that do the actual defragmation operation.
This can close issue #448.
Please note that we are disabling small string in this PR!.
Also note that we are changing mi_malloc memory decommit policy to wait for 0 ms from the default value.
Right now the flag that control the actual execution of the shard scan - "mem_defrag_threshold" is set to 1, this means that the task will do actual work only if we are at memory limits. To make it more reasonable we should change this to 0.05 or run with --mem_defrag_threshold=0.05