Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.netflix.archaius.api.PropertyRepository;
import com.netflix.evcache.EVCacheInMemoryCache.DataNotFoundException;
import com.netflix.evcache.EVCacheLatch.Policy;
import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.dto.KeyMapDto;
import com.netflix.evcache.event.EVCacheEvent;
import com.netflix.evcache.event.EVCacheEventListener;
Expand Down Expand Up @@ -76,6 +77,7 @@
public class EVCacheImpl implements EVCache, EVCacheImplMBean {

private static final Logger log = LoggerFactory.getLogger(EVCacheImpl.class);
private static final int COMPRESSION_THRESHOLD_BYTES = Integer.MAX_VALUE;

private final Clock clock;
private final String _appName;
Expand Down Expand Up @@ -164,9 +166,9 @@ public class EVCacheImpl implements EVCache, EVCacheImplMBean {
this.maxHashLength = propertyRepository.get(appName + ".max.hash.length", Integer.class).orElse(-1);
this.encoderBase = propertyRepository.get(appName + ".hash.encoder", String.class).orElse("base64");
this.autoHashKeys = propertyRepository.get(_appName + ".auto.hash.keys", Boolean.class).orElseGet("evcache.auto.hash.keys").orElse(false);
this.evcacheValueTranscoder = new EVCacheTranscoder(_appName, propertyRepository);
evcacheValueTranscoder.setCompressionThreshold(Integer.MAX_VALUE);

final EVCacheTranscoderProperties evCacheTranscoderProperties = new EVCacheTranscoderProperties(_appName, propertyRepository);
this.evcacheValueTranscoder = new EVCacheTranscoder(evCacheTranscoderProperties);
evcacheValueTranscoder.setCompressionThreshold(COMPRESSION_THRESHOLD_BYTES);
// default max key length is 200, instead of using what is defined in MemcachedClientIF.MAX_KEY_LENGTH (250). This is to accommodate
// auto key prepend with appname for duet feature.
this.maxKeyLength = propertyRepository.get(_appName + ".max.key.length", Integer.class).orElseGet("evcache.max.key.length").orElse(200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdInputStream;
import com.netflix.archaius.api.Property;
import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.config.EVCacheTranscoderProperties.CompressionAlgorithm;
import com.netflix.evcache.metrics.EVCacheMetricsFactory;
import com.netflix.evcache.util.EVCacheConfig;
import com.netflix.spectator.api.BasicTag;
import com.netflix.spectator.api.DistributionSummary;
import com.netflix.spectator.api.Tag;
Expand Down Expand Up @@ -70,16 +72,12 @@ public class EVCacheSerializingTranscoder extends BaseSerializingTranscoder impl
static final int SPECIAL_DOUBLE = (7 << 8);
static final int SPECIAL_BYTEARRAY = (8 << 8);

public enum CompressionAlgorithm { GZIP, ZSTD }

public static final int DEFAULT_ZSTD_COMPRESSION_LEVEL = 3;

private static final int ZSTD_MAGIC = 0xFD2FB528;

private final TranscoderUtils tu = new TranscoderUtils(true);
private Property<String> compressionAlgorithmProperty;
private Property<Integer> zstdLevelProperty;
protected final String appName;

protected final EVCacheTranscoderProperties properties;

private final EnumMap<CompressionAlgorithm, DistributionSummary> compressionRatioSummaries;

/**
Expand All @@ -90,19 +88,26 @@ public EVCacheSerializingTranscoder() {
}

/**
* Get a serializing transcoder that specifies the max data size.
* Get a serializing transcoder that specifies the max data size. Builds a default
* {@link EVCacheTranscoderProperties} bundle from {@link EVCacheConfig#getInstance()} —
* subclasses/callers that want per-app resolution should use
* {@link #EVCacheSerializingTranscoder(EVCacheTranscoderProperties, int)}.
*/
public EVCacheSerializingTranscoder(int max) {
this(null, max);
this(new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()), max);
}

/**
* Get a serializing transcoder that specifies the owning app name and the max data size.
* Get a serializing transcoder with the supplied transcoder-property bundle. The bundle is
* exposed to subclasses via {@link #properties} so downstream transcoders can consult the
* same three-level (per-app → global → static default) resolution chain. Compression
* algorithm and zstd level are resolved dynamically on every {@code compress()} call via
* the {@link Property} handles below, so live FP updates propagate without JVM restart.
*/
public EVCacheSerializingTranscoder(String appName, int max) {
public EVCacheSerializingTranscoder(EVCacheTranscoderProperties properties, int max) {
super(max);
this.appName = appName;
this.compressionRatioSummaries = buildCompressionRatioSummaries(appName);
this.properties = properties;
this.compressionRatioSummaries = buildCompressionRatioSummaries(properties.getAppName());
}

private static EnumMap<CompressionAlgorithm, DistributionSummary> buildCompressionRatioSummaries(String appName) {
Expand All @@ -118,14 +123,6 @@ private static EnumMap<CompressionAlgorithm, DistributionSummary> buildCompressi
return summaries;
}

public void setCompressionAlgorithmProperty(Property<String> algorithmProperty) {
this.compressionAlgorithmProperty = algorithmProperty;
}

public void setCompressionLevelProperty(Property<Integer> levelProperty) {
this.zstdLevelProperty = levelProperty;
}

@Override
public boolean asyncDecode(CachedData d) {
if ((d.getFlags() & COMPRESSED) != 0 || (d.getFlags() & SERIALIZED) != 0) {
Expand Down Expand Up @@ -242,19 +239,17 @@ public CachedData encode(Object o) {
protected byte[] compress(byte[] in) {
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());
CompressionAlgorithm compressionAlgorithm = properties.getCompressionAlgorithmProperty().get();

byte[] compressed;
switch (compressionAlgorithm) {
case ZSTD:
int zstdLevel = zstdLevelProperty == null ? DEFAULT_ZSTD_COMPRESSION_LEVEL
: zstdLevelProperty.orElse(DEFAULT_ZSTD_COMPRESSION_LEVEL).get();
logger.debug("algorithm: {}, level: {}, appName: {}", compressionAlgorithm, zstdLevel, appName);
int zstdLevel = properties.getZstdCompressionLevelProperty().get();
logger.debug("algorithm: {}, level: {}, appName: {}", compressionAlgorithm, zstdLevel, properties.getAppName());
compressed = Zstd.compress(in, zstdLevel);
break;
case GZIP:
logger.debug("algorithm: {}, appName: {}", compressionAlgorithm, appName);
logger.debug("algorithm: {}, appName: {}", compressionAlgorithm, properties.getAppName());
compressed = super.compress(in);
break;
default:
Expand Down Expand Up @@ -293,7 +288,8 @@ private byte[] decompressZstd(byte[] in) {
}
// Slow path: declared size is 0, unknown (-1), or invalid (-2) — stream-decode and let
// ZstdInputStream surface any frame errors.
logger.warn("Zstd frame missing content-size header (getFrameContentSize={}); falling back to stream decode. appName={}", originalSize, appName);
logger.warn("Zstd frame missing content-size header (getFrameContentSize={}); falling back to stream decode. appName={}",
originalSize, properties.getAppName());
ZstdInputStream zis = null;
try {
zis = new ZstdInputStream(new ByteArrayInputStream(in));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,76 @@
package com.netflix.evcache;

import com.netflix.archaius.api.Property;
import com.netflix.archaius.api.PropertyRepository;
import com.netflix.evcache.config.EVCacheTranscoderProperties;
import com.netflix.evcache.pool.EVCacheValue;
import com.netflix.evcache.pool.EVCacheValueSerde;
import com.netflix.evcache.util.EVCacheConfig;

import net.spy.memcached.CachedData;

import static com.netflix.evcache.config.EVCacheTranscoderProperties.DEFAULT_COMPRESSION_THRESHOLD_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.DEFAULT_MAX_DATA_SIZE_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.Key.COMPRESSION_THRESHOLD_BYTES;
import static com.netflix.evcache.config.EVCacheTranscoderProperties.Key.MAX_DATA_SIZE_BYTES;

public class EVCacheTranscoder extends EVCacheSerializingTranscoder {

public EVCacheTranscoder() {
this((String) null);
/**
* @param properties the transcoder property bundle.
* {@link EVCacheTranscoderProperties.Key#MAX_DATA_SIZE_BYTES} and
* {@link EVCacheTranscoderProperties.Key#COMPRESSION_THRESHOLD_BYTES}
* are read from the bundle at construction and set as fields on the
* underlying {@link EVCacheSerializingTranscoder} for legacy reasons;
* new properties should be added to {@link EVCacheTranscoderProperties}
* rather than plumbed through further constructor arguments.
*/
public EVCacheTranscoder(EVCacheTranscoderProperties properties) {
this(properties.getProperty(MAX_DATA_SIZE_BYTES, Integer.class, DEFAULT_MAX_DATA_SIZE_BYTES),
properties.getProperty(COMPRESSION_THRESHOLD_BYTES, Integer.class, DEFAULT_COMPRESSION_THRESHOLD_BYTES),
properties
);
}

public EVCacheTranscoder(String appName) {
this(appName, EVCacheConfig.getInstance().getPropertyRepository());
public EVCacheTranscoder() {
this(new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

public EVCacheTranscoder(int max) {
this(null, max);
}

public EVCacheTranscoder(String appName, int max) {
this(appName, EVCacheConfig.getInstance().getPropertyRepository(), max);
this(max, new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

public EVCacheTranscoder(int max, int compressionThreshold) {
this(null, max, compressionThreshold);
}

public EVCacheTranscoder(String appName, int max, int compressionThreshold) {
this(appName, EVCacheConfig.getInstance().getPropertyRepository(), max, compressionThreshold);
this(max, compressionThreshold, new EVCacheTranscoderProperties(null, EVCacheConfig.getInstance().getPropertyRepository()));
}

/**
* Repository-aware constructors. The compression algorithm/level are read dynamically from the
* supplied {@link PropertyRepository}, so callers must pass the repository that is wired to Fast
* Properties (e.g. {@code poolManager.getEVCacheConfig().getPropertyRepository()}) for FP overrides
* to take effect. The no-repository constructors above fall back to {@link EVCacheConfig#getInstance()}.
*/
public EVCacheTranscoder(String appName, PropertyRepository config) {
this(appName, config, config.get("default.evcache.max.data.size", Integer.class).orElse(20 * 1024 * 1024).get());
private EVCacheTranscoder(int max, EVCacheTranscoderProperties properties) {
this(max, properties.getProperty(COMPRESSION_THRESHOLD_BYTES, Integer.class, DEFAULT_COMPRESSION_THRESHOLD_BYTES), properties);
}

public EVCacheTranscoder(String appName, PropertyRepository config, int max) {
this(appName, config, max, config.get("default.evcache.compression.threshold", Integer.class).orElse(120).get());
private EVCacheTranscoder(int max, int compressionThreshold, EVCacheTranscoderProperties properties) {
super(properties, max);
setCompressionThreshold(compressionThreshold);
}

public EVCacheTranscoder(String appName, PropertyRepository config, int max, int compressionThreshold) {
super(appName, max);
setCompressionThreshold(compressionThreshold);
Property<String> algoProperty = getProperty(config, "default.evcacheclient.compression.algo", String.class);
setCompressionAlgorithmProperty(algoProperty);
Property<Integer> zstdLevelProperty = getProperty(config, "default.evcacheclient.compression.zstd.level", Integer.class);
setCompressionLevelProperty(zstdLevelProperty);
@Override
public CachedData encode(Object o) {
if (o != null && o instanceof CachedData) return (CachedData) o;
return super.encode(o);
}

/**
* Resolves a property preferring the appName-prefixed key and falling back to the global {@code evcache.*} key when
* no app-specific override exists.
*/
private <T> Property<T> getProperty(PropertyRepository config, String key, Class<T> type) {
if (appName == null || appName.isEmpty()) {
return config.get("default." + key, type);
@Override
protected byte[] serialize(Object o) {
if (this.properties.isBinarySerializationEnabled() && o instanceof EVCacheValue) {
return EVCacheValueSerde.serialize((EVCacheValue) o);
}
return config.get(appName + "." + key, type).orElseGet(key);
return super.serialize(o);
}

@Override
public CachedData encode(Object o) {
if (o != null && o instanceof CachedData) return (CachedData) o;
return super.encode(o);
protected Object deserialize(byte[] in) {
if (EVCacheValueSerde.isBinaryFormat(in)) {
return EVCacheValueSerde.deserialize(in);
}
return super.deserialize(in);
}

}
Loading
Loading