Skip to content

Add Zstd support for EVCache#197

Open
janewang1680 wants to merge 10 commits into
masterfrom
janewang1680/evcache_zstd
Open

Add Zstd support for EVCache#197
janewang1680 wants to merge 10 commits into
masterfrom
janewang1680/evcache_zstd

Conversation

@janewang1680

@janewang1680 janewang1680 commented Jun 10, 2026

Copy link
Copy Markdown

Summary
Add pluggable compression algorithm support (gzip, zstd) via a new CompressionAlgorithm enum on EVCacheSerializingTranscoder, with magic-byte auto-detection on decode so existing gzip-compressed cache entries
continue to decode correctly. Algorithm and zstd level are configurable via two new fast properties:
default.evcache.compression.algorithm (default GZIP)
default.evcache.compression.zstd.level (default 3)
Fix a pre-existing bug in the compression ratio metric where compressed.length / b.length always evaluated to 1.0 because b was reassigned to compressed before the ratio calculation. The metric now uses the
captured originalLength, producing accurate ratios.
Test plan
./gradlew :evcache-core:test --tests "com.netflix.evcache.EVCacheSerializingTranscoderTest" — 17 tests covering enum values, default constructor, gzip/zstd round-trip, cross-decode (gzip-written read by zstd
transcoder and vice versa), and FP wiring.
Verify backward compatibility: data written with gzip in production decodes correctly after deploy.
Verify compression ratio metric (COMPRESSION_RATIO timer) reports realistic ratios (not always ~100%) in a canary.
Verify it works in our internal services

Comment thread evcache-core/src/main/java/com/netflix/evcache/EVCacheSerializingTranscoder.java Outdated
Comment thread evcache-core/src/main/java/com/netflix/evcache/EVCacheSerializingTranscoder.java Outdated
Comment thread evcache-core/src/main/java/com/netflix/evcache/EVCacheSerializingTranscoder.java Outdated
Comment thread evcache-core/src/main/java/com/netflix/evcache/EVCacheSerializingTranscoder.java Outdated
Comment thread evcache-core/build.gradle Outdated
@srrangarajan srrangarajan requested a review from shy-1234 June 22, 2026 16:42
@srrangarajan

Copy link
Copy Markdown
Contributor

@shy-1234 can you also review the PR please

Comment thread evcache-core/src/main/java/com/netflix/evcache/EVCacheSerializingTranscoder.java Outdated
public Transcoder<Object> getDefaultTranscoder() {
return new EVCacheTranscoder();
return new EVCacheTranscoder(appName,
client.getPool().getEVCacheClientPoolManager().getEVCacheConfig().getPropertyRepository());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why not just appName? seems like there is a constructor that handles this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the constructor without PropertyRepository config argumnt uses EVCacheConfig.getInstance().getPropertyRepository(), which is a global setting. If an application uses multiple evcaches, they all share the same config, which is NOT optimal, but maybe ok for default.evcache.max.data.size. Ideally, we should have different config for different evache as each may have different requirement. per cache FP should use client.getPool().getEVCacheClientPoolManager().getEVCacheConfig().getPropertyRepository().

For backward compatibility, we should not change default.evcache.max.data.size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the statement is not correct, but please correct me if I am wrong.
First of all

  EVCacheConfig.propertyRepository is a static field (line 27). There is only ever one value for the entire JVM, regardless of how many EVCacheConfig instances exist.     
  EVCacheConfig is also a singleton with a single INSTANCE.                                                                                                                
                                                                                                                                                                           
  Following the longer path:                                                                                                                                               
  - client.getPool().getEVCacheClientPoolManager() returns the pool manager                                                                                                
  - .getEVCacheConfig() returns this.evcConfig — which is set from line 153: new EVCacheClientPoolManager(..., EVCacheConfig.getInstance())                                
  - .getPropertyRepository() returns the static propertyRepository field                                                                                                   
                                                                                                                                                                           
  Both paths terminate at the exact same static field. They return the same object.

Secondly,

If an application uses multiple evcaches, they all share the same config, which is NOT optimal

Agree, but IIUC the way the client code achieves per cluster config is by appending evcache cluster name in front of the FP and fallback to the global one (prefix with "evcache") if not provided. (not saying it's good or bad though, but in this case, if your intention is to achieve per cluster config, then i don't think it does the work.)

if (in == null) throw new NullPointerException("Can't compress null");

CompressionAlgorithm compressionAlgorithm = compressionAlgorithmProperty == null ? CompressionAlgorithm.GZIP
: CompressionAlgorithm.valueOf(compressionAlgorithmProperty.orElse(CompressionAlgorithm.GZIP.name()).get().toUpperCase());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must the choice of algorithm and the level be dynamic? Could this be something we set at evcache client creation time? This would mean you would need to restart the server to pick up a client change.

/**
* Transcoder that serializes and compresses objects.
*/
public class EVCacheSerializingTranscoder extends BaseSerializingTranscoder implements

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@janewang1680
Can we move the compression changes from EVCacheSerializingTranscoder down it's subclass EVCacheTranscoder.

Benefits:

  1. Isolates most of the compression logic to a single transcoder file.
  2. Removes the need for setter methods, default property constants, and null checks (compressionAlgorithmProperty == null ? ...,
  3. Keeps EVCacheSerializingTranscoder as a generic serialize base class.

public EVCacheTranscoder(String appName, PropertyRepository config, int max, int compressionThreshold) {
super(appName, max);
setCompressionThreshold(compressionThreshold);
Property<String> algoProperty = getProperty(config, "evcacheclient.compression.algo", String.class);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this so the naming convention is uniform?

default.evcache.compression.algo

setCompressionThreshold(compressionThreshold);
Property<String> algoProperty = getProperty(config, "evcacheclient.compression.algo", String.class);
setCompressionAlgorithmProperty(algoProperty);
Property<Integer> zstdLevelProperty = getProperty(config, "evcacheclient.compression.zstd.level", Integer.class);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

default.evcache.compression.zstd.level

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants