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 03c4bab5a perf(table): specialize binary partition lookups in
partitions metadata (#2199)
03c4bab5a is described below
commit 03c4bab5a4594eede844801fbf85e7ec36bc1af4
Author: Minh Vu <[email protected]>
AuthorDate: Mon Oct 19 19:32:54 2026 +0200
perf(table): specialize binary partition lookups in partitions metadata
(#2199)
* perf(table): specialize binary partition lookups in partitions metadata
* fix(table): satisfy nlreturn lint in binary partition lookup
---
table/inspect_partitions.go | 47 ++++++++++++---
table/inspect_partitions_bench_test.go | 42 +++++++++++++
table/inspect_partitions_internal_test.go | 99 +++++++++++++++++++++++++++++++
3 files changed, 179 insertions(+), 9 deletions(-)
diff --git a/table/inspect_partitions.go b/table/inspect_partitions.go
index 53f5aad5b..c63dc4877 100644
--- a/table/inspect_partitions.go
+++ b/table/inspect_partitions.go
@@ -46,20 +46,50 @@ type inspectPartitionAggregate struct {
}
type inspectPartitionAggregateTree struct {
- children map[any]*inspectPartitionAggregateTree
- aggregate *inspectPartitionAggregate
+ children map[any]*inspectPartitionAggregateTree
+ binaryChildren map[string]*inspectPartitionAggregateTree
+ aggregate *inspectPartitionAggregate
}
func newInspectPartitionAggregateTree() *inspectPartitionAggregateTree {
- return &inspectPartitionAggregateTree{
- children: make(map[any]*inspectPartitionAggregateTree),
+ return &inspectPartitionAggregateTree{}
+}
+
+// child uses a typed map for binary values to avoid interface-backed keys
+// during repeated lookups. Other values retain their existing key types.
+func (t *inspectPartitionAggregateTree) child(value any)
(*inspectPartitionAggregateTree, bool) {
+ if binary, ok := value.([]byte); ok {
+ child, found := t.binaryChildren[string(binary)]
+
+ return child, found
+ }
+
+ child, found := t.children[comparablePartitionKey(value)]
+
+ return child, found
+}
+
+func (t *inspectPartitionAggregateTree) setChild(value any, child
*inspectPartitionAggregateTree) {
+ if binary, ok := value.([]byte); ok {
+ if t.binaryChildren == nil {
+ t.binaryChildren =
make(map[string]*inspectPartitionAggregateTree)
+ }
+ // A stored string key owns its bytes even if the input buffer
changes.
+ t.binaryChildren[string(binary)] = child
+
+ return
+ }
+
+ if t.children == nil {
+ t.children = make(map[any]*inspectPartitionAggregateTree)
}
+ t.children[comparablePartitionKey(value)] = child
}
func (t *inspectPartitionAggregateTree) lookup(record partitionRecord)
*inspectPartitionAggregate {
node := t
for _, value := range record {
- child, ok := node.children[comparablePartitionKey(value)]
+ child, ok := node.child(value)
if !ok {
return nil
}
@@ -75,7 +105,7 @@ func (t *inspectPartitionAggregateTree) lookupPartition(
) *inspectPartitionAggregate {
node := t
for _, field := range partitionType.FieldList {
- child, ok :=
node.children[comparablePartitionKey(partition[field.ID])]
+ child, ok := node.child(partition[field.ID])
if !ok {
return nil
}
@@ -91,11 +121,10 @@ func (t *inspectPartitionAggregateTree) lookupPartition(
func (t *inspectPartitionAggregateTree) insert(record partitionRecord,
aggregate *inspectPartitionAggregate) {
node := t
for _, value := range record {
- key := comparablePartitionKey(value)
- child, ok := node.children[key]
+ child, ok := node.child(value)
if !ok {
child = newInspectPartitionAggregateTree()
- node.children[key] = child
+ node.setChild(value, child)
}
node = child
}
diff --git a/table/inspect_partitions_bench_test.go
b/table/inspect_partitions_bench_test.go
index cee48c384..aedf0c1ba 100644
--- a/table/inspect_partitions_bench_test.go
+++ b/table/inspect_partitions_bench_test.go
@@ -151,3 +151,45 @@ func benchmarkInspectPartitionFiles(
return partitionType, files
}
+
+func BenchmarkInspectPartitionBinaryLookup(b *testing.B) {
+ const (
+ partitionCount = 1024
+ fieldCount = 8
+ )
+
+ fields := make([]iceberg.NestedField, fieldCount)
+ for i := range fields {
+ fields[i] = iceberg.NestedField{
+ ID: 1000 + i, Name: fmt.Sprintf("part_%d", i), Type:
iceberg.PrimitiveTypes.Binary,
+ }
+ }
+ partitionType := &iceberg.StructType{FieldList: fields}
+
+ tree := newInspectPartitionAggregateTree()
+ partitions := make([]map[int]any, partitionCount)
+ for i := range partitions {
+ record := make(partitionRecord, fieldCount)
+ partition := make(map[int]any, fieldCount)
+ for j, field := range fields {
+ value := []byte(fmt.Sprintf("partition-%d-field-%d", i,
j))
+ record[j] = value
+ partition[field.ID] = value
+ }
+ tree.insert(record, &inspectPartitionAggregate{})
+ partitions[i] = partition
+ }
+
+ index := 0
+ b.ReportAllocs()
+ b.ResetTimer()
+ for b.Loop() {
+ if tree.lookupPartition(partitions[index], partitionType) ==
nil {
+ b.Fatal("partition not found")
+ }
+ index++
+ if index == len(partitions) {
+ index = 0
+ }
+ }
+}
diff --git a/table/inspect_partitions_internal_test.go
b/table/inspect_partitions_internal_test.go
new file mode 100644
index 000000000..5d0d0f320
--- /dev/null
+++ b/table/inspect_partitions_internal_test.go
@@ -0,0 +1,99 @@
+// 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 table
+
+import (
+ "math"
+ "testing"
+
+ "github.com/apache/iceberg-go"
+)
+
+func TestInspectPartitionAggregateTreeBinaryKeys(t *testing.T) {
+ partitionType := &iceberg.StructType{FieldList: []iceberg.NestedField{
+ {ID: 1000, Name: "first", Type: iceberg.PrimitiveTypes.Binary},
+ {ID: 1001, Name: "second", Type: iceberg.PrimitiveTypes.Binary},
+ }}
+ tree := newInspectPartitionAggregateTree()
+
+ check := func(first, second any, want *inspectPartitionAggregate) {
+ t.Helper()
+ if got := tree.lookup(partitionRecord{first, second}); got !=
want {
+ t.Fatalf("positional lookup (%v, %v) = %p, want %p",
first, second, got, want)
+ }
+ partition := map[int]any{1000: first, 1001: second}
+ if got := tree.lookupPartition(partition, partitionType); got
!= want {
+ t.Fatalf("map lookup (%v, %v) = %p, want %p", first,
second, got, want)
+ }
+ }
+
+ check([]byte("missing"), []byte("value"), nil)
+
+ first := []byte{0, 0xff}
+ second := []byte("x/y")
+ original := &inspectPartitionAggregate{}
+ tree.insert(partitionRecord{first, second}, original)
+ first[0] = 1
+ second[0] = 'z'
+ check([]byte{0, 0xff}, []byte("x/y"), original)
+ check(first, second, nil)
+
+ nullFirst := &inspectPartitionAggregate{}
+ emptyFirst := &inspectPartitionAggregate{}
+ tree.insert(partitionRecord{nil, []byte("x")}, nullFirst)
+ tree.insert(partitionRecord{[]byte{}, []byte("x")}, emptyFirst)
+ check(nil, []byte("x"), nullFirst)
+ check([]byte{}, []byte("x"), emptyFirst)
+ check([]byte(nil), []byte("x"), emptyFirst)
+
+ binaryFirst := &inspectPartitionAggregate{}
+ stringFirst := &inspectPartitionAggregate{}
+ tree.insert(partitionRecord{[]byte("same"), []byte("tail")},
binaryFirst)
+ tree.insert(partitionRecord{"same", []byte("tail")}, stringFirst)
+ check([]byte("same"), []byte("tail"), binaryFirst)
+ check("same", []byte("tail"), stringFirst)
+
+ otherNested := &inspectPartitionAggregate{}
+ tree.insert(partitionRecord{[]byte("same"), []byte("other")},
otherNested)
+ check([]byte("same"), []byte("other"), otherNested)
+ check([]byte("same"), []byte("tail"), binaryFirst)
+}
+
+func TestInspectPartitionAggregateTreeMixedKeys(t *testing.T) {
+ tree := newInspectPartitionAggregateTree()
+ negativeZero := &inspectPartitionAggregate{}
+ positiveZero := &inspectPartitionAggregate{}
+ nan := &inspectPartitionAggregate{}
+
+ tree.insert(partitionRecord{[]byte("scope"), math.Copysign(0, -1)},
negativeZero)
+ tree.insert(partitionRecord{[]byte("scope"), float64(0)}, positiveZero)
+ tree.insert(partitionRecord{[]byte("scope"),
math.Float64frombits(0x7ff8000000000001)}, nan)
+
+ for _, test := range []struct {
+ value float64
+ want *inspectPartitionAggregate
+ }{
+ {math.Copysign(0, -1), negativeZero},
+ {0, positiveZero},
+ {math.Float64frombits(0x7ff8000000000002), nan},
+ } {
+ if got := tree.lookup(partitionRecord{[]byte("scope"),
test.value}); got != test.want {
+ t.Fatalf("lookup (%v) = %p, want %p", test.value, got,
test.want)
+ }
+ }
+}