-
Notifications
You must be signed in to change notification settings - Fork 887
LittDB compression #3769
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
Open
cody-littley
wants to merge
5
commits into
main
Choose a base branch
from
cjl/litt-compression
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
LittDB compression #3769
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package disktable | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "log/slog" | ||
| "time" | ||
|
|
||
| "github.com/sei-protocol/sei-chain/sei-db/db_engine/litt/metrics" | ||
| "github.com/sei-protocol/sei-chain/sei-db/db_engine/litt/types" | ||
| "github.com/sei-protocol/sei-chain/sei-db/db_engine/litt/util" | ||
| ) | ||
|
|
||
| // compressionLoop compresses value bytes off the control-loop goroutine. It sits in front of the control | ||
| // loop: when compression is enabled, controlLoop.enqueue sends every control message to inputChannel, | ||
| // this loop compresses write requests, and forwards all messages (compressed writes and everything else, | ||
| // verbatim) to outputChannel (the control loop's controllerChannel) in arrival order. | ||
| // | ||
| // Forwarding all message types in order is what makes flush correct: a flush request travels the same | ||
| // channel behind the writes it must follow, so the control loop applies those writes first. Because this | ||
| // loop is single-threaded and finishes compressing a write before it reads the next message, any in-flight | ||
| // compression is complete before a following flush is forwarded; the ordering barrier is automatic. | ||
| type compressionLoop struct { | ||
| // logger for the compression loop. | ||
| logger *slog.Logger | ||
|
|
||
| // errorMonitor is used to react to fatal errors anywhere in the disk table. | ||
| errorMonitor *util.ErrorMonitor | ||
|
|
||
| // algorithm is the compression algorithm applied to write-request values. | ||
| algorithm types.CompressionAlgorithm | ||
|
|
||
| // inputChannel receives messages from controlLoop.enqueue. | ||
| inputChannel chan any | ||
|
|
||
| // outputChannel forwards messages to the control loop (its controllerChannel). | ||
| outputChannel chan any | ||
|
|
||
| // metrics encapsulates metrics for the DB. May be nil, in which case no metrics are reported. | ||
| metrics *metrics.LittDBMetrics | ||
|
|
||
| // name is the table name, used to tag metrics. | ||
| name string | ||
|
|
||
| // clock provides the current time, used to measure compression latency. | ||
| clock func() time.Time | ||
| } | ||
|
|
||
| // run processes messages until shutdown. It compresses write requests and forwards every message to the | ||
| // control loop in arrival order. | ||
| func (cl *compressionLoop) run() { | ||
| for { | ||
| select { | ||
| case <-cl.errorMonitor.ImmediateShutdownRequired(): | ||
| return | ||
| case message := <-cl.inputChannel: | ||
| if req, ok := message.(*controlLoopWriteRequest); ok { | ||
| if !cl.compress(req) { | ||
| // compress panicked the DB via the error monitor; stop forwarding. | ||
| return | ||
| } | ||
| } | ||
|
|
||
| // Forward every message (compressed writes and all others) in arrival order. | ||
| if err := util.Send(cl.errorMonitor, cl.outputChannel, message); err != nil { | ||
| return | ||
| } | ||
|
|
||
| // The shutdown request is the last message the control loop will process; stop after | ||
| // forwarding it so this goroutine does not outlive the table. | ||
| if _, ok := message.(*controlLoopShutdownRequest); ok { | ||
| return | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // compress fills req.compressedValues with the compressed form of each value. It returns false if | ||
| // compression failed (in which case it has already panicked the DB via the error monitor). | ||
| func (cl *compressionLoop) compress(req *controlLoopWriteRequest) bool { | ||
| var start time.Time | ||
| if cl.metrics != nil { | ||
| start = cl.clock() | ||
| } | ||
|
|
||
| compressed := make([][]byte, len(req.values)) | ||
| var uncompressedBytes uint64 | ||
| var compressedBytes uint64 | ||
| for i, kv := range req.values { | ||
| blob, err := types.Compress(cl.algorithm, kv.Value) | ||
| if err != nil { | ||
| cl.errorMonitor.Panic(fmt.Errorf("failed to compress value: %w", err)) | ||
| return false | ||
| } | ||
| compressed[i] = blob | ||
| uncompressedBytes += uint64(len(kv.Value)) | ||
| compressedBytes += uint64(len(blob)) | ||
| } | ||
| req.compressedValues = compressed | ||
|
|
||
| if cl.metrics != nil { | ||
| cl.metrics.ReportCompression(cl.name, cl.clock().Sub(start), uncompressedBytes, compressedBytes) | ||
| } | ||
| return true | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
The compression loop processes one batch at a time on a single goroutine, which is necessary to preserve flush ordering. Under high write throughput with large batches, compression could become the pipeline bottleneck. The design is correct as-is, but if this becomes a problem, we could parallelize compression within a batch (each value is independent) while keeping inter-batch ordering serial.