SlideShare a Scribd company logo
1 of 82
Download to read offline
natans@wix.com twitter@NSilnitsky linkedin/natansilnitsky github.com/natansil
Greyhound -
A Powerful Pure Functional Kafka library
Natan Silnitsky
Backend Infra Developer, Wix.com
A Scala/Java high-level SDK for Apache Kafka.
Powered by ZIO
Greyhound
* features...
But first…
a few Kafka terms
@NSilnitsky
Kafka
Producer
Kafka Broker
A few
Kafka terms
@NSilnitsky
Kafka
Producer
Topic
Partition
Partition
Partition
Kafka Broker
Topic Topic
Partition
Partition
Partition
Partition
Partition
Partition
A few
Kafka terms
@NSilnitsky
Topic TopicTopic
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Kafka
Producer
Partition
0 1 2 3 4 5
append-only log
A few
Kafka terms
@NSilnitsky
Topic TopicTopic
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Partition
Kafka
Consumers
A few
Kafka terms
0 1 2 3 4 5 6 7 8 9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
Partition
Greyhound
Wraps
Kafka
Kafka Broker
Service A Service B
Kafka
Consumer
Kafka
Producer
@NSilnitsky
Kafka Broker
Service A Service B
Abstract
so that it is easy to
change for everyone
Simplify
APIs, with additional
features
Greyhound
wraps Kafka
Kafka
Consumer
Kafka
Producer
@NSilnitsky
Multiple APIs
For Java, Scala and
Wix Devs
Greyhound
wraps Kafka
Scala Future ZIO Java
Kafka
Consumer
Kafka
Producer
Kafka Broker
ZIO Core
Service A Service B
* all logic
@NSilnitsky
Greyhound
wraps Kafka
Scala Future ZIO Java
Kafka
Consumer
Kafka
Producer
Kafka Broker
ZIO Core
Wix Interop
OSS
Private
Service A Service B
Kafka Broker
Service A Service B
Kafka
Consumer
Kafka
Producer
- Boilerplate
Greyhound
wraps Kafka
What do we
want it to do?
val consumer: KafkaConsumer[String, SomeMessage] =
createConsumer()
def pollProcessAndCommit(): Unit = {
val consumerRecords = consumer.poll(1000).asScala
consumerRecords.foreach(record => {
println(s"Record value: ${record.value.messageValue}")
})
consumer.commitAsync()
pollProcessAndCommit()
}
pollProcessAndCommit()
Kafka
Consumer API
* Broker location, serde
val consumer: KafkaConsumer[String, SomeMessage] =
createConsumer()
def pollProcessAndCommit(): Unit = {
val consumerRecords = consumer.poll(1000).asScala
consumerRecords.foreach(record => {
println(s"Record value: ${record.value.messageValue}")
})
consumer.commitAsync()
pollProcessAndCommit()
}
pollProcessAndCommit()
Kafka
Consumer API
val consumer: KafkaConsumer[String, SomeMessage] =
createConsumer()
def pollProcessAndCommit(): Unit = {
val consumerRecords = consumer.poll(1000).asScala
consumerRecords.foreach(record => {
println(s"Record value: ${record.value.messageValue}")
})
consumer.commitAsync()
pollProcessAndCommit()
}
pollProcessAndCommit()
Kafka
Consumer API
val handler: RecordHandler[Console, Nothing,
String, SomeMessage] =
RecordHandler { record =>
zio.console.putStrLn(record.value.messageValue)
}
GreyhoundConsumersBuilder
.withConsumer(RecordConsumer(
topic = "some-group",
group = "group-2",
handle = handler))
Greyhound
Consumer API
* No commit, wix broker location
Functional
Composition
Greyhound
wraps Kafka
✔ Simple Consumer API
+ Composable Record
Handler
What do we
want it to do?
@NSilnitsky
Kafka Broker
Greyhound
Consumer
Kafka
Consumer
COMPOSABLE
RECORD HANDLER
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Commit
trait RecordHandler[-R, +E, K, V] {
def handle(record: ConsumerRecord[K, V]): ZIO[R, E, Any]
def contramap: RecordHandler
def contramapM: RecordHandler
def mapError: RecordHandler
def withErrorHandler: RecordHandler
def ignore: RecordHandler
def provide: RecordHandler
def andThen: RecordHandler
def withDeserializers: RecordHandler
}
Composable
Handler
@NSilnitsky
trait RecordHandler[-R, +E, K, V] {
def handle(record: ConsumerRecord[K, V]): ZIO[R, E, Any]
def contramap: RecordHandler
def contramapM: RecordHandler
def mapError: RecordHandler
def withErrorHandler: RecordHandler
def ignore: RecordHandler
def provide: RecordHandler
def andThen: RecordHandler
def withDeserializers: RecordHandler
}
Composable
Handler
@NSilnitsky * change type
def contramapM[R, E, K, V](f: ConsumerRecord[K2, V2] =>
ZIO[R, E, ConsumerRecord[K, V]])
: RecordHandler[R, E, K2, V2] =
new RecordHandler[R, E, K2, V2] {
override def handle(record: ConsumerRecord[K2, V2]): ZIO[R, E, Any] =
f(record).flatMap(self.handle)
}
def withDeserializers(keyDeserializer: Deserializer[K],
valueDeserializer: Deserializer[V])
: RecordHandler[R, Either[SerializationError, E], Chunk[Byte], Chunk[Byte]] =
mapError(Right(_)).contramapM { record =>
record.bimapM(
key => keyDeserializer.deserialize(record.topic, record.headers, key),
value => valueDeserializer.deserialize(record.topic, record.headers, value)
).mapError(e => Left(SerializationError(e)))
}
Composable
Handler
@NSilnitsky
def contramapM[R, E, K, V](f: ConsumerRecord[K2, V2] =>
ZIO[R, E, ConsumerRecord[K, V]])
: RecordHandler[R, E, K2, V2] =
new RecordHandler[R, E, K2, V2] {
override def handle(record: ConsumerRecord[K2, V2]): ZIO[R, E, Any] =
f(record).flatMap(self.handle)
}
def withDeserializers(keyDeserializer: Deserializer[K],
valueDeserializer: Deserializer[V])
: RecordHandler[R, Either[SerializationError, E], Chunk[Byte], Chunk[Byte]] =
mapError(Right(_)).contramapM { record =>
record.bimapM(
key => keyDeserializer.deserialize(record.topic, record.headers, key),
value => valueDeserializer.deserialize(record.topic, record.headers, value)
).mapError(e => Left(SerializationError(e)))
}
Composable
Handler
@NSilnitsky
RecordHandler(
(r: ConsumerRecord[String, Duration]) =>
putStrLn(s"duration: ${r.value.toMillis}"))
.withDeserializers(StringSerde, DurationSerde)
=>
RecordHandler[Console, scala.Either[SerializationError, scala.RuntimeException],
Chunk[Byte], Chunk[Byte]]
Composable
Handler
@NSilnitsky
@NSilnitsky
Kafka Broker
Greyhound
Consumer
Kafka
Consumer
DRILL DOWN
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
@NSilnitsky
Kafka Broker
Kafka
Consumer
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Event loop
Greyhound
Consumer
@NSilnitsky
Kafka Broker
Kafka
Consumer
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Event loop
Greyhound
Consumer
Workers
Message
Dispatcher
@NSilnitsky
Kafka Broker
Kafka
Consumer
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Event loop
Greyhound
Consumer
Workers
Message
Dispatcher
DRILL DOWN
object EventLoop {
def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
running <- Ref.make(true)
fiber <- pollLoop(running, consumer/*...*/).forkDaemon
} yield (fiber, running /*...*/)
start.toManaged {
case (fiber, running /*...*/) => for {
_ <- running.set(false)
// ...
} yield ()
}
}
EventLoop
Polling
object EventLoop {
def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
running <- Ref.make(true)
fiber <- pollLoop(running, consumer/*...*/).forkDaemon
} yield (fiber, running /*...*/)
start.toManaged {
case (fiber, running /*...*/) => for {
_ <- running.set(false)
// ...
} yield ()
}
}
EventLoop
Polling
@NSilnitsky * dispatcher.shutdown
object EventLoop {
def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
running <- Ref.make(true)
fiber <- pollLoop(running, consumer/*...*/).forkDaemon
} yield (fiber, running /*...*/)
start.toManaged {
case (fiber, running /*...*/) => for {
_ <- running.set(false)
// ...
} yield ()
}
}
EventLoop
Polling
* mem leak@NSilnitsky
object EventLoop {
def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
running <- Ref.make(true)
fiber <- pollLoop(running, consumer/*...*/).forkDaemon
} yield (fiber, running /*...*/)
start.toManaged {
case (fiber, running /*...*/) => for {
_ <- running.set(false)
// ...
} yield ()
}
}
EventLoop
Polling
@NSilnitsky
def pollLoop[R1](running: Ref[Boolean],
consumer: Consumer
// ...
): URIO[R1 with GreyhoundMetrics, Unit] =
running.get.flatMap {
case true => for {
//...
_ <- pollAndHandle(consumer /*...*/)
//...
result <- pollLoop(running, consumer /*...*/)
} yield result
case false => ZIO.unit
}
TailRec in ZIO
@NSilnitsky
def pollLoop[R1](running: Ref[Boolean],
consumer: Consumer
// ...
): URIO[R1 with GreyhoundMetrics, Unit] =
running.get.flatMap {
case true => // ...
pollAndHandle(consumer /*...*/)
// ...
.flatMap(_ =>
pollLoop(running, consumer /*...*/)
.map(result => result)
)
case false => ZIO.unit
}
TailRec in ZIO
https://github.com/oleg-py/better-monadic-for
@NSilnitsky
object EventLoop {
def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
running <- Ref.make(true)
fiber <- pollOnce(running, consumer/*, ...*/)
.doWhile(_ == true).forkDaemon
} yield (fiber, running /*...*/)
start.toManaged {
case (fiber, running /*...*/) => for {
_ <- running.set(false)
// ...
} yield ()
}
TailRec in ZIO
@NSilnitsky
object EventLoop {
type Handler[-R] = RecordHandler[R, Nothing, Chunk[Byte], Chunk[Byte]]
def make[R](handler: Handler[R] /*...*/): RManaged[Env, EventLoop[...]] = {
val start = for {
// ...
handle = handler.andThen(offsets.update).handle(_)
dispatcher <- Dispatcher.make(handle, /**/)
// ...
} yield (fiber, running /*...*/)
}
def pollOnce(/*...*/) = {
// poll and handle...
_ <- offsets.commit
Commit Offsets
@NSilnitsky
* old -> pass down
Greyhound
wraps Kafka
✔ Simple Consumer API
✔ Composable Record
Handler
+ Parallel Consumption!
What do we
want it to do?
val consumer: KafkaConsumer[String, SomeMessage] =
createConsumer()
def pollProcessAndCommit(): Unit = {
val consumerRecords = consumer.poll(1000).asScala
consumerRecords.foreach(record => {
println(s"Record value: ${record.value.messageValue}")
})
consumer.commitAsync()
pollProcessAndCommit()
}
pollProcessAndCommit()
Kafka
Consumer API
@NSilnitsky
Kafka Broker
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Greyhound
Consumer
ZIO FIBERS +
QUEUES
@NSilnitsky
Kafka Broker
Kafka
Consumer
Site
Published
Topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Event loop
Greyhound
Consumer
Workers
Message
Dispatcher
(THREAD-SAFE)
PARALLEL
CONSUMPTION
object Dispatcher {
def make[R](handle: Record => URIO[R, Any]): UIO[Dispatcher[R]] = for {
// ...
workers <- Ref.make(Map.empty[TopicPartition, Worker])
} yield new Dispatcher[R] {
override def submit(record: Record): URIO[..., SubmitResult] =
for {
// ...
worker <- workerFor(TopicPartition(record))
submitted <- worker.submit(record)
} yield // …
}
}
Parallel
Consumption
@NSilnitsky
object Dispatcher {
def make[R](handle: Record => URIO[R, Any]): UIO[Dispatcher[R]] = for {
// ...
workers <- Ref.make(Map.empty[TopicPartition, Worker])
} yield new Dispatcher[R] {
override def submit(record: Record): URIO[..., SubmitResult] =
for {
// ...
worker <- workerFor(TopicPartition(record))
submitted <- worker.submit(record)
} yield // …
}
}
Parallel
Consumption
* lazily
@NSilnitsky
object Worker {
def make[R](handle: Record => URIO[R, Any],
capacity: Int,/*...*/): URIO[...,Worker] = for {
queue <- Queue.dropping[Record](capacity)
_ <- // simplified
queue.take.flatMap { record =>
handle(record).as(true)
}.doWhile(_ == true).forkDaemon
} yield new Worker {
override def submit(record: Record): UIO[Boolean] =
queue.offer(record)
// ...
}
}
Parallel
Consumption
@NSilnitsky
object Worker {
def make[R](handle: Record => URIO[R, Any],
capacity: Int,/*...*/): URIO[...,Worker] = for {
queue <- Queue.dropping[Record](capacity)
_ <- // simplified
queue.take.flatMap { record =>
handle(record).as(true)
}.doWhile(_ == true).forkDaemon
} yield new Worker {
override def submit(record: Record): UIO[Boolean] =
queue.offer(record)
// ...
}
}
Parallel
Consumption
* semaphore
@NSilnitsky
object Worker {
def make[R](handle: Record => URIO[R, Any],
capacity: Int,/*...*/): URIO[...,Worker] = for {
queue <- Queue.dropping[Record](capacity)
_ <- // simplified
queue.take.flatMap { record =>
handle(record).as(true)
}.doWhile(_ == true).forkDaemon
} yield new Worker {
override def submit(record: Record): UIO[Boolean] =
queue.offer(record)
// ...
}
}
Parallel
Consumption
@NSilnitsky
class OldWorker(capacity: Int, /*...*/) {
private val tasksQueue = new LinkedBlockingDeque(capacity)
start()
private def start() = {
// simplified
val thread = new Thread(new TaskLoop)
thread.start()
}
private class TaskLoop extends Runnable {
override def run() = {
// simplified
while (true) {
val task = tasksQueue.take()
task.run()
}
}
}
...
}
Old Worker
class OldWorker(capacity: Int, /*...*/) {
private val tasksQueue = new LinkedBlockingDeque(capacity)
start()
private def start() = {
// simplified
val thread = new Thread(new TaskLoop)
thread.start()
}
private class TaskLoop extends Runnable {
override def run() = {
// simplified
while (true) {
val task = tasksQueue.take()
task.run()
}
}
}
...
}
Old Worker
* resource, context, maxPar
Greyhound
wraps Kafka
✔ Simple Consumer API
✔ Composable Record
Handler
✔ Parallel Consumption
+ Retries!
What do we
want it to do?
...what about
Error handling?
val retryConfig = RetryConfig.nonBlocking(
1.second, 10.minutes)
GreyhoundConsumersBuilder
.withConsumer(GreyhoundConsumer(
topic = "some-group",
group = "group-2",
handle = handler,
retryConfig = retryConfig))
Non-blocking
Retries
@NSilnitsky
Kafka Broker
renew-sub-topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Greyhound Consumer
FAILED
PROCESSING
Kafka Consumer
@NSilnitsky
Kafka Broker
renew-sub-topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
renew-sub-topic-retry-0
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
renew-sub-topic-retry-1
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Inspired by Uber
RETRY!
Greyhound Consumer
Kafka Consumer
RETRY
PRODUCER
@NSilnitsky
Kafka Broker
renew-sub-topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
renew-sub-topic-retry-0
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
0 1 2 3 4 5
renew-sub-topic-retry-1
Inspired by Uber
RETRY!
0 1 2 3 4 5
Greyhound Consumer
Kafka Consumer
RETRY
PRODUCER
Retries same message on failure
BLOCKING
POLICY
HANDLER
Kafka Broker
source-control-
update-topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Greyhound Consumer
Kafka Consumer
build-log-service
val retryConfig = RetryConfig.finiteBlocking(
1.second, 1.minutes)
GreyhoundConsumersBuilder
.withConsumer(GreyhoundConsumer(
topic = "some-group",
group = "group-2",
handle = handler,
retryConfig = retryConfig))
Blocking Retries
* exponential
Retries same message on failure
* lag
BLOCKING
POLICY
HANDLER
Kafka Broker
source-control-
update-topic
0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
Greyhound Consumer
Kafka Consumer
build-log-service
BLOCKING
POLICY
HANDLER
handle()
.retry(
Schedule.doWhile(_ => shouldBlock(blockingStateRef)) &&
Schedule.fromDurations(blockingBackoffs)
)
First Approach
handle()
.retry(
Schedule.doWhile(_ => shouldBlock(blockingStateRef)) &&
Schedule.fromDurations(blockingBackoffs)
)
First Approach
Doesn’t allow
delay
interruptions
foreachWhile(blockingBackoffs) { interval =>
handleAndBlockWithPolling(interval, blockingStateRef)
}
Current Solution
foreachWhile(blockingBackoffs) { interval =>
handleAndBlockWithPolling(interval, blockingStateRef)
}
Current Solution
Checks
blockingState
between short
sleeps
Allows user
request to
unblock
foreachWhile(blockingBackoffs) { interval =>
handleAndBlockWithPolling(interval, blockingStateRef)
}
Current Solution
def foreachWhile[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, Boolean]):
ZIO[R, E, Unit] =
ZIO.effectTotal(as.iterator).flatMap { i =>
def loop: ZIO[R, E, Unit] =
if (i.hasNext) f(i.next).flatMap(result => if(result) loop else
ZIO.unit)
else ZIO.unit
loop
}
foreachWhile(blockingBackoffs) { interval =>
handleAndBlockWithPolling(interval, blockingStateRef)
}
Current Solution
def foreachWhile[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, Boolean]):
ZIO[R, E, Unit] =
ZIO.effectTotal(as.iterator).flatMap { i =>
def loop: ZIO[R, E, Unit] =
if (i.hasNext) f(i.next).flatMap(result => if(result) loop else
ZIO.unit)
else ZIO.unit
loop
}
Stream.fromIterable(blockingBackoffs).foreachWhile(...)
Greyhound
wraps Kafka
✔ Simple Consumer API
✔ Composable Record
Handler
✔ Parallel Consumption
✔ Retries
+ Resilient Producer
What do we
want it to do?
and when
Kafka brokers
are
unavailable...
+ Retry
on Error
Kafka Broker
Producer
Use Case:
Guarantee completion
Consumer
Wix
Payments
Service
Subscription
renewal
Job
Scheduler
+ Retry
on Error
Kafka Broker
Producer
Use Case:
Guarantee completion
Consumer
Wix
Payments
Service
Job
Scheduler
Kafka Broker
Producer
Wix
Payments
Service
Job
Scheduler
RESILIENT
PRODUCER
Kafka Broker
Producer
Wix
Payments
Service
Job
Scheduler
FAILS TO
PRODUCE
Save message
to disk
Kafka Broker
Producer
Wix
Payments
Service
Job
Scheduler
FAILS TO
PRODUCE
Save message
to disk
Retry on failure
Greyhound
wraps Kafka
✔ Simple Consumer API
✔ Composable Record
Handler
✔ Parallel Consumption
✔ Retries
✔ Resilient Producer
+ Context Propagation
What do we
want it to do?
Super cool for
us
@NSilnitsky
CONTEXT
PROPAGATION
Language
User
Type
USER REQUEST METADATA
Sign up
Site-Members
ServiceGeo
...
Browser
CONTEXT
PROPAGATION
Site-Members
Service
Kafka Broker
Producer
Topic/Partition/Offset
Headers
Key
Value
timestamp
Browser
CONTEXT
PROPAGATION
Site-Members
Service
Kafka Broker
Producer
Contacts
Service
Consumer
Browser
context = contextFrom(record.headers, token)
handler.handle(record).provideSomeLayer[UserEnv](Context.layerFrom(context))
Context
Propagation
context = contextFrom(record.headers, token)
handler.handle(record,
controller).provideSomeLayer[UserEnv](Context.layerFrom(context))
RecordHandler((r: ConsumerRecord[String, Contact]) => for {
context <- Context.retrieve
_ <- ContactsDB.write(r.value, context)
} yield ()
=>
RecordHandler[ContactsDB with Context, Throwable, Chunk[Byte], Chunk[Byte]]
Context
Propagation
Greyhound
wraps Kafka
more features
✔ Simple Consumer API
✔ Composable Record Handler
✔ Parallel Consumption
✔ Retries
✔ Resilient Producer
✔ Context Propagation
✔ Pause/resume consumption
✔ Metrics reporting
Greyhound
wraps Kafka
✔ Simple Consumer API
✔ Composable Record Handler
✔ Parallel Consumption
✔ Retries
✔ Resilient Producer
✔ Context Propagation
✔ Pause/resume consumption
✔ Metrics reporting
future plans
+ Batch Consumer
+ Exactly Once Processing
+ In-Memory KV Stores
Will be much
simpler with
ZIO
Greyhound
Producer
Greyhound
Consumer
Kafka
Consumer
Kafka
Producer
Kafka Broker
GREYHOUND
USE CASES
AT WIX
Pub/Sub
CDC
Offline Scheduled Jobs
DB (Elastic
Search) replication
Action retries
Materialized
Views
- Much less boilerplate
- Code that’s easier to understand
- Fun
REWRITING
GREYHOUND IN ZIO
RESULTED IN...
...but ZIO offers lower level abstractions too, like Promise and clock.sleep.
SOMETIMES YOU CAN’T DO
EXACTLY WHAT YOU WANT WITH
HIGH LEVEL ZIO OPERATORS
A Scala/Java high-level SDK for Apache Kafka.
0.1 is out!
github.com/wix/greyhound
Thank You
natans@wix.com twitter @NSilnitsky linkedin/natansilnitsky github.com/natansil
Slides & More
slideshare.net/NatanSilnitsky
medium.com/@natansil
twitter.com/NSilnitsky
natansil.com

More Related Content

What's hot

Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Natan Silnitsky
 
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUANatan Silnitsky
 
How to build 1000 microservices with Kafka and thrive
How to build 1000 microservices with Kafka and thriveHow to build 1000 microservices with Kafka and thrive
How to build 1000 microservices with Kafka and thriveNatan Silnitsky
 
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...Natan Silnitsky
 
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...Natan Silnitsky
 
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Natan Silnitsky
 
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...confluent
 
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative Natan Silnitsky
 
WebSocket MicroService vs. REST Microservice
WebSocket MicroService vs. REST MicroserviceWebSocket MicroService vs. REST Microservice
WebSocket MicroService vs. REST MicroserviceRick Hightower
 
Open sourcing a successful internal project - Reversim 2021
Open sourcing a successful internal project - Reversim 2021Open sourcing a successful internal project - Reversim 2021
Open sourcing a successful internal project - Reversim 2021Natan Silnitsky
 
Using Microservices Architecture and Patterns to Address Applications Require...
Using Microservices Architecture and Patterns to Address Applications Require...Using Microservices Architecture and Patterns to Address Applications Require...
Using Microservices Architecture and Patterns to Address Applications Require...Prem Sankar Gopannan
 
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020HostedbyConfluent
 
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...KCDItaly
 
Comprehensive container based service monitoring with kubernetes and istio
Comprehensive container based service monitoring with kubernetes and istioComprehensive container based service monitoring with kubernetes and istio
Comprehensive container based service monitoring with kubernetes and istioFred Moyer
 
Serverless architecture: introduction & first steps
Serverless architecture: introduction & first stepsServerless architecture: introduction & first steps
Serverless architecture: introduction & first stepsThe Software House
 
Building a Service Mesh with Envoy (Kubecon May 2018)
Building a Service Mesh with Envoy (Kubecon May 2018)Building a Service Mesh with Envoy (Kubecon May 2018)
Building a Service Mesh with Envoy (Kubecon May 2018)Douglas Jones
 
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...confluent
 
Java Microservices with Netflix OSS & Spring
Java Microservices with Netflix OSS & Spring Java Microservices with Netflix OSS & Spring
Java Microservices with Netflix OSS & Spring Conor Svensson
 
Introducing envoy-based service mesh at Booking.com
Introducing envoy-based service mesh at Booking.comIntroducing envoy-based service mesh at Booking.com
Introducing envoy-based service mesh at Booking.comIvan Kruglov
 

What's hot (20)

Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
 
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA
10 Lessons Learned from using Kafka in 1000 microservices - ScalaUA
 
How to build 1000 microservices with Kafka and thrive
How to build 1000 microservices with Kafka and thriveHow to build 1000 microservices with Kafka and thrive
How to build 1000 microservices with Kafka and thrive
 
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...
Polyglot, Fault Tolerant Event-Driven Programming with Kafka, Kubernetes and ...
 
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...
Battle Tested Event-Driven Patterns for your Microservices Architecture - Ris...
 
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
Polyglot, fault-tolerant event-driven programming with kafka, kubernetes and ...
 
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...
Making Sense of Your Event-Driven Dataflows (Jorge Esteban Quilcate Otoya, SY...
 
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative
Migrating to Multi Cluster Managed Kafka - Conf42 - CloudNative
 
WebSocket MicroService vs. REST Microservice
WebSocket MicroService vs. REST MicroserviceWebSocket MicroService vs. REST Microservice
WebSocket MicroService vs. REST Microservice
 
Network Service Mesh
Network Service MeshNetwork Service Mesh
Network Service Mesh
 
Open sourcing a successful internal project - Reversim 2021
Open sourcing a successful internal project - Reversim 2021Open sourcing a successful internal project - Reversim 2021
Open sourcing a successful internal project - Reversim 2021
 
Using Microservices Architecture and Patterns to Address Applications Require...
Using Microservices Architecture and Patterns to Address Applications Require...Using Microservices Architecture and Patterns to Address Applications Require...
Using Microservices Architecture and Patterns to Address Applications Require...
 
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020
Can Kafka Handle a Lyft Ride? (Andrey Falko & Can Cecen, Lyft) Kafka Summit 2020
 
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...
Multi-Clusters Made Easy with Liqo:
Getting Rid of Your Clusters Keeping Them...
 
Comprehensive container based service monitoring with kubernetes and istio
Comprehensive container based service monitoring with kubernetes and istioComprehensive container based service monitoring with kubernetes and istio
Comprehensive container based service monitoring with kubernetes and istio
 
Serverless architecture: introduction & first steps
Serverless architecture: introduction & first stepsServerless architecture: introduction & first steps
Serverless architecture: introduction & first steps
 
Building a Service Mesh with Envoy (Kubecon May 2018)
Building a Service Mesh with Envoy (Kubecon May 2018)Building a Service Mesh with Envoy (Kubecon May 2018)
Building a Service Mesh with Envoy (Kubecon May 2018)
 
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...
Kafka on Kubernetes: Does it really have to be "The Hard Way"? (Viktor Gamov ...
 
Java Microservices with Netflix OSS & Spring
Java Microservices with Netflix OSS & Spring Java Microservices with Netflix OSS & Spring
Java Microservices with Netflix OSS & Spring
 
Introducing envoy-based service mesh at Booking.com
Introducing envoy-based service mesh at Booking.comIntroducing envoy-based service mesh at Booking.com
Introducing envoy-based service mesh at Booking.com
 

Similar to Greyhound - Powerful Pure Functional Kafka Library

Reactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysReactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysManuel Bernhardt
 
Symfony War Stories
Symfony War StoriesSymfony War Stories
Symfony War StoriesJakub Zalas
 
Torquebox OSCON Java 2011
Torquebox OSCON Java 2011Torquebox OSCON Java 2011
Torquebox OSCON Java 2011tobiascrawley
 
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...Natan Silnitsky
 
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)James Titcumb
 
Deep dive in container service discovery
Deep dive in container service discoveryDeep dive in container service discovery
Deep dive in container service discoveryDocker, Inc.
 
Building Stream Processing as a Service
Building Stream Processing as a ServiceBuilding Stream Processing as a Service
Building Stream Processing as a ServiceSteven Wu
 
Kubernetes Networking
Kubernetes NetworkingKubernetes Networking
Kubernetes NetworkingCJ Cullen
 
Istio Playground
Istio PlaygroundIstio Playground
Istio PlaygroundQAware GmbH
 
Serverless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopServerless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopLuciano Mammino
 
Getting up to speed with Kafka Connect: from the basics to the latest feature...
Getting up to speed with Kafka Connect: from the basics to the latest feature...Getting up to speed with Kafka Connect: from the basics to the latest feature...
Getting up to speed with Kafka Connect: from the basics to the latest feature...HostedbyConfluent
 
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...HostedbyConfluent
 
Ring: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic ClojureRing: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic ClojureMark McGranaghan
 
Puppet Performance Profiling
Puppet Performance ProfilingPuppet Performance Profiling
Puppet Performance Profilingripienaar
 
Building Cloud Castles
Building Cloud CastlesBuilding Cloud Castles
Building Cloud CastlesBen Scofield
 
Scaling docker with kubernetes
Scaling docker with kubernetesScaling docker with kubernetes
Scaling docker with kubernetesLiran Cohen
 
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...Natan Silnitsky
 
Integrating Flex And Rails With Ruby Amf
Integrating Flex And Rails With Ruby AmfIntegrating Flex And Rails With Ruby Amf
Integrating Flex And Rails With Ruby Amfrailsconf
 

Similar to Greyhound - Powerful Pure Functional Kafka Library (20)

Reactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDaysReactive Web-Applications @ LambdaDays
Reactive Web-Applications @ LambdaDays
 
Rack Middleware
Rack MiddlewareRack Middleware
Rack Middleware
 
Symfony War Stories
Symfony War StoriesSymfony War Stories
Symfony War Stories
 
Torquebox OSCON Java 2011
Torquebox OSCON Java 2011Torquebox OSCON Java 2011
Torquebox OSCON Java 2011
 
Pharos
PharosPharos
Pharos
 
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...
8 Lessons Learned from Using Kafka in 1500 microservices - confluent streamin...
 
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)
Adding 1.21 Gigawatts to Applications with RabbitMQ (PHPNW Dec 2014 Meetup)
 
Deep dive in container service discovery
Deep dive in container service discoveryDeep dive in container service discovery
Deep dive in container service discovery
 
Building Stream Processing as a Service
Building Stream Processing as a ServiceBuilding Stream Processing as a Service
Building Stream Processing as a Service
 
Kubernetes Networking
Kubernetes NetworkingKubernetes Networking
Kubernetes Networking
 
Istio Playground
Istio PlaygroundIstio Playground
Istio Playground
 
Serverless, The Middy Way - Workshop
Serverless, The Middy Way - WorkshopServerless, The Middy Way - Workshop
Serverless, The Middy Way - Workshop
 
Getting up to speed with Kafka Connect: from the basics to the latest feature...
Getting up to speed with Kafka Connect: from the basics to the latest feature...Getting up to speed with Kafka Connect: from the basics to the latest feature...
Getting up to speed with Kafka Connect: from the basics to the latest feature...
 
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...
Kafka as a Platform: the Ecosystem from the Ground Up with Robin Moffatt | Ka...
 
Ring: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic ClojureRing: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic Clojure
 
Puppet Performance Profiling
Puppet Performance ProfilingPuppet Performance Profiling
Puppet Performance Profiling
 
Building Cloud Castles
Building Cloud CastlesBuilding Cloud Castles
Building Cloud Castles
 
Scaling docker with kubernetes
Scaling docker with kubernetesScaling docker with kubernetes
Scaling docker with kubernetes
 
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...
8 Lessons Learned from Using Kafka in 1000 Scala microservices - Scale by the...
 
Integrating Flex And Rails With Ruby Amf
Integrating Flex And Rails With Ruby AmfIntegrating Flex And Rails With Ruby Amf
Integrating Flex And Rails With Ruby Amf
 

More from Natan Silnitsky

Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...Workflow Engines & Event Streaming Brokers - Can they work together? [Current...
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...Natan Silnitsky
 
DevSum - Lessons Learned from 2000 microservices
DevSum - Lessons Learned from 2000 microservicesDevSum - Lessons Learned from 2000 microservices
DevSum - Lessons Learned from 2000 microservicesNatan Silnitsky
 
GeeCon - Lessons Learned from 2000 microservices
GeeCon - Lessons Learned from 2000 microservicesGeeCon - Lessons Learned from 2000 microservices
GeeCon - Lessons Learned from 2000 microservicesNatan Silnitsky
 
Migrating to Multi Cluster Managed Kafka - ApacheKafkaIL
Migrating to Multi Cluster Managed Kafka - ApacheKafkaILMigrating to Multi Cluster Managed Kafka - ApacheKafkaIL
Migrating to Multi Cluster Managed Kafka - ApacheKafkaILNatan Silnitsky
 
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven Microservices
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven MicroservicesWix+Confluent Meetup - Lessons Learned from 2000 Event Driven Microservices
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven MicroservicesNatan Silnitsky
 
BuildStuff - Lessons Learned from 2000 Event Driven Microservices
BuildStuff - Lessons Learned from 2000 Event Driven MicroservicesBuildStuff - Lessons Learned from 2000 Event Driven Microservices
BuildStuff - Lessons Learned from 2000 Event Driven MicroservicesNatan Silnitsky
 
Lessons Learned from 2000 Event Driven Microservices - Reversim
Lessons Learned from 2000 Event Driven Microservices - ReversimLessons Learned from 2000 Event Driven Microservices - Reversim
Lessons Learned from 2000 Event Driven Microservices - ReversimNatan Silnitsky
 
Devoxx Ukraine - Kafka based Global Data Mesh
Devoxx Ukraine - Kafka based Global Data MeshDevoxx Ukraine - Kafka based Global Data Mesh
Devoxx Ukraine - Kafka based Global Data MeshNatan Silnitsky
 
Devoxx UK - Migrating to Multi Cluster Managed Kafka
Devoxx UK - Migrating to Multi Cluster Managed KafkaDevoxx UK - Migrating to Multi Cluster Managed Kafka
Devoxx UK - Migrating to Multi Cluster Managed KafkaNatan Silnitsky
 
Dev Days Europe - Kafka based Global Data Mesh at Wix
Dev Days Europe - Kafka based Global Data Mesh at WixDev Days Europe - Kafka based Global Data Mesh at Wix
Dev Days Europe - Kafka based Global Data Mesh at WixNatan Silnitsky
 
Kafka Summit London - Kafka based Global Data Mesh at Wix
Kafka Summit London - Kafka based Global Data Mesh at WixKafka Summit London - Kafka based Global Data Mesh at Wix
Kafka Summit London - Kafka based Global Data Mesh at WixNatan Silnitsky
 
5 Takeaways from Migrating a Library to Scala 3 - Scala Love
5 Takeaways from Migrating a Library to Scala 3 - Scala Love5 Takeaways from Migrating a Library to Scala 3 - Scala Love
5 Takeaways from Migrating a Library to Scala 3 - Scala LoveNatan Silnitsky
 
Migrating to Multi Cluster Managed Kafka - DevopStars 2022
Migrating to Multi Cluster Managed Kafka - DevopStars 2022Migrating to Multi Cluster Managed Kafka - DevopStars 2022
Migrating to Multi Cluster Managed Kafka - DevopStars 2022Natan Silnitsky
 
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021Natan Silnitsky
 
Advanced Caching Patterns used by 2000 microservices - Code Motion
Advanced Caching Patterns used by 2000 microservices - Code MotionAdvanced Caching Patterns used by 2000 microservices - Code Motion
Advanced Caching Patterns used by 2000 microservices - Code MotionNatan Silnitsky
 
Advanced Caching Patterns used by 2000 microservices - Devoxx Ukraine
Advanced Caching Patterns used by 2000 microservices - Devoxx UkraineAdvanced Caching Patterns used by 2000 microservices - Devoxx Ukraine
Advanced Caching Patterns used by 2000 microservices - Devoxx UkraineNatan Silnitsky
 
Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Natan Silnitsky
 
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021Natan Silnitsky
 
Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Natan Silnitsky
 

More from Natan Silnitsky (20)

Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...Workflow Engines & Event Streaming Brokers - Can they work together? [Current...
Workflow Engines & Event Streaming Brokers - Can they work together? [Current...
 
DevSum - Lessons Learned from 2000 microservices
DevSum - Lessons Learned from 2000 microservicesDevSum - Lessons Learned from 2000 microservices
DevSum - Lessons Learned from 2000 microservices
 
GeeCon - Lessons Learned from 2000 microservices
GeeCon - Lessons Learned from 2000 microservicesGeeCon - Lessons Learned from 2000 microservices
GeeCon - Lessons Learned from 2000 microservices
 
Migrating to Multi Cluster Managed Kafka - ApacheKafkaIL
Migrating to Multi Cluster Managed Kafka - ApacheKafkaILMigrating to Multi Cluster Managed Kafka - ApacheKafkaIL
Migrating to Multi Cluster Managed Kafka - ApacheKafkaIL
 
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven Microservices
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven MicroservicesWix+Confluent Meetup - Lessons Learned from 2000 Event Driven Microservices
Wix+Confluent Meetup - Lessons Learned from 2000 Event Driven Microservices
 
BuildStuff - Lessons Learned from 2000 Event Driven Microservices
BuildStuff - Lessons Learned from 2000 Event Driven MicroservicesBuildStuff - Lessons Learned from 2000 Event Driven Microservices
BuildStuff - Lessons Learned from 2000 Event Driven Microservices
 
Lessons Learned from 2000 Event Driven Microservices - Reversim
Lessons Learned from 2000 Event Driven Microservices - ReversimLessons Learned from 2000 Event Driven Microservices - Reversim
Lessons Learned from 2000 Event Driven Microservices - Reversim
 
Devoxx Ukraine - Kafka based Global Data Mesh
Devoxx Ukraine - Kafka based Global Data MeshDevoxx Ukraine - Kafka based Global Data Mesh
Devoxx Ukraine - Kafka based Global Data Mesh
 
Devoxx UK - Migrating to Multi Cluster Managed Kafka
Devoxx UK - Migrating to Multi Cluster Managed KafkaDevoxx UK - Migrating to Multi Cluster Managed Kafka
Devoxx UK - Migrating to Multi Cluster Managed Kafka
 
Dev Days Europe - Kafka based Global Data Mesh at Wix
Dev Days Europe - Kafka based Global Data Mesh at WixDev Days Europe - Kafka based Global Data Mesh at Wix
Dev Days Europe - Kafka based Global Data Mesh at Wix
 
Kafka Summit London - Kafka based Global Data Mesh at Wix
Kafka Summit London - Kafka based Global Data Mesh at WixKafka Summit London - Kafka based Global Data Mesh at Wix
Kafka Summit London - Kafka based Global Data Mesh at Wix
 
5 Takeaways from Migrating a Library to Scala 3 - Scala Love
5 Takeaways from Migrating a Library to Scala 3 - Scala Love5 Takeaways from Migrating a Library to Scala 3 - Scala Love
5 Takeaways from Migrating a Library to Scala 3 - Scala Love
 
Migrating to Multi Cluster Managed Kafka - DevopStars 2022
Migrating to Multi Cluster Managed Kafka - DevopStars 2022Migrating to Multi Cluster Managed Kafka - DevopStars 2022
Migrating to Multi Cluster Managed Kafka - DevopStars 2022
 
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
How to successfully manage a ZIO fiber’s lifecycle - Functional Scala 2021
 
Advanced Caching Patterns used by 2000 microservices - Code Motion
Advanced Caching Patterns used by 2000 microservices - Code MotionAdvanced Caching Patterns used by 2000 microservices - Code Motion
Advanced Caching Patterns used by 2000 microservices - Code Motion
 
Advanced Caching Patterns used by 2000 microservices - Devoxx Ukraine
Advanced Caching Patterns used by 2000 microservices - Devoxx UkraineAdvanced Caching Patterns used by 2000 microservices - Devoxx Ukraine
Advanced Caching Patterns used by 2000 microservices - Devoxx Ukraine
 
Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...
 
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021
Advanced Caching Patterns used by 2000 microservices - WeAreDevelopers 2021
 
Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...Battle-tested event-driven patterns for your microservices architecture - Sca...
Battle-tested event-driven patterns for your microservices architecture - Sca...
 

Recently uploaded

Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationKnoldus Inc.
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Strongerpanagenda
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Hiroshi SHIBATA
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentPim van der Noll
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI AgeCprime
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...Wes McKinney
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...AliaaTarek5
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxLoriGlavin3
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesKari Kakkonen
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesThousandEyes
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 

Recently uploaded (20)

Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Data governance with Unity Catalog Presentation
Data governance with Unity Catalog PresentationData governance with Unity Catalog Presentation
Data governance with Unity Catalog Presentation
 
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better StrongerModern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
Modern Roaming for Notes and Nomad – Cheaper Faster Better Stronger
 
Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024Long journey of Ruby standard library at RubyConf AU 2024
Long journey of Ruby standard library at RubyConf AU 2024
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native developmentEmixa Mendix Meetup 11 April 2024 about Mendix Native development
Emixa Mendix Meetup 11 April 2024 about Mendix Native development
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
A Framework for Development in the AI Age
A Framework for Development in the AI AgeA Framework for Development in the AI Age
A Framework for Development in the AI Age
 
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
The Future Roadmap for the Composable Data Stack - Wes McKinney - Data Counci...
 
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
(How to Program) Paul Deitel, Harvey Deitel-Java How to Program, Early Object...
 
The State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptxThe State of Passkeys with FIDO Alliance.pptx
The State of Passkeys with FIDO Alliance.pptx
 
Testing tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examplesTesting tools and AI - ideas what to try with some tool examples
Testing tools and AI - ideas what to try with some tool examples
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyesAssure Ecommerce and Retail Operations Uptime with ThousandEyes
Assure Ecommerce and Retail Operations Uptime with ThousandEyes
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 

Greyhound - Powerful Pure Functional Kafka Library

  • 1. natans@wix.com twitter@NSilnitsky linkedin/natansilnitsky github.com/natansil Greyhound - A Powerful Pure Functional Kafka library Natan Silnitsky Backend Infra Developer, Wix.com
  • 2. A Scala/Java high-level SDK for Apache Kafka. Powered by ZIO Greyhound * features...
  • 3. But first… a few Kafka terms
  • 8. Greyhound Wraps Kafka Kafka Broker Service A Service B Kafka Consumer Kafka Producer
  • 9. @NSilnitsky Kafka Broker Service A Service B Abstract so that it is easy to change for everyone Simplify APIs, with additional features Greyhound wraps Kafka Kafka Consumer Kafka Producer
  • 10. @NSilnitsky Multiple APIs For Java, Scala and Wix Devs Greyhound wraps Kafka Scala Future ZIO Java Kafka Consumer Kafka Producer Kafka Broker ZIO Core Service A Service B * all logic
  • 11. @NSilnitsky Greyhound wraps Kafka Scala Future ZIO Java Kafka Consumer Kafka Producer Kafka Broker ZIO Core Wix Interop OSS Private Service A Service B
  • 12. Kafka Broker Service A Service B Kafka Consumer Kafka Producer - Boilerplate Greyhound wraps Kafka What do we want it to do?
  • 13. val consumer: KafkaConsumer[String, SomeMessage] = createConsumer() def pollProcessAndCommit(): Unit = { val consumerRecords = consumer.poll(1000).asScala consumerRecords.foreach(record => { println(s"Record value: ${record.value.messageValue}") }) consumer.commitAsync() pollProcessAndCommit() } pollProcessAndCommit() Kafka Consumer API * Broker location, serde
  • 14. val consumer: KafkaConsumer[String, SomeMessage] = createConsumer() def pollProcessAndCommit(): Unit = { val consumerRecords = consumer.poll(1000).asScala consumerRecords.foreach(record => { println(s"Record value: ${record.value.messageValue}") }) consumer.commitAsync() pollProcessAndCommit() } pollProcessAndCommit() Kafka Consumer API
  • 15. val consumer: KafkaConsumer[String, SomeMessage] = createConsumer() def pollProcessAndCommit(): Unit = { val consumerRecords = consumer.poll(1000).asScala consumerRecords.foreach(record => { println(s"Record value: ${record.value.messageValue}") }) consumer.commitAsync() pollProcessAndCommit() } pollProcessAndCommit() Kafka Consumer API
  • 16. val handler: RecordHandler[Console, Nothing, String, SomeMessage] = RecordHandler { record => zio.console.putStrLn(record.value.messageValue) } GreyhoundConsumersBuilder .withConsumer(RecordConsumer( topic = "some-group", group = "group-2", handle = handler)) Greyhound Consumer API * No commit, wix broker location
  • 17. Functional Composition Greyhound wraps Kafka ✔ Simple Consumer API + Composable Record Handler What do we want it to do?
  • 18. @NSilnitsky Kafka Broker Greyhound Consumer Kafka Consumer COMPOSABLE RECORD HANDLER Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Commit
  • 19. trait RecordHandler[-R, +E, K, V] { def handle(record: ConsumerRecord[K, V]): ZIO[R, E, Any] def contramap: RecordHandler def contramapM: RecordHandler def mapError: RecordHandler def withErrorHandler: RecordHandler def ignore: RecordHandler def provide: RecordHandler def andThen: RecordHandler def withDeserializers: RecordHandler } Composable Handler @NSilnitsky
  • 20. trait RecordHandler[-R, +E, K, V] { def handle(record: ConsumerRecord[K, V]): ZIO[R, E, Any] def contramap: RecordHandler def contramapM: RecordHandler def mapError: RecordHandler def withErrorHandler: RecordHandler def ignore: RecordHandler def provide: RecordHandler def andThen: RecordHandler def withDeserializers: RecordHandler } Composable Handler @NSilnitsky * change type
  • 21. def contramapM[R, E, K, V](f: ConsumerRecord[K2, V2] => ZIO[R, E, ConsumerRecord[K, V]]) : RecordHandler[R, E, K2, V2] = new RecordHandler[R, E, K2, V2] { override def handle(record: ConsumerRecord[K2, V2]): ZIO[R, E, Any] = f(record).flatMap(self.handle) } def withDeserializers(keyDeserializer: Deserializer[K], valueDeserializer: Deserializer[V]) : RecordHandler[R, Either[SerializationError, E], Chunk[Byte], Chunk[Byte]] = mapError(Right(_)).contramapM { record => record.bimapM( key => keyDeserializer.deserialize(record.topic, record.headers, key), value => valueDeserializer.deserialize(record.topic, record.headers, value) ).mapError(e => Left(SerializationError(e))) } Composable Handler @NSilnitsky
  • 22. def contramapM[R, E, K, V](f: ConsumerRecord[K2, V2] => ZIO[R, E, ConsumerRecord[K, V]]) : RecordHandler[R, E, K2, V2] = new RecordHandler[R, E, K2, V2] { override def handle(record: ConsumerRecord[K2, V2]): ZIO[R, E, Any] = f(record).flatMap(self.handle) } def withDeserializers(keyDeserializer: Deserializer[K], valueDeserializer: Deserializer[V]) : RecordHandler[R, Either[SerializationError, E], Chunk[Byte], Chunk[Byte]] = mapError(Right(_)).contramapM { record => record.bimapM( key => keyDeserializer.deserialize(record.topic, record.headers, key), value => valueDeserializer.deserialize(record.topic, record.headers, value) ).mapError(e => Left(SerializationError(e))) } Composable Handler @NSilnitsky
  • 23. RecordHandler( (r: ConsumerRecord[String, Duration]) => putStrLn(s"duration: ${r.value.toMillis}")) .withDeserializers(StringSerde, DurationSerde) => RecordHandler[Console, scala.Either[SerializationError, scala.RuntimeException], Chunk[Byte], Chunk[Byte]] Composable Handler @NSilnitsky
  • 24. @NSilnitsky Kafka Broker Greyhound Consumer Kafka Consumer DRILL DOWN Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5
  • 25. @NSilnitsky Kafka Broker Kafka Consumer Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Event loop Greyhound Consumer
  • 26. @NSilnitsky Kafka Broker Kafka Consumer Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Event loop Greyhound Consumer Workers Message Dispatcher
  • 27. @NSilnitsky Kafka Broker Kafka Consumer Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Event loop Greyhound Consumer Workers Message Dispatcher DRILL DOWN
  • 28. object EventLoop { def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { running <- Ref.make(true) fiber <- pollLoop(running, consumer/*...*/).forkDaemon } yield (fiber, running /*...*/) start.toManaged { case (fiber, running /*...*/) => for { _ <- running.set(false) // ... } yield () } } EventLoop Polling
  • 29. object EventLoop { def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { running <- Ref.make(true) fiber <- pollLoop(running, consumer/*...*/).forkDaemon } yield (fiber, running /*...*/) start.toManaged { case (fiber, running /*...*/) => for { _ <- running.set(false) // ... } yield () } } EventLoop Polling @NSilnitsky * dispatcher.shutdown
  • 30. object EventLoop { def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { running <- Ref.make(true) fiber <- pollLoop(running, consumer/*...*/).forkDaemon } yield (fiber, running /*...*/) start.toManaged { case (fiber, running /*...*/) => for { _ <- running.set(false) // ... } yield () } } EventLoop Polling * mem leak@NSilnitsky
  • 31. object EventLoop { def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { running <- Ref.make(true) fiber <- pollLoop(running, consumer/*...*/).forkDaemon } yield (fiber, running /*...*/) start.toManaged { case (fiber, running /*...*/) => for { _ <- running.set(false) // ... } yield () } } EventLoop Polling @NSilnitsky
  • 32. def pollLoop[R1](running: Ref[Boolean], consumer: Consumer // ... ): URIO[R1 with GreyhoundMetrics, Unit] = running.get.flatMap { case true => for { //... _ <- pollAndHandle(consumer /*...*/) //... result <- pollLoop(running, consumer /*...*/) } yield result case false => ZIO.unit } TailRec in ZIO @NSilnitsky
  • 33. def pollLoop[R1](running: Ref[Boolean], consumer: Consumer // ... ): URIO[R1 with GreyhoundMetrics, Unit] = running.get.flatMap { case true => // ... pollAndHandle(consumer /*...*/) // ... .flatMap(_ => pollLoop(running, consumer /*...*/) .map(result => result) ) case false => ZIO.unit } TailRec in ZIO https://github.com/oleg-py/better-monadic-for @NSilnitsky
  • 34. object EventLoop { def make[R](consumer: Consumer /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { running <- Ref.make(true) fiber <- pollOnce(running, consumer/*, ...*/) .doWhile(_ == true).forkDaemon } yield (fiber, running /*...*/) start.toManaged { case (fiber, running /*...*/) => for { _ <- running.set(false) // ... } yield () } TailRec in ZIO @NSilnitsky
  • 35. object EventLoop { type Handler[-R] = RecordHandler[R, Nothing, Chunk[Byte], Chunk[Byte]] def make[R](handler: Handler[R] /*...*/): RManaged[Env, EventLoop[...]] = { val start = for { // ... handle = handler.andThen(offsets.update).handle(_) dispatcher <- Dispatcher.make(handle, /**/) // ... } yield (fiber, running /*...*/) } def pollOnce(/*...*/) = { // poll and handle... _ <- offsets.commit Commit Offsets @NSilnitsky * old -> pass down
  • 36. Greyhound wraps Kafka ✔ Simple Consumer API ✔ Composable Record Handler + Parallel Consumption! What do we want it to do?
  • 37. val consumer: KafkaConsumer[String, SomeMessage] = createConsumer() def pollProcessAndCommit(): Unit = { val consumerRecords = consumer.poll(1000).asScala consumerRecords.foreach(record => { println(s"Record value: ${record.value.messageValue}") }) consumer.commitAsync() pollProcessAndCommit() } pollProcessAndCommit() Kafka Consumer API
  • 38. @NSilnitsky Kafka Broker Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Greyhound Consumer ZIO FIBERS + QUEUES
  • 39. @NSilnitsky Kafka Broker Kafka Consumer Site Published Topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Event loop Greyhound Consumer Workers Message Dispatcher (THREAD-SAFE) PARALLEL CONSUMPTION
  • 40. object Dispatcher { def make[R](handle: Record => URIO[R, Any]): UIO[Dispatcher[R]] = for { // ... workers <- Ref.make(Map.empty[TopicPartition, Worker]) } yield new Dispatcher[R] { override def submit(record: Record): URIO[..., SubmitResult] = for { // ... worker <- workerFor(TopicPartition(record)) submitted <- worker.submit(record) } yield // … } } Parallel Consumption @NSilnitsky
  • 41. object Dispatcher { def make[R](handle: Record => URIO[R, Any]): UIO[Dispatcher[R]] = for { // ... workers <- Ref.make(Map.empty[TopicPartition, Worker]) } yield new Dispatcher[R] { override def submit(record: Record): URIO[..., SubmitResult] = for { // ... worker <- workerFor(TopicPartition(record)) submitted <- worker.submit(record) } yield // … } } Parallel Consumption * lazily @NSilnitsky
  • 42. object Worker { def make[R](handle: Record => URIO[R, Any], capacity: Int,/*...*/): URIO[...,Worker] = for { queue <- Queue.dropping[Record](capacity) _ <- // simplified queue.take.flatMap { record => handle(record).as(true) }.doWhile(_ == true).forkDaemon } yield new Worker { override def submit(record: Record): UIO[Boolean] = queue.offer(record) // ... } } Parallel Consumption @NSilnitsky
  • 43. object Worker { def make[R](handle: Record => URIO[R, Any], capacity: Int,/*...*/): URIO[...,Worker] = for { queue <- Queue.dropping[Record](capacity) _ <- // simplified queue.take.flatMap { record => handle(record).as(true) }.doWhile(_ == true).forkDaemon } yield new Worker { override def submit(record: Record): UIO[Boolean] = queue.offer(record) // ... } } Parallel Consumption * semaphore @NSilnitsky
  • 44. object Worker { def make[R](handle: Record => URIO[R, Any], capacity: Int,/*...*/): URIO[...,Worker] = for { queue <- Queue.dropping[Record](capacity) _ <- // simplified queue.take.flatMap { record => handle(record).as(true) }.doWhile(_ == true).forkDaemon } yield new Worker { override def submit(record: Record): UIO[Boolean] = queue.offer(record) // ... } } Parallel Consumption @NSilnitsky
  • 45. class OldWorker(capacity: Int, /*...*/) { private val tasksQueue = new LinkedBlockingDeque(capacity) start() private def start() = { // simplified val thread = new Thread(new TaskLoop) thread.start() } private class TaskLoop extends Runnable { override def run() = { // simplified while (true) { val task = tasksQueue.take() task.run() } } } ... } Old Worker
  • 46. class OldWorker(capacity: Int, /*...*/) { private val tasksQueue = new LinkedBlockingDeque(capacity) start() private def start() = { // simplified val thread = new Thread(new TaskLoop) thread.start() } private class TaskLoop extends Runnable { override def run() = { // simplified while (true) { val task = tasksQueue.take() task.run() } } } ... } Old Worker * resource, context, maxPar
  • 47. Greyhound wraps Kafka ✔ Simple Consumer API ✔ Composable Record Handler ✔ Parallel Consumption + Retries! What do we want it to do? ...what about Error handling?
  • 48. val retryConfig = RetryConfig.nonBlocking( 1.second, 10.minutes) GreyhoundConsumersBuilder .withConsumer(GreyhoundConsumer( topic = "some-group", group = "group-2", handle = handler, retryConfig = retryConfig)) Non-blocking Retries
  • 49. @NSilnitsky Kafka Broker renew-sub-topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Greyhound Consumer FAILED PROCESSING Kafka Consumer
  • 50. @NSilnitsky Kafka Broker renew-sub-topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 renew-sub-topic-retry-0 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 renew-sub-topic-retry-1 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Inspired by Uber RETRY! Greyhound Consumer Kafka Consumer RETRY PRODUCER
  • 51. @NSilnitsky Kafka Broker renew-sub-topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 renew-sub-topic-retry-0 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 0 1 2 3 4 5 renew-sub-topic-retry-1 Inspired by Uber RETRY! 0 1 2 3 4 5 Greyhound Consumer Kafka Consumer RETRY PRODUCER
  • 52. Retries same message on failure BLOCKING POLICY HANDLER Kafka Broker source-control- update-topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Greyhound Consumer Kafka Consumer build-log-service
  • 53. val retryConfig = RetryConfig.finiteBlocking( 1.second, 1.minutes) GreyhoundConsumersBuilder .withConsumer(GreyhoundConsumer( topic = "some-group", group = "group-2", handle = handler, retryConfig = retryConfig)) Blocking Retries * exponential
  • 54. Retries same message on failure * lag BLOCKING POLICY HANDLER Kafka Broker source-control- update-topic 0 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 50 1 2 3 4 5 Greyhound Consumer Kafka Consumer build-log-service
  • 56. handle() .retry( Schedule.doWhile(_ => shouldBlock(blockingStateRef)) && Schedule.fromDurations(blockingBackoffs) ) First Approach
  • 57. handle() .retry( Schedule.doWhile(_ => shouldBlock(blockingStateRef)) && Schedule.fromDurations(blockingBackoffs) ) First Approach Doesn’t allow delay interruptions
  • 58. foreachWhile(blockingBackoffs) { interval => handleAndBlockWithPolling(interval, blockingStateRef) } Current Solution
  • 59. foreachWhile(blockingBackoffs) { interval => handleAndBlockWithPolling(interval, blockingStateRef) } Current Solution Checks blockingState between short sleeps Allows user request to unblock
  • 60. foreachWhile(blockingBackoffs) { interval => handleAndBlockWithPolling(interval, blockingStateRef) } Current Solution def foreachWhile[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, Boolean]): ZIO[R, E, Unit] = ZIO.effectTotal(as.iterator).flatMap { i => def loop: ZIO[R, E, Unit] = if (i.hasNext) f(i.next).flatMap(result => if(result) loop else ZIO.unit) else ZIO.unit loop }
  • 61. foreachWhile(blockingBackoffs) { interval => handleAndBlockWithPolling(interval, blockingStateRef) } Current Solution def foreachWhile[R, E, A](as: Iterable[A])(f: A => ZIO[R, E, Boolean]): ZIO[R, E, Unit] = ZIO.effectTotal(as.iterator).flatMap { i => def loop: ZIO[R, E, Unit] = if (i.hasNext) f(i.next).flatMap(result => if(result) loop else ZIO.unit) else ZIO.unit loop } Stream.fromIterable(blockingBackoffs).foreachWhile(...)
  • 62. Greyhound wraps Kafka ✔ Simple Consumer API ✔ Composable Record Handler ✔ Parallel Consumption ✔ Retries + Resilient Producer What do we want it to do? and when Kafka brokers are unavailable...
  • 63. + Retry on Error Kafka Broker Producer Use Case: Guarantee completion Consumer Wix Payments Service Subscription renewal Job Scheduler
  • 64. + Retry on Error Kafka Broker Producer Use Case: Guarantee completion Consumer Wix Payments Service Job Scheduler
  • 68. Greyhound wraps Kafka ✔ Simple Consumer API ✔ Composable Record Handler ✔ Parallel Consumption ✔ Retries ✔ Resilient Producer + Context Propagation What do we want it to do? Super cool for us
  • 72. context = contextFrom(record.headers, token) handler.handle(record).provideSomeLayer[UserEnv](Context.layerFrom(context)) Context Propagation
  • 73. context = contextFrom(record.headers, token) handler.handle(record, controller).provideSomeLayer[UserEnv](Context.layerFrom(context)) RecordHandler((r: ConsumerRecord[String, Contact]) => for { context <- Context.retrieve _ <- ContactsDB.write(r.value, context) } yield () => RecordHandler[ContactsDB with Context, Throwable, Chunk[Byte], Chunk[Byte]] Context Propagation
  • 74. Greyhound wraps Kafka more features ✔ Simple Consumer API ✔ Composable Record Handler ✔ Parallel Consumption ✔ Retries ✔ Resilient Producer ✔ Context Propagation ✔ Pause/resume consumption ✔ Metrics reporting
  • 75. Greyhound wraps Kafka ✔ Simple Consumer API ✔ Composable Record Handler ✔ Parallel Consumption ✔ Retries ✔ Resilient Producer ✔ Context Propagation ✔ Pause/resume consumption ✔ Metrics reporting future plans + Batch Consumer + Exactly Once Processing + In-Memory KV Stores Will be much simpler with ZIO
  • 77. GREYHOUND USE CASES AT WIX Pub/Sub CDC Offline Scheduled Jobs DB (Elastic Search) replication Action retries Materialized Views
  • 78. - Much less boilerplate - Code that’s easier to understand - Fun REWRITING GREYHOUND IN ZIO RESULTED IN...
  • 79. ...but ZIO offers lower level abstractions too, like Promise and clock.sleep. SOMETIMES YOU CAN’T DO EXACTLY WHAT YOU WANT WITH HIGH LEVEL ZIO OPERATORS
  • 80. A Scala/Java high-level SDK for Apache Kafka. 0.1 is out! github.com/wix/greyhound
  • 81. Thank You natans@wix.com twitter @NSilnitsky linkedin/natansilnitsky github.com/natansil