{"record":{"id":"d50d2584c22f3373","repo":"prestodb/presto","slug":"error-decoding-historicalplanstatistics-value","errorCode":null,"errorMessage":"Error decoding historicalPlanStatistics value","messagePattern":"Error decoding historicalPlanStatistics value","errorType":"exception","errorClass":"RedisProviderSerdeException","httpStatus":null,"severity":"error","filePath":"redis-hbo-provider/src/main/java/com/facebook/presto/statistic/HistoricalStatisticsSerde.java","lineNumber":78,"sourceCode":"        SliceOutput dynamicSliceOutput = new DynamicSliceOutput(ESTIMATED_BUFFER_SIZE_BYTES);\n        try {\n            ThriftProtocolUtils.write(historicalPlanStatistics, writeCodec, Protocol.BINARY, dynamicSliceOutput);\n            return ByteBuffer.wrap(dynamicSliceOutput.slice().getBytes());\n        }\n        catch (ThriftProtocolException e) {\n            throw new RedisProviderSerdeException(\"Error encoding historicalPlanStatistics value\", e);\n        }\n    }\n\n    @Override\n    public HistoricalPlanStatistics decodeValue(ByteBuffer byteBuffer)\n    {\n        ThriftCodec<HistoricalPlanStatistics> readCodec = thriftCodecManager.getCodec(HistoricalPlanStatistics.class);\n        try {\n            return ThriftProtocolUtils.read(readCodec, Protocol.BINARY, Slices.wrappedBuffer(byteBuffer).getInput());\n        }\n        catch (ThriftProtocolException e) {\n            throw new RedisProviderSerdeException(\"Error decoding historicalPlanStatistics value\", e);\n        }\n    }\n}\n","sourceCodeStart":60,"sourceCodeEnd":82,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/redis-hbo-provider/src/main/java/com/facebook/presto/statistic/HistoricalStatisticsSerde.java#L60-L82","documentation":"HistoricalStatisticsSerde is a Redis codec that deserializes Redis byte values into HistoricalPlanStatistics using the Drift Thrift codec with the BINARY protocol. When the underlying ThriftProtocolUtils.read call fails with a ThriftProtocolException, it is wrapped in a RedisProviderSerdeException with this message. This means the bytes read from Redis are not a valid Thrift-BINARY-encoded HistoricalPlanStatistics payload.","triggerScenarios":"Redis returns bytes that cannot be parsed as a Thrift BINARY encoding of HistoricalPlanStatistics: truncated/corrupted records, values written with a different Thrift protocol (e.g. COMPACT) or an incompatible HistoricalPlanStatistics schema version, stale entries persisted by an older Presto build, or manual writes to the Redis keyspace by another tool.","commonSituations":"Upgrading Presto where the HistoricalPlanStatistics Thrift schema changed but old Redis entries remain; pointing a cluster at a Redis DB populated by a different component or protocol; Redis data corruption or partial writes; keys written by a test/other service using a non-Binary protocol.","solutions":["Flush the Redis keyspace (or delete the offending plan-hash keys) so stale/incompatible encoded values are repopulated by encodeValue with the current schema.","Verify values were written with Protocol.BINARY via ThriftProtocolUtils.write and not by another serializer/protocol; re-encode with the current writer.","Check for a Presto/Drift version skew between the writer and reader clusters and align versions so the Thrift schema matches.","Inspect the wrapped ThriftProtocolException (getCause) for the exact decode offset/field to confirm corruption vs schema mismatch.","Add entry validation (try decode, skip/log on RedisProviderSerdeException) so a single bad entry does not break statistics collection."],"exampleFix":"// before\nreturn ThriftProtocolUtils.read(readCodec, Protocol.BINARY, Slices.wrappedBuffer(byteBuffer).getInput());\n// after\nif (byteBuffer == null || !byteBuffer.hasRemaining()) {\n    return null; // skip empty/invalid Redis value instead of failing decode\n}\nreturn ThriftProtocolUtils.read(readCodec, Protocol.BINARY, Slices.wrappedBuffer(byteBuffer).getInput());","handlingStrategy":"try-catch","validationCode":"// validate before trusting Redis bytes\nif (byteBuffer == null || !byteBuffer.hasRemaining()) {\n    throw new IllegalArgumentException(\"Empty value for historicalPlanStatistics\");\n}\nHistoricalPlanStatistics stats;\ntry {\n    stats = ThriftProtocolUtils.read(readCodec, Protocol.BINARY, Slices.wrappedBuffer(byteBuffer).getInput());\n}\ncatch (ThriftProtocolException e) {\n    throw new RedisProviderSerdeException(\"Error decoding historicalPlanStatistics value\", e);\n}","typeGuard":"private static boolean isDecodableValue(ByteBuffer buf)\n{\n    return buf != null && buf.hasRemaining();\n}","tryCatchPattern":"try {\n    HistoricalPlanStatistics stats = serde.decodeValue(byteBuffer);\n    useStats(stats);\n}\ncatch (RedisProviderSerdeException e) {\n    log.warn(e, \"Skipping undecodable historicalPlanStatistics entry (cause: %s)\", e.getCause());\n    redis.delete(key); // drop corrupt/stale entry so it is re-encoded\n}","preventionTips":["Always write values with the matching encoder (encodeValue, Protocol.BINARY) rather than hand-crafting bytes","Clear or version-key the Redis namespace when upgrading Presto/Drift so old-schema entries are not decoded by new code","Wrap decode calls per entry so one corrupt key cannot break the whole statistics flow","Monitor and alert on RedisProviderSerdeException rates to catch schema skew early","Use a version prefix in Redis keys to distinguish schema generations"],"tags":["thrift","redis","deserialization","codec","presto"],"backgroundTag":"thrift-decode-failed","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}