シリアライズできないマップにでくわした時
どういう話?
kafkaでアプリケーションやっている→何かのシリアライザーを使っている シリアライザーに想定外のデータが渡ってしまうことがある
アプリケーションが停止してしまい困る
のを何とかした
前提
kafka-streamsを書く社内フレームワークがある
色々良くできている部分があるので概要をまとめたい
素のkafka-streamsは処理の内部でcatchされない例外が出ると停止する
これに対応するために社内フレームワークにはstreamsの操作 (map,filter etc)をtry/catchでラップし、例外が出た場合に処理しようとしていたメッセージをdead letterトピックに流す機能がある
このdead letterトピックに流すメッセージのシリアライズが失敗する、という状況があった
問題のコード
code:clojure
(ns kafka-client.foo
(:import (org.apache.kafka.common.serialization Serializer Deserializer))
Serializer
(configure configs key?
(.configure franzy-ser configs key?))
(serialize topic data
(let [data (some->> data
(record? %) (into {}))))]
(.serialize franzy-ser topic data)))
(.close franzy-ser)))
Deserializer
(configure configs key?
(.configure franzy-deser configs key?))
(deserialize topic data
(some->> data
(.deserialize franzy-deser topic)
(clojure.walk/prewalk
(instance? java.util.Collection %)
(into [])))))
(.close franzy-deser)))
(defn proxy-fressian-deserializer
[]
(ProxyFressianDeserializer. (fressian-de/fressian-deserializer)))
(defn proxy-fressian-serializer
[]
(ProxyFressianSerializer. (fressian-ser/fressian-serializer)))
Error
このように実装されているシリアライザーは普通のマップはシリアライズに成功するが、適当なオブジェクトを渡すと例外が出る
code:clojure
(.serialize (proxy-fressian-serializer) "topic" {:foo "bar"})
(.serialize (kafka-client.foo/proxy-fressian-serializer) "topic" {:foo (Exception. "Unserializable")})
=> java.lang.IllegalArgumentException Cannot write java.lang.Exception: Unserializable as tag null
Fix
とりあえずシリアライズをtry catchする
例外はcatchしてデータ構造をpostwalk(= 枝から)してシリアライズに失敗するオブジェクトをpr-strする
シリアライズが可能かどうかの判定はシリアライズをしてみる、という作戦
データ構造を再帰的にシリアライズするコストは?
シリアライズに失敗して例外が発生することがレアケースだと仮定すれば(でないと困る)全体としては問題にならない。
code:clojure
(defn ^:private unserializable?
"Check if a instance is not serializable"
(try
(fress/write inst)
false
(catch java.lang.IllegalArgumentException _
true)))
Serializer
(configure configs key?
(.configure franzy-ser configs key?))
(serialize topic data
(let [data (some->> data
(record? %) (into {}))))]
(try
(.serialize franzy-ser topic data)
(catch java.lang.IllegalArgumentException ex
;; Fallback for the cause input data contains unserializable value.
(->> data
(unserializable? %) pr-str))
(.serialize franzy-ser topic))))))
(.close franzy-ser)))
教訓
外界とのやりとりの部分でシリアライズできない物が渡る可能性が無いかは注意するべき
clojure.walkは色々強力
Thanks
同僚の@223さんが対応した内容を大いに参考にしている。 とても誠実で頼もしい方と同じ会社で働けて幸せです。よりかかり過ぎないように精進します!