From 695513703160ac88c782aff03ee898a1b46bc448 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 13:20:44 +0400 Subject: [PATCH 01/10] Update Gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c61a118..1a70468 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0262dcb..739907d 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From 9df4b64e1361ad1d8e144bbb45445cf2daef9688 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 13:23:01 +0400 Subject: [PATCH 02/10] Update libs --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eb26651..111d40e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,16 +10,16 @@ kotlin = "2.3.20" nebula = "20.2.0" nexus = "2.0.0" -prometheus = "1.5.1" +prometheus = "1.6.1" opentelemetry = "1.60.1" -opentelemetry-instrumentation = "2.26.1" -opentelemetry-instrumentation-incubator = "2.26.1-alpha" +opentelemetry-instrumentation = "2.27.0" +opentelemetry-instrumentation-incubator = "2.27.0-alpha" opentelemetry-semconv = "1.40.0" hikaricp = "7.0.2" postgresql = "42.7.10" logback = "1.5.32" -testcontainers = "2.0.4" +testcontainers = "2.0.5" junit = "6.0.3" mockk = "1.14.9" From 648dcafb4c2e2586c0383370f9421a5d5edc9038 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 13:38:36 +0400 Subject: [PATCH 03/10] Fix text --- .../kolbasa/cluster/butcher/check/OrphanTablesResult.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/check/OrphanTablesResult.kt b/src/main/kotlin/kolbasa/cluster/butcher/check/OrphanTablesResult.kt index 3c8be9a..f5827aa 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/check/OrphanTablesResult.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/check/OrphanTablesResult.kt @@ -17,16 +17,16 @@ internal data class OrphanTablesResult( override fun toString(): String = buildString { if (isClean) { - append("Orphan tables: no orphan companion tables") + append("Orphan tables: no orphan queue tables") return@buildString } - appendLine("Orphan tables: $totalOrphans orphan companion(s) across ${orphansByNode.size} node(s)") + appendLine("Orphan tables: $totalOrphans orphan queue(s) across ${orphansByNode.size} node(s)") orphansByNode.toSortedMap().forEach { (node, orphans) -> appendLine(" ${node.id}:") val width = orphans.maxOf { it.companionTable.length } orphans.sortedBy { it.companionTable }.forEach { orphan -> - appendLine(" ${orphan.companionTable.padEnd(width)} (main ${orphan.missingMainTable} missing)") + appendLine(" ${orphan.companionTable.padEnd(width)} (main queue ${orphan.missingMainTable} missing)") } } }.trimEnd() From 774f9f197c248eb4afe5eedf6b2208393e434497 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 13:40:29 +0400 Subject: [PATCH 04/10] Fix test --- .../kolbasa/cluster/butcher/check/OrphanTablesTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/kolbasa/cluster/butcher/check/OrphanTablesTest.kt b/src/test/kotlin/kolbasa/cluster/butcher/check/OrphanTablesTest.kt index d569f92..1329b51 100644 --- a/src/test/kotlin/kolbasa/cluster/butcher/check/OrphanTablesTest.kt +++ b/src/test/kotlin/kolbasa/cluster/butcher/check/OrphanTablesTest.kt @@ -162,7 +162,7 @@ class OrphanTablesTest { fun testToString_Clean() { val result = OrphanTables(emptyMap()).compute() - assertEquals("Orphan tables: no orphan companion tables", result.toString()) + assertEquals("Orphan tables: no orphan queue tables", result.toString()) } @Test @@ -176,14 +176,14 @@ class OrphanTablesTest { val text = OrphanTables(tables).compute().toString() - assertTrue(text.contains("3 orphan companion(s) across 2 node(s)"), text) + assertTrue(text.contains("3 orphan queue(s) across 2 node(s)"), text) assertTrue(text.contains("n1"), text) assertTrue(text.contains("n2"), text) assertTrue(text.contains("q_orders_dlq"), text) assertTrue(text.contains("q_orders_arc"), text) assertTrue(text.contains("q_payments_arc"), text) - assertTrue(text.contains("main q_orders missing"), text) - assertTrue(text.contains("main q_payments missing"), text) + assertTrue(text.contains("main queue q_orders missing"), text) + assertTrue(text.contains("main queue q_payments missing"), text) // Nodes sorted by id: n1 before n2. val n1Pos = text.indexOf("n1:") val n2Pos = text.indexOf("n2:") From 59be8ffe40119c44dd71c2f3ffa679daff8deb97 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 18:20:24 +0400 Subject: [PATCH 05/10] Fix text/tests --- .../kolbasa/cluster/butcher/check/ShardBalanceResult.kt | 7 +++---- .../kolbasa/cluster/butcher/check/ShardBalanceTest.kt | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/check/ShardBalanceResult.kt b/src/main/kotlin/kolbasa/cluster/butcher/check/ShardBalanceResult.kt index 51fe261..a23a40b 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/check/ShardBalanceResult.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/check/ShardBalanceResult.kt @@ -31,10 +31,9 @@ internal data class ShardBalanceResult( appendLine(" Proposed moves ($totalMoves):") proposedMoves.toSortedMap().forEach { (target, shards) -> val sorted = shards.sortedBy { it.shard } - appendLine(" -> ${target.id} (${sorted.size} shards):") - sorted.forEach { shard -> - appendLine(" shard ${shard.shard.toString().padStart(4)}: ${shard.producerNode.id} -> ${target.id}") - } + appendLine(" ⟶ ${target.id} (${sorted.size} shards):") + appendLine(" shards: ${sorted.joinToString(separator = ",") { it.shard.toString() }}") + appendLine(" target: ${target.id}") } }.trimEnd() } diff --git a/src/test/kotlin/kolbasa/cluster/butcher/check/ShardBalanceTest.kt b/src/test/kotlin/kolbasa/cluster/butcher/check/ShardBalanceTest.kt index 65aea74..c68b2d9 100644 --- a/src/test/kotlin/kolbasa/cluster/butcher/check/ShardBalanceTest.kt +++ b/src/test/kotlin/kolbasa/cluster/butcher/check/ShardBalanceTest.kt @@ -173,7 +173,7 @@ class ShardBalanceTest { val text = ShardBalance(shards, nodes.toSet()).compute().toString() assertTrue(text.contains("Proposed moves"), text) - assertTrue(text.contains("n1 -> n2"), text) + assertTrue(text.contains("⟶ n2"), text) } @Test From d47fbe850e69c2414d1a18a2c7be9c3a4ebb4b0e Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 18:34:47 +0400 Subject: [PATCH 06/10] Fix error --- src/main/kotlin/kolbasa/cluster/butcher/MoveHelpers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/MoveHelpers.kt b/src/main/kotlin/kolbasa/cluster/butcher/MoveHelpers.kt index 7ed9c85..d45edfa 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/MoveHelpers.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/MoveHelpers.kt @@ -43,7 +43,7 @@ internal object MoveHelpers { } } - throw IllegalStateException("Initialized shard table not found") + throw ButcherException.ExecutionException("Initialized shard table ${ShardSchema.SHARD_TABLE_NAME} not found. Is it a Kolbasa cluster?") } fun splitNodes(nodes: SortedMap, targetNodeId: NodeId): SourceAndTargetNodes { From 4556df942bd43170890a645a92da96e6e22d758e Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 18:53:28 +0400 Subject: [PATCH 07/10] Fix text/tests --- .../butcher/check/MigrationStateResult.kt | 10 ++++++---- .../butcher/check/MigrationStateTest.kt | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt b/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt index 19d95de..051a52a 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt @@ -20,10 +20,12 @@ internal data class MigrationStateResult( appendLine("Migration state: $totalMigratingShards shard(s) in migration") migratingShardsByTarget.toSortedMap().forEach { (target, shards) -> val sorted = shards.sortedBy { it.shard } - appendLine(" -> ${target.id} (${sorted.size} shards):") - sorted.forEach { shard -> - appendLine(" shard ${shard.shard.toString().padStart(4)}") - } + appendLine(" ⟶ ${target.id} (${sorted.size} shards):") + appendLine(" shards: ${sorted.joinToString(separator = ",") { it.shard.toString() }}") + appendLine(" target: ${target.id}") +// sorted.forEach { shard -> +// appendLine(" shard ${shard.shard.toString().padStart(4)}") +// } } }.trimEnd() } diff --git a/src/test/kotlin/kolbasa/cluster/butcher/check/MigrationStateTest.kt b/src/test/kotlin/kolbasa/cluster/butcher/check/MigrationStateTest.kt index d7ca4dd..bd5b410 100644 --- a/src/test/kotlin/kolbasa/cluster/butcher/check/MigrationStateTest.kt +++ b/src/test/kotlin/kolbasa/cluster/butcher/check/MigrationStateTest.kt @@ -87,6 +87,8 @@ class MigrationStateTest { val n2 = NodeId("n2") val n3 = NodeId("n3") val shards = listOf( + migratingShard(50, n3), + migratingShard(40, n3), migratingShard(30, n3), migratingShard(10, n2), migratingShard(20, n2), @@ -94,16 +96,16 @@ class MigrationStateTest { val text = MigrationState(shards).compute().toString() - assertTrue(text.contains("3 shard(s) in migration"), text) - assertTrue(text.contains("-> n2"), text) - assertTrue(text.contains("-> n3"), text) + assertTrue(text.contains("5 shard(s) in migration"), text) + assertTrue(text.contains("⟶ n2"), text) + assertTrue(text.contains("⟶ n3"), text) // Targets sorted by id (n2 before n3) and shards sorted ascending within each group. - val n2Pos = text.indexOf("-> n2") - val n3Pos = text.indexOf("-> n3") + val n2Pos = text.indexOf("⟶ n2") + val n3Pos = text.indexOf("⟶ n3") assertTrue(n2Pos < n3Pos, "n2 group should come before n3 group:\n$text") - val shard10Pos = text.indexOf("shard 10") - val shard20Pos = text.indexOf("shard 20") - assertTrue(shard10Pos in 0 until shard20Pos, "shard 10 should appear before shard 20:\n$text") + // Check shards list + assertTrue(text.contains("shards: 10,20"), text) + assertTrue(text.contains("shards: 30,40,50"), text) } // ---------- helpers ---------- From 9702f0f57b0f933e95a3a220b55a6fc9a3987f94 Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 18:53:42 +0400 Subject: [PATCH 08/10] Fix text/tests --- .../kolbasa/cluster/butcher/check/MigrationStateResult.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt b/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt index 051a52a..ef8b7a3 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/check/MigrationStateResult.kt @@ -23,9 +23,6 @@ internal data class MigrationStateResult( appendLine(" ⟶ ${target.id} (${sorted.size} shards):") appendLine(" shards: ${sorted.joinToString(separator = ",") { it.shard.toString() }}") appendLine(" target: ${target.id}") -// sorted.forEach { shard -> -// appendLine(" shard ${shard.shard.toString().padStart(4)}") -// } } }.trimEnd() } From 50b613dd86e7c3221542feab91bd006ab971e2bd Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 21:36:41 +0400 Subject: [PATCH 09/10] Add colors --- .../kotlin/kolbasa/cluster/butcher/Butcher.kt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/kolbasa/cluster/butcher/Butcher.kt b/src/main/kotlin/kolbasa/cluster/butcher/Butcher.kt index 5381959..f138626 100644 --- a/src/main/kotlin/kolbasa/cluster/butcher/Butcher.kt +++ b/src/main/kotlin/kolbasa/cluster/butcher/Butcher.kt @@ -9,7 +9,7 @@ fun main(args: Array) { Command.parseCommand(args) } catch (e: ButcherException.InvalidConfigurationException) { println("=================================================") - println("Invalid configuration.") + println(red("Invalid configuration.")) println(e.messageToShow) println("=================================================") exitProcess(1) @@ -24,14 +24,26 @@ fun main(args: Array) { } } catch (e: ButcherException.ExecutionException) { println("=================================================") - println("Execution error.") + println(red("Execution error.")) println(e.messageToShow) println("=================================================") exitProcess(1) } println("=================================================") - println("Completed successfully.") + println(green("Completed successfully.")) println(result) println("=================================================") } + +private fun red(msg: String): String { + return "$ANSI_RED$msg$ANSI_RESET" +} + +private fun green(msg: String): String { + return "$ANSI_GREEN$msg$ANSI_RESET" +} + +private const val ANSI_RESET = "\u001B[0m" +private const val ANSI_RED = "\u001B[31m" +private const val ANSI_GREEN = "\u001B[32m" From 543734f876bc07172fa3ef616d44f172181f562d Mon Sep 17 00:00:00 2001 From: Vasily Vasilkov Date: Sun, 3 May 2026 21:49:04 +0400 Subject: [PATCH 10/10] Fix test --- src/test/kotlin/kolbasa/cluster/butcher/MoveHelpersTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/kolbasa/cluster/butcher/MoveHelpersTest.kt b/src/test/kotlin/kolbasa/cluster/butcher/MoveHelpersTest.kt index 2d346fa..92ab737 100644 --- a/src/test/kotlin/kolbasa/cluster/butcher/MoveHelpersTest.kt +++ b/src/test/kotlin/kolbasa/cluster/butcher/MoveHelpersTest.kt @@ -32,7 +32,7 @@ class MoveHelpersTest : AbstractPostgresqlTest() { val nodes = ClusterHelper.readNodes(listOf(dataSource, dataSourceFirstSchema, dataSourceSecondSchema)) // Without shard table we expect an exception - assertThrows { + assertThrows { MoveHelpers.readShards(nodes) } }