Skip to content

Instantly share code, notes, and snippets.

@mlafeldt
Created December 28, 2025 17:39
Show Gist options
  • Select an option

  • Save mlafeldt/5ee9694769e803957a3ccca687c53a08 to your computer and use it in GitHub Desktop.

Select an option

Save mlafeldt/5ee9694769e803957a3ccca687c53a08 to your computer and use it in GitHub Desktop.
Ion 1.1 research report (generated with Claude Opus 4.5)

Amazon Ion 1.1: Major Upgrades to Encoding, Performance, and Macros

Amazon Ion 1.1 represents the most significant update to the Ion data format since its public release, introducing a macro system for templated data, switching to little-endian encoding for 1.5-3x performance gains, and adding delimited containers for streaming writes—all while maintaining full backward compatibility with Ion 1.0. The specification's final comment period closed in March 2025, but official finalization remains pending as of late December 2025. ion-rust serves as the reference implementation with full Ion 1.1 support, while ion-java offers preview support. The update has also attracted criticism for its complexity, with some observers noting "second system syndrome" patterns in the ambitious scope of changes and the slow pace of both specification finalization and library adoption.

What Amazon Ion Is and Why It Matters

Amazon Ion is a richly-typed, self-describing, hierarchical data serialization format developed internally at Amazon over a decade ago. It offers interchangeable binary and text representations designed to solve three critical challenges in service-oriented architectures: enabling rapid development through human-readable text (a JSON superset), supporting system decoupling through self-describing payloads that don't require schema negotiation, and delivering efficiency through compact binary encoding with fast parsing.

Ion's type system encompasses 13 data types that extend well beyond JSON's capabilities. These include arbitrary-precision integers and decimals (critical for financial data), timestamps with variable precision and timezone offsets, symbols (interned strings encoded as integers in binary), blobs and clobs for binary and character data, and s-expressions for domain-specific semantics. Every type supports typed null values (e.g., null.int, null.struct), and values can carry annotations for metadata like dollars::100.0.

AWS services using Ion natively include Amazon QLDB (Quantum Ledger Database), DynamoDB exports to S3, Amazon Athena for querying Ion datasets, Redshift Spectrum, and the PartiQL query language.


The Macro System Transforms Repetitive Data Encoding

The most substantial new feature in Ion 1.1 is the introduction of macros—fill-in-the-blank templates that fundamentally change how repetitive data structures are encoded. Templates can capture both the structure and shape of values as well as entire values themselves, allowing applications to focus exclusively on encoding distinctive data while skipping the work normally required for boilerplate.

Macros are invoked through e-expressions in both text and binary formats. Template definitions can use positional or named arguments for flexibility and readability. This system proves particularly valuable for datasets with highly repetitive schemas—common in logging, IoT telemetry, and database exports—where structural information would otherwise be duplicated across thousands of records.

The design explicitly preserves Ion's self-describing nature: macro definitions travel with the data, meaning consumers don't need external schema files to interpret templated values.

Macro Definition and Invocation Examples

Basic Macro Definition Using the Encoding Directive

$ion_1_1

// Define macros in an encoding directive
$ion_encoding::(
  (macro_table
    (macro employee (name title department)
      {
        name: (%name),
        title: (%title),
        department: (%department),
        company: "Acme Corp",
        active: true
      }
    )
  )
)

// Invoke the macro using e-expression syntax
(:employee "Alice Smith" "Engineer" "Platform")

// Expands to:
{
  name: "Alice Smith",
  title: "Engineer",
  department: "Platform",
  company: "Acme Corp",
  active: true
}

Log Event Macro with Timestamp Compression

$ion_1_1

$ion_encoding::(
  (macro_table
    (macro log_event (level message timestamp)
      {
        level: (%level),
        message: (%message),
        timestamp: (%timestamp),
        source: "api-gateway",
        version: "2.1.0"
      }
    )
  )
)

// Multiple invocations - only variable data needs encoding
(:log_event INFO "Request received" 2025-01-15T10:30:00Z)
(:log_event WARN "Rate limit approached" 2025-01-15T10:30:01Z)
(:log_event ERROR "Connection timeout" 2025-01-15T10:30:02Z)

Nested Struct Macro for Complex Schemas

$ion_1_1

$ion_encoding::(
  (macro_table
    (macro address (street city state zip)
      {
        street: (%street),
        city: (%city),
        state: (%state),
        zip: (%zip),
        country: "USA"
      }
    )
    (macro customer (id name address_data)
      {
        customer_id: (%id),
        name: (%name),
        shipping_address: (%address_data),
        account_type: "standard",
        created: 2025-01-01T
      }
    )
  )
)

