发布

  • [OPIK-6370] [BE] fix: tolerate plain-string UUIDs in Redis stream deserialization (#6610)

    frostbyte_neo 发布于 2026-05-07 15:52:35 +00:00

    • [NA] [BE] fix: tolerate plain-string UUIDs in Redis stream deserialization

    The Redis stream codec used by experiment_denormalization_stream and other
    Java-codec streams wraps a Jackson ObjectMapper with default typing enabled.
    Older opik-backend versions ran on a different default-typing configuration
    and left messages with UUID fields serialized as plain strings in PEL; the
    current version's deserializer expects Jackson's As.WRAPPER_ARRAY shape
    (["java.util.UUID",""]). XAUTOCLAIM repeatedly tries to reclaim those
    stuck messages, fails with MismatchedInputException, and emits an ERROR log
    every pending-message-duration window.

    Add a LenientUUIDDeserializer that accepts both shapes and register it on
    the mapper handed to JsonJacksonCodec inside RedisStreamCodec. Scoped to
    the Redis stream codec only — the global JsonUtils.MAPPER is untouched, so
    HTTP API JSON behavior is unchanged.

    Includes a unit test covering plain-string, As.WRAPPER_ARRAY, and a
    round-trip case to confirm we don't break the happy path.

    • [NA] [BE] fix: override deserializeWithType so the lenient UUID deserializer fires under WRAPPER_ARRAY default typing

    The original commit registered LenientUUIDDeserializer via addDeserializer
    on the codec mapper, but that only wires it as the value-level deserializer
    called AFTER the TypeDeserializer strips the wrapper. With Redisson's
    JsonJacksonCodec enabling default typing, every UUID property gets an
    AsArrayTypeDeserializer in front of it: it expects START_ARRAY for the
    type id and explicitly throws MismatchedInputException on a bare string
    before delegating to the value deserializer. So plain-string UUIDs in PEL
    were never reaching our lenient logic.

    Fix by overriding deserializeWithType: it runs BEFORE the type deserializer
    takes over. We peek at the current token and short-circuit when it's a
    VALUE_STRING (legacy bare-string format), letting the standard UUIDDeserializer
    parse it directly. For START_ARRAY we delegate to the type deserializer's
    normal path, so current WRAPPER_ARRAY-format messages still decode correctly.

    Also adds a production-shape test that wires LenientUUIDDeserializer onto a
    real JsonJacksonCodec (default typing on, snake_case naming, etc.) and
    verifies all three round-trips: encoded shape contains the wrapper as
    expected, plain-string payloads decode, WRAPPER_ARRAY payloads decode.
    This is the test that caught the gap left by the original commit.

    • [NA] [BE] fix: assert END_ARRAY in WRAPPER_ARRAY UUID branch to reject malformed payloads

    The wrapper-array branch advanced past the type-id and value tokens but
    didn't assert the next token was END_ARRAY, so a payload like
    ["...","","extra"] would silently parse the UUID and leave the
    parser positioned mid-array — corrupting the next field. Throw a
    MismatchedInputException when the closing token isn't END_ARRAY.

    Adds a regression test that builds a 3-element wrapper-array payload and
    asserts the deserializer rejects it instead of silently accepting it.

    下载附件