{"record":{"id":"ef8f9215d4e59172","repo":"apache/druid","slug":"ran-out-of-objects-while-reading-aggregators-from","errorCode":null,"errorMessage":"Ran out of objects while reading aggregators from cache!","messagePattern":"Ran out of objects while reading aggregators from cache!","errorType":"exception","errorClass":"ISE","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/CacheStrategy.java","lineNumber":157,"sourceCode":"   * When using the result level cache, the agg values seen here are\n   * finalized values generated by AggregatorFactory.finalizeComputation().\n   * These finalized values are deserialized from the cache as generic Objects, which will\n   * later be reserialized and returned to the user without further modification.\n   * Because the agg values are deserialized as generic Objects, the values are subject to the same\n   * type consistency issues handled by DimensionHandlerUtils.convertObjectToType() in the pullFromCache implementations\n   * for dimension values (e.g., a Float would become Double).\n   */\n  static void fetchAggregatorsFromCache(\n      List<AggregatorFactory> aggregators,\n      Iterator<Object> resultIter,\n      boolean isResultLevelCache,\n      AddToResultFunction addToResultFunction\n  )\n  {\n    for (int i = 0; i < aggregators.size(); i++) {\n      final AggregatorFactory aggregator = aggregators.get(i);\n      if (!resultIter.hasNext()) {\n        throw new ISE(\"Ran out of objects while reading aggregators from cache!\");\n      }\n\n      ColumnType resultType = aggregator.getResultType();\n      ColumnType intermediateType = aggregator.getIntermediateType();\n\n      boolean needsDeserialize = !isResultLevelCache || resultType.equals(intermediateType);\n\n      if (needsDeserialize) {\n        addToResultFunction.apply(aggregator.getName(), i, aggregator.deserialize(resultIter.next()));\n      } else {\n        addToResultFunction.apply(aggregator.getName(), i, resultIter.next());\n      }\n    }\n  }\n\n  interface AddToResultFunction\n  {\n    void apply(String aggregatorName, int aggregatorIndex, Object object);","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/CacheStrategy.java#L139-L175","documentation":"fetchAggregatorsFromCache iterates the cached result objects in lockstep with the configured AggregatorFactory list; if the cache payload runs out of entries before all aggregators are populated, it throws an ISE. This signals that the cached blob's shape doesn't match the current aggregator spec — a cache-corruption or cache-key/versioning mismatch, never a user data problem.","triggerScenarios":"Reading a segment-level or result-level cache entry whose number of result objects is fewer than aggregators.size(): stale cache entries from before a query spec/schema change, a cache populated by a different Druid version, or corrupted/partially-written cache rows (e.g. in memcached/Redis).","commonSituations":"Upgrading Druid or changing an ingestion/aggregation spec without bumping the cache identifier prefix (druid.cache.identifier / cache config), so old cache entries deserialize against new expectations; hit-ratio misconfigurations mixing cache namespaces; truncated cache values from an eviction-buggy external cache.","solutions":["Clear the cache (or bump the cache identifierPrefix in the cache config) so stale entries created with a different schema/version are invalidated.","Confirm all Druid nodes sharing the cache run the same version; mixed-version clusters sharing one cache commonly produce shape mismatches.","Reproduce with caching disabled (set druid.caching.cache.enable=false or useCache/populateCache=false in query context) to confirm the query itself is fine.","If it recurs, check the external cache (memcached/Redis) for truncation/eviction bugs and enable cache-populate/populateCache logging."],"exampleFix":"// before\ncacheConfig = CacheConfig.builder().setIdentifierPrefix(\"druid-segment-v1\")... // reused across upgrades\n// after\nCacheConfig cacheConfig = CacheConfig.builder().setIdentifierPrefix(\"druid-segment-v2-<newAggregatorSchema>\")... // bump prefix when aggregator schema or Druid version changes","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return cachedStrategyRead(cacheEntry, aggregators);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Ran out of objects while reading aggregators\")) {\n    cacheClient.invalidate(cacheKey); // drop mismatched entry\n    return executeWithoutCache(query); // recompute from segments\n  }\n  throw e;\n}","preventionTips":["Bump the cache identifierPrefix whenever the aggregation schema or Druid version changes.","Keep all cluster nodes on the same Druid version when sharing a cache.","Periodically flush the cache after upgrades and schema migrations.","Validate external cache integrity (memcached/Redis) if truncation is suspected."],"tags":["cache","deserialization","internal-state","aggregators"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}