// Compose macros together
(:customer 12345 "John Doe" 
  (:address "123 Main St" "Seattle" "WA" "98101"))

System Macros for Common Operations

Ion 1.1 includes built-in system macros for frequently needed operations:

$ion_1_1

// The 'values' system macro produces multiple values
(:values 1 2 3)  // Expands to: 1 2 3

// The 'make_string' system macro concatenates strings
(:make_string "Hello, " "World!")  // Expands to: "Hello, World!"

// The 'annotate' system macro adds annotations dynamically
(:annotate USD (: 99.99))  // Expands to: USD::99.99

// The 'make_list' system macro constructs lists
(:make_list 1 2 3)  // Expands to: [1, 2, 3]

Real-World Use Case: IoT Sensor Data

$ion_1_1

$ion_encoding::(
  (macro_table
    (macro sensor_reading (device_id temp humidity ts)
      {
        device: (%device_id),
        readings: {
          temperature_celsius: (%temp),
          humidity_percent: (%humidity)
        },
        timestamp: (%ts),
        firmware: "v3.2.1",
        protocol: "mqtt",
        qos: 1
      }
    )
  )
)

// Thousands of readings with minimal overhead
(:sensor_reading "sensor-001" 22.5 45.2 2025-01-15T10:00:00Z)
(:sensor_reading "sensor-001" 22.6 45.0 2025-01-15T10:01:00Z)
(:sensor_reading "sensor-002" 21.8 48.1 2025-01-15T10:00:00Z)

In binary encoding, macro invocations are extremely compact—only the macro ID and argument values are encoded, eliminating all the repeated field names and constant values that would otherwise consume significant space.


Binary Encoding Receives a Complete Overhaul

Ion 1.1 introduces four major binary encoding changes that deliver substantial performance improvements:

Aspect Ion 1.0 Ion 1.1
Byte order Big-Endian Little-Endian
Containers Length-prefixed only Delimited or length-prefixed
Symbol tokens Must pre-exist in symbol table Inline text option
Timestamps Octet-aligned sub-fields Packed bit encoding
Integer encoding VarInt/VarUInt FlexInt/FlexUInt

Delimited containers represent a key architectural shift. Ion 1.0 required all containers to be length-prefixed, forcing writers to buffer serialized contents before writing. Ion 1.1 allows streaming writes by using end-of-container markers instead, trading slightly slower skip-scanning on reads for significantly faster write performance.

Symbol tokens with inline text address a longstanding friction point. In Ion 1.0 binary, symbols, annotations, and struct field names had to be added to the symbol table before use. Ion 1.1 allows writers to include symbol text inline, choosing whether and when to intern symbols—matching the flexibility that text Ion has always offered.

The binary version marker changes from 0xE0 0x01 0x00 0xEA (Ion 1.0) to 0xE0 0x01 0x01 0xEA (Ion 1.1), allowing readers to immediately identify the format version.


Performance Benchmarks Show 1.5-3x Improvements

The switch to little-endian encoding primitives combined with more efficient variable-width integer representations (FlexInt/FlexUInt) delivers 1.5-3x faster read and write performance on both x86_64 and aarch64 architectures. This improvement stems from better alignment with modern processor architectures, which are predominantly little-endian.

Timestamp encoding improvements are equally significant. Ion 1.0 encoded timestamp sub-field components in octet-aligned fields; Ion 1.1 uses packed bit encoding, allowing common timestamp precisions to fit within a 64-bit word. For comparison, the timestamp 2017-07-26T16:30:04.076Z requires 26 bytes as a JSON string but only 11 bytes in Ion binary.

Additional performance work in the ion-java library has yielded up to 57% faster hashCode operations, 41% faster cloning, and 12% improvement in write operations through optimized patch point management.


Type System Remains Unchanged by Design

A critical design constraint governs Ion 1.1: as a minor version bump, it cannot change the Ion data model. This means Ion 1.1 cannot add or remove data types, and all legal Ion 1.0 data must remain representable in Ion 1.1.

The existing 13 types carry forward unchanged:

  • Primitives: null, bool, int, float, decimal, timestamp, string, symbol
  • Binary types: blob, clob
  • Containers: struct, list, sexp

What changes are the encodings of these types—more efficient binary representations, packed formats, and flexible interning—not their semantics or capabilities.


