EVCacheValue: opt-in compact binary serialization with backwards-compatible reads#196
Open
joegoogle123 wants to merge 1 commit into
Open
EVCacheValue: opt-in compact binary serialization with backwards-compatible reads#196joegoogle123 wants to merge 1 commit into
joegoogle123 wants to merge 1 commit into
Conversation
d443e30 to
3892873
Compare
665a6d9 to
6bde69d
Compare
The hashed-key EVCacheValue envelope is serialized with Java ObjectOutputStream, adding ~50-80 bytes/item. Add a compact length-prefixed binary format and have EVCacheTranscoder override serialize()/deserialize() so super.encode() still sets the SERIALIZED flag and CachedData as before. - Binary writing is OPT-IN and OFF BY DEFAULT (EVCacheTranscoder.setUseBinarySerialization); by default EVCacheValue is still Java-serialized. This is the backwards-compat gate: readers must ship the decode change before any writer enables binary. - Reads always auto-detect by leading byte (0xAC ED = legacy Java, 0x0C = binary), so a client with this change decodes existing Java-serialized values unchanged. - Wire format reserves a version byte (0x00) after the magic byte for future breaking changes; versioning is not implemented yet (documented as reserved). Tests: EVCacheValueSerdeTest (17 cases) — binary round-trip, transcoder encode/decode with the flag on, default-off (Java) write + dual-format read, legacy-Java backwards-compat decode, non-EVCacheValue passthrough, magic/reserved-byte dispatch, size win, malformed input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6bde69d to
d46bf97
Compare
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.
Summary
Hashed-key values are wrapped in an
EVCacheValueenvelope(key, value, flags, ttl, createTime)that is currently serialized with JavaObjectOutputStream, adding ~50–80 bytes of structural overhead per item. This adds a compact, length-prefixed binary format for the envelope while remaining fully backwards-compatible on reads.What changed
EVCacheValueSerdeclass (com.netflix.evcache.pool) — public-final-non-instantiable codec, owns the wire format and all error handling:static byte[] serialize(EVCacheValue)— length-prefixed binary layout:[magic 0x0C][reserved 0x00][int keyLen][key UTF-8][int valLen][value][int flags][long ttl][long createTime].static EVCacheValue deserialize(byte[])— bounds-checks length prefixes before allocating; on any corruption / unexpected exception warn-logs the failing field and a truncated hex dump (via Apache CommonsHex.encodeHexString, capped at 1024 bytes) and returnsnull. MatchesBaseSerializingTranscoder's resilience contract (corruption → cache miss → caller refills from source of truth) so a single corrupt entry never crashes a get / getBulk / async pipeline.static boolean isBinaryFormat(byte[])— exposed for the dispatcher.EVCacheTranscoderbecomes a thin dispatcher (no try/catch):serialize: gates onuseBinarySerialization && o instanceof EVCacheValue→EVCacheValueSerde.serialize; elsesuper.serialize(Java).deserialize: dispatches onEVCacheValueSerde.isBinaryFormat→EVCacheValueSerde.deserialize; elsesuper.deserialize.EVCacheValuestays a pure POJO (codec moved out; constructor unchanged from pre-PR).EVCacheImplreads a Feature Property at client construction and injects it into the (immutable) envelope transcoder.0xAC 0xED= legacy Java,0x0C= binary), so a new client decodes existing cache entries unchanged.Format-flag decision (reuse
SERIALIZED+ magic byte, not a fresh flag)The binary envelope keeps the existing
SERIALIZEDflag and is disambiguated from Java by the leading byte, rather than allocating a newCachedDataflag. Rationale:SERIALIZEDsemantically still means "serialized object →deserialize()"; the codec choice (Java vs binary) lives insidedeserialize(). No flag constant is reassigned or repurposed, anddecode()branch order is untouched.SERIALIZED(e.g. the admin inspector, cache-warmer) keep working without a new flag constant to propagate.SERIALIZEDthrowsStreamCorruptedException(fails loud) rather than silently decoding garbage — which a fresh low-byte flag would cause (decodeString) on old readers.EVCacheValueSerdeJavadoc):SERIALIZEDpayloads are self-describing by leading byte; a future third format must use a distinct non-colliding magic + the reserved version byte.Reserved version byte
Byte index 1 of the binary payload is reserved (always
0x00today). Reader read-and-ignores; not validated. Reason: forward-compat without an emergency reader rollout. If today's readers rejected any non-zero version, introducing a v2 in the future would require shipping reader support fleet-wide before any writer could emit a v2 byte, and a single misconfigured writer would crash all readers. By accepting any value today, future readers can branch on this byte to introduce breaking format changes backwards-compatibly.Feature Property (rollout gate)
<appName>.envelope.binary.serialization.enabled(global fallbackevcache.envelope.binary.serialization.enabled), defaultfalse.envelopeTranscoderinEVCacheMemcachedClient).Compatibility
Testing
EVCacheValueSerdeTest— 17 cases via the publicEVCacheTranscoder.encode/decodeAPI:0x0C, reserved byte0x000xAC 0xED) but reads both formatsObjectOutputStream-serialized envelope still decodesEVCacheValuepassthrough (ArrayList stays on the Java path even with binary flag on)Full evcache-core suite (
./gradlew :evcache-core:test): 28/28 green (EVCacheValueSerdeTest 17, NodeLocatorLookupTest 3, MockEVCacheTest 7, plus runtime tests in other modules).Chunked-payload integration is not covered by an automated test in this PR — chunking lives in
EVCacheClient.createChunks/assembleChunks, which are content-opaque (byte copy + CRC + manifest) and require a live client to exercise. The binary format introduces no new chunking risk by construction:assembleChunksreassembles bytes byte-for-byte and CRC-checks them against the manifest before handing the result to the transcoder.🤖 Generated with Claude Code