/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.qubole.presto.kinesis;
import io.airlift.log.Logger;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.kinesis.leases.exceptions.DependencyException;
import com.amazonaws.services.kinesis.leases.exceptions.InvalidStateException;
import com.amazonaws.services.kinesis.leases.exceptions.ProvisionedThroughputException;
import com.amazonaws.services.kinesis.leases.impl.KinesisClientLease;
import com.amazonaws.services.kinesis.leases.impl.KinesisClientLeaseManager;
import com.amazonaws.services.kinesis.clientlibrary.types.ExtendedSequenceNumber;
import com.google.common.base.Throwables;
public class KinesisShardCheckpointer
{
private static final Logger log = Logger.get(KinesisShardCheckpointer.class);
private KinesisClientLeaseManager kinesisClientLeaseManager;
private KinesisSplit kinesisSplit;
private String logicalProcessName;
private int curIterationNumber;
private long dynamoReadCapacity;
private long dynamoWriteCapacity;
private KinesisClientLease kinesisClientLease;
private long checkpointIntervalMs;
private long nextCheckpointTimeMs;
public KinesisShardCheckpointer(AmazonDynamoDB dynamoDBClient,
String dynamoDBTable,
KinesisSplit kinesisSplit,
String logicalProcessName,
int curIterationNumber,
long checkpointIntervalMS,
long dynamoReadCapacity,
long dynamoWriteCapacity)
{
this(new KinesisClientLeaseManager(dynamoDBTable, dynamoDBClient),
kinesisSplit,
logicalProcessName,
curIterationNumber,
checkpointIntervalMS,
dynamoReadCapacity,
dynamoWriteCapacity);
}
public KinesisShardCheckpointer(KinesisClientLeaseManager kinesisClientLeaseManager,
KinesisSplit kinesisSplit,
String logicalProcessName,
int curIterationNumber,
long checkpointIntervalMS,
long dynamoReadCapacity,
long dynamoWriteCapacity)
{
this.kinesisClientLeaseManager = kinesisClientLeaseManager;
this.kinesisSplit = kinesisSplit;
this.logicalProcessName = logicalProcessName;
this.curIterationNumber = curIterationNumber;
this.checkpointIntervalMs = checkpointIntervalMS;
this.dynamoReadCapacity = dynamoReadCapacity;
this.dynamoWriteCapacity = dynamoWriteCapacity;
try {
this.kinesisClientLeaseManager.createLeaseTableIfNotExists(this.dynamoReadCapacity, this.dynamoWriteCapacity);
KinesisClientLease oldLease = this.kinesisClientLeaseManager.getLease(createCheckpointKey(curIterationNumber));
if (oldLease != null) {
this.kinesisClientLease = oldLease;
}
else {
this.kinesisClientLease = new KinesisClientLease();
this.kinesisClientLease.setLeaseKey(createCheckpointKey(curIterationNumber));
}
}
catch (DependencyException e) {
throw Throwables.propagate(e);
}
catch (ProvisionedThroughputException e) {
throw Throwables.propagate(e);
}
catch (InvalidStateException e) {
throw Throwables.propagate(e);
}
resetNextCheckpointTime();
}
private void resetNextCheckpointTime()
{
nextCheckpointTimeMs = System.currentTimeMillis() + checkpointIntervalMs;
}
private String createCheckpointKey(int iterationNo)
{
return new StringBuilder(this.logicalProcessName)
.append("_")
.append(this.kinesisSplit.getStreamName())
.append("_")
.append(this.kinesisSplit.getShardId())
.append("_")
.append(String.valueOf(iterationNo))
.toString();
}
// storing last read sequence no. in dynamodb table
public void checkpoint(String lastReadSequenceNo)
{
log.info("Trying to checkpoint at " + lastReadSequenceNo);
try {
ExtendedSequenceNumber esn = new ExtendedSequenceNumber(lastReadSequenceNo);
kinesisClientLease.setCheckpoint(esn);
kinesisClientLeaseManager.createLeaseIfNotExists(kinesisClientLease);
if (!kinesisClientLeaseManager.updateLease(kinesisClientLease)) {
log.info("Checkpointing unsucessful");
}
}
catch (DependencyException e) {
throw Throwables.propagate(e);
}
catch (InvalidStateException e) {
throw Throwables.propagate(e);
}
catch (ProvisionedThroughputException e) {
throw Throwables.propagate(e);
}
resetNextCheckpointTime();
}
//return checkpoint of previous iteration if found
public String getLastReadSeqNo()
{
String lastReadSeqNo = null;
KinesisClientLease oldLease = null;
if (curIterationNumber > 0) {
try {
oldLease = kinesisClientLeaseManager.getLease(createCheckpointKey(curIterationNumber - 1));
}
catch (DependencyException e) {
throw Throwables.propagate(e);
}
catch (InvalidStateException e) {
throw Throwables.propagate(e);
}
catch (ProvisionedThroughputException e) {
throw Throwables.propagate(e);
}
if (oldLease != null) {
// ExtendedSequenceNumber type in latest API:
lastReadSeqNo = oldLease.getCheckpoint().toString();
}
}
if (lastReadSeqNo == null) {
log.info("Previous checkpoint not found. Starting from beginning of shard");
}
else {
log.info("Resuming from " + lastReadSeqNo);
}
return lastReadSeqNo;
}
public void checkpointIfTimeUp(String lastReadSeqNo)
{
if (System.currentTimeMillis() >= nextCheckpointTimeMs) {
checkpoint(lastReadSeqNo);
}
}
}