Backward Compatibility Ensures Smooth Migration

The Ion team has been explicit that all Ion 1.0 data remains valid in Ion 1.1. Libraries implementing Ion 1.1 should read Ion 1.0 data seamlessly, with the binary version marker indicating which format applies to each stream.

There are no breaking changes to the data model. Applications can adopt Ion 1.1 features incrementally—using delimited containers for streaming writes, inline symbols for flexibility, or macros for compression—without requiring wholesale migration. Mixed-version environments work naturally, as readers detect the version marker and adapt accordingly.

Migration guidance varies by implementation maturity: ion-rust users can experiment with 1.0.0-rc releases now, ion-java users can test with preview releases (v1.11.9-ion-11-rc3), while users of other language bindings should continue with stable Ion 1.0 releases until Ion 1.1 support arrives.


Implementation Status Varies Significantly Across Libraries

Ion 1.1 library support shows a clear maturity hierarchy as of late 2025:

Library Latest Version Ion 1.1 Status Macro Support
ion-rust 1.0.0-rc.11 ✅ Reference implementation ✅ Yes
ion-java 1.11.11 (stable) / 1.11.9-ion-11-rc3 (preview) ⚠️ Preview available ⚠️ Partial
ion-c 1.1.4 ❌ Not yet
ion-python 0.13.0 ❌ Not yet
ion-go 1.5.0 ❌ Not yet
ion-js 5.2.1 ❌ Not yet
ion-dotnet 1.3.4 ❌ Not yet

ion-rust serves as the leading reference implementation, actively used to validate the specification. The ion-java preview claims mutual intelligibility with ion-rust v1.0.0-rc.10, though some features remain incomplete: tagless encodings, macro-shaped parameters, and module imports are still in development.

The specification timeline shows the draft entered final public comment on February 26, 2025, with the comment period closing March 12, 2025. At that time, the Ion team stated they would be "working to incorporate these suggestions and improvements in the coming weeks" and would "post another notice when they have been added to the published specification."

As of late December 2025—over nine months after the comment period closed—no such announcement has been made. The specification remains in draft status, and subsequent Ion news has covered only library releases and community updates, not specification finalization. This extended timeline, despite the relatively brief two-week comment period, suggests the feedback incorporation process has proven more complex than anticipated.


Key Resources


Critical Analysis: Second System Syndrome Concerns

Ion 1.1 has attracted criticism that echoes Fred Brooks' "second system effect"—the tendency for designers to overload a successor system with features they regret omitting from the first version. While explicit "over-engineered" critiques are scattered across GitHub discussions and occasional Hacker News threads rather than consolidated reviews, several patterns emerge that warrant scrutiny.

The Macro System's Complexity Burden

