-
Notifications
You must be signed in to change notification settings - Fork 45
do not save _dynamo_op_type during dynamo stream replication #239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cd72e14
c79ac80
0e80e3d
cc1e391
586a2af
e57b89b
e5bde8e
97a2ce0
552eab0
84094d2
dffad6e
163b550
b225726
988a46a
8ad00b5
cab99dc
282ba17
b75dac3
b36d9c2
568982c
dd69003
4448917
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -9,14 +9,70 @@ import org.apache.hadoop.mapred.JobConf | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.apache.log4j.LogManager | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.apache.spark.rdd.RDD | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.apache.spark.sql.SparkSession | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import software.amazon.awssdk.services.dynamodb.model.{ AttributeValue, TableDescription } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import software.amazon.awssdk.services.dynamodb.model.{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| AttributeValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeleteItemRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TableDescription | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.stream.Collectors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| object DynamoDB { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val log = LogManager.getLogger("com.scylladb.migrator.writers.DynamoDB") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def deleteRDD(target: TargetSettings.DynamoDB, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| targetTableDesc: TableDescription, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rdd: RDD[util.Map[String, AttributeValue]])(implicit spark: SparkSession): Unit = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val keySchema = targetTableDesc.keySchema() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rdd.foreachPartition { partition => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (partition.nonEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val dynamoDB = DynamoUtils.buildDynamoClient( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.endpoint, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.finalCredentials.map(_.toProvider), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target.region, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Seq.empty | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| partition.foreach { item => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val keyToDelete = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new util.HashMap[String, AttributeValue]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keySchema.forEach { keyElement => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val keyName = keyElement.attributeName() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (item.containsKey(keyName)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keyToDelete.put(keyName, item.get(keyName)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!keyToDelete.isEmpty) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
tarzanek marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dynamoDB.deleteItem( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DeleteItemRequest | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .builder() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tableName(target.table) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .key(keyToDelete) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case e: Exception => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| s"Failed to delete item with key ${keyToDelete} from table ${target.table}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+52
to
+66
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!keyToDelete.isEmpty) { | |
| try { | |
| dynamoDB.deleteItem( | |
| DeleteItemRequest | |
| .builder() | |
| .tableName(target.table) | |
| .key(keyToDelete) | |
| .build() | |
| ) | |
| } catch { | |
| case e: Exception => | |
| log.error( | |
| s"Failed to delete item with key ${keyToDelete} from table ${target.table}", | |
| e) | |
| } | |
| try { | |
| dynamoDB.deleteItem( | |
| DeleteItemRequest | |
| .builder() | |
| .tableName(target.table) | |
| .key(keyToDelete) | |
| .build() | |
| ) | |
| } catch { | |
| case e: Exception => | |
| log.error( | |
| s"Failed to delete item with key ${keyToDelete} from table ${target.table}", | |
| e) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,24 +4,32 @@ import com.amazonaws.services.dynamodbv2.streamsadapter.model.RecordAdapter | |||||||||||||||||||||||||||||
| import com.amazonaws.services.dynamodbv2.model.{ AttributeValue => AttributeValueV1 } | ||||||||||||||||||||||||||||||
| import com.scylladb.migrator.AttributeValueUtils | ||||||||||||||||||||||||||||||
| import com.scylladb.migrator.config.{ AWSCredentials, SourceSettings, TargetSettings } | ||||||||||||||||||||||||||||||
| import org.apache.hadoop.dynamodb.DynamoDBItemWritable | ||||||||||||||||||||||||||||||
| import org.apache.hadoop.io.Text | ||||||||||||||||||||||||||||||
| import org.apache.log4j.LogManager | ||||||||||||||||||||||||||||||
| import org.apache.spark.rdd.RDD | ||||||||||||||||||||||||||||||
| import org.apache.spark.sql.SparkSession | ||||||||||||||||||||||||||||||
| import org.apache.spark.streaming.StreamingContext | ||||||||||||||||||||||||||||||
| import org.apache.spark.streaming.kinesis.{ | ||||||||||||||||||||||||||||||
| KinesisDynamoDBInputDStream, | ||||||||||||||||||||||||||||||
| KinesisInitialPositions, | ||||||||||||||||||||||||||||||
| SparkAWSCredentials | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| import software.amazon.awssdk.services.dynamodb.model.TableDescription | ||||||||||||||||||||||||||||||
| import com.scylladb.migrator.DynamoUtils | ||||||||||||||||||||||||||||||
| import software.amazon.awssdk.services.dynamodb.model.{ | ||||||||||||||||||||||||||||||
| AttributeValue => AttributeValueV2, | ||||||||||||||||||||||||||||||
| DeleteItemRequest, | ||||||||||||||||||||||||||||||
| PutItemRequest, | ||||||||||||||||||||||||||||||
| TableDescription | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import java.util | ||||||||||||||||||||||||||||||
| import java.util.stream.Collectors | ||||||||||||||||||||||||||||||
| import scala.jdk.CollectionConverters._ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| object DynamoStreamReplication { | ||||||||||||||||||||||||||||||
| val log = LogManager.getLogger("com.scylladb.migrator.writers.DynamoStreamReplication") | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| type DynamoItem = util.Map[String, AttributeValueV1] | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // We enrich the table items with a column `operationTypeColumn` describing the type of change | ||||||||||||||||||||||||||||||
| // applied to the item. | ||||||||||||||||||||||||||||||
| // We have to deal with multiple representation of the data because `spark-kinesis-dynamodb` | ||||||||||||||||||||||||||||||
|
|
@@ -30,6 +38,78 @@ object DynamoStreamReplication { | |||||||||||||||||||||||||||||
| private val putOperation = new AttributeValueV1().withBOOL(true) | ||||||||||||||||||||||||||||||
| private val deleteOperation = new AttributeValueV1().withBOOL(false) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private[writers] def run( | ||||||||||||||||||||||||||||||
| msgs: RDD[Option[DynamoItem]], | ||||||||||||||||||||||||||||||
| target: TargetSettings.DynamoDB, | ||||||||||||||||||||||||||||||
| renamesMap: Map[String, String], | ||||||||||||||||||||||||||||||
| targetTableDesc: TableDescription)(implicit spark: SparkSession): Unit = { | ||||||||||||||||||||||||||||||
| val rdd = msgs.flatMap(_.toSeq) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| val putCount = spark.sparkContext.longAccumulator("putCount") | ||||||||||||||||||||||||||||||
| val deleteCount = spark.sparkContext.longAccumulator("deleteCount") | ||||||||||||||||||||||||||||||
| val keyAttributeNames = targetTableDesc.keySchema.asScala.map(_.attributeName).toSet | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| rdd.foreachPartition { partition => | ||||||||||||||||||||||||||||||
| if (partition.nonEmpty) { | ||||||||||||||||||||||||||||||
| val client = | ||||||||||||||||||||||||||||||
| DynamoUtils.buildDynamoClient( | ||||||||||||||||||||||||||||||
| target.endpoint, | ||||||||||||||||||||||||||||||
| target.finalCredentials.map(_.toProvider), | ||||||||||||||||||||||||||||||
| target.region, | ||||||||||||||||||||||||||||||
| Seq.empty | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| partition.foreach { item => | ||||||||||||||||||||||||||||||
| val isPut = item.get(operationTypeColumn) == putOperation | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| val itemWithoutOp = item.asScala.collect { | ||||||||||||||||||||||||||||||
| case (k, v) if k != operationTypeColumn => k -> AttributeValueUtils.fromV1(v) | ||||||||||||||||||||||||||||||
| }.asJava | ||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+67
|
||||||||||||||||||||||||||||||
| val itemWithoutOp = item.asScala.collect { | |
| case (k, v) if k != operationTypeColumn => k -> AttributeValueUtils.fromV1(v) | |
| }.asJava | |
| val itemWithoutOp = { | |
| val m = new java.util.HashMap[String, AttributeValueV2]() | |
| val it = item.entrySet().iterator() | |
| while (it.hasNext) { | |
| val entry = it.next() | |
| if (entry.getKey != operationTypeColumn) { | |
| m.put(entry.getKey, AttributeValueUtils.fromV1(entry.getValue)) | |
| } | |
| } | |
| m | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there should be function for delete operations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is exactly the big question
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was told that current version will remove rows that should be deleted but keeps row key with the "_dynamo_op_type" , all other cells are gone
which is what confuses me and I didn't try myself if it really is like that (since the person who reported it might not know fully)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any stage to test it e2e ?
Within the integration test in this PR I can observe that row is not removed w/o this additional delete operation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is possible , since rec. event type is not used later and dynamowriteable item doesn't seem to have an optype
I am curious if the rdd we have could be reused for both operations to avoid creating new client connection