This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-go.git


The following commit(s) were added to refs/heads/main by this push:
     new c6494edbc perf(table): speed up compaction partition tuple keys (#2204)
c6494edbc is described below

commit c6494edbce9e523fca90ef6f9e048ff86e336854
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 20 19:14:02 2026 +0200

    perf(table): speed up compaction partition tuple keys (#2204)
    
    * perf(table): format compaction partition tuples without repeated fmt calls
    
    * test(table): verify and benchmark compaction partition tuple formatting
    
    * style: format partition tuple test values for gofumpt
    
    * perf(compaction): cover manifest types in tuple formatter
---
 table/compaction/eq_delete_decision.go            | 35 +++++++-
 table/compaction/eq_delete_decision_bench_test.go | 37 +++++----
 table/compaction/partition_tuple_test.go          | 98 +++++++++++++++++++++++
 3 files changed, 153 insertions(+), 17 deletions(-)

diff --git a/table/compaction/eq_delete_decision.go 
b/table/compaction/eq_delete_decision.go
index 9fbd068ac..25144289f 100644
--- a/table/compaction/eq_delete_decision.go
+++ b/table/compaction/eq_delete_decision.go
@@ -21,6 +21,7 @@ import (
        "fmt"
        "math"
        "slices"
+       "strconv"
 
        "github.com/apache/iceberg-go"
        "github.com/apache/iceberg-go/internal"
@@ -246,7 +247,8 @@ func partitionBucketKey(specID int32, part map[int]any) 
string {
 
 func partitionBucketKeyFromTuple(specID int32, tuple []byte) string {
        key := make([]byte, 0, len(tuple)+12)
-       key = fmt.Appendf(key, "%d:", specID)
+       key = strconv.AppendInt(key, int64(specID), 10)
+       key = append(key, ':')
 
        return string(append(key, tuple...))
 }
@@ -254,13 +256,40 @@ func partitionBucketKeyFromTuple(specID int32, tuple 
[]byte) string {
 // appendPartitionTuple emits the sorted "id=value;" tuple into dst.
 // Caller is responsible for any leading prefix (e.g. specID).
 func appendPartitionTuple(dst []byte, part map[int]any) []byte {
-       ids := make([]int, 0, len(part))
+       var inlineIDs [8]int
+       ids := inlineIDs[:0]
+       if len(part) > len(inlineIDs) {
+               ids = make([]int, 0, len(part))
+       }
        for id := range part {
                ids = append(ids, id)
        }
        slices.Sort(ids)
+
+       if cap(dst) == 0 && len(part) != 0 {
+               dst = make([]byte, 0, len(part)*16)
+       }
        for _, id := range ids {
-               dst = fmt.Appendf(dst, "%d=%v;", id, part[id])
+               dst = strconv.AppendInt(dst, int64(id), 10)
+               dst = append(dst, '=')
+               // Match fmt's %v output exactly; these partition keys are 
externally visible.
+               switch value := part[id].(type) {
+               case nil:
+                       dst = append(dst, "<nil>"...)
+               case string:
+                       dst = append(dst, value...)
+               case int32:
+                       dst = strconv.AppendInt(dst, int64(value), 10)
+               case iceberg.Date:
+                       dst = strconv.AppendInt(dst, int64(value), 10)
+               case int64:
+                       dst = strconv.AppendInt(dst, value, 10)
+               case bool:
+                       dst = strconv.AppendBool(dst, value)
+               default:
+                       dst = fmt.Append(dst, value)
+               }
+               dst = append(dst, ';')
        }
 
        return dst
diff --git a/table/compaction/eq_delete_decision_bench_test.go 
b/table/compaction/eq_delete_decision_bench_test.go
index bd814cd25..35290c109 100644
--- a/table/compaction/eq_delete_decision_bench_test.go
+++ b/table/compaction/eq_delete_decision_bench_test.go
@@ -32,21 +32,30 @@ var (
 
 func BenchmarkAddSurvivorWithSpec(b *testing.B) {
        for _, fields := range []int{1, 8, 32} {
-               b.Run(fmt.Sprintf("fields=%d", fields), func(b *testing.B) {
-                       partition := make(map[int]any, fields)
-                       for i := range fields {
-                               partition[1000+i] = i
-                       }
+               for _, kind := range []string{"int32", "string", "date"} {
+                       b.Run(fmt.Sprintf("fields=%d/%s", fields, kind), func(b 
*testing.B) {
+                               partition := make(map[int]any, fields)
+                               for i := range fields {
+                                       switch kind {
+                                       case "int32":
+                                               partition[1000+i] = int32(i * 5)
+                                       case "string":
+                                               partition[1000+i] = 
fmt.Sprintf("partition-%d", i)
+                                       case "date":
+                                               partition[1000+i] = 
iceberg.Date(20000 + i)
+                                       }
+                               }
 
-                       survey := compaction.NewSurvivorSurvey()
-                       b.ReportAllocs()
-                       b.ResetTimer()
-                       for range b.N {
-                               survey.AddSurvivorWithSpec(1, partition, 10)
-                       }
-                       b.StopTimer()
-                       benchmarkSurvivorSurvey = survey
-               })
+                               survey := compaction.NewSurvivorSurvey()
+                               b.ReportAllocs()
+                               b.ResetTimer()
+                               for range b.N {
+                                       survey.AddSurvivorWithSpec(1, 
partition, 10)
+                               }
+                               b.StopTimer()
+                               benchmarkSurvivorSurvey = survey
+                       })
+               }
        }
 }
 
diff --git a/table/compaction/partition_tuple_test.go 
b/table/compaction/partition_tuple_test.go
new file mode 100644
index 000000000..ca2cffddc
--- /dev/null
+++ b/table/compaction/partition_tuple_test.go
@@ -0,0 +1,98 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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 compaction
+
+import (
+       "bytes"
+       "fmt"
+       "math"
+       "slices"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/iceberg-go"
+)
+
+type partitionTupleStringer string
+
+func (s partitionTupleStringer) String() string { return "custom:" + string(s) 
}
+
+func TestAppendPartitionTupleMatchesFmt(t *testing.T) {
+       values := []any{
+               nil,
+               "",
+               "a=b;:é",
+               -123,
+               int32(-45),
+               iceberg.Date(-1),
+               iceberg.Date(20500),
+               int64(1234567890123),
+               true,
+               false,
+               uint64(1 << 63),
+               math.NaN(),
+               math.Inf(1),
+               math.Copysign(0, -1),
+               float32(3.5),
+               []byte{0, 255},
+               []int{1, 2},
+               partitionTupleStringer("x"),
+               time.Unix(0, 0).UTC(),
+               (*int)(nil),
+       }
+
+       for _, fields := range []int{0, 1, 3, 8, 9, 16, 32} {
+               for seed := range 32 {
+                       partition := make(map[int]any, fields)
+                       for i := range fields {
+                               partition[1000+i*7] = 
values[(i*3+seed)%len(values)]
+                       }
+
+                       ids := make([]int, 0, len(partition))
+                       for id := range partition {
+                               ids = append(ids, id)
+                       }
+                       slices.Sort(ids)
+
+                       for _, prefix := range []string{"", "42:"} {
+                               want := []byte(prefix)
+                               for _, id := range ids {
+                                       want = fmt.Appendf(want, "%d=%v;", id, 
partition[id])
+                               }
+                               got := appendPartitionTuple([]byte(prefix), 
partition)
+                               if !bytes.Equal(got, want) {
+                                       t.Fatalf("fields=%d seed=%d prefix=%q: 
got %q, want %q",
+                                               fields, seed, prefix, got, want)
+                               }
+                       }
+               }
+       }
+}
+
+func TestPartitionBucketKeyFromTupleMatchesFmt(t *testing.T) {
+       for _, specID := range []int32{math.MinInt32, -1, 0, 1, math.MaxInt32} {
+               for _, tuple := range []string{"", "1000=abc;", 
strings.Repeat("x", 128)} {
+                       got := partitionBucketKeyFromTuple(specID, 
[]byte(tuple))
+                       want := fmt.Sprintf("%d:%s", specID, tuple)
+                       if got != want {
+                               t.Fatalf("spec ID %d: got %q, want %q", specID, 
got, want)
+                       }
+               }
+       }
+}

Reply via email to