The most significant concern centers on the macro system itself. During the RFC process (PR #104), a reviewer noted that even simple examples with only 8 templates were "confusing even with the included comments." The Ion team's response is revealing: they acknowledged that templates are not expected to be used in Ion's text encoding and will "only be used in earnest by binary Ion writers/readers." The text syntax (e.g., @_, @0(...), (:macro_name ...)) is characterized as "system-level"—technically valid but never intended for human use.

This acknowledgment undermines one of Ion's original value propositions: seamless interchangeability between human-readable text and efficient binary formats. If macros are effectively binary-only constructs with awkward text representations, the dual-format interoperability promise weakens.

Another reviewer characterized the RFC analysis as "hand-wavy" and questioned how features would work in practice, noting that these additions would be "hard to retrofit onto APIs that generally work very hard to be output-format-agnostic." The concern was that existing Ion libraries have deliberately avoided encoding-specific optimizations, and macros push against that design philosophy.

Specification Scope and Implementation Burden

Ion 1.1's scope is substantial. Beyond macros, it introduces:

  • E-expressions with new invocation syntax
  • Encoding directives and a modules system for macro organization
  • Inline symbol definitions (addressing a 1.0 limitation)
  • Delimited containers (vs. length-prefixed only)
  • FlexInt/FlexUInt variable-width integers (replacing VarInt/VarUInt)
  • Packed timestamp encodings
  • A complete binary format overhaul (big-endian → little-endian)

Each feature solves a real problem, but their aggregate effect creates implementation complexity that manifests in the fragmented library support landscape. After years of development, only ion-rust has full Ion 1.1 macro support. ion-java offers partial preview support. The remaining official libraries—Go, Python, C, JavaScript, .NET—remain Ion 1.0 only as of late 2025.

The ion-rust library itself has been in release candidate status (1.0.0-rc.X) for over two years, suggesting the complexity of achieving a stable Ion 1.1 implementation even in a language well-suited to systems programming.

Internal Experience and Developer Ergonomics

A comment from someone who used Ion internally at Amazon offers a sobering perspective:

"Ion never had nice code wrappers around serialized structures, and most of the time, especially with rich structures it was frustrating experience."

While this critique predates Ion 1.1, it highlights a recurring theme: Ion's power comes at the cost of developer ergonomics. The 1.1 additions don't directly address this pain point and may exacerbate it by adding more concepts (macros, encoding directives, modules) that developers must understand.

The Adoption Question

Ion competes in a crowded serialization landscape:

Format Schema Required Self-Describing Ecosystem Maturity
Protobuf Yes No Massive
MessagePack No Yes Strong
CBOR No Yes Growing
Avro Yes Optional Hadoop ecosystem
Ion 1.0 No Yes AWS-centric
Ion 1.1 No Yes Emerging

Protobuf's schema requirement is often cited as a drawback, yet its ecosystem dominance demonstrates that simplicity in the common case matters more than flexibility in edge cases. MessagePack and CBOR offer self-describing binary formats without Ion's macro complexity. Ion's "JSON superset" advantage—valid JSON is valid Ion—diminishes when the binary format diverges significantly and when the text format gains syntax that JSON parsers can't handle.

Counterarguments: Where Complexity May Be Justified

The Ion team's defense centers on Amazon-scale problems that external observers may underestimate:

  1. Performance gains are real. The 1.5-3x improvements on x86_64/aarch64 aren't marketing claims—they result from principled encoding changes that align with modern processor architectures.

  2. Macros solve genuine problems. For telemetry, logging, and IoT use cases where 90%+ of each record is boilerplate, macros deliver order-of-magnitude compression improvements. This isn't theoretical—it's been validated internally at Amazon.

  3. Backward compatibility is maintained. All Ion 1.0 data remains valid, and the data model is unchanged. The complexity is in encoding, not semantics—applications don't need to understand macros to consume data produced with them.

  4. Self-describing nature preserved. Unlike Protobuf, macro definitions travel with the data. No external schema files are required for consumers to interpret templated values.

The Version Timeline Tells a Story

Year Milestone
2016 Ion 1.0 open-sourced
2020 Ion 1.1 RFC development begins (Templates/Macros proposal via PR #104)
2023 ion-rust 1.0.0-rc.1 released
Feb 2025 Ion 1.1 draft enters final comment period
Mar 2025 Comment period closes; team commits to finalization "in coming weeks"
Dec 2025 Specification still in draft status; no finalization announcement

Five years from RFC to the comment period, then nine months (and counting) from comment period close to... still waiting. The extended timeline could indicate careful design and thorough feedback incorporation—or scope creep that has proven difficult to resolve.

Verdict

Ion 1.1's complexity appears to be essential rather than accidental for Amazon's internal use cases, but potentially accidental for external adopters. The macro system and encoding changes solve real problems at Amazon scale; whether they justify the implementation burden for smaller deployments remains an open question.

Organizations considering Ion 1.1 should ask: Do we have the data volumes and repetition patterns that justify macros? Can we wait for library support beyond Rust and Java? Are the 1.5-3x performance gains worth the migration complexity?

For many use cases, Ion 1.0's simpler model—or even switching to MessagePack/CBOR—may be the pragmatic choice until the Ion 1.1 ecosystem matures.


Conclusion

Ion 1.1 represents an ambitious evolution of Amazon's internal serialization format, delivering genuine performance improvements and powerful new capabilities for data-intensive applications. The macro system addresses real pain points in encoding repetitive structures, while the encoding overhaul brings performance in line with contemporary processor architectures.

However, the upgrade is not without trade-offs. The specification's complexity has resulted in a fragmented implementation landscape, with full support limited to Rust and partial support in Java as of late 2025. The macro system's text syntax—acknowledged by the Ion team as "system-level" rather than intended for human use—arguably weakens Ion's original dual-format interoperability promise.

Organizations currently using Ion 1.0 in production should evaluate adoption carefully: backward compatibility is guaranteed, but realizing Ion 1.1's benefits requires libraries that may not yet exist for your platform. For those with Amazon-scale data volumes and repetitive encoding patterns, the investment will likely pay off. For smaller deployments, Ion 1.0 or simpler alternatives like MessagePack may remain the pragmatic choice until the ecosystem matures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment