packages feed

nri-kafka-0.4.0.1: src/Kafka/Worker/Settings.hs

module Kafka.Worker.Settings
  ( Settings (..),
    decoder,
    MaxMsgsPerSecondPerPartition (..),
    MaxMsgsPerPartitionBufferedLocally (..),
    MaxPollIntervalMs (..),
    SkipOrNot (..),
  )
where

import qualified Environment
import qualified Kafka.Consumer as Consumer
import qualified Kafka.Settings.Internal as Internal
import qualified Observability
import qualified Prelude

-- | Settings required to process kafka messages
data Settings = Settings
  { -- | broker addresses. See hw-kafka's documentation for more info
    brokerAddresses :: [Consumer.BrokerAddress],
    -- | Worker will poll Kafka for new messages. This is the timeout
    pollingTimeout :: Consumer.Timeout,
    -- | Used for throttling. Turn this down to give Kafka a speed limit.
    maxMsgsPerSecondPerPartition :: MaxMsgsPerSecondPerPartition,
    logLevel :: Internal.KafkaLogLevel,
    observability :: Observability.Settings,
    -- | Provides backpressure from message-workers to the queue-reader worker.
    -- Ensures that the thread responsible for pulling messages off of kafka
    -- doesn't race ahead / steal resources from the threads executing messages.
    maxMsgsPerPartitionBufferedLocally :: MaxMsgsPerPartitionBufferedLocally,
    pollBatchSize :: Consumer.BatchSize,
    -- | Time between polling
    maxPollIntervalMs :: MaxPollIntervalMs,
    -- | This option provides us the possibility to skip messages on failure.
    -- Useful for testing Kafka worker. DoNotSkip is a reasonable default!
    onProcessMessageSkip :: SkipOrNot,
    -- | Compression codec used for topics
    compressionCodec :: Internal.KafkaCompressionCodec
  }

-- | This option provides us the possibility to skip messages on failure.
-- Useful for testing Kafka worker. DoNotSkip is a reasonable default!
data SkipOrNot = Skip | DoNotSkip

-- | Used for throttling. Turn this down to give Kafka a speed limit.
data MaxMsgsPerSecondPerPartition = ThrottleAt Int | DontThrottle

-- | Provides backpressure from message-workers to the queue-reader worker.
-- Ensures that the thread responsible for pulling messages off of kafka
-- doesn't race ahead / steal resources from the threads executing messages.
newtype MaxMsgsPerPartitionBufferedLocally = MaxMsgsPerPartitionBufferedLocally {unMaxMsgsPerPartitionBufferedLocally :: Int}

-- | Time between polling
newtype MaxPollIntervalMs = MaxPollIntervalMs {unMaxPollIntervalMs :: Int}

-- | decodes Settings from environmental variables
-- Also consumes Observability env variables (see nri-observability)
-- KAFKA_BROKER_ADDRESSES=localhost:9092 # comma delimeted list
-- KAFKA_LOG_LEVEL=Debug
-- KAFKA_POLLING_TIMEOUT=1000
-- KAFKA_MAX_MESSAGES_PER_SECOND_PER_PARTITION=0 (disabled)
-- KAFKA_MAX_POLL_INTERVAL_MS=300000
-- KAFKA_MAX_MSGS_PER_PARTITION_BUFFERED_LOCALLY=100
-- KAFKA_POLL_BATCH_SIZE=100
-- KAFKA_SKIP_ON_PROCESS_MESSAGE_FAILURE=0
-- KAFKA_GROUP_ID=0
decoder :: Environment.Decoder Settings
decoder =
  Prelude.pure Settings
    |> andMap Internal.decoderBrokerAddresses
    |> andMap decoderPollingTimeout
    |> andMap decoderMaxMessagesPerSecondPerPartition
    |> andMap Internal.decoderKafkaLogLevel
    |> andMap Observability.decoder
    |> andMap decoderMaxMsgsPerPartitionBufferedLocally
    |> andMap decoderPollBatchSize
    |> andMap decoderMaxPollIntervalMs
    |> andMap decoderOnProcessMessageFailure
    |> andMap Internal.decoderCompressionCodec

decoderPollingTimeout :: Environment.Decoder Consumer.Timeout
decoderPollingTimeout =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_POLLING_TIMEOUT",
        Environment.description = "Polling timout for consumers",
        Environment.defaultValue = "1000"
      }
    (map Consumer.Timeout Environment.int)

decoderMaxMessagesPerSecondPerPartition :: Environment.Decoder MaxMsgsPerSecondPerPartition
decoderMaxMessagesPerSecondPerPartition =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_MAX_MESSAGES_PER_SECOND_PER_PARTITION",
        Environment.description = "This is how we throttle workers. Sets the maximum amount of messages this worker should process per second per partition. 0 is disabled.",
        Environment.defaultValue = "0"
      }
    ( map
        ( \maxPerSecond ->
            ( if maxPerSecond == 0
                then DontThrottle
                else ThrottleAt maxPerSecond
            )
        )
        Environment.int
    )

decoderMaxPollIntervalMs :: Environment.Decoder MaxPollIntervalMs
decoderMaxPollIntervalMs =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_MAX_POLL_INTERVAL_MS",
        Environment.description = "This is used to set max.poll.interval.ms",
        Environment.defaultValue = "300000"
      }
    (map MaxPollIntervalMs Environment.int)

decoderMaxMsgsPerPartitionBufferedLocally :: Environment.Decoder MaxMsgsPerPartitionBufferedLocally
decoderMaxMsgsPerPartitionBufferedLocally =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_MAX_MSGS_PER_PARTITION_BUFFERED_LOCALLY",
        Environment.description = "Pausing reading from kafka when we have this many messages queued up but not yet processed",
        Environment.defaultValue = "100"
      }
    (map MaxMsgsPerPartitionBufferedLocally Environment.int)

decoderPollBatchSize :: Environment.Decoder Consumer.BatchSize
decoderPollBatchSize =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_POLL_BATCH_SIZE",
        Environment.description = "The amount of messages we request in a single poll request to Kafka",
        Environment.defaultValue = "100"
      }
    (map Consumer.BatchSize Environment.int)

decoderOnProcessMessageFailure :: Environment.Decoder SkipOrNot
decoderOnProcessMessageFailure =
  Environment.variable
    Environment.Variable
      { Environment.name = "KAFKA_SKIP_ON_PROCESS_MESSAGE_FAILURE",
        Environment.description = "Whether to skip message that are failing processing. 1 means on, 0 means off.",
        Environment.defaultValue = "0"
      }
    ( Environment.custom
        Environment.int
        ( \int ->
            if int >= 1
              then Ok Skip
              else Ok DoNotSkip
        )
    )