Repository navigation
(wip draft proposal) Reduce memory used by snapshot load and generation - #8649
Draft
Ivar (aɪvɑr) (ivarprudnikov) wants to merge 1 commit into
Draft
Ivar (aɪvɑr) (ivarprudnikov) wants to merge 1 commit into
Ivar (aɪvɑr) (ivarprudnikov) wants to merge 1 commit into
Conversation
Disaster recovery from a snapshot of size S peaked at about 5.4 x S
(5.3 GiB for a 1 GiB snapshot), mostly from redundant full-size copies
of the snapshot. Remove them, without any ledger or snapshot format
change: the peak falls to about 2.4 x S (2.4 GiB for 1 GiB, 4.6 GiB for
1.9 GiB), and a primary generating a periodic snapshot falls from about
4.5 x S to 2.5 x S.
1a. Decrypt and parse without copies:
- Span-based AbstractTxEncryptor and KeyAesGcm decrypt write the
plaintext directly into the output, instead of a temporary copy
of the ciphertext, a pre-sized output and a third buffer.
- Snapshot tables are parsed from spans of the deserialised entry
rather than from per-table copies.
1b. Generate a snapshot into one exactly-sized buffer:
- A SizeWriter dry run measures both domains, a SpanWriter
serialises directly into the output, and the private domain is
encrypted in place. Output is byte-identical to the previous
serialisation (unit tested).
1c. Decrypt the private domain in place:
- Private recovery and joining an open service decrypt inside the
snapshot buffer the node already holds. The GCM tag is verified
before anything is parsed, and the output is cleansed on every
failure path, including exceptions. Private recovery consumes the
startup snapshot; a second attempt in the same process fails with
an explicit error.
1d. Reuse public state in the private recovery store:
- Public maps captured when the snapshot's public domain is
installed (persistent state, version, SHA-256 of the serialised
bytes) are installed into the recovery store instead of
deserialising a second copy, once the snapshot version, every
public table's version and digest, and the exact set of tables
have been checked. The captured set is opaque and move-only.
Validated on this code: kv, crypto, encryptor, snapshot, snapshotter,
history, ledger_secrets and historical_queries unit tests; e2e tests
recovery_test, recovery_stale_snapshot_join_test,
recovery_intermediate_snapshot_join_test and
recovery_snapshot_endorsements_test; clang-tidy on translation units
covering all changed code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a298c662-aec4-443f-b243-37d4a6c6f6d6
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
The context here is our instances limited to 16G memory and edge case ledger with snapshot approaching 2G. As we know this is not great because DR needs multiples of snapshot memory which can end up in a situation when it gets OOM killed in the process of a DR.
As a dutiful citizen of the internet I ran some checks locally and let the agent go through the existing code to find some low hanging fruit to improve the state of affairs. It came out with some elaborate multi phase improvement but I want to start with this proposal that focuses on the sane parts of not copying data multiple times. It looks legit and I went through the code, local tests show it would use close to 50% less memory during DR and snapshot generation times. The issue, as you can imagine, is that I might be blind to the edge cases.
Summary
DR from a snapshot of size S peaked at about 5.4 x S (5.3 GiB for a 1 GiB snapshot), mostly from redundant full-size copies of the snapshot. Remove them, without any ledger or snapshot format change: the peak falls to about 2.4 x S (2.4 GiB for 1 GiB, 4.6 GiB for 1.9 GiB), and a primary generating a periodic snapshot falls from about 4.5 x S to 2.5 x S.
Structured changes
2-3 x S7. Files changed
include/ccf/crypto/symmetric_key.hdecrypt()into a caller-provided span onKeyAesGcmandKeyAesGcm::Contextsrc/crypto/symmetric_key.cppdecrypt()src/crypto/openssl/symmetric_key.h,.cppsrc/kv/kv_types.hdecrypt(),decrypt_in_place(),serialise_raw_in_place(),deserialise_raw_span()src/kv/encryptor.hTxEncryptorspandecrypt()anddecrypt_in_place()src/kv/null_encryptor.hdecrypt()and in-place encryption, for testssrc/kv/raw_serialise.hRawReader::read_next_span(),SpanWriter, in-place appends and buffer views for all writerssrc/kv/generic_serialise_wrapper.hfinalise_in_place(), domain sizes, copy-freeget_raw_data(),init_in_place()src/kv/kv_serialiser.hSpanKvStoreSerialisersrc/kv/snapshot.hsrc/kv/untyped_map.hsrc/kv/store.hdeserialise_snapshot_in_place(),SnapshotPublicMaps, capture and checked reusesrc/node/snapshot_serdes.hsrc/node/node_state.hsrc/kv/test/kv_snapshot.cppCompatibility
main's, so services mixing versions are unaffected.include/ccf/crypto/symmetric_key.his a public header:KeyAesGcmandKeyAesGcm::Contextgain a virtualdecrypt()overload with a default implementation.Existing implementations still compile; as with any change to a virtual interface, code built against the old header must be rebuilt.
Deserialising snapshot (size: N, public only: true, capturing public maps)andDeserialising snapshot (size: N, public only: false, in place, reusing captured public maps: true|false), so log-based tooling may need updating.