SlideShare a Scribd company logo
1 of 79
1@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
Streaming Apps and Poison Pills:
handle the unexpected with Kafka Streams
28 Jul. 2020 - Online Kafka Meetup
2@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
Loïc DIVAD
Software Engineer
@loicmdivad
Technical Officer @PubSapientEng
3@loicmdivad @PubSapientEng
Streaming Apps and Poison Pills
San Francisco, CA - October 2019
Ratatouille: handle the unexpected with Kafka Streams
4@loicmdivad @PubSapientEng 4@loicmdivad @PubSapientEng
> println(sommaire)
Incoming records may be corrupted, or cannot be
handled by the serializer / deserializer. These
records are referred to as “poison pills”
1. Log and Crash
2. Skip the Corrupted
3. Sentinel Value Pattern
4. Dead Letter Queue Pattern
5@loicmdivad @PubSapientEng
Ratatouille app, a delicious use case
Streaming
APP
6@loicmdivad @PubSapientEng
Ratatouille app, a delicious use case
Streaming
APP
7@loicmdivad @PubSapientEng 7@loicmdivad @PubSapientEng
Streaming App Poison Pills
1. Log and Crash - Breakfast
2. Skip the Corrupted - Lunch
3. Sentinel Value Pattern - Drink
4. Dead Letter Queue Pattern - Dinner
8@loicmdivad @PubSapientEng
Apache Kafka Brokers / Clients
9@loicmdivad @PubSapientEng
Log and Crash
Exercise #1 - breakfast
10@loicmdivad @PubSapientEng
Really old systems receive raw bytes
directly from message queues
10100110111010101
Exercise #1 - breakfast
11@loicmdivad @PubSapientEng
Really old systems receive raw bytes
directly from message queues
With Kafka (Connect and Streams)
we’d like to continuously transform
these messages
10100110111010101
Kafka Connect
Kafka Brokers
Exercise #1 - breakfast
12@loicmdivad @PubSapientEng
Really old systems receive raw bytes
directly from message queues
With Kafka (Connect and Streams)
we’d like to continuously transform
these messages
But we need a deserializer with
special decoder to understand each
event
What happens if we get a buggy
implementation of the deserializer?
10100110111010101
Kafka Connect
Kafka Brokers
Kafka Streams
Exercise #1 - breakfast
13@loicmdivad @PubSapientEng
The Tooling Team
They will provide an appropriate deserializer
14@loicmdivad @PubSapientEng
// Exercise #1: Breakfast
sealed trait FoodOrder
case class Breakfast(lang: Lang,
fruit: Fruit,
liquid: Liquid,
pastries: Vector[Pastry] = Vector.empty) extends FoodOrder
15@loicmdivad @PubSapientEng
// Exercise #1: Breakfast
sealed trait FoodOrder
case class Breakfast(lang: Lang,
fruit: Fruit,
liquid: Liquid,
pastries: Vector[Pastry] = Vector.empty) extends FoodOrder
implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ???
16@loicmdivad @PubSapientEng
// Exercise #1: Breakfast
sealed trait FoodOrder
case class Breakfast(lang: Lang,
fruit: Fruit,
liquid: Liquid,
pastries: Vector[Pastry] = Vector.empty) extends FoodOrder
implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ???
class FoodOrderSerializer extends Serializer[FoodOrder] = ???
class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
17@loicmdivad @PubSapientEng
// Exercise #1: Breakfast
sealed trait FoodOrder
case class Breakfast(lang: Lang,
fruit: Fruit,
liquid: Liquid,
pastries: Vector[Pastry] = Vector.empty) extends FoodOrder
implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ???
class FoodOrderSerializer extends Serializer[FoodOrder] = ???
class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
org.apache.kafka.common.serialization
Take
Away
18@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
19@loicmdivad @PubSapientEng
Log and Crash
2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during
Deserialization, taskId: 0_0, topic: input-food-order, partition: 0, offset: 109
Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1"
org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a
deserialization error. If you would rather have the streaming pipeline continue after a deserialization error, please
set the default.deserialization.exception.handler appropriately.
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80)
at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101)
at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124)
...
at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711)
at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747)
Caused by: java.lang.IllegalArgumentException: dishes: Insufficient number of elements: decoded 0 but should have
decoded 268435712
at scodec.Attempt$Failure.require(Attempt.scala:108)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60)
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
20@loicmdivad @PubSapientEng
Log and Crash
2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during
Deserialization, taskId: 0_0, topic: exercise-breakfast, partition: 0, offset: 109
Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1"
org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a
deserialization error. If you would rather have the streaming pipeline continue after a deserialization error, please
set the default.deserialization.exception.handler appropriately.
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80)
at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101)
at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124)
...
at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711)
at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747)
Caused by: java.lang.IllegalArgumentException: dishes: Insufficient number of
elements: decoded 0 but should have decoded 268435712
at scodec.Attempt$Failure.require(Attempt.scala:108)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60)
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
21@loicmdivad @PubSapientEng
|val frame1: Array[Byte] = Array(0x33, 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5)
|val frame2: Array[Byte] = Array(0x44, 0xd2, 0xfe, 0x10, 0x02, 0x03, 0x01)
22@loicmdivad @PubSapientEng
|val frame1: Array[Byte] = Array( , 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5)
|val frame2: Array[Byte] = Array( , 0xd2, 0xfe, 0x10, 0x02, 0x03, 0x01)
23@loicmdivad @PubSapientEng
|val frame1: Array[Byte] = Array( , 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5)
|val frame2: Array[Byte] = Array( , 0xd2, 0xfe, 0x10, x2, 0x03, 0x01)
|case class Meat(sausages: Int, bacons: Int, . . . )
24@loicmdivad @PubSapientEng
▼ Change consumer group
▼ Manually update my offsets
▼ Reset my streaming app and set my auto reset to
LATEST
▽ $ kafka-streams-application-reset ...
▼ Destroy the topic, no message = no poison pill
▽ $ kafka-topics --delete --topic ...
▼ My favourite <3
▽ $ confluent destroy && confluent start
Don’t Do
▼ Fill an issue and suggest a fix to the tooling team
25@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
26@loicmdivad @PubSapientEng 26@loicmdivad @PubSapientEng
Log and Crash
Like all consumers, Kafka Streams applications
deserialize messages from the broker.
The deserialization process can fail. It raises an
exception that cannot be caught by our code.
Buggy deserializers have to be fixed before the
application restarts, by default ...
27@loicmdivad @PubSapientEng
Skip the Corrupted
Exercise #2 - lunch
28@loicmdivad @PubSapientEng
// Exercise #2: Lunch
sealed trait FoodOrder
case class Lunch(name: String, price: Double, `type`: LunchType) extends FoodOrder
29@loicmdivad @PubSapientEng
// Exercise #2: Lunch
sealed trait FoodOrder
case class Lunch(name: String, price: Double, `type`: LunchType) extends FoodOrder
● starter
● main
● dessert
30@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
31@loicmdivad @PubSapientEng
Skip the Corrupted
2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during
Deserialization, taskId: 0_0, topic: exercise-breakfast, partition: 0, offset: 109
Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1"
org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a
deserialization error. If you would rather have the streaming pipeline continue after a
deserialization error, please set the default.deserialization.exception.handler
appropriately.
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80)
at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101)
at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124)
...
at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711)
at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747)
Caused by: java.lang.IllegalArgumentException: ... decoded 0 but should have decoded 268435712
at scodec.Attempt$Failure.require(Attempt.scala:108)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58)
at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15)
at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60)
at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
32@loicmdivad @PubSapientEng 32@loicmdivad @PubSapientEng
public class LogAndFailExceptionHandler implements DeserializationExceptionHandler
/* ... */
public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler
/* ... */
33@loicmdivad @PubSapientEng
public class LogAndFailExceptionHandler implements DeserializationExceptionHandler
/* ... */
public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler
/* ... */
public interface DeserializationExceptionHandler extends Configurable {
DeserializationHandlerResponse handle(final ProcessorContext context,
final ConsumerRecord<byte[], byte[]> record,
final Exception exception);
enum DeserializationHandlerResponse {
CONTINUE(0, "CONTINUE"),
FAIL(1, "FAIL");
/* ... */
}
}
}
34@loicmdivad @PubSapientEng
public class LogAndFailExceptionHandler implements DeserializationExceptionHandler
/* ... */
public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler
/* ... */
public interface DeserializationExceptionHandler extends Configurable {
DeserializationHandlerResponse handle(final ProcessorContext context,
final ConsumerRecord<byte[], byte[]> record,
final Exception exception);
enum DeserializationHandlerResponse {
CONTINUE(0, "CONTINUE"),
FAIL(1, "FAIL");
/* ... */
}
}
}
Take
Away
35@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
36@loicmdivad @PubSapientEng 36@loicmdivad @PubSapientEng
The Exception Handler in the call stack
Powered by the Flow intelliJ plugin ➞ findtheflow.io
37@loicmdivad @PubSapientEng 37@loicmdivad @PubSapientEng
Powered by the Flow intelliJ plugin ➞ findtheflow.io
The Exception Handler in the call stack
38@loicmdivad @PubSapientEng 38@loicmdivad @PubSapientEng
Powered by the Flow intelliJ plugin ➞ findtheflow.io
The Exception Handler in the call stack
39@loicmdivad @PubSapientEng 39@loicmdivad @PubSapientEng
Powered by the Flow intelliJ plugin ➞ findtheflow.io
The Exception Handler in the call stack
40@loicmdivad @PubSapientEng 40@loicmdivad @PubSapientEng
Skip the Corrupted
All exceptions thrown by deserializers are caught by
a DeserializationExceptionHandler
A handler returns Fail or Continue
You can implement your own Handler
But the two handlers provided by the library are
really basic… let’s explore other methods
41@loicmdivad @PubSapientEng 41@loicmdivad @PubSapientEng
All exceptions thrown by deserializers are caught by
a DeserializationExceptionHandler
A handler returns Fail or Continue
You can implement your own Handler
But the two handlers provided by the library are
really basic… let’s explore other methods
Skip the Corrupted
Take
Away
42@loicmdivad @PubSapientEng
Sentinel Value Pattern
Exercise #3 - drinks
43@loicmdivad @PubSapientEng
// Exercise #3: Drink
sealed trait FoodOrder
case class Drink(name: String,
quantity: Int,
`type`: DrinkType,
alcohol: Option[Double]) extends FoodOrder
44@loicmdivad @PubSapientEng
// Exercise #3: Drink
sealed trait FoodOrder
case class Drink(name: String,
quantity: Int,
`type`: DrinkType,
alcohol: Option[Double]) extends FoodOrder
● wine
● rhum
● beer
● champagne
● ...
45@loicmdivad @PubSapientEng
We need to turn the deserialization process into a
pure transformation that cannot crash
To do so, we will replace corrupted message by a
sentinel value. It’s a special-purpose record (e.g: null,
None, Json.Null, etc ...)
Sentinel Value Pattern
f: G → H
G H
46@loicmdivad @PubSapientEng
We need to turn the deserialization process into a
pure transformation that cannot crash
To do so, we will replace corrupted message by a
sentinel value. It’s a special-purpose record (e.g: null,
None, Json.Null, etc ...)
This allows downstream processors to recognize and
handle such sentinel values
Sentinel Value Pattern
f: G → H
G H
G H
47@loicmdivad @PubSapientEng
We need to turn the deserialization process into a
pure transformation that cannot crash
To do so, we will replace corrupted message by a
sentinel value. It’s a special-purpose record (e.g: null,
None, Json.Null, etc ...)
This allows downstream processors to recognize and
handle such sentinel values
With Kafka Streams this can be achieved by
implementing a Deserializer
Sentinel Value Pattern
f: G → H
G H
G H
null
48@loicmdivad @PubSapientEng
case object FoodOrderError extends FoodOrder
class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
49@loicmdivad @PubSapientEng
case object FoodOrderError extends FoodOrder
class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
class SentinelValueDeserializer extends FoodOrderDeserializer {
override def deserialize(topic: String, data: Array[Byte]): FoodOrder =
Try(super.deserialize(topic, data)).getOrElse(FoodOrderErr)
}
50@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
51@loicmdivad @PubSapientEng
class FoodOrderSentinelValueProcessor extends ValueTransformer[FoodOrder, Unit] {
var sensor: Sensor = _
var context: ProcessorContext = _
def metricName(stat: String): MetricName = ???
override def init(context: ProcessorContext): Unit = {
this.context = context
this.sensor = this.context.metrics.addSensor("sentinel-value", INFO)
sensor.add(metricName("total"), new Total())
sensor.add(metricName("rate"), new Rate(TimeUnit.SECONDS, new Count()))
}
override def transform(value: FoodOrder): Unit = sensor.record()
}
52@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
53@loicmdivad @PubSapientEng
54@loicmdivad @PubSapientEng 54@loicmdivad @PubSapientEng
Sentinel Value Pattern
By implementing a custom serde we can create a safe
Deserializer.
Downstreams now receive a sentinel value
indicating a deserialization error.
Errors can then be treated correctly, example:
monitoring the number of deserialization
errors with a custom metric
But we lost a lot of information about the error…
let’s see a last method
55@loicmdivad @PubSapientEng 55@loicmdivad @PubSapientEng
Sentinel Value Pattern
By implementing a custom serde we can create a safe
Deserializer.
Downstreams now receive a sentinel value
indicating a deserialization error.
Errors can then be treated correctly, example:
monitoring the number of deserialization
errors with a custom metric
But we lost a lot of information about the error…
let’s see a last method
Take
Away
56@loicmdivad @PubSapientEng
Dead Letter Queue Pattern
Exercise #4 - dinner
57@loicmdivad @PubSapientEng
// Exercise #4: Dinner
sealed trait FoodOrder
case class Dinner(dish: Command,
zone: String,
moment: Moment,
maybeClient: Option[Client]) extends FoodOrder
58@loicmdivad @PubSapientEng
Dead Letter Queue Pattern
In this method we will let the deserializer fail.
For each failure we will send a message to a topic
containing corrupted messages.
Each message will have the original content of the
input message (for reprocessing) and additional
meta data about the failure.
With Kafka Streams this can be achieved by
implementing a DeserializationExceptionHandler
Streaming
APP
dead letter queue
input topic output topic
59@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
}
60@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava)
producer.send(producerRecord, /* Producer Callback */ )
DeserializationHandlerResponse.CONTINUE
}
61@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
var topic: String = _
var producer: KafkaProducer[Array[Byte], Array[Byte]] = _
override def configure(configs: util.Map[String, _]): Unit = ???
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava)
producer.send(producerRecord, /* Producer Callback */ )
DeserializationHandlerResponse.CONTINUE
}
62@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
var topic: String = _
var producer: KafkaProducer[Array[Byte], Array[Byte]] = _
override def configure(configs: util.Map[String, _]): Unit = ???
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
val headers = record.headers().toArray ++ Array[Header](
new RecordHeader("processing-time", ???),
new RecordHeader("hexa-datetime", ???),
new RecordHeader("error-message", ???),
...
)
val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava)
producer.send(producerRecord, /* Producer Callback */ )
DeserializationHandlerResponse.CONTINUE
}
63@loicmdivad @PubSapientEng
Fill the headers with some meta data
01061696e0016536f6d6500000005736f6d65206f
Value message to hexa
Restaurant
description
Event date and time
Food order category
64@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
var topic: String = _
var producer: KafkaProducer[Array[Byte], Array[Byte]] = _
override def configure(configs: util.Map[String, _]): Unit = ???
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
val headers = record.headers().toArray ++ Array[Header](
new RecordHeader("processing-time", ???),
new RecordHeader("hexa-datetime", ???),
new RecordHeader("error-message", ???),
...
)
val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava)
producer.send(producerRecord, /* Producer Callback */ )
DeserializationHandlerResponse.CONTINUE
}
65@loicmdivad @PubSapientEng
class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler {
var topic: String = _
var producer: KafkaProducer[Array[Byte], Array[Byte]] = _
override def configure(configs: util.Map[String, _]): Unit = ???
override def handle(context: ProcessorContext,
record: ConsumerRecord[Array[Byte], Array[Byte]],
exception: Exception): DeserializationHandlerResponse = {
val headers = record.headers().toArray ++ Array[Header](
new RecordHeader("processing-time", ???),
new RecordHeader("hexa-datetime", ???),
new RecordHeader("error-message", ???),
...
)
val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava)
producer.send(producerRecord, /* Producer Callback */ )
DeserializationHandlerResponse.CONTINUE
}
Take
Away
66@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
67@loicmdivad @PubSapientEng
68@loicmdivad @PubSapientEng
414554=AET=
Australia/Sydney
69@loicmdivad @PubSapientEng 69@loicmdivad @PubSapientEng
Dead Letter Queue Pattern
You can provide your own implementation of
DeserializationExceptionHandler.
This lets you use the Producer API to write a
corrupted record directly to a quarantine topic.
Then you can manually analyse your corrupted
records
⚠Warning: This approach have side effects that are
invisible to the Kafka Streams runtime.
70@loicmdivad @PubSapientEng 70@loicmdivad @PubSapientEng
Dead Letter Queue Pattern
You can provide your own implementation of
DeserializationExceptionHandler.
This lets you use the Producer API to write a
corrupted record directly to a quarantine topic.
Then you can manually analyse your corrupted
records
⚠Warning: This approach have side effects that are
invisible to the Kafka Streams runtime.
Take
Away
71@loicmdivad @PubSapientEng
Conclusion
Exercise #NaN - take aways
72@loicmdivad @PubSapientEng 72@loicmdivad @PubSapientEng
CONFLUENT FAQ
Links
XKE-RATATOUILLE
73@loicmdivad @PubSapientEng 73@loicmdivad @PubSapientEng
Related Post
Kafka Connect Deep Dive – Error Handling and
Dead Letter Queues - by Robin Moffatt
Building Reliable Reprocessing and Dead Letter
Queues with Apache Kafka - by Ning Xia
Handling bad messages using Kafka's Streams API -
answer by Matthias J. Sax
74@loicmdivad @PubSapientEng 74@loicmdivad @PubSapientEng
Conclusion
When using Kafka, deserialization is the
responsibility of the clients.
These internal errors are not easy to catch
When it’s possible, use Avro + Schema Registry
When it’s not possible, Kafka Streams applies
techniques to deal with serde errors:
- DLQ: By extending a ExceptionHandler
- Sentinel Value: By extending a Deserializer
75@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng
MERCI
(Thank you)
76@loicmdivad @PubSapientEng 76@loicmdivad @PubSapientEng
Images
Photo by rawpixel on Unsplash
Photo by João Marcelo Martins on Unsplash
Photo by Jordane Mathieu on Unsplash
Photo by Brooke Lark on Unsplash
Photo by Jakub Kapusnak on Unsplash
Photo by Melissa Walker Horn on Unsplash
Photo by Aneta Pawlik on Unsplash
77@loicmdivad @PubSapientEng 77@loicmdivad @PubSapientEng
With special thanks to
Robin M.
Sylvain L.
Giulia B.
78@loicmdivad @PubSapientEng
How the generator works?
79@loicmdivad @PubSapientEng
Pure HTML
Akka Http Server
Akka Actor System
Kafka Topic
Exercise1
Exercise2
Me, clicking
everywhere
Akka Stream
Kafka

More Related Content

What's hot

Why Domain-Driven Design and Reactive Programming?
Why Domain-Driven Design and Reactive Programming?Why Domain-Driven Design and Reactive Programming?
Why Domain-Driven Design and Reactive Programming?
VMware Tanzu
 
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
Spark Summit
 

What's hot (20)

Error Management: Future vs ZIO
Error Management: Future vs ZIOError Management: Future vs ZIO
Error Management: Future vs ZIO
 
The java interview questions ebook - confused coders
The java interview questions ebook -  confused codersThe java interview questions ebook -  confused coders
The java interview questions ebook - confused coders
 
Webinaire Technologia - Les nouveautés Teams – 5 octobre 2022
Webinaire Technologia - Les nouveautés Teams – 5 octobre 2022Webinaire Technologia - Les nouveautés Teams – 5 octobre 2022
Webinaire Technologia - Les nouveautés Teams – 5 octobre 2022
 
MongoDB et Elasticsearch, meilleurs ennemis ?
MongoDB et Elasticsearch, meilleurs ennemis ?MongoDB et Elasticsearch, meilleurs ennemis ?
MongoDB et Elasticsearch, meilleurs ennemis ?
 
The SOLID Principles Illustrated by Design Patterns
The SOLID Principles Illustrated by Design PatternsThe SOLID Principles Illustrated by Design Patterns
The SOLID Principles Illustrated by Design Patterns
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Why Domain-Driven Design and Reactive Programming?
Why Domain-Driven Design and Reactive Programming?Why Domain-Driven Design and Reactive Programming?
Why Domain-Driven Design and Reactive Programming?
 
Introducing Saga Pattern in Microservices with Spring Statemachine
Introducing Saga Pattern in Microservices with Spring StatemachineIntroducing Saga Pattern in Microservices with Spring Statemachine
Introducing Saga Pattern in Microservices with Spring Statemachine
 
Maven
MavenMaven
Maven
 
Spring boot jpa
Spring boot jpaSpring boot jpa
Spring boot jpa
 
Logback
LogbackLogback
Logback
 
Real Life Clean Architecture
Real Life Clean ArchitectureReal Life Clean Architecture
Real Life Clean Architecture
 
50.000 orange stickies later
50.000 orange stickies later50.000 orange stickies later
50.000 orange stickies later
 
Getting Started with Spring Authorization Server
Getting Started with Spring Authorization ServerGetting Started with Spring Authorization Server
Getting Started with Spring Authorization Server
 
Design Patterns
Design PatternsDesign Patterns
Design Patterns
 
Diving into Java Class Loader
Diving into Java Class LoaderDiving into Java Class Loader
Diving into Java Class Loader
 
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
Beyond unit tests: Testing for Spark/Hadoop Workflows with Shankar Manian Ana...
 
Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans Spring - Part 1 - IoC, Di and Beans
Spring - Part 1 - IoC, Di and Beans
 
Domain Driven Design - Strategic Patterns and Microservices
Domain Driven Design - Strategic Patterns and MicroservicesDomain Driven Design - Strategic Patterns and Microservices
Domain Driven Design - Strategic Patterns and Microservices
 
