Skip to content

Commit

Permalink
Adds a way to backfill dynamo gsi's for the v2 sdk client. (#382)
Browse files Browse the repository at this point in the history
This is the V2 version of #326
  • Loading branch information
mpawliszyn authored Apr 16, 2024
1 parent 97e4ac4 commit f17a44d
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 2 deletions.
6 changes: 6 additions & 0 deletions client-dynamodb-v2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ dependencies {
testImplementation(project(":backfila-embedded"))
testImplementation(project(":client-testing"))

if (org.apache.tools.ant.taskdefs.condition.Os.isArch("aarch64")) {
// Without this, we can't compile on Apple Silicon currently. This is likely not necessary to
// have longterm, so we should remove it when platform fixes things across Square.
testImplementation("io.github.ganadist.sqlite4java:libsqlite4java-osx-aarch64:1.0.392")
}

// ****************************************
// For TESTING purposes only. We only want Misk for easy testing.
// DO NOT turn these into regular dependencies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,7 @@ abstract class DynamoDbBackfill<I : Any, P : Any> : Backfill {

/** See [ScanRequest.setExpressionAttributeNames]. */
open fun expressionAttributeNames(config: BackfillConfig<P>): Map<String, String>? = null

/** See [ScanRequest.setIndexName]. */
open fun indexName(config: BackfillConfig<P>): String? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ abstract class UpdateInPlaceDynamoDbBackfill<I : Any, P : Any>(
dynamoDbTable.tableName() to itemsToSave.map {
WriteRequest.builder().putRequest(
PutRequest.builder().item(
dynamoDbTable.tableSchema().itemToMap(it, false),
dynamoDbTable.tableSchema().itemToMap(it, true),
).build(),
).build()
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class DynamoDbBackfillOperator<I : Any, P : Any>(
.filterExpression(backfill.filterExpression(config))
.expressionAttributeValues(backfill.expressionAttributeValues(config))
.expressionAttributeNames(backfill.expressionAttributeNames(config))
.indexName(backfill.indexName(config))
.build()

val result = dynamoDbClient.scan(scanRequest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ class BackfillsModule : KAbstractModule() {
install(DynamoDbBackfillModule.create<DynamoDbLastEvaluatedKeyTest.PausingBackfill>())
install(DynamoDbBackfillModule.create<DynamoDbBillingModeTest.EmptyTrackBackfill>())
install(DynamoDbBackfillModule.create<DynamoDbBillingModeTest.ReallyExpensiveBackfill>())
install(DynamoDbBackfillModule.create<DynamoDbIndexTest.MakeTracksAsSinglesBackfill>())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package app.cash.backfila.client.dynamodbv2

import app.cash.backfila.client.BackfillConfig
import app.cash.backfila.client.misk.TestingModule
import app.cash.backfila.embedded.Backfila
import app.cash.backfila.embedded.createWetRun
import javax.inject.Inject
import misk.testing.MiskTest
import misk.testing.MiskTestModule
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable
import software.amazon.awssdk.enhanced.dynamodb.Key
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.model.ScanEnhancedRequest
import software.amazon.awssdk.services.dynamodb.DynamoDbClient

@MiskTest(startService = true)
class DynamoDbIndexTest {
@Suppress("unused")
@MiskTestModule
val module = TestingModule()

@Inject
lateinit var dynamoDb: DynamoDbTable<TrackItem>

@Inject
lateinit var backfila: Backfila

@Inject
lateinit var testData: TrackData

@Test
fun `only items in the specified index are processed`() {
val trackItem = TrackItem().apply {
this.album_token = "ALBUM_2"
this.sort_key = "TRACK_03"
this.track_title = "Thriller"
this.album_title = "MJ"
}
dynamoDb.putItem(trackItem)

val trackItem2 = TrackItem().apply {
this.album_token = "ALBUM_3"
this.sort_key = "TRACK_01"
this.track_title = "Anon"
// No album title so its excluded from the trackTitleIndex sparse GSI.
}
dynamoDb.putItem(trackItem2)

assertThat(rowsInIndex().size).isEqualTo(1)
assertThat(testData.getRowsDump().size).isEqualTo(2)

val run = backfila.createWetRun<MakeTracksAsSinglesBackfill>()
run.execute()

// Only rows from the index were updated.
assertThat(dynamoDb.getItem(Key.builder().partitionValue("ALBUM_2").sortValue("TRACK_03").build()).track_title)
.isEqualTo("Thriller (Single)")
assertThat(dynamoDb.getItem(Key.builder().partitionValue("ALBUM_3").sortValue("TRACK_01").build()).track_title)
.isEqualTo("Anon")
}

private fun rowsInIndex(): List<TrackItem> {
val scanRequest = ScanEnhancedRequest.builder()
.limit(10000)
.build()
return dynamoDb.index("trackTitleIndex").scan(scanRequest).stream().flatMap { it.items().stream() }.toList()
}

class MakeTracksAsSinglesBackfill @Inject constructor(
dynamoDb: DynamoDbClient,
private val dynamoDbEnhancedClient: DynamoDbEnhancedClient,
) : UpdateInPlaceDynamoDbBackfill<TrackItem, MakeTracksAsSinglesBackfill.SingleParameters>(dynamoDb) {

override fun runOne(item: TrackItem, config: BackfillConfig<SingleParameters>): Boolean {
val trackTitle = item.track_title ?: return false
if (trackTitle.endsWith(" (Single)")) return true
item.track_title = "$trackTitle (Single)"
return true
}

data class SingleParameters(val validate: Boolean = true)

override fun indexName(config: BackfillConfig<SingleParameters>): String? = "trackTitleIndex"
override fun dynamoDbTable(): DynamoDbTable<TrackItem> {
return dynamoDbEnhancedClient.table(
TrackItem.TABLE_NAME,
TableSchema.fromClass(TrackItem::class.java),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import misk.inject.KAbstractModule
import misk.logging.LogCollectorModule
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient
import software.amazon.awssdk.enhanced.dynamodb.TableSchema
import software.amazon.awssdk.enhanced.dynamodb.model.EnhancedGlobalSecondaryIndex
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.ProjectionType
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput

/**
* Simulates a specific service implementation module
Expand All @@ -27,7 +30,24 @@ class TestingModule : KAbstractModule() {

install(EmbeddedBackfilaModule())

install(InProcessDynamoDbModule(DynamoDbTable(TrackItem.TABLE_NAME, TrackItem::class)))
install(
InProcessDynamoDbModule(
DynamoDbTable(TrackItem.TABLE_NAME, TrackItem::class) { table ->
val provisionedThroughput = ProvisionedThroughput.builder()
.readCapacityUnits(100)
.writeCapacityUnits(100)
.build()
table.globalSecondaryIndices(
EnhancedGlobalSecondaryIndex.builder()
.indexName("trackTitleIndex")
.projection { it.projectionType(ProjectionType.KEYS_ONLY) }
.provisionedThroughput(provisionedThroughput)
.build(),
)
table
},
),
)
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package app.cash.backfila.client.dynamodbv2

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey

@DynamoDbBean()
Expand All @@ -17,8 +19,10 @@ class TrackItem {
@get:DynamoDbSortKey
var sort_key: String? = null

@get:DynamoDbSecondaryPartitionKey(indexNames = ["trackTitleIndex"])
var track_title: String? = null

@get:DynamoDbSecondarySortKey(indexNames = ["trackTitleIndex"])
var album_title: String? = null

var artist_name: String? = null
Expand Down

0 comments on commit f17a44d

Please sign in to comment.