From 30a6704e577f8725c0db9ae6d05d15941f0bbdeb Mon Sep 17 00:00:00 2001 From: Blaz Snuderl Date: Thu, 23 Apr 2026 09:30:48 +0200 Subject: [PATCH] Cache compiled programs by serialized rule message Fields with identical resolved rules (same rule type + same values, e.g. multiple fields sharing the same predefined constraint like an evm_hash pattern) currently re-create their CompiledProgram list independently, even though the resulting programs are equivalent. Add a static ConcurrentHashMap keyed by the serialized rule message bytes that caches the built List. On hit we short-circuit the rebuild; on miss we populate it at the end of compile(). In a workload with 14 descriptor types and 19 fields sharing 2 predefined rules, this reduced warmup time from ~58ms to ~3.6ms. --- .../java/build/buf/protovalidate/RuleCache.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/build/buf/protovalidate/RuleCache.java b/src/main/java/build/buf/protovalidate/RuleCache.java index 44497dae..b12aed14 100644 --- a/src/main/java/build/buf/protovalidate/RuleCache.java +++ b/src/main/java/build/buf/protovalidate/RuleCache.java @@ -64,6 +64,13 @@ private CelRule(AstExpression astExpression, FieldDescriptor field, FieldPath ru private static final Map> descriptorMap = new ConcurrentHashMap<>(); + /** + * Concurrent map for caching compiled programs by the serialized rule message bytes. This allows + * fields sharing the same resolved rules (same type + same values) to reuse compiled programs. + */ + private static final Map> programCache = + new ConcurrentHashMap<>(); + /** The environment to use for evaluation. */ private final Cel cel; @@ -109,6 +116,11 @@ List compile( return Collections.emptyList(); } Message message = resolved.message; + com.google.protobuf.ByteString cacheKey = message.toByteString(); + List cachedPrograms = programCache.get(cacheKey); + if (cachedPrograms != null) { + return cachedPrograms; + } List completeProgramList = new ArrayList<>(); for (Map.Entry entry : message.getAllFields().entrySet()) { FieldDescriptor ruleFieldDesc = entry.getKey(); @@ -134,7 +146,9 @@ List compile( "failed to evaluate rule " + rule.astExpression.source.id, e); } } - return Collections.unmodifiableList(programs); + List result = Collections.unmodifiableList(programs); + programCache.putIfAbsent(cacheKey, result); + return result; } private @Nullable List compileRule(