Hi Alex,
Thanks, these are the right questions. Let me answer
them, but I think all three are really facets of one
decision worth pulling out, so I'll do that at the end.
Original values vs sort keys. I don't think the two
limitations are symmetric. You're right that a bound
stored under version X may not hold under Y for either
representation, and a naive reader prunes only on an
exact version match either way. But they degrade
differently: a sort key from X is incomparable under Y
(nothing a Y reader can do with it) while an original
value is the actual string, so a Y reader can
re-interpret it. In the case you describe at the end of
your mail, where only a small code-point range moved and
a file's values fall outside it, that reader can prove
the X bound still holds and prune across versions. Sort
keys foreclose that; original values keep it open. So
original values are a superset: worst case they match
sort keys, best case they prune across versions.
Your two sort-key advantages are real, I just don't
think they belong in the format. Truncation: agreed it's
hard for collated strings (your abcเก contraction case
is exactly the trap), so I sidestepped it, collation
bounds must be tight, a writer that can't store the
exact min/max omits the bound. Collation-aware
truncation with CollationElementIterator is a possible
later optimization. Compare cost: pruning is per-file at
planning time, not per-row, so collator vs byte compare
is in the noise; and an engine that wants the byte path
can derive and cache the sort key from the stored value.
Original values don't block that, they just don't bake a
version-specific encoding into the format.
Column vs file-level version. As you say, every engine
can read regardless, so this is a pruning-performance
choice, not correctness. In the
schema-registered-metrics design a file carries bounds
under a declared (collation, version), and a reader
prunes any file with a metric for a version it can
produce, not only files it wrote. So convergence on one
ICU version gives full cross-engine pruning, same as
column-level, and a writer or compaction can populate
several versions at once. It gives the column-level
benefit by convention without making a version bump a
format-breaking change.
[One data point from the engine side, since I'm coming
at this from the Databricks runtime: our runtime is
effectively versionless (customers don't pin an ICU
version, and upgrades happen under them) so "the same
table read by clients on different ICU versions" isn't a
corner case for us, it's the default. That's what pushes
me toward per-file versioning: pinning one version per
table or column means either forcing the whole fleet to
upgrade in lockstep or breaking pruning on every bump,
and neither survives a versionless fleet. And in
practice most version bumps we've gone through don't
reorder the data in a given column at all, which is
exactly why keeping original values, and eventually your
code-point-range idea, lets a reader keep pruning across
a bump instead of falling back to a scan.]
Providers. Agreed. I'll tighten the spec to a registered
set like geo, icu to start, utf8 reserved, non-ICU
collations added by spec change rather than ad hoc.
Interop is the whole point and an open namespace
undercuts it.
-----------
Stepping back: I think the three above collapse into one
question that needs broader alignment than the two of us:
> how much cross-engine pruning interoperability should
the format guarantee, versus leave to convention?
Original-vs-sortkey, column-vs-file version, and
open-vs-restricted providers are all that same tradeoff
from different angles. It's a values call more than a
correctness one, and it binds every engine that hasn't
weighed in yet: Trino, Flink, Spark, PyIceberg, rust.
From the DBR side I can say the multi-version case is
real rather than theoretical, but that's one engine's
vantage point. I'd like to get the interop question in
front of the other implementers before we fix field IDs,
the dev list is probably enough for now, and a community
sync is there if it needs more than async. No rush on
that; I'd rather let the thread settle the mechanics first.
Best,
Andrei
On Fri, Jul 3, 2026 at 12:22 AM Alexander Löser
<[email protected]> wrote:
Hi Andrei,
Thanks for putting together the spec PR and the
detailed write-up! The approach mostly looks solid
to me. I have a few questions/initial thoughts
regarding the changes you proposed (compared to the
original proposal):
> 1 - Bounds store original values, not sort keys,
tagged with a per-file collation version. ICU/CLDR
sort keys aren't stable across versions, so storing
keys ties every reader to one exact version;
original values plus a per-file version (readers
prune only on an exact match) degrade gracefully
instead of breaking. The schema keeps the collation
name unversioned so anyone can read
If I understand correctly, we’re talking about two
separate things here:
1. Tagging a column vs a single file with a certain
ICU version
2. Using collation keys vs original strings (the
ones that will produce the min/max collation keys)
For 1, it comes down to a tradeoff:
* If we tag the column with the ICU version, we’d
force engines to support one agreed-on ICU
version if they want to prune files. Engines
would be able to prune every file (if they
support the specific ICU version), or none at
all, so there is more incentive to support a
specific version
* If we tag individual files with ICU versions, we
gain the big advantage that engines do not need
to agree on a single ICU version. However, if I
understand correctly, this comes at the cost of
“fractured” pruning - engines will only be able
to prune files that were written by themselves
(or rather, with the same ICU version). As a
consequence, performance might not really be
interoperable between different engines.
Regardless of the approach we choose, all engines
should be able to read the data - they might just
not be able to prune files.
For 2, I’m not sure if I understand the advantages
of original strings yet. As you already pointed out,
the collation keys depend on the ICU version.
However, if I understand correctly, the same
limitation would apply to the original strings: the
sort order may (and does) change between different
ICU versions, too. As a consequence, we can’t assume
the original lower/upper bound strings we stored for
version X will also be lower/upper bounds for
version Y - at least in the general case. So if I
understand correctly, we would not gain additional
pruning opportunities compared to using collation
keys. Or am I missing something here?
At the same time, sort keys do have advantages:
* Iceberg allows the truncation of upper- and
lower bounds. This is trivial for binary
collation keys. For original strings, the task
becomes significantly harder: truncating at a
character boundary, for example, would lead to
wrong results, as there are some
context-sensitive sequences: e.g., with the CLDR
root locale, abcเก < abcเ. I think it might be
doable with ICU’s CollationElementIterator, but
it will be tricky to get right.
* Lower/upper bounds are computed once, but will
be compared many times. With original strings,
we would need to either convert to the collation
key on the fly, or use ICU’s collator for a
direct comparison. Both options will be slower
than a raw byte-sequence comparison
There is one scenario where original values would
shine, though. I analyzed the order-changes between
various ICU versions: in many cases, only a small
range of code points changes/is moved. If we had
additional metadata about which code point ranges a
file contains (e.g., whether it is ASCII only),
engines might be able to prove that the original
string bounds for version X are still valid for
version Y.
If I'm not mistaken, this could allow to prune
across different ICU versions in certain situations,
which I’d consider a point in favor of original
values (and file-level ICU versions).
> 2 - A provider-qualified identifier
(icu.en_US-ci), leaving room for non-ICU collations
like Spark's UTF8_LCASE, rather than assuming ICU as
the sole provider.
Adding a provider-mechanism sounds like a good
approach to keep the spec open for future collations
:slightly_smiling_face: I wonder whether we should
restrict the set of allowed providers, though,
similar to how it was done with geo
<https://lists.apache.org/thread/r5x0do8f241bpf565rx8s5s3wc9ogp0f>.
My main motivation for this proposal is
interoperability. I worry that interoperability
might suffer or vanish completely if every engine
can come up with their own definitions.
Happy to hear your thoughts on this!
Best, Alex
On 6/27/26 01:37, Szehon Ho wrote:
Very nice direction, left some comments on the spec
proposal.
Thanks to you folks for working on it !
Szehon
On Fri, Jun 26, 2026 at 3:29 AM Andrei Tserakhau
via dev <[email protected]> wrote:
Hi all,
I've spend some cycle on the collation
discussion and make something more concrete to
react to: a spec-change PR plus reference
implementations (go and java).
- Spec change (apache/iceberg#16972): a
"collation" annotation on string fields, and a
data_file.collation_bounds field so collated
columns stay prunable.
- Reference implementation in iceberg-go
(apache/iceberg-go#1318): the full path end to
end - schema annotation, collation-aware
comparison (CLDR/UCA), collation bounds in the
manifest, and version-gated data-file pruning,
with an Avro round-trip and pruning tests.
- A lightweight Java POC (link below): the
schema annotation plus a Collator-backed
comparator, to match where the discussion is. I
deliberately left the manifest/bounds side out
of Java for now.
The design follows the original proposal but
takes a few different turns, mostly to adopt
what we learned in Delta. The ones I'd most
like input on:
1 - Bounds store original values, not sort
keys, tagged with a per-file collation version.
ICU/CLDR sort keys aren't stable across
versions, so storing keys ties every reader to
one exact version; original values plus a
per-file version (readers prune only on an
exact match) degrade gracefully instead of
breaking. The schema keeps the collation name
unversioned so anyone can read.
2 - A provider-qualified identifier
(icu.en_US-ci), leaving room for non-ICU
collations like Spark's UTF8_LCASE, rather than
assuming ICU as the sole provider.
3 - One structural question I don't have a
strong opinion on yet: I put collation_bounds
on data_file as a standalone v3 field, but
field id 146 is already the v4 content_stats
struct, and collation bounds might belong
inside that typed-stats framework instead.
Worth settling before we fix field ids.
The full set of differences and the
reader/writer rules are in the PR description
and the write-up. Comments very welcome — both
on the calls above and on whether the
standalone-field vs content_stats direction is
the right one.
Best, Andrei
- original proposal:
https://docs.google.com/document/d/1m8b7u97uteHYjXk-4DNglJSpQO8OcZOCzW2tApCNTW4/edit?tab=t.0
- spec change:
https://github.com/apache/iceberg/pull/16972
- POC in go:
https://github.com/apache/iceberg-go/pull/1318
- java POC:
https://github.com/laskoviymishka/iceberg/tree/prototype/collation-support
On Mon, Mar 30, 2026 at 10:54 PM Alexander
Löser <[email protected]> wrote:
Hi Andrei,
I'm glad you're interested. Looking forward
to collaborate with you!
Thanks for all the feedback here and in the
doc. I only had a quick glance, but I think
you raised some good points. I'll
address/respond to your comments as soon
as I get the chance, hopefully tomorrow.
I think you also left some comments in this
mail that are not yet in the doc - I'll
move those to a dedicated section at the
end of the doc, so we can use the doc as a
single source of truth/discussion.
> Happy to share our Delta design doc and
implementation learnings in more detail.
Sure, sounds good :)
Best,
Alex
On 3/29/26 01:25, Andrei Tserakhau via dev
wrote:
Hi Alexander,
This looks really interesting. We've been
working on collation support in Delta and
have shipped it in production for some
time, so this is an area we care about a
lot. If this proposal moves forward we'd
be happy to collaborate on the design and
implementation.
The pseudo-field approach for collation
metrics is clean and composes well with
existing Iceberg infrastructure. The
specifier coverage is comprehensive.
A few areas worth discussing as this evolves:
1 - Sort key stability and versioning
ICU sort keys are not stable across
versions, so a pinned ICU version bump in
a future Iceberg release would invalidate
all existing collation metrics. In
multi-engine environments, requiring all
engines to converge on one ICU version is
unrealistic.
We store original string values instead of
sort keys and allow per-file version
annotations -- worth discussing whether
something similar could work here.
2 - Provider abstraction
The proposal assumes ICU as the sole
provider, but Spark ships non-ICU
collations like UTF8_LCASE that are widely
used. A provider or namespace layer would
prevent name collisions and support
engine-specific collations without future
spec changes.
3 - Operational surface
A few things that turned out
correctness-critical in our
implementation: partition transforms on
collated columns (collation-equal but
byte-distinct values in different
directories), sort order semantics,
equality deletes under collation, and
Parquet filter pushdown (must be disabled
since Parquet has no collation concept).
These don't all need to be solved in v1
but would help to scope them.
4 - Smaller items (nit's)
UTF-8 bounds for the original field id
should be "must write" not "should" --
otherwise backward compat breaks for
non-aware engines. Engine fallback
behavior (case-sensitive vs older ICU vs
fail) could use a recommended preference
order to avoid divergent results across
engines. The collation specifier syntax
would benefit from a formal grammar.
---
Happy to share our Delta design doc and
implementation learnings in more detail.
Looking forward to the discussion.
Best,
Andrei
On Sat, Mar 28, 2026 at 11:49 PM Alexander
Löser <[email protected]> wrote:
Hi everyone,
this is my first interaction with the
Iceberg community, so here a few words
about myself:
- I'm Alex, a Berlin-based software
engineer
- I've been working at Snowflake for 4
years now
- I spend most of my time on data
types, particularly binary, strings
and collations.
I'd like to start a discussion about
adding collations to the Iceberg spec.
Conceptually, collations are an
annotation on the string data type. By
default, most engines perform string
operations case-sensitively.
Collations allow specifying
alternative comparison rules. This is
useful for achieving, e.g., case- or
accent-insensitive string operations,
or language-specific string sorting.
Collations are supported by many
engines: Databricks
<https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-collation>,
Spark
<https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.collate.html>,
Snowflake
<https://docs.snowflake.com/en/sql-reference/collation>,
Oracle
<https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/COLLATION.html>
- to
name just a few - this list is not
complete.
In Snowflake, we see heavy use of the
collation feature. Several users have
approached us, mentioning they want to
migrate to Iceberg tables, but are
currently blocked by Iceberg's lack of
collation support.
Given the widespread support for
collations across different engines, I
believe introducing collations to
Iceberg will increase interoperability
and boost its adoption.
I'd be curious about your thoughts.
*Goal of the proposal*
- Support collation specifications for
columns
- Define how collation bounds should
be stored - UTF-8 based bounds are not
useful for collated columns
*Required Changes*
- Extend the schema to let (string)
fields be annotated with a collation
More details can be found in this doc
<https://docs.google.com/document/d/1m8b7u97uteHYjXk-4DNglJSpQO8OcZOCzW2tApCNTW4/edit?tab=t.0#heading=h.y1ant4w2163k>.
I'm also hoping to present the idea in
the next community sync.
Best, Alex