All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.snowplowanalytics.snowplow.sources.kinesis.KinesisCheckpointer.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023-present Snowplow Analytics Ltd. All rights reserved.
 *
 * This program is licensed to you under the Snowplow Community License Version 1.0,
 * and you may not use this file except in compliance with the Snowplow Community License Version 1.0.
 * You may obtain a copy of the Snowplow Community License Version 1.0 at https://docs.snowplow.io/community-license-1.0
 */
package com.snowplowanalytics.snowplow.sources.kinesis

import cats.effect.{Async, Sync}
import cats.implicits._
import cats.effect.implicits._
import com.snowplowanalytics.snowplow.sources.internal.Checkpointer
import org.typelevel.log4cats.Logger
import software.amazon.kinesis.exceptions.ShutdownException
import software.amazon.kinesis.processor.RecordProcessorCheckpointer
import software.amazon.kinesis.retrieval.kpl.ExtendedSequenceNumber

import java.util.concurrent.CountDownLatch

private class KinesisCheckpointer[F[_]: Async: Logger] extends Checkpointer[F, Map[String, Checkpointable]] {

  override val empty: Map[String, Checkpointable] = Map.empty

  override def combine(x: Map[String, Checkpointable], y: Map[String, Checkpointable]): Map[String, Checkpointable] =
    x |+| y

  override def ack(c: Map[String, Checkpointable]): F[Unit] =
    c.toList.parTraverse_ {
      case (shardId, Checkpointable.Record(extendedSequenceNumber, checkpointer)) =>
        checkpointRecord(shardId, extendedSequenceNumber, checkpointer)
      case (shardId, Checkpointable.ShardEnd(checkpointer, release)) =>
        checkpointShardEnd(shardId, checkpointer, release)
    }

  override def nack(c: Map[String, Checkpointable]): F[Unit] =
    Sync[F].unit

  private def checkpointShardEnd(
    shardId: String,
    checkpointer: RecordProcessorCheckpointer,
    release: CountDownLatch
  ) =
    Logger[F].debug(s"Checkpointing shard $shardId at SHARD_END") *>
      Sync[F].blocking(checkpointer.checkpoint()).recoverWith(ignoreShutdownExceptions(shardId)) *>
      Sync[F].delay(release.countDown())

  private def checkpointRecord(
    shardId: String,
    extendedSequenceNumber: ExtendedSequenceNumber,
    checkpointer: RecordProcessorCheckpointer
  ) =
    Logger[F].debug(s"Checkpointing shard $shardId at $extendedSequenceNumber") *>
      Sync[F]
        .blocking(
          checkpointer.checkpoint(extendedSequenceNumber.sequenceNumber, extendedSequenceNumber.subSequenceNumber)
        )
        .recoverWith(ignoreShutdownExceptions(shardId))

  private def ignoreShutdownExceptions(shardId: String): PartialFunction[Throwable, F[Unit]] = { case _: ShutdownException =>
    // The ShardRecordProcessor instance has been shutdown. This just means another KCL
    // worker has stolen our lease. It is expected during autoscaling of instances, and is
    // safe to ignore.
    Logger[F].warn(s"Skipping checkpointing of shard $shardId because this worker no longer owns the lease")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy