Skip to content

Commit 4accfaa

Browse files
authored
Add kafka batch consumer (#217)
1 parent 869b084 commit 4accfaa

File tree

3 files changed

+70
-8
lines changed

3 files changed

+70
-8
lines changed

freckle-app/CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-app-v1.21.0.0...main)
1+
## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-app-v1.21.1.0...main)
2+
3+
## [v1.21.1.0](https://github.com/freckle/freckle-app/compare/freckle-app-v1.21.0.0...freckle-app-v1.21.1.0)
4+
5+
Add `Freckle.App.Kafka.Consumer.runConsumerBatched`
26

37
## [v1.21.0.0](https://github.com/freckle/freckle-app/compare/freckle-app-v1.20.3.0...freckle-app-v1.21.0.0)
48

freckle-kafka/freckle-kafka.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 1.18
22

3-
-- This file has been generated from package.yaml by hpack version 0.36.0.
3+
-- This file has been generated from package.yaml by hpack version 0.37.0.
44
--
55
-- see: https://github.com/sol/hpack
66

freckle-kafka/library/Freckle/App/Kafka/Consumer.hs

+64-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module Freckle.App.Kafka.Consumer
66
, KafkaConsumerConfig (..)
77
, envKafkaConsumerConfig
88
, runConsumer
9+
, runConsumerBatched
910
) where
1011

1112
import Prelude
@@ -18,17 +19,19 @@ import Control.Exception.Annotated.UnliftIO
1819
, displayException
1920
)
2021
import Control.Exception.Annotated.UnliftIO qualified as Annotated
21-
import Control.Lens (Lens', view)
22+
import Control.Lens (Lens', over, view, _Left)
2223
import Control.Monad (forever, (<=<))
2324
import Control.Monad.IO.Class (MonadIO)
2425
import Control.Monad.Reader (MonadReader)
2526
import Data.Aeson
2627
import Data.ByteString (ByteString)
27-
import Data.Foldable (for_)
28-
import Data.List.NonEmpty (NonEmpty)
28+
import Data.Either (partitionEithers)
29+
import Data.Foldable (for_, traverse_)
30+
import Data.List.NonEmpty (NonEmpty, nonEmpty)
2931
import Data.List.NonEmpty qualified as NE
3032
import Data.Map.Strict (Map)
3133
import Data.Map.Strict qualified as Map
34+
import Data.Maybe (mapMaybe)
3235
import Data.Text (Text)
3336
import Data.Text qualified as T
3437
import Data.Text.Encoding qualified as T
@@ -198,7 +201,7 @@ runConsumer pollTimeout onMessage =
198201
"kafka.consumer"
199202
(defaultSpanArguments {Trace.kind = Consumer})
200203
$ do
201-
mRecord <- fromKafkaError =<< pollMessage consumer kTimeout
204+
mRecord <- fromKafkaError =<< pollMessage consumer (kafkaTimeout pollTimeout)
202205

203206
for_ (crValue =<< mRecord) $ \bs -> do
204207
a <-
@@ -207,8 +210,6 @@ runConsumer pollTimeout onMessage =
207210
eitherDecodeStrict bs
208211
inSpan "kafka.consumer.message.handle" defaultSpanArguments $ onMessage a
209212
where
210-
kTimeout = Kafka.Timeout $ timeoutMs pollTimeout
211-
212213
handlers =
213214
[ Annotated.Handler $
214215
logErrorNS "kafka"
@@ -220,6 +221,63 @@ runConsumer pollTimeout onMessage =
220221
(const "Could not decode message value")
221222
]
222223

224+
data BatchConsumerError
225+
= BatchConsumerDecodeError KafkaMessageDecodeError
226+
| BatchConsumerKafkaError KafkaError
227+
deriving stock (Show)
228+
229+
runConsumerBatched
230+
:: forall a m env
231+
. ( MonadUnliftIO m
232+
, MonadReader env m
233+
, MonadLogger m
234+
, MonadTracer m
235+
, HasKafkaConsumer env
236+
, FromJSON a
237+
, HasCallStack
238+
)
239+
=> Timeout
240+
-> BatchSize
241+
-> ([a] -> m ())
242+
-> m ()
243+
runConsumerBatched pollTimeout batchSize onBatch =
244+
forever $ do
245+
consumer <- view kafkaConsumerL
246+
247+
errors <- inSpan
248+
"kafka.batchConsumer"
249+
(defaultSpanArguments {Trace.kind = Consumer})
250+
$ do
251+
(errors, records) <- do
252+
(kafkaErrors, batch) <-
253+
partitionEithers
254+
<$> pollMessageBatch consumer (kafkaTimeout pollTimeout) batchSize
255+
(decodeErrors, records) <- fmap partitionEithers $ inSpan "kafka.batchConsumer.messages.decode" defaultSpanArguments $ do
256+
let errorDecode bs = over _Left (KafkaMessageDecodeError bs) $ eitherDecodeStrict @a bs
257+
pure $ mapMaybe (fmap errorDecode . crValue) batch
258+
pure
259+
( fmap BatchConsumerKafkaError kafkaErrors
260+
<> fmap BatchConsumerDecodeError decodeErrors
261+
, records
262+
)
263+
264+
inSpan "kafka.batchConsumer.messages.handle" defaultSpanArguments $
265+
onBatch records
266+
pure $ nonEmpty errors
267+
268+
traverse_ logErrors errors
269+
where
270+
displayConsumerError = \case
271+
BatchConsumerDecodeError e -> displayException e
272+
BatchConsumerKafkaError e -> displayException e
273+
logErrors errors =
274+
logErrorNS "kafka" $
275+
"Batch consumer errors"
276+
:# ["errors" .= fmap displayConsumerError errors]
277+
278+
kafkaTimeout :: Timeout -> Kafka.Timeout
279+
kafkaTimeout = Kafka.Timeout . timeoutMs
280+
223281
-- | Like 'annotatedExceptionMessage', but use the supplied function to
224282
-- construct an initial 'Message' that it will augment.
225283
annotatedExceptionMessageFrom

0 commit comments

Comments
 (0)