-
Notifications
You must be signed in to change notification settings - Fork 1.1k
chore: Limit expiry value to 32 bits. #5797
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,9 +8,13 @@ | |
|
||
namespace dfly { | ||
|
||
// ExpirePeriod encapsulates the expiration period of a key. | ||
// It can represent periods in milliseconds up to ~49 days and in seconds up to ~136 years. | ||
// If value in milliseconds is too high, it switches to seconds precision. | ||
// And if it's still too high, it silently clamps to the max value. | ||
class ExpirePeriod { | ||
public: | ||
static constexpr size_t kMaxGenId = 15; | ||
static constexpr uint32_t kMaxGenId = 15; | ||
|
||
ExpirePeriod() : val_(0), gen_(0), precision_(0) { | ||
static_assert(sizeof(ExpirePeriod) == 8); // TODO | ||
|
@@ -35,28 +39,30 @@ class ExpirePeriod { | |
|
||
void Set(uint64_t ms); | ||
|
||
bool is_second_precision() { return precision_ == 1;} | ||
bool is_second_precision() const { | ||
return precision_ == 1; | ||
} | ||
|
||
private: | ||
uint64_t val_ : 59; | ||
uint64_t gen_ : 4; | ||
uint64_t precision_ : 1; // 0 - ms, 1 - sec. | ||
uint32_t val_; | ||
uint8_t gen_ : 2; // generation id. | ||
uint8_t precision_ : 1; // 0 - ms, 1 - sec. | ||
}; | ||
|
||
inline void ExpirePeriod::Set(uint64_t ms) { | ||
constexpr uint64_t kBarrier = (1ULL << 48); | ||
|
||
if (ms < kBarrier) { | ||
val_ = ms; | ||
precision_ = 0; // ms | ||
if (ms < UINT32_MAX) { | ||
val_ = ms; // about 49 days in ms. | ||
precision_ = 0; // ms | ||
return; | ||
} | ||
|
||
precision_ = 1; | ||
if (ms < kBarrier << 10) { | ||
ms = (ms + 500) / 1000; // seconds | ||
precision_ = 1; // seconds | ||
if (ms < UINT64_MAX / 2) { | ||
ms = (ms + 500) / 1000; | ||
val_ = ms >= UINT32_MAX ? UINT32_MAX - 1 : ms; | ||
} else { | ||
val_ = UINT32_MAX - 1; | ||
} | ||
Comment on lines
+59
to
65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I understand the if with int64_t::max / 2, why not just calculate the seconds value and see if it fits into int32_t 👀 Just check to divide first and then add the remainder > 0 (I assume the if is about this) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just a little less intuitive |
||
val_ = ms >= kBarrier ? kBarrier - 1 : ms; | ||
} | ||
|
||
} // namespace dfly |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -252,11 +252,11 @@ class DbSlice { | |
} | ||
|
||
int64_t ExpireTime(const ExpirePeriod& val) const { | ||
return expire_base_[0] + val.duration_ms(); | ||
return expire_base_[val.generation_id()] + val.duration_ms(); | ||
} | ||
|
||
ExpirePeriod FromAbsoluteTime(uint64_t time_ms) const { | ||
return ExpirePeriod{time_ms - expire_base_[0]}; | ||
return ExpirePeriod{time_ms - expire_base_[expire_base_index_], expire_base_index_}; | ||
} | ||
|
||
struct ItAndUpdater { | ||
|
@@ -607,11 +607,11 @@ class DbSlice { | |
|
||
ShardId shard_id_; | ||
uint8_t cache_mode_ : 1; | ||
|
||
uint8_t expire_allowed_ : 1; | ||
uint8_t expire_base_index_ : 1; | ||
Comment on lines
+610
to
+611
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you like this so much 😆 |
||
EngineShard* owner_; | ||
|
||
int64_t expire_base_[2]; // Used for expire logic, represents a real clock. | ||
bool expire_allowed_ = true; | ||
|
||
uint64_t version_ = 1; // Used to version entries in the PrimeTable. | ||
uint64_t next_moved_id_ = 1; | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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.
Not sure if you're optimizing with all those bitfields, but the struct is not packed either way 🙂

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 know the memory usage in this PR does not go down. It's not the goal here. I want first to introduce all the supporting functionality to support 5 bytes expire offset and then eliminate the expire table completely by moving 5 byte expire value into PrimeValue. This will reduce the memory usage for TTL enabled use-cases but will increase it for use-cases that do not use TTLs. This is why I want to use 5 bytes for expire value so we could reduce the negative impact for these use-cases.
In order to support a 5-bytes expire offset, we must model the absolute time as
base + period
because we must update base from time to time, otherwise after 49 days of process being run you won't be able to represent a millisecond precision with the oldbase
. And since updating the base needs to be done gradually (by traversing the whole table), we need to have multiple bases to support the transition state.