prestodb/presto · error

FUNCTION_IMPLEMENTATION_ERROR

FUNCTION_IMPLEMENTATION_ERROR

Error message

loadAll called with a non-homogeneous collection of cache keys

What it means

ColumnCardinalityCache.loadAll (a LoadingCache CacheLoader) assumes, as an implementation simplification, that every CacheKey in a batch shares the same schema/table/family/qualifier so a single metrics-table scan can serve the whole batch. When any key differs in one of those fields it throws FUNCTION_IMPLEMENTATION_ERROR - an internal invariant violation, not user input error.

Source

Thrown at presto-accumulo/src/main/java/com/facebook/presto/accumulo/index/ColumnCardinalityCache.java:376

        }

        @Override
        public Map<CacheKey, Long> loadAll(Iterable<? extends CacheKey> keys)
                throws Exception
        {
            int size = Iterables.size(keys);
            if (size == 0) {
                return ImmutableMap.of();
            }

            LOG.debug("Loading %s exact ranges from Accumulo", size);

            // In order to simplify the implementation, we are making a (safe) assumption
            // that the CacheKeys will all contain the same combination of schema/table/family/qualifier
            // This is asserted with the below implementation error just to make sure
            CacheKey anyKey = stream(keys).findAny().get();
            if (stream(keys).anyMatch(k -> !k.getSchema().equals(anyKey.getSchema()) || !k.getTable().equals(anyKey.getTable()) || !k.getFamily().equals(anyKey.getFamily()) || !k.getQualifier().equals(anyKey.getQualifier()))) {
                throw new PrestoException(FUNCTION_IMPLEMENTATION_ERROR, "loadAll called with a non-homogeneous collection of cache keys");
            }

            Map<Range, CacheKey> rangeToKey = stream(keys).collect(Collectors.toMap(CacheKey::getRange, Function.identity()));
            LOG.debug("rangeToKey size is %s", rangeToKey.size());

            // Get metrics table name and the column family for the scanner
            String metricsTable = getMetricsTableName(anyKey.getSchema(), anyKey.getTable());
            Text columnFamily = new Text(getIndexColumnFamily(anyKey.getFamily().getBytes(UTF_8), anyKey.getQualifier().getBytes(UTF_8)).array());

            BatchScanner scanner = connector.createBatchScanner(metricsTable, anyKey.getAuths(), 10);
            try {
                scanner.setRanges(stream(keys).map(CacheKey::getRange).collect(Collectors.toList()));
                scanner.fetchColumn(columnFamily, CARDINALITY_CQ_AS_TEXT);

                // Create a new map to hold our cardinalities for each range, returning a default of
                // Zero for each non-existent Key
                Map<CacheKey, Long> rangeValues = new HashMap<>();
                stream(keys).forEach(key -> rangeValues.put(key, 0L));

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Do not call ColumnCardinalityCache.loadAll directly; go through getCardinalities, which batches keys correctly.
  2. If you modified the cache or its callers, partition keys by (schema, table, family, qualifier) and issue one loadAll call per group.
  3. If you hit this in a stock connector, file a bug with the full stack trace - it indicates a connector defect.
  4. In tests, build CacheKeys that all share the same schema/table/family/qualifier.

Example fix

// before
cache.loadAll(keys); // keys span multiple tables/columns
// after
Map<List<String>, List<CacheKey>> grouped = keys.stream()
    .collect(Collectors.groupingBy(k -> Arrays.asList(k.getSchema(), k.getTable(), k.getFamily(), k.getQualifier())));
grouped.values().forEach(cache::loadAll);
Defensive patterns

Strategy: validation

Validate before calling

// before calling cache.loadAll directly, ensure keys are homogeneous
boolean homogeneous(List<ColumnCardinalityCache.CacheKey> keys) {
    var any = keys.get(0);
    return keys.stream().allMatch(k ->
        any.getSchema().equals(k.getSchema()) && any.getTable().equals(k.getTable())
        && any.getFamily().equals(k.getFamily()) && any.getQualifier().equals(k.getQualifier()));
}

Type guard

boolean sameColumn(ColumnCardinalityCache.CacheKey a, ColumnCardinalityCache.CacheKey b) {
    return a.getSchema().equals(b.getSchema()) && a.getTable().equals(b.getTable())
        && a.getFamily().equals(b.getFamily()) && a.getQualifier().equals(b.getQualifier());
}

Try / catch

try {
    cache.loadAll(keys);
} catch (PrestoException e) {
    if (e.getErrorCode().getName().equals("FUNCTION_IMPLEMENTATION_ERROR")) {
        // partition keys by schema/table/family/qualifier and load per group
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: A batch of cache keys with mixed schema/table/family/qualifier values is passed to loadAll - only possible via a code-level bug in how keys are grouped before invoking the cache, or by calling the cache loader directly with heterogeneous keys.

Common situations: Custom modifications or patches to ColumnCardinalityCache; calling loadAll directly in tests or tooling with arbitrary keys; a connector change that no longer partitions keys per column before invoking the cache.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/1c05d4ba4788ee59. Report an issue: GitHub.