Brochure industrial-ethernet-switches-english
Brochure industrial-ethernet-switches-englishBrochure industrial-ethernet-switches-english
Brochure industrial-ethernet-switches-english
 

Similar to Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams

Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docxApplets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
armitageclaire49
 

Similar to Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams (8)

Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams (Lo...
Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams (Lo...Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams (Lo...
Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams (Lo...
 
Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docxApplets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
Applets_Lab3Character Codes.jarMETA-INFMANIFEST.MFManife.docx
 
The WordPress way, the modern way: developing as if it were 2016
The WordPress way, the modern way: developing as if it were 2016The WordPress way, the modern way: developing as if it were 2016
The WordPress way, the modern way: developing as if it were 2016
 
COCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate AscentCOCOA: Communication-Efficient Coordinate Ascent
COCOA: Communication-Efficient Coordinate Ascent
 
Impossible Programs
Impossible ProgramsImpossible Programs
Impossible Programs
 
Model-checking for efficient malware detection
Model-checking for efficient malware detectionModel-checking for efficient malware detection
Model-checking for efficient malware detection
 
Weapons for Boilerplate Destruction
Weapons for Boilerplate DestructionWeapons for Boilerplate Destruction
Weapons for Boilerplate Destruction
 
Puppet and Software Delivery
Puppet and Software DeliveryPuppet and Software Delivery
Puppet and Software Delivery
 

More from confluent

More from confluent (20)

Speed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in MinutesSpeed Wins: From Kafka to APIs in Minutes
Speed Wins: From Kafka to APIs in Minutes
 
Evolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI EraEvolving Data Governance for the Real-time Streaming and AI Era
Evolving Data Governance for the Real-time Streaming and AI Era
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
Santander Stream Processing with Apache Flink
Santander Stream Processing with Apache FlinkSantander Stream Processing with Apache Flink
Santander Stream Processing with Apache Flink
 
Unlocking the Power of IoT: A comprehensive approach to real-time insights
Unlocking the Power of IoT: A comprehensive approach to real-time insightsUnlocking the Power of IoT: A comprehensive approach to real-time insights
Unlocking the Power of IoT: A comprehensive approach to real-time insights
 
Workshop híbrido: Stream Processing con Flink
Workshop híbrido: Stream Processing con FlinkWorkshop híbrido: Stream Processing con Flink
Workshop híbrido: Stream Processing con Flink
 
Industry 4.0: Building the Unified Namespace with Confluent, HiveMQ and Spark...
Industry 4.0: Building the Unified Namespace with Confluent, HiveMQ and Spark...Industry 4.0: Building the Unified Namespace with Confluent, HiveMQ and Spark...
Industry 4.0: Building the Unified Namespace with Confluent, HiveMQ and Spark...
 
AWS Immersion Day Mapfre - Confluent
AWS Immersion Day Mapfre   -   ConfluentAWS Immersion Day Mapfre   -   Confluent
AWS Immersion Day Mapfre - Confluent
 
Eventos y Microservicios - Santander TechTalk
Eventos y Microservicios - Santander TechTalkEventos y Microservicios - Santander TechTalk
Eventos y Microservicios - Santander TechTalk
 
Q&A with Confluent Experts: Navigating Networking in Confluent Cloud
Q&A with Confluent Experts: Navigating Networking in Confluent CloudQ&A with Confluent Experts: Navigating Networking in Confluent Cloud
Q&A with Confluent Experts: Navigating Networking in Confluent Cloud
 
Citi TechTalk Session 2: Kafka Deep Dive
Citi TechTalk Session 2: Kafka Deep DiveCiti TechTalk Session 2: Kafka Deep Dive
Citi TechTalk Session 2: Kafka Deep Dive
 
Build real-time streaming data pipelines to AWS with Confluent
Build real-time streaming data pipelines to AWS with ConfluentBuild real-time streaming data pipelines to AWS with Confluent
Build real-time streaming data pipelines to AWS with Confluent
 
Q&A with Confluent Professional Services: Confluent Service Mesh
Q&A with Confluent Professional Services: Confluent Service MeshQ&A with Confluent Professional Services: Confluent Service Mesh
Q&A with Confluent Professional Services: Confluent Service Mesh
 
Citi Tech Talk: Event Driven Kafka Microservices
Citi Tech Talk: Event Driven Kafka MicroservicesCiti Tech Talk: Event Driven Kafka Microservices
Citi Tech Talk: Event Driven Kafka Microservices
 
Confluent & GSI Webinars series - Session 3
Confluent & GSI Webinars series - Session 3Confluent & GSI Webinars series - Session 3
Confluent & GSI Webinars series - Session 3
 
Citi Tech Talk: Messaging Modernization
Citi Tech Talk: Messaging ModernizationCiti Tech Talk: Messaging Modernization
Citi Tech Talk: Messaging Modernization
 
Citi Tech Talk: Data Governance for streaming and real time data
Citi Tech Talk: Data Governance for streaming and real time dataCiti Tech Talk: Data Governance for streaming and real time data
Citi Tech Talk: Data Governance for streaming and real time data
 
Confluent & GSI Webinars series: Session 2
Confluent & GSI Webinars series: Session 2Confluent & GSI Webinars series: Session 2
Confluent & GSI Webinars series: Session 2
 
Data In Motion Paris 2023
Data In Motion Paris 2023Data In Motion Paris 2023
Data In Motion Paris 2023
 
Confluent Partner Tech Talk with Synthesis
Confluent Partner Tech Talk with SynthesisConfluent Partner Tech Talk with Synthesis
Confluent Partner Tech Talk with Synthesis
 

Recently uploaded

Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
panagenda
 

Recently uploaded (20)

Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptxUnpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
Unpacking Value Delivery - Agile Oxford Meetup - May 2024.pptx
 
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
ASRock Industrial FDO Solutions in Action for Industrial Edge AI _ Kenny at A...
 
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdfIntroduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
Introduction to FDO and How It works Applications _ Richard at FIDO Alliance.pdf
 
Enterprise Knowledge Graphs - Data Summit 2024
Enterprise Knowledge Graphs - Data Summit 2024Enterprise Knowledge Graphs - Data Summit 2024
Enterprise Knowledge Graphs - Data Summit 2024
 
Designing for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at ComcastDesigning for Hardware Accessibility at Comcast
Designing for Hardware Accessibility at Comcast
 
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdfWhere to Learn More About FDO _ Richard at FIDO Alliance.pdf
Where to Learn More About FDO _ Richard at FIDO Alliance.pdf
 
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
Behind the Scenes From the Manager's Chair: Decoding the Secrets of Successfu...
 
Optimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through ObservabilityOptimizing NoSQL Performance Through Observability
Optimizing NoSQL Performance Through Observability
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
 
Easier, Faster, and More Powerful – Notes Document Properties Reimagined
Easier, Faster, and More Powerful – Notes Document Properties ReimaginedEasier, Faster, and More Powerful – Notes Document Properties Reimagined
Easier, Faster, and More Powerful – Notes Document Properties Reimagined
 
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdfHow Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
How Red Hat Uses FDO in Device Lifecycle _ Costin and Vitaliy at Red Hat.pdf
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?
 
WebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM PerformanceWebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM Performance
 
Google I/O Extended 2024 Warsaw
Google I/O Extended 2024 WarsawGoogle I/O Extended 2024 Warsaw
Google I/O Extended 2024 Warsaw
 
Syngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdf
 
What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024What's New in Teams Calling, Meetings and Devices April 2024
What's New in Teams Calling, Meetings and Devices April 2024
 
IESVE for Early Stage Design and Planning
IESVE for Early Stage Design and PlanningIESVE for Early Stage Design and Planning
IESVE for Early Stage Design and Planning
 
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
Choosing the Right FDO Deployment Model for Your Application _ Geoffrey at In...
 
Overview of Hyperledger Foundation
Overview of Hyperledger FoundationOverview of Hyperledger Foundation
Overview of Hyperledger Foundation
 
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
 

Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams

  • 1. 1@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng Streaming Apps and Poison Pills: handle the unexpected with Kafka Streams 28 Jul. 2020 - Online Kafka Meetup
  • 2. 2@loicmdivad @PubSapientEng@loicmdivad @PubSapientEng Loïc DIVAD Software Engineer @loicmdivad Technical Officer @PubSapientEng
  • 3. 3@loicmdivad @PubSapientEng Streaming Apps and Poison Pills San Francisco, CA - October 2019 Ratatouille: handle the unexpected with Kafka Streams
  • 4. 4@loicmdivad @PubSapientEng 4@loicmdivad @PubSapientEng > println(sommaire) Incoming records may be corrupted, or cannot be handled by the serializer / deserializer. These records are referred to as “poison pills” 1. Log and Crash 2. Skip the Corrupted 3. Sentinel Value Pattern 4. Dead Letter Queue Pattern
  • 5. 5@loicmdivad @PubSapientEng Ratatouille app, a delicious use case Streaming APP
  • 6. 6@loicmdivad @PubSapientEng Ratatouille app, a delicious use case Streaming APP
  • 7. 7@loicmdivad @PubSapientEng 7@loicmdivad @PubSapientEng Streaming App Poison Pills 1. Log and Crash - Breakfast 2. Skip the Corrupted - Lunch 3. Sentinel Value Pattern - Drink 4. Dead Letter Queue Pattern - Dinner
  • 9. 9@loicmdivad @PubSapientEng Log and Crash Exercise #1 - breakfast
  • 10. 10@loicmdivad @PubSapientEng Really old systems receive raw bytes directly from message queues 10100110111010101 Exercise #1 - breakfast
  • 11. 11@loicmdivad @PubSapientEng Really old systems receive raw bytes directly from message queues With Kafka (Connect and Streams) we’d like to continuously transform these messages 10100110111010101 Kafka Connect Kafka Brokers Exercise #1 - breakfast
  • 12. 12@loicmdivad @PubSapientEng Really old systems receive raw bytes directly from message queues With Kafka (Connect and Streams) we’d like to continuously transform these messages But we need a deserializer with special decoder to understand each event What happens if we get a buggy implementation of the deserializer? 10100110111010101 Kafka Connect Kafka Brokers Kafka Streams Exercise #1 - breakfast
  • 13. 13@loicmdivad @PubSapientEng The Tooling Team They will provide an appropriate deserializer
  • 14. 14@loicmdivad @PubSapientEng // Exercise #1: Breakfast sealed trait FoodOrder case class Breakfast(lang: Lang, fruit: Fruit, liquid: Liquid, pastries: Vector[Pastry] = Vector.empty) extends FoodOrder
  • 15. 15@loicmdivad @PubSapientEng // Exercise #1: Breakfast sealed trait FoodOrder case class Breakfast(lang: Lang, fruit: Fruit, liquid: Liquid, pastries: Vector[Pastry] = Vector.empty) extends FoodOrder implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ???
  • 16. 16@loicmdivad @PubSapientEng // Exercise #1: Breakfast sealed trait FoodOrder case class Breakfast(lang: Lang, fruit: Fruit, liquid: Liquid, pastries: Vector[Pastry] = Vector.empty) extends FoodOrder implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ??? class FoodOrderSerializer extends Serializer[FoodOrder] = ??? class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
  • 17. 17@loicmdivad @PubSapientEng // Exercise #1: Breakfast sealed trait FoodOrder case class Breakfast(lang: Lang, fruit: Fruit, liquid: Liquid, pastries: Vector[Pastry] = Vector.empty) extends FoodOrder implicit lazy val BreakfastCodec: Codec[Breakfast] = new Codec[Breakfast] = ??? class FoodOrderSerializer extends Serializer[FoodOrder] = ??? class FoodOrderDeserializer extends Deserializer[FoodOrder] = ??? org.apache.kafka.common.serialization Take Away
  • 19. 19@loicmdivad @PubSapientEng Log and Crash 2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during Deserialization, taskId: 0_0, topic: input-food-order, partition: 0, offset: 109 Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1" org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a deserialization error. If you would rather have the streaming pipeline continue after a deserialization error, please set the default.deserialization.exception.handler appropriately. at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80) at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101) at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124) ... at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711) at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747) Caused by: java.lang.IllegalArgumentException: dishes: Insufficient number of elements: decoded 0 but should have decoded 268435712 at scodec.Attempt$Failure.require(Attempt.scala:108) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60) at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
  • 20. 20@loicmdivad @PubSapientEng Log and Crash 2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during Deserialization, taskId: 0_0, topic: exercise-breakfast, partition: 0, offset: 109 Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1" org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a deserialization error. If you would rather have the streaming pipeline continue after a deserialization error, please set the default.deserialization.exception.handler appropriately. at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80) at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101) at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124) ... at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711) at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747) Caused by: java.lang.IllegalArgumentException: dishes: Insufficient number of elements: decoded 0 but should have decoded 268435712 at scodec.Attempt$Failure.require(Attempt.scala:108) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60) at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
  • 21. 21@loicmdivad @PubSapientEng |val frame1: Array[Byte] = Array(0x33, 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5) |val frame2: Array[Byte] = Array(0x44, 0xd2, 0xfe, 0x10, 0x02, 0x03, 0x01)
  • 22. 22@loicmdivad @PubSapientEng |val frame1: Array[Byte] = Array( , 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5) |val frame2: Array[Byte] = Array( , 0xd2, 0xfe, 0x10, 0x02, 0x03, 0x01)
  • 23. 23@loicmdivad @PubSapientEng |val frame1: Array[Byte] = Array( , 0xd4, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xa5) |val frame2: Array[Byte] = Array( , 0xd2, 0xfe, 0x10, x2, 0x03, 0x01) |case class Meat(sausages: Int, bacons: Int, . . . )
  • 24. 24@loicmdivad @PubSapientEng ▼ Change consumer group ▼ Manually update my offsets ▼ Reset my streaming app and set my auto reset to LATEST ▽ $ kafka-streams-application-reset ... ▼ Destroy the topic, no message = no poison pill ▽ $ kafka-topics --delete --topic ... ▼ My favourite <3 ▽ $ confluent destroy && confluent start Don’t Do ▼ Fill an issue and suggest a fix to the tooling team
  • 26. 26@loicmdivad @PubSapientEng 26@loicmdivad @PubSapientEng Log and Crash Like all consumers, Kafka Streams applications deserialize messages from the broker. The deserialization process can fail. It raises an exception that cannot be caught by our code. Buggy deserializers have to be fixed before the application restarts, by default ...
  • 27. 27@loicmdivad @PubSapientEng Skip the Corrupted Exercise #2 - lunch
  • 28. 28@loicmdivad @PubSapientEng // Exercise #2: Lunch sealed trait FoodOrder case class Lunch(name: String, price: Double, `type`: LunchType) extends FoodOrder
  • 29. 29@loicmdivad @PubSapientEng // Exercise #2: Lunch sealed trait FoodOrder case class Lunch(name: String, price: Double, `type`: LunchType) extends FoodOrder ● starter ● main ● dessert
  • 31. 31@loicmdivad @PubSapientEng Skip the Corrupted 2019-04-17 03:43:12 macbook-de-lolo [ERROR] (LogAndFailExceptionHandler.java:39) - Exception caught during Deserialization, taskId: 0_0, topic: exercise-breakfast, partition: 0, offset: 109 Exception in thread "answer-one-breakfast-0d808ce7-0ef1-44c6-808a-f594bc7fceae-StreamThread-1" org.apache.kafka.streams.errors.StreamsException: Deserialization exception handler is set to fail upon a deserialization error. If you would rather have the streaming pipeline continue after a deserialization error, please set the default.deserialization.exception.handler appropriately. at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:80) at org.apache.kafka.streams.processor.internals.RecordQueue.addRawRecords(RecordQueue.java:101) at org.apache.kafka.streams.processor.internals.PartitionGroup.addRawRecords(PartitionGroup.java:124) ... at org.apache.kafka.streams.processor.internals.StreamTask.addRecords(StreamTask.java:711) at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:747) Caused by: java.lang.IllegalArgumentException: ... decoded 0 but should have decoded 268435712 at scodec.Attempt$Failure.require(Attempt.scala:108) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:22) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.common.serialization.Deserializer.deserialize(Deserializer.java:58) at fr.xebia.ldi.ratatouille.serde.BreakfastDeserializer.deserialize(BreakfastDeserializer.scala:15) at org.apache.kafka.streams.processor.internals.SourceNode.deserializeValue(SourceNode.java:60) at org.apache.kafka.streams.processor.internals.RecordDeserializer.deserialize(RecordDeserializer.java:66)
  • 32. 32@loicmdivad @PubSapientEng 32@loicmdivad @PubSapientEng public class LogAndFailExceptionHandler implements DeserializationExceptionHandler /* ... */ public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler /* ... */
  • 33. 33@loicmdivad @PubSapientEng public class LogAndFailExceptionHandler implements DeserializationExceptionHandler /* ... */ public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler /* ... */ public interface DeserializationExceptionHandler extends Configurable { DeserializationHandlerResponse handle(final ProcessorContext context, final ConsumerRecord<byte[], byte[]> record, final Exception exception); enum DeserializationHandlerResponse { CONTINUE(0, "CONTINUE"), FAIL(1, "FAIL"); /* ... */ } } }
  • 34. 34@loicmdivad @PubSapientEng public class LogAndFailExceptionHandler implements DeserializationExceptionHandler /* ... */ public class LogAndContinueExceptionHandler implements DeserializationExceptionHandler /* ... */ public interface DeserializationExceptionHandler extends Configurable { DeserializationHandlerResponse handle(final ProcessorContext context, final ConsumerRecord<byte[], byte[]> record, final Exception exception); enum DeserializationHandlerResponse { CONTINUE(0, "CONTINUE"), FAIL(1, "FAIL"); /* ... */ } } } Take Away
  • 36. 36@loicmdivad @PubSapientEng 36@loicmdivad @PubSapientEng The Exception Handler in the call stack Powered by the Flow intelliJ plugin ➞ findtheflow.io
  • 37. 37@loicmdivad @PubSapientEng 37@loicmdivad @PubSapientEng Powered by the Flow intelliJ plugin ➞ findtheflow.io The Exception Handler in the call stack
  • 38. 38@loicmdivad @PubSapientEng 38@loicmdivad @PubSapientEng Powered by the Flow intelliJ plugin ➞ findtheflow.io The Exception Handler in the call stack
  • 39. 39@loicmdivad @PubSapientEng 39@loicmdivad @PubSapientEng Powered by the Flow intelliJ plugin ➞ findtheflow.io The Exception Handler in the call stack
  • 40. 40@loicmdivad @PubSapientEng 40@loicmdivad @PubSapientEng Skip the Corrupted All exceptions thrown by deserializers are caught by a DeserializationExceptionHandler A handler returns Fail or Continue You can implement your own Handler But the two handlers provided by the library are really basic… let’s explore other methods
  • 41. 41@loicmdivad @PubSapientEng 41@loicmdivad @PubSapientEng All exceptions thrown by deserializers are caught by a DeserializationExceptionHandler A handler returns Fail or Continue You can implement your own Handler But the two handlers provided by the library are really basic… let’s explore other methods Skip the Corrupted Take Away
  • 42. 42@loicmdivad @PubSapientEng Sentinel Value Pattern Exercise #3 - drinks
  • 43. 43@loicmdivad @PubSapientEng // Exercise #3: Drink sealed trait FoodOrder case class Drink(name: String, quantity: Int, `type`: DrinkType, alcohol: Option[Double]) extends FoodOrder
  • 44. 44@loicmdivad @PubSapientEng // Exercise #3: Drink sealed trait FoodOrder case class Drink(name: String, quantity: Int, `type`: DrinkType, alcohol: Option[Double]) extends FoodOrder ● wine ● rhum ● beer ● champagne ● ...
  • 45. 45@loicmdivad @PubSapientEng We need to turn the deserialization process into a pure transformation that cannot crash To do so, we will replace corrupted message by a sentinel value. It’s a special-purpose record (e.g: null, None, Json.Null, etc ...) Sentinel Value Pattern f: G → H G H
  • 46. 46@loicmdivad @PubSapientEng We need to turn the deserialization process into a pure transformation that cannot crash To do so, we will replace corrupted message by a sentinel value. It’s a special-purpose record (e.g: null, None, Json.Null, etc ...) This allows downstream processors to recognize and handle such sentinel values Sentinel Value Pattern f: G → H G H G H
  • 47. 47@loicmdivad @PubSapientEng We need to turn the deserialization process into a pure transformation that cannot crash To do so, we will replace corrupted message by a sentinel value. It’s a special-purpose record (e.g: null, None, Json.Null, etc ...) This allows downstream processors to recognize and handle such sentinel values With Kafka Streams this can be achieved by implementing a Deserializer Sentinel Value Pattern f: G → H G H G H null
  • 48. 48@loicmdivad @PubSapientEng case object FoodOrderError extends FoodOrder class FoodOrderDeserializer extends Deserializer[FoodOrder] = ???
  • 49. 49@loicmdivad @PubSapientEng case object FoodOrderError extends FoodOrder class FoodOrderDeserializer extends Deserializer[FoodOrder] = ??? class SentinelValueDeserializer extends FoodOrderDeserializer { override def deserialize(topic: String, data: Array[Byte]): FoodOrder = Try(super.deserialize(topic, data)).getOrElse(FoodOrderErr) }
  • 51. 51@loicmdivad @PubSapientEng class FoodOrderSentinelValueProcessor extends ValueTransformer[FoodOrder, Unit] { var sensor: Sensor = _ var context: ProcessorContext = _ def metricName(stat: String): MetricName = ??? override def init(context: ProcessorContext): Unit = { this.context = context this.sensor = this.context.metrics.addSensor("sentinel-value", INFO) sensor.add(metricName("total"), new Total()) sensor.add(metricName("rate"), new Rate(TimeUnit.SECONDS, new Count())) } override def transform(value: FoodOrder): Unit = sensor.record() }
  • 54. 54@loicmdivad @PubSapientEng 54@loicmdivad @PubSapientEng Sentinel Value Pattern By implementing a custom serde we can create a safe Deserializer. Downstreams now receive a sentinel value indicating a deserialization error. Errors can then be treated correctly, example: monitoring the number of deserialization errors with a custom metric But we lost a lot of information about the error… let’s see a last method
  • 55. 55@loicmdivad @PubSapientEng 55@loicmdivad @PubSapientEng Sentinel Value Pattern By implementing a custom serde we can create a safe Deserializer. Downstreams now receive a sentinel value indicating a deserialization error. Errors can then be treated correctly, example: monitoring the number of deserialization errors with a custom metric But we lost a lot of information about the error… let’s see a last method Take Away
  • 56. 56@loicmdivad @PubSapientEng Dead Letter Queue Pattern Exercise #4 - dinner
  • 57. 57@loicmdivad @PubSapientEng // Exercise #4: Dinner sealed trait FoodOrder case class Dinner(dish: Command, zone: String, moment: Moment, maybeClient: Option[Client]) extends FoodOrder
  • 58. 58@loicmdivad @PubSapientEng Dead Letter Queue Pattern In this method we will let the deserializer fail. For each failure we will send a message to a topic containing corrupted messages. Each message will have the original content of the input message (for reprocessing) and additional meta data about the failure. With Kafka Streams this can be achieved by implementing a DeserializationExceptionHandler Streaming APP dead letter queue input topic output topic
  • 59. 59@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { }
  • 60. 60@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava) producer.send(producerRecord, /* Producer Callback */ ) DeserializationHandlerResponse.CONTINUE }
  • 61. 61@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { var topic: String = _ var producer: KafkaProducer[Array[Byte], Array[Byte]] = _ override def configure(configs: util.Map[String, _]): Unit = ??? override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava) producer.send(producerRecord, /* Producer Callback */ ) DeserializationHandlerResponse.CONTINUE }
  • 62. 62@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { var topic: String = _ var producer: KafkaProducer[Array[Byte], Array[Byte]] = _ override def configure(configs: util.Map[String, _]): Unit = ??? override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { val headers = record.headers().toArray ++ Array[Header]( new RecordHeader("processing-time", ???), new RecordHeader("hexa-datetime", ???), new RecordHeader("error-message", ???), ... ) val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava) producer.send(producerRecord, /* Producer Callback */ ) DeserializationHandlerResponse.CONTINUE }
  • 63. 63@loicmdivad @PubSapientEng Fill the headers with some meta data 01061696e0016536f6d6500000005736f6d65206f Value message to hexa Restaurant description Event date and time Food order category
  • 64. 64@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { var topic: String = _ var producer: KafkaProducer[Array[Byte], Array[Byte]] = _ override def configure(configs: util.Map[String, _]): Unit = ??? override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { val headers = record.headers().toArray ++ Array[Header]( new RecordHeader("processing-time", ???), new RecordHeader("hexa-datetime", ???), new RecordHeader("error-message", ???), ... ) val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava) producer.send(producerRecord, /* Producer Callback */ ) DeserializationHandlerResponse.CONTINUE }
  • 65. 65@loicmdivad @PubSapientEng class DeadLetterQueueFoodExceptionHandler() extends DeserializationExceptionHandler { var topic: String = _ var producer: KafkaProducer[Array[Byte], Array[Byte]] = _ override def configure(configs: util.Map[String, _]): Unit = ??? override def handle(context: ProcessorContext, record: ConsumerRecord[Array[Byte], Array[Byte]], exception: Exception): DeserializationHandlerResponse = { val headers = record.headers().toArray ++ Array[Header]( new RecordHeader("processing-time", ???), new RecordHeader("hexa-datetime", ???), new RecordHeader("error-message", ???), ... ) val producerRecord = new ProducerRecord(topic, /*same key, value and ts,*/ headers.asJava) producer.send(producerRecord, /* Producer Callback */ ) DeserializationHandlerResponse.CONTINUE } Take Away
  • 69. 69@loicmdivad @PubSapientEng 69@loicmdivad @PubSapientEng Dead Letter Queue Pattern You can provide your own implementation of DeserializationExceptionHandler. This lets you use the Producer API to write a corrupted record directly to a quarantine topic. Then you can manually analyse your corrupted records ⚠Warning: This approach have side effects that are invisible to the Kafka Streams runtime.
  • 70. 70@loicmdivad @PubSapientEng 70@loicmdivad @PubSapientEng Dead Letter Queue Pattern You can provide your own implementation of DeserializationExceptionHandler. This lets you use the Producer API to write a corrupted record directly to a quarantine topic. Then you can manually analyse your corrupted records ⚠Warning: This approach have side effects that are invisible to the Kafka Streams runtime. Take Away
  • 72. 72@loicmdivad @PubSapientEng 72@loicmdivad @PubSapientEng CONFLUENT FAQ Links XKE-RATATOUILLE
  • 73. 73@loicmdivad @PubSapientEng 73@loicmdivad @PubSapientEng Related Post Kafka Connect Deep Dive – Error Handling and Dead Letter Queues - by Robin Moffatt Building Reliable Reprocessing and Dead Letter Queues with Apache Kafka - by Ning Xia Handling bad messages using Kafka's Streams API - answer by Matthias J. Sax
  • 74. 74@loicmdivad @PubSapientEng 74@loicmdivad @PubSapientEng Conclusion When using Kafka, deserialization is the responsibility of the clients. These internal errors are not easy to catch When it’s possible, use Avro + Schema Registry When it’s not possible, Kafka Streams applies techniques to deal with serde errors: - DLQ: By extending a ExceptionHandler - Sentinel Value: By extending a Deserializer
  • 76. 76@loicmdivad @PubSapientEng 76@loicmdivad @PubSapientEng Images Photo by rawpixel on Unsplash Photo by João Marcelo Martins on Unsplash Photo by Jordane Mathieu on Unsplash Photo by Brooke Lark on Unsplash Photo by Jakub Kapusnak on Unsplash Photo by Melissa Walker Horn on Unsplash Photo by Aneta Pawlik on Unsplash
  • 77. 77@loicmdivad @PubSapientEng 77@loicmdivad @PubSapientEng With special thanks to Robin M. Sylvain L. Giulia B.
  • 79. 79@loicmdivad @PubSapientEng Pure HTML Akka Http Server Akka Actor System Kafka Topic Exercise1 Exercise2 Me, clicking everywhere Akka Stream Kafka