prestodb/presto · critical · IllegalArgumentException

Deserialized SingleMapBlock violates invariants: key

Error message

Deserialized SingleMapBlock violates invariants: key %d, value %d

What it means

SingleMapBlockEncoding.readBlock deserializes key and value blocks plus an optional hash table from a SliceInput. It validates that keyBlock and valueBlock have equal position counts, because every map entry needs one key and one value. A mismatch means the serialized stream is corrupted or written by an incompatible producer, so readBlock throws IllegalArgumentException with the offending counts.

Solutions

  1. Verify all nodes run the same Presto version; roll back or upgrade to align serialization formats.
  2. Re-generate the corrupted spill/exchange data or retry the query; if corruption recurs, check storage/network integrity.
  3. If a custom serializer produced the bytes, fix the writer so it emits key and value blocks with identical position counts.
  4. Add a checksum/validation layer on serialized pages to detect truncation early.

Example fix

// before
// writer emits keys and values from different sources
SliceOutput out = ...;
blockEncodings.writeBlock(out, keyBlock);
blockEncodings.writeBlock(out, valueBlock.slice(0, keyBlock.getPositionCount() - 1)); // mismatch
// after
checkState(keyBlock.getPositionCount() == valueBlock.getPositionCount());
blockEncodings.writeBlock(out, keyBlock);
blockEncodings.writeBlock(out, valueBlock);
Defensive patterns

Strategy: try-catch

Validate before calling

// before decoding, sanity-check the serialized stream size
if (sliceInput.available() < MIN_MAP_BLOCK_BYTES) {
    throw new IOException("truncated map block payload");
}

Try / catch

try {
    Block block = blockEncoding.readBlock(sliceInput);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Deserialized SingleMapBlock violates invariants")) {
        throw new CorruptedPageException("Discard/regenerate spill or exchange data; check cluster version skew", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Reading a MapBlock from serialized page data where the key block's positionCount differs from the value block's positionCount (e.g. truncated stream, wrong byte offsets, cross-version serialization incompatibility, or data written by a buggy custom serializer).

Common situations: Exchange/spill file corruption, mixing Presto versions in a cluster (different block encodings), third-party connectors writing raw serialized blocks, network truncation.

Related errors


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

Appendix: source

Thrown at presto-common/src/main/java/com/facebook/presto/common/block/SingleMapBlockEncoding.java:74

            sliceOutput.appendInt(-1);
        }
    }

    @Override
    public Block readBlock(BlockEncodingSerde blockEncodingSerde, SliceInput sliceInput)
    {
        Block keyBlock = blockEncodingSerde.readBlock(sliceInput);
        Block valueBlock = blockEncodingSerde.readBlock(sliceInput);

        int hashTableLength = sliceInput.readInt();
        int[] hashTable = null;
        if (hashTableLength >= 0) {
            hashTable = new int[hashTableLength];
            sliceInput.readBytes(wrappedIntArray(hashTable));
        }

        if (keyBlock.getPositionCount() != valueBlock.getPositionCount()) {
            throw new IllegalArgumentException(
                    format("Deserialized SingleMapBlock violates invariants: key %d, value %d", keyBlock.getPositionCount(), valueBlock.getPositionCount()));
        }

        if (hashTable != null && keyBlock.getPositionCount() * HASH_MULTIPLIER != hashTable.length) {
            throw new IllegalArgumentException(
                    format("Deserialized SingleMapBlock violates invariants: expected hashtable size %d, actual hashtable size %d", keyBlock.getPositionCount() * HASH_MULTIPLIER, hashTable.length));
        }

        MapBlock mapBlock = MapBlock.createMapBlockInternal(
                0,
                1,
                Optional.empty(),
                new int[] {0, keyBlock.getPositionCount()},
                keyBlock,
                valueBlock,
                new HashTables(Optional.ofNullable(hashTable), 1));

        return new SingleMapBlock(0, 0, keyBlock.getPositionCount() * 2, mapBlock);

View on GitHub (pinned to 55bb57d202)