feat(server): Journal omits#7060
Conversation
751383e to
cda32b8
Compare
cda32b8 to
ffbdbf5
Compare
| @@ -631,6 +654,9 @@ class DbSlice { | |||
| // Record whenever a key expired to DbTable::expired_keys_events_ for keyspace notifications | |||
| bool expired_keys_events_recording_ = true; | |||
|
|
|||
| // Provided for the next FindMutableInternal operation | |||
| MutationHints* mutation_hints_ = nullptr; | |||
There was a problem hiding this comment.
what is the size of MutationHints ? why do you need to pass by pointer and not by value?
looks fragile
There was a problem hiding this comment.
Because it is also used to get output variables
| @@ -47,8 +47,8 @@ error_code Close() { | |||
| return {}; | |||
| } | |||
|
|
|||
| bool HasRegisteredCallbacks() { | |||
| return journal_slice.HasRegisteredCallbacks(); | |||
| unsigned CallbackNumber() { | |||
There was a problem hiding this comment.
CallbackNumber is not a great name.
| @@ -836,10 +837,15 @@ OpStatus SetCmd::Set(const SetParams& params, string_view key, string_view value | |||
| } | |||
| } | |||
|
|
|||
| DbSlice::MutationHints hints{.hint{.single_key = true, .support_omit = explicit_journal_}}; | |||
There was a problem hiding this comment.
can we do it more elegantly?
- at least in this example you pass hints with
.single_key = trueand setsupport_omitthen why passing them at all? why not pass a final boolean true if the caller supports omit - the boolean could be added to
op_args_.db_cntx, maybe so you won't need to inject this before the find call. skip_journal_can be returned by AddOrFind insideop_res.
There was a problem hiding this comment.
I agree, let's move it to existing structures
7fdf5f8 to
99c0270
Compare
|
Will rebase to include #7253 |
056e6e1 to
18b5818
Compare
more small fixes Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
18b5818 to
47434d4
Compare
|
augment review |
🤖 Augment PR SummarySummary: Adds an experimental “journal omit” optimization to avoid emitting redundant journal entries during replica full-sync. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| @@ -152,7 +153,7 @@ void SerializerBase::UnregisterChangeListener() { | |||
| bool SerializerBase::ProcessBucket(DbIndex db_index, PrimeTable::bucket_iterator it, | |||
| bool on_update) { | |||
| // Check if this bucket is stale | |||
| if (it.is_done() || it.GetVersion() >= snapshot_version_) { | |||
| if (it.GetVersion() >= snapshot_version_) { | |||
There was a problem hiding this comment.
In SerializerBase::ProcessBucket, the logic inside the stale-bucket branch still contains an it.GetVersion() < snapshot_version_ check that can never be true once the outer condition is it.GetVersion() >= snapshot_version_. This looks like leftover logic from the earlier it.is_done() condition and may hide an intended version-stamping behavior for empty buckets.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| @@ -836,9 +837,13 @@ OpStatus SetCmd::Set(const SetParams& params, string_view key, string_view value | |||
| } | |||
| } | |||
|
|
|||
| // Enable journal omits for this operation | |||
| op_args_.db_cntx.is_omittable_operation = true; | |||
There was a problem hiding this comment.
skip_journal_ is only set via the AddOrFind path; the SET XX early-return path goes through FindMutable/SetExisting and never consults omitted_journal, so the new omit optimization won’t trigger for XX updates during full sync. If SET ... XX is used during replica full sync, this can still produce redundant/double-applied updates compared to the main AddOrFind path.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
9e8c13b to
6a5510a
Compare
6a5510a to
fec262c
Compare
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
ba7f5b9 to
63148bc
Compare
Review Summary by QodoImplement journal omit optimization for eventually-consistent replicas
WalkthroughsDescription• Implements journal omit optimization for eventually-consistent replicas • Skips journal writes and version updates when safe during replication • Adds IsOmittableWrite() to detect omittable operations based on snapshot state • Tracks journal omit events in metrics and updates replication test coverage Diagramflowchart LR
A["Write Operation"] --> B{"is_omittable_operation<br/>set?"}
B -->|Yes| C["Check IsOmittableWrite"]
C --> D{"Single replica<br/>+ not visited yet?"}
D -->|Yes| E["Skip journal write<br/>Skip version update"]
D -->|No| F["Normal write path"]
E --> G["Mark omitted_journal"]
F --> H["Record journal entry"]
G --> I["Increment journal_omit counter"]
H --> I
File Changes1. src/server/db_slice.h
|
Code Review by Qodo
1. Missing <algorithm> include
|
| auto cb2 = [cb1](const PrimeTable::BucketSet& bs) -> uint64_t { | ||
| DCHECK(!std::ranges::empty(bs.buckets())); | ||
| return std::ranges::max(bs.buckets(), {}, cb1).GetVersion(); | ||
| }; |
There was a problem hiding this comment.
1. Missing include 🐞 Bug ⚙ Maintainability
DbSlice::IsOmittableWrite uses std::ranges::max, but db_slice.cc does not include <algorithm> (the standard header that declares ranges algorithms), relying on transitive includes and risking build failures on some toolchains.
Agent Prompt
## Issue description
`src/server/db_slice.cc` calls `std::ranges::max(...)` inside `DbSlice::IsOmittableWrite`, but the file only adds `#include <ranges>` and does not include `<algorithm>`. `std::ranges::max` is declared in `<algorithm>`; relying on transitive includes can break builds depending on the standard library / compiler configuration.
## Issue Context
This PR introduces `DbSlice::IsOmittableWrite` and uses `std::ranges::max` over a buckets view.
## Fix Focus Areas
- src/server/db_slice.cc[4-12]
- src/server/db_slice.cc[1965-1970]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| @@ -138,9 +138,10 @@ SerializerBase::~SerializerBase() { | |||
| // same fiber, so the baseline is serialized first. big_value_mu_ prevents this callback path | |||
| // from interleaving with the traversal fiber's bucket serialization, which may preempt while | |||
| // emitting large values. | |||
| void SerializerBase::RegisterChangeListener() { | |||
| void SerializerBase::RegisterChangeListener(bool replica) { | |||
There was a problem hiding this comment.
can you explain replica argument?
| @@ -935,7 +940,11 @@ void SetCmd::PostEdit(const SetParams& params, std::string_view key, std::string | |||
| } | |||
|
|
|||
| if (explicit_journal_ && op_args_.shard->journal()) { | |||
| RecordJournal(params, key, value); | |||
| // Skipping a journal write breaks the LSN buffer, so clear it | |||
There was a problem hiding this comment.
can you please expand? what do you mean it breaks the LSN buffer?
| @@ -935,7 +940,13 @@ void SetCmd::PostEdit(const SetParams& params, std::string_view key, std::string | |||
| } | |||
|
|
|||
| if (explicit_journal_ && op_args_.shard->journal()) { | |||
| RecordJournal(params, key, value); | |||
| // A skipped journal write is not added to the journal ring buffer, so it goes unnoticed for | |||
There was a problem hiding this comment.
I feel it's still hard to understand the exact scenario of when it happens as the main scenario of snapshotting is unrelated to psync.
up to you.
|
@dranikpg one thing I forgot to add - can we add a runtime flag to be able to revert the feature if needed? |
|
will add it |
Experimental journal omits
Implements
DbSlice::IsOmittableWritethat can skip the CallOnChangeCallbacks + version update and has to notify the command to omit the journal write.This is safe if we have one eventually consistent snapshot (replica, migration) and the bucket was not visited yet. It will be fully serialized once the iteration loop reaches it