This is an automated email from the ASF dual-hosted git repository.
sk0x50 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-website.git
The following commit(s) were added to refs/heads/master by this push:
new 05681807b1 IGNITE-27663 Schema and data mapping blog post (#311)
05681807b1 is described below
commit 05681807b150520222a837d892b3fb5222a2f219
Author: jinxxxoid <[email protected]>
AuthorDate: Tue Jan 27 20:55:34 2026 +0400
IGNITE-27663 Schema and data mapping blog post (#311)
---
_src/_blog/schema-and-data-mapping-ai3.pug | 503 ++++++++++++++++++++
blog/apache-ignite-3-architecture-part-1.html | 3 +
blog/apache-ignite-3-architecture-part-2.html | 3 +
blog/apache-ignite-3-architecture-part-3.html | 3 +
blog/apache-ignite-3-architecture-part-4.html | 3 +
blog/apache-ignite-3-architecture-part-5.html | 3 +
blog/apache-ignite-3-architecture-part-6.html | 3 +
blog/apache-ignite-3-architecture-part-7.html | 3 +
blog/apache-ignite-3-architecture-part-8.html | 3 +
...pache-ignite-3-client-connections-handling.html | 3 +
blog/apache-ignite-essentials-series-for.html | 3 +
blog/apache/index.html | 3 +
blog/getting-to-know-apache-ignite-3.html | 3 +
blog/ignite/index.html | 3 +
blog/index.html | 30 +-
...ite-3.html => schema-and-data-mapping-ai3.html} | 520 +++++++++++----------
.../schema-design-for-distributed-systems-ai3.html | 3 +
blog/whats-new-in-apache-ignite-3-0.html | 3 +
blog/whats-new-in-apache-ignite-3-1.html | 3 +
19 files changed, 848 insertions(+), 253 deletions(-)
diff --git a/_src/_blog/schema-and-data-mapping-ai3.pug
b/_src/_blog/schema-and-data-mapping-ai3.pug
new file mode 100644
index 0000000000..3f0bde967d
--- /dev/null
+++ b/_src/_blog/schema-and-data-mapping-ai3.pug
@@ -0,0 +1,503 @@
+---
+title: "Schema and Data Mapping in Apache Ignite 3"
+author: "Michael Aglietti"
+date: 2026-01-27
+tags:
+ - technical
+ - ignite3
+ - java
+---
+
+p
+ | Your schema came from somewhere. Maybe SQL scripts, maybe another
developer, maybe your own DDL. Your application uses
+ |
+ code Mapper.of()
+ | for data access, and the workflow is functional. But if your POJO field
names don't exactly match your column names, you're writing
+ |
+ code map()
+ | calls to bridge them, and that can start to feel heavy.
+<!-- end -->
+
+h2 Schema and Data Mapping in Apache Ignite 3
+br
+p
+ | When you use
+ |
+ code automap()
+ | from the
+ |
+ code table.mapper
+ | package, it reads those annotations and builds the mappings for you.
+
+p Learn how these packages work together, and your future self will thank you.
+br
+h2 Connecting Schema to Code
+br
+p
+ | Database columns typically use names like
+ |
+ code order_date
+ | . Java fields typically use names like
+ |
+ code orderDate
+ | . When Ignite normalizes these to uppercase, the SQL column becomes
+ |
+ code ORDER_DATE
+ | while the Java field becomes
+ |
+ code ORDERDATE
+ | . Different strings that don't map.
+
+p
+ | The
+ |
+ code catalog.annotations
+ | package gives you a way to declare mappings in your POJO:
+
+pre
+ code.java.
+ // @Column declares the column mapping at the source
+ public class Order {
+ @Id
+ @Column("ORDER_ID")
+ Integer orderId;
+ @Column("CUSTOMER_ID")
+ String customerId;
+ @Column("ORDER_DATE")
+ LocalDate orderDate;
+ @Column("TOTAL_AMOUNT")
+ BigDecimal totalAmount;
+ @Column("STATUS")
+ String status;
+ }
+
+p
+ | Each
+ |
+ code @Column
+ | annotation tells Ignite exactly which column the field maps to. The
annotation value goes through the same normalization as SQL identifiers, so
everything lines up.
+
+p Now your mapper call becomes straightforward:
+
+pre
+ code.java.
+ // automap() reads @Column annotations and builds mappings automatically
+ Mapper<Order> mapper = Mapper.builder(Order.class)
+ .automap()
+ .build();
+
+p
+ | The
+ |
+ code automap()
+ | method scans your class, finds the annotations, and builds the mappings.
You annotate the POJO once. Every developer who writes a service or batch job
using that class gets the mapping without thinking about it. The SRE debugging
at 2am can read the class and understand the schema.
+
+p
+ | If you built your schema with SQL, this approach still works. Add
+ |
+ code @Column
+ | annotations to your POJO fields, and
+ |
+ code automap()
+ | handles the mapping. Your schema stays in SQL. Your mappings stay visible
in your code.
+
+p
+ | If you want to go further, the
+ |
+ code catalog.annotations
+ | package lets you define your entire schema in Java. Annotations like
+ |
+ code @Table
+ | ,
+ |
+ code @Zone
+ | , and
+ |
+ code @Index
+ | replace your DDL scripts with compile-time-checked code. The POJO becomes
the single source of truth for both schema and mapping. That's a topic for
another post, but worth exploring if you prefer keeping schema close to your
application code.
+
+br
+h2 Why This Matters
+br
+p When the mapping lives on the field, you get benefits that compound over
time.
+
+p
+ | Your IDE becomes more helpful. The
+ |
+ code @Column
+ | annotation uses
+ |
+ code @Retention(RUNTIME)
+ | , so IntelliJ sees it and can provide context. Hover over a field to see
the column name. Use Find Usages to trace references. Refactor with confidence
because the annotation moves with the field.
+
+p Future you can quickly jump back into the code. When you return to this
class in six months, the column name is right there on the field. No hunting
through mapper files. No tracing string literals across your codebase. The
mapping is visible where you're already looking.
+
+p
+ | The alternative is explicit
+ |
+ code map()
+ | calls scattered through your mapper code:
+
+pre
+ code.java.
+ // Explicit mapping: column names scattered in mapper code, far from fields
+ Mapper<Order> mapper = Mapper.builder(Order.class)
+ .map("orderId", "ORDER_ID")
+ .map("customerId", "CUSTOMER_ID")
+ .map("orderDate", "ORDER_DATE")
+ .map("totalAmount", "TOTAL_AMOUNT")
+ .map("status", "STATUS")
+ .build();
+
+p This works, and sometimes it's the right choice. But the column names live
far from the fields they describe, which makes the code harder to maintain and
gives your IDE less to work with.
+br
+h2 The Packages Work Together
+br
+p
+ | The
+ |
+ code @Column
+ | annotation comes from
+ |
+ code org.apache.ignite.catalog.annotations
+ | . The
+ |
+ code automap()
+ | method comes from
+ |
+ code org.apache.ignite.table.mapper
+ | . These are separate packages with separate purposes, but they were
designed to complement each other.
+
+p
+ | When you call
+ |
+ code automap()
+ | on a
+ |
+ code MapperBuilder
+ | , it scans your class for fields. For each field, it checks for a
+ |
+ code @Column
+ | annotation and uses that value as the column name. If there's no
annotation, it falls back to the field name, normalized to uppercase. Fields
you've already mapped explicitly with
+ |
+ code map()
+ | are skipped.
+
+p
+ | The scanning finds only fields declared directly in your class. If your
POJO extends a parent class, inherited fields won't be discovered, even if they
have
+ |
+ code @Column
+ | annotations. The same applies to
+ |
+ code createTable(Class)
+ | . For shared fields across multiple tables, define them in each class or
use composition.
+
+p This means you can mix approaches when you need to. Use annotations for most
fields and explicit mapping for edge cases that require type conversion:
+
+pre
+ code.java.
+ // Mix explicit mapping for type conversion with automap() for everything
else
+ Mapper<Event> mapper = Mapper.builder(Event.class)
+ .map("eventDate", "EVENT_DATE", new LocalDateConverter())
+ .automap()
+ .build();
+
+p
+ | The explicit
+ |
+ code map()
+ | handles the field that needs special treatment. The
+ |
+ code automap()
+ | handles everything else based on your annotations.
+
+p The relationship between these components looks like this:
+
+pre.mermaid.
+ graph TB
+ ANN["@Column Annotations"]
+ subgraph CAT["Catalog API - Schema"]
+ OPT1["@Table/@Zone/@Index on POJO"]
+ OPT2[SQL DDL]
+ OPT3[Java Catalog DSL]
+ end
+ subgraph MAP["Mapper API - Data Mapping"]
+ OPT4["automap() reads @Column"]
+ OPT5["Explicit map() no annotations"]
+ end
+ ANN -.Shared by.-> OPT1
+ ANN -.Shared by.-> OPT4
+ OPT1 --> SCHEMA[Table Schema]
+ OPT2 --> SCHEMA
+ OPT3 --> SCHEMA
+ OPT4 --> MAPPING[Runtime Mapping]
+ OPT5 --> MAPPING
+ SCHEMA -.Work Together.-> MAPPING
+
+p
+ | The
+ |
+ code @Column
+ | annotation bridges both APIs, and each API has alternatives. You can
define schema with SQL DDL and still use
+ |
+ code automap()
+ | for mapping. You can create tables from annotations and still use explicit
+ |
+ code map()
+ | calls. The APIs work together but remain independent.
+
+br
+h2 The Full Annotation Set
+br
+p
+ | Now that you understand how
+ |
+ code automap()
+ | reads
+ |
+ code @Column
+ | , here's the full toolkit. The
+ |
+ code catalog.annotations
+ | package includes more than just
+ |
+ code @Column
+ | . If you want to define your entire schema from Java classes, you have the
tools:
+
+table
+ thead
+ tr
+ th Annotation
+ th Purpose
+ tbody
+ tr
+ td
+ code @Table
+ td Table name, zone, indexes, colocation
+ tr
+ td
+ code @Column
+ td Column name and properties
+ tr
+ td
+ code @Id
+ td Primary key field
+ tr
+ td
+ code @Index
+ td Secondary index
+ tr
+ td
+ code @ColumnRef
+ td Column reference for indexes
+ tr
+ td
+ code @Zone
+ td Distribution zone configuration
+
+p You can use these annotations to generate your schema instead of writing
SQL. Or you can use just
+ |
+ |
+ code @Column
+ | to help your mappers while managing schema separately. The packages
support both workflows.
+
+p
+ | The
+ |
+ code table.mapper
+ | package provides the runtime side:
+
+table
+ thead
+ tr
+ th Component
+ th Purpose
+ tbody
+ tr
+ td
+ code Mapper.of()
+ td Factory for standard mappings
+ tr
+ td
+ code MapperBuilder
+ td Fluent API for custom mappings
+ tr
+ td
+ code TypeConverter
+ td Type transformation
+ tr
+ td
+ code automap()
+ td Automatic mapping using annotations
+
+br
+h2 Complete Example
+br
+p Here's a complete example using both packages. The POJO becomes your single
source of truth for schema, distribution strategy, and field mappings:
+
+pre
+ code.java.
+ // Single POJO defines everything: schema, zone, indexes, and column
mappings
+ // This class is the source of truth for both DDL generation and runtime
mapping
+ @Table(
+ value = "orders",
+ zone = @Zone(value = "orders_zone", storageProfiles = "default"),
+ colocateBy = @ColumnRef("CUSTOMER_ID"),
+ indexes = @Index(value = "idx_order_date", columns = {
+ @ColumnRef(value = "ORDER_DATE", sort = SortOrder.DESC)
+ })
+ )
+ public class Order {
+ @Id
+ @Column(value = "ORDER_ID", nullable = false)
+ Integer orderId;
+ @Id
+ @Column(value = "CUSTOMER_ID", nullable = false)
+ String customerId;
+ @Column(value = "ORDER_DATE", nullable = false)
+ LocalDate orderDate;
+ @Column(value = "TOTAL_AMOUNT", precision = 10, scale = 2)
+ BigDecimal totalAmount;
+ @Column(value = "STATUS", length = 20)
+ String status;
+ // Constructors, getters, setters...
+ }
+
+p Create the table from your annotated class:
+
+pre
+ code.java.
+ // Schema generated from annotations, table created in one call
+ Table ordersTable = client.catalog().createTable(Order.class);
+
+p Use the data with a mapper that reads those same annotations:
+
+pre
+ code.java.
+ // RecordView uses your POJO directly
+ // Mapper reads @Column annotations through automap()
+ RecordView<Order> orders =
ordersTable.recordView(Mapper.of(Order.class));
+ Order order = new Order(1001, "cust-001", LocalDate.now(),
+ new BigDecimal("299.99"), "PENDING");
+ orders.upsert(null, order);
+ Order retrieved = orders.get(null, order);
+
+br
+h2 Beyond Annotations
+br
+p
+ | The examples use annotations for both schema definition (
+ |
+ code @Table
+ | ,
+ |
+ code @Zone
+ | ,
+ |
+ code @Index
+ | ) and data mapping (
+ |
+ code @Column
+ | ). This is the simplest approach when you control both schema and code,
but both APIs can also work independently.
+
+p #[strong Alternative schema definition:]
+
+ul
+ li Use SQL DDL instead of
+ |
+ |
+ code catalog.createTable(Class)
+ | if your schema already exists or your team prefers SQL
+ li Use the Java Catalog DSL for programmatic schema creation without
annotations
+ li These approaches support schema evolution with
+ |
+ |
+ code ALTER TABLE
+ | operations
+
+p #[strong Alternative data mapping:]
+
+ul
+ li Use
+ |
+ |
+ code Mapper.builder().map("field", "COLUMN")
+ | for explicit control without annotations
+ li Mix explicit mapping with
+ |
+ |
+ code automap()
+ | for type conversions or special cases
+ li Create custom
+ |
+ |
+ code Mapper
+ | implementations for advanced scenarios
+ li The mapper works regardless of how you created the table
+
+p #[strong Schema evolution consideration:]
+
+p
+ | Annotations provide convenience for the create-once pattern. The
+ |
+ code createTable(Class)
+ | method generates schema from your POJO, but it doesn't support altering
existing tables. If your application needs to evolve the schema over time,
define your tables with SQL DDL. You can still use
+ |
+ code @Column
+ | annotations on your POJOs for mapping, even if the table was created with
DDL.
+
+p The key is that annotations bridge the two APIs when you want them to, but
you're not locked into using them for both. Pick the approach that fits your
workflow.
+
+br
+h2 Summary
+br
+
+table
+ thead
+ tr
+ th Want to...
+ th Use this
+ tbody
+ tr
+ td Define column name for a field
+ td
+ code @Column("COLUMN_NAME")
+ tr
+ td Map fields automatically
+ td
+ code Mapper.builder(Class).automap().build()
+ tr
+ td Map a specific field manually
+ td
+ code .map("fieldName", "COLUMN_NAME")
+ tr
+ td Convert types during mapping
+ td
+ code .map("field", "COLUMN", new TypeConverter())
+ tr
+ td Create table from annotations
+ td
+ code client.catalog().createTable(Order.class)
+
+p
+ | Database conventions and Java conventions don't naturally align, and
Ignite's identifier normalization adds another consideration. The
+ |
+ code catalog.annotations
+ | package lets you declare the mapping at the source, right on your fields.
The
+ |
+ code table.mapper
+ | package reads those declarations through
+ |
+ code automap()
+ | , turning what could be scattered configuration into a single point of
truth.
+
+p The result is code that your IDE can help you navigate and that future you
can understand without archaeology.
+
+p
+ | For more details, see the
+ |
+
a(href="https://ignite.apache.org/docs/ignite3/latest/developers-guide/java-to-tables")
Creating Tables from Java Classes
+ | and
+ |
+
a(href="https://ignite.apache.org/docs/ignite3/latest/developers-guide/table-api")
Table API
+ | documentation.
diff --git a/blog/apache-ignite-3-architecture-part-1.html
b/blog/apache-ignite-3-architecture-part-1.html
index c673b2f541..ce4553ee9b 100644
--- a/blog/apache-ignite-3-architecture-part-1.html
+++ b/blog/apache-ignite-3-architecture-part-1.html
@@ -663,6 +663,9 @@ CustomerRecord record = customerTable.recordView()
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-2.html
b/blog/apache-ignite-3-architecture-part-2.html
index efc6ebaf5a..0d12dea7b2 100644
--- a/blog/apache-ignite-3-architecture-part-2.html
+++ b/blog/apache-ignite-3-architecture-part-2.html
@@ -578,6 +578,9 @@ long totalTime = System.nanoTime() - startTime;
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-3.html
b/blog/apache-ignite-3-architecture-part-3.html
index 5f13e53a8c..70acd4454c 100644
--- a/blog/apache-ignite-3-architecture-part-3.html
+++ b/blog/apache-ignite-3-architecture-part-3.html
@@ -709,6 +709,9 @@ public class FlexibleFeatureDevelopment {
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-4.html
b/blog/apache-ignite-3-architecture-part-4.html
index aa291cfbf6..8a3de11f0e 100644
--- a/blog/apache-ignite-3-architecture-part-4.html
+++ b/blog/apache-ignite-3-architecture-part-4.html
@@ -765,6 +765,9 @@ flowchart LR
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-5.html
b/blog/apache-ignite-3-architecture-part-5.html
index df3ef51f57..711f51babd 100644
--- a/blog/apache-ignite-3-architecture-part-5.html
+++ b/blog/apache-ignite-3-architecture-part-5.html
@@ -647,6 +647,9 @@ ResultSet<SqlRow> customerAnalysis =
client.sql().execute(tx, """
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-6.html
b/blog/apache-ignite-3-architecture-part-6.html
index 299e56e240..b1be23a325 100644
--- a/blog/apache-ignite-3-architecture-part-6.html
+++ b/blog/apache-ignite-3-architecture-part-6.html
@@ -874,6 +874,9 @@ public class AdvancedInventoryProcessor {
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-7.html
b/blog/apache-ignite-3-architecture-part-7.html
index 865b66b40b..3821f2bb9f 100644
--- a/blog/apache-ignite-3-architecture-part-7.html
+++ b/blog/apache-ignite-3-architecture-part-7.html
@@ -652,6 +652,9 @@ public TradeResult processTradeWithMVCC(TradeRequest
tradeRequest) {
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-architecture-part-8.html
b/blog/apache-ignite-3-architecture-part-8.html
index 2d3897e508..8144464ca7 100644
--- a/blog/apache-ignite-3-architecture-part-8.html
+++ b/blog/apache-ignite-3-architecture-part-8.html
@@ -782,6 +782,9 @@ public class BusinessMetricsCollector {
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-3-client-connections-handling.html
b/blog/apache-ignite-3-client-connections-handling.html
index 24aed6eaab..98f6bc8294 100644
--- a/blog/apache-ignite-3-client-connections-handling.html
+++ b/blog/apache-ignite-3-client-connections-handling.html
@@ -426,6 +426,9 @@ Verified connectivity in 00:00:09.1446883
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache-ignite-essentials-series-for.html
b/blog/apache-ignite-essentials-series-for.html
index ca9d6eda70..8c9b60c21b 100644
--- a/blog/apache-ignite-essentials-series-for.html
+++ b/blog/apache-ignite-essentials-series-for.html
@@ -383,6 +383,9 @@
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/apache/index.html b/blog/apache/index.html
index aebab03255..070be22c5e 100644
--- a/blog/apache/index.html
+++ b/blog/apache/index.html
@@ -511,6 +511,9 @@
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/getting-to-know-apache-ignite-3.html
b/blog/getting-to-know-apache-ignite-3.html
index 84f85dfa62..3dc03f1ae6 100644
--- a/blog/getting-to-know-apache-ignite-3.html
+++ b/blog/getting-to-know-apache-ignite-3.html
@@ -629,6 +629,9 @@ CompletableFuture<Void> streaming = ignite.sql()
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/ignite/index.html b/blog/ignite/index.html
index ffa4c26913..75fc22abff 100644
--- a/blog/ignite/index.html
+++ b/blog/ignite/index.html
@@ -512,6 +512,9 @@
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/index.html b/blog/index.html
index efe9715efc..fc3c956cf4 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -341,6 +341,22 @@
<div class="blog__content">
<main class="blog_main">
<section class="blog__posts">
+ <article class="post">
+ <div class="post__header">
+ <h2><a href="/blog/schema-and-data-mapping-ai3.html">Schema
and Data Mapping in Apache Ignite 3</a></h2>
+ <div>
+ January 27, 2026 by Michael Aglietti. Share in <a
href="http://www.facebook.com/sharer.php?u=https://ignite.apache.org/blog/schema-and-data-mapping-ai3.html">Facebook</a><span>,
</span
+ ><a href="http://twitter.com/home?status=Schema and Data
Mapping in Apache Ignite
3%20https://ignite.apache.org/blog/schema-and-data-mapping-ai3.html">Twitter</a>
+ </div>
+ </div>
+ <div class="post__content">
+ <p>
+ Your schema came from somewhere. Maybe SQL scripts, maybe
another developer, maybe your own DDL. Your application uses
<code>Mapper.of()</code> for data access, and the workflow is functional. But
if your POJO field names
+ don't exactly match your column names, you're writing
<code>map()</code> calls to bridge them, and that can start to feel heavy.
+ </p>
+ </div>
+ <div class="post__footer"><a class="more"
href="/blog/schema-and-data-mapping-ai3.html">↓ Read all</a></div>
+ </article>
<article class="post">
<div class="post__header">
<h2><a
href="/blog/apache-ignite-3-architecture-part-8.html">Part 8 of 8: Apache
Ignite 3 Architecture Series - The Business Case for Architectural Evolution:
Platform Consolidation Benefits</a></h2>
@@ -489,17 +505,6 @@
</div>
<div class="post__footer"><a class="more"
href="/blog/apache-ignite-3-architecture-part-1.html">↓ Read all</a></div>
</article>
- <article class="post">
- <div class="post__header">
- <h2><a
href="/blog/schema-design-for-distributed-systems-ai3.html">Schema Design for
Distributed Systems: Why Data Placement Matters</a></h2>
- <div>
- November 18, 2025 by Michael Aglietti. Share in <a
href="http://www.facebook.com/sharer.php?u=https://ignite.apache.org/blog/schema-design-for-distributed-systems-ai3.html">Facebook</a><span>,
</span
- ><a href="http://twitter.com/home?status=Schema Design for
Distributed Systems: Why Data Placement
Matters%20https://ignite.apache.org/blog/schema-design-for-distributed-systems-ai3.html">Twitter</a>
- </div>
- </div>
- <div class="post__content"><p>Discover how Apache Ignite keeps
related data together with schema-driven colocation, cutting cross-node traffic
and making distributed queries fast, local and predictable.</p></div>
- <div class="post__footer"><a class="more"
href="/blog/schema-design-for-distributed-systems-ai3.html">↓ Read all</a></div>
- </article>
</section>
<section class="blog__footer">
<ul class="pagination">
@@ -512,6 +517,9 @@
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/getting-to-know-apache-ignite-3.html
b/blog/schema-and-data-mapping-ai3.html
similarity index 60%
copy from blog/getting-to-know-apache-ignite-3.html
copy to blog/schema-and-data-mapping-ai3.html
index 84f85dfa62..dd8c482303 100644
--- a/blog/getting-to-know-apache-ignite-3.html
+++ b/blog/schema-and-data-mapping-ai3.html
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1" />
- <title>Getting to Know Apache Ignite 3: A Schema-Driven Distributed
Computing Platform</title>
+ <title>Schema and Data Mapping in Apache Ignite 3</title>
<link rel="stylesheet"
href="/js/vendor/hystmodal/hystmodal.min.css?ver=0.9" />
<link rel="stylesheet" href="/css/utils.css?ver=0.9" />
<link rel="stylesheet" href="/css/site.css?ver=0.9" />
@@ -334,10 +334,10 @@
<div class="container blog">
<section class="blog__header post_page__header">
<a href="/blog/">← Apache Ignite Blog</a>
- <h1>Getting to Know Apache Ignite 3: A Schema-Driven Distributed
Computing Platform</h1>
+ <h1>Schema and Data Mapping in Apache Ignite 3</h1>
<p>
- November 11, 2025 by <strong>Michael Aglietti. Share in </strong><a
href="http://www.facebook.com/sharer.php?u=https://ignite.apache.org/blog/undefined">Facebook</a><span>,
</span
- ><a href="http://twitter.com/home?status=Getting to Know Apache
Ignite 3: A Schema-Driven Distributed Computing
Platform%20https://ignite.apache.org/blog/undefined">Twitter</a>
+ January 27, 2026 by <strong>Michael Aglietti. Share in </strong><a
href="http://www.facebook.com/sharer.php?u=https://ignite.apache.org/blog/undefined">Facebook</a><span>,
</span
+ ><a href="http://twitter.com/home?status=Schema and Data Mapping in
Apache Ignite 3%20https://ignite.apache.org/blog/undefined">Twitter</a>
</p>
</section>
<div class="blog__content">
@@ -346,289 +346,325 @@
<article class="post">
<div>
<p>
- Apache Ignite 3 is a memory-first distributed SQL database
platform that consolidates transactions, analytics, and compute workloads
previously requiring separate systems. Built from the ground up, it represents
a complete
- departure from traditional caching solutions toward a
unified distributed computing platform with microsecond latencies and
collocated processing capabilities.
+ Your schema came from somewhere. Maybe SQL scripts, maybe
another developer, maybe your own DDL. Your application uses
<code>Mapper.of()</code> for data access, and the workflow is functional. But
if your POJO field names
+ don't exactly match your column names, you're writing
<code>map()</code> calls to bridge them, and that can start to feel heavy.
</p>
<!-- end -->
+ <h2>Schema and Data Mapping in Apache Ignite 3</h2>
+ <br />
+ <p>When you use <code>automap()</code> from the
<code>table.mapper</code> package, it reads those annotations and builds the
mappings for you.</p>
+ <p>Learn how these packages work together, and your future
self will thank you.</p>
+ <br />
+ <h2>Connecting Schema to Code</h2>
+ <br />
<p>
- <strong>Forget everything you knew about Apache
Ignite.</strong> Version 3.0 is a complete architectural rewrite that
transforms Ignite from a caching platform into a memory-first distributed
computing platform with
- microsecond latencies and collocated processing.
+ Database columns typically use names like
<code>order_date</code>. Java fields typically use names like
<code>orderDate</code>. When Ignite normalizes these to uppercase, the SQL
column becomes
+ <code>ORDER_DATE</code> while the Java field becomes
<code>ORDERDATE</code>. Different strings that don't map.
+ </p>
+ <p>The <code>catalog.annotations</code> package gives you a
way to declare mappings in your POJO:</p>
+ <pre><code class="java">// @Column declares the column mapping
at the source
+public class Order {
+ @Id
+ @Column("ORDER_ID")
+ Integer orderId;
+ @Column("CUSTOMER_ID")
+ String customerId;
+ @Column("ORDER_DATE")
+ LocalDate orderDate;
+ @Column("TOTAL_AMOUNT")
+ BigDecimal totalAmount;
+ @Column("STATUS")
+ String status;
+}
+</code></pre>
+ <p>Each <code>@Column</code> annotation tells Ignite exactly
which column the field maps to. The annotation value goes through the same
normalization as SQL identifiers, so everything lines up.</p>
+ <p>Now your mapper call becomes straightforward:</p>
+ <pre><code class="java">// automap() reads @Column annotations
and builds mappings automatically
+Mapper<Order> mapper = Mapper.builder(Order.class)
+ .automap()
+ .build();
+</code></pre>
+ <p>
+ The <code>automap()</code> method scans your class, finds
the annotations, and builds the mappings. You annotate the POJO once. Every
developer who writes a service or batch job using that class gets the mapping
without
+ thinking about it. The SRE debugging at 2am can read the
class and understand the schema.
+ </p>
+ <p>
+ If you built your schema with SQL, this approach still
works. Add <code>@Column</code> annotations to your POJO fields, and
<code>automap()</code> handles the mapping. Your schema stays in SQL. Your
mappings stay visible
+ in your code.
+ </p>
+ <p>
+ If you want to go further, the
<code>catalog.annotations</code> package lets you define your entire schema in
Java. Annotations like <code>@Table</code>, <code>@Zone</code>, and
<code>@Index</code> replace your DDL scripts
+ with compile-time-checked code. The POJO becomes the single
source of truth for both schema and mapping. That's a topic for another post,
but worth exploring if you prefer keeping schema close to your application code.
+ </p>
+ <br />
+ <h2>Why This Matters</h2>
+ <br />
+ <p>When the mapping lives on the field, you get benefits that
compound over time.</p>
+ <p>
+ Your IDE becomes more helpful. The <code>@Column</code>
annotation uses <code>@Retention(RUNTIME)</code>, so IntelliJ sees it and can
provide context. Hover over a field to see the column name. Use Find Usages to
trace
+ references. Refactor with confidence because the annotation
moves with the field.
</p>
- <h3>Architectural Foundation: Schema-Driven Design</h3>
<p>
- The core architectural shift in Ignite 3 is that your schema
becomes the foundation for data placement, query optimization, and compute job
scheduling. Instead of managing separate systems with different data models, you
- define your schema once and it drives everything.
+ Future you can quickly jump back into the code. When you
return to this class in six months, the column name is right there on the
field. No hunting through mapper files. No tracing string literals across your
codebase.
+ The mapping is visible where you're already looking.
</p>
- <pre><code>// Unified platform connection
-IgniteClient ignite = IgniteClient.builder()
- .addresses("node1:10800", "node2:10800", "node3:10800")
+ <p>The alternative is explicit <code>map()</code> calls
scattered through your mapper code:</p>
+ <pre><code class="java">// Explicit mapping: column names
scattered in mapper code, far from fields
+Mapper<Order> mapper = Mapper.builder(Order.class)
+ .map("orderId", "ORDER_ID")
+ .map("customerId", "CUSTOMER_ID")
+ .map("orderDate", "ORDER_DATE")
+ .map("totalAmount", "TOTAL_AMOUNT")
+ .map("status", "STATUS")
.build();
</code></pre>
- <p><strong>Schema Creation: Ignite 3 supports three approaches
for schema creation:</strong></p>
- <ul>
- <li><strong>SQL DDL</strong> - Traditional <code>CREATE
TABLE</code> statements</li>
- <li><strong>Java Annotations API</strong> - POJO markup with
<code>@Table</code>, <code>@Column</code>, etc.</li>
- <li><strong>Java Builder API</strong> - Programmatic
<code>TableDefinition.builder()</code> approach</li>
- </ul>
- <p>We use the Java Annotations API in this blog for their
compile-time type safety and clear colocation syntax.</p>
- <pre><code>@Table(zone = @Zone(value = "MusicStore",
storageProfiles = "default"))
-public class Artist {
- @Id
- private Integer ArtistId;
-
- @Column(value = "Name", length = 120, nullable = false)
- private String Name;
-
- // Constructors, getters, setters...
-}
-
+ <p>This works, and sometimes it's the right choice. But the
column names live far from the fields they describe, which makes the code
harder to maintain and gives your IDE less to work with.</p>
+ <br />
+ <h2>The Packages Work Together</h2>
+ <br />
+ <p>
+ The <code>@Column</code> annotation comes from
<code>org.apache.ignite.catalog.annotations</code>. The <code>automap()</code>
method comes from <code>org.apache.ignite.table.mapper</code>. These are
separate packages with
+ separate purposes, but they were designed to complement each
other.
+ </p>
+ <p>
+ When you call <code>automap()</code> on a
<code>MapperBuilder</code>, it scans your class for fields. For each field, it
checks for a <code>@Column</code> annotation and uses that value as the column
name. If there's no
+ annotation, it falls back to the field name, normalized to
uppercase. Fields you've already mapped explicitly with <code>map()</code> are
skipped.
+ </p>
+ <p>
+ The scanning finds only fields declared directly in your
class. If your POJO extends a parent class, inherited fields won't be
discovered, even if they have <code>@Column</code> annotations. The same
applies to
+ <code>createTable(Class)</code>. For shared fields across
multiple tables, define them in each class or use composition.
+ </p>
+ <p>This means you can mix approaches when you need to. Use
annotations for most fields and explicit mapping for edge cases that require
type conversion:</p>
+ <pre><code class="java">// Mix explicit mapping for type
conversion with automap() for everything else
+Mapper<Event> mapper = Mapper.builder(Event.class)
+ .map("eventDate", "EVENT_DATE", new LocalDateConverter())
+ .automap()
+ .build();
+</code></pre>
+ <p>The explicit <code>map()</code> handles the field that
needs special treatment. The <code>automap()</code> handles everything else
based on your annotations.</p>
+ <p>The relationship between these components looks like
this:</p>
+ <pre class="mermaid">
+graph TB
+ ANN["@Column Annotations"]
+ subgraph CAT["Catalog API - Schema"]
+ OPT1["@Table/@Zone/@Index on POJO"]
+ OPT2[SQL DDL]
+ OPT3[Java Catalog DSL]
+ end
+ subgraph MAP["Mapper API - Data Mapping"]
+ OPT4["automap() reads @Column"]
+ OPT5["Explicit map() no annotations"]
+ end
+ ANN -.Shared by.-> OPT1
+ ANN -.Shared by.-> OPT4
+ OPT1 --> SCHEMA[Table Schema]
+ OPT2 --> SCHEMA
+ OPT3 --> SCHEMA
+ OPT4 --> MAPPING[Runtime Mapping]
+ OPT5 --> MAPPING
+ SCHEMA -.Work Together.-> MAPPING
+</pre
+ >
+ <p>
+ The <code>@Column</code> annotation bridges both APIs, and
each API has alternatives. You can define schema with SQL DDL and still use
<code>automap()</code> for mapping. You can create tables from annotations and
still
+ use explicit <code>map()</code> calls. The APIs work
together but remain independent.
+ </p>
+ <br />
+ <h2>The Full Annotation Set</h2>
+ <br />
+ <p>
+ Now that you understand how <code>automap()</code> reads
<code>@Column</code>, here's the full toolkit. The
<code>catalog.annotations</code> package includes more than just
<code>@Column</code>. If you want to define your
+ entire schema from Java classes, you have the tools:
+ </p>
+ <table>
+ <thead>
+ <tr>
+ <th>Annotation</th>
+ <th>Purpose</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>@Table</code></td>
+ <td>Table name, zone, indexes, colocation</td>
+ </tr>
+ <tr>
+ <td><code>@Column</code></td>
+ <td>Column name and properties</td>
+ </tr>
+ <tr>
+ <td><code>@Id</code></td>
+ <td>Primary key field</td>
+ </tr>
+ <tr>
+ <td><code>@Index</code></td>
+ <td>Secondary index</td>
+ </tr>
+ <tr>
+ <td><code>@ColumnRef</code></td>
+ <td>Column reference for indexes</td>
+ </tr>
+ <tr>
+ <td><code>@Zone</code></td>
+ <td>Distribution zone configuration</td>
+ </tr>
+ </tbody>
+ </table>
+ <p>You can use these annotations to generate your schema
instead of writing SQL. Or you can use just <code>@Column</code> to help your
mappers while managing schema separately. The packages support both
workflows.</p>
+ <p>The <code>table.mapper</code> package provides the runtime
side:</p>
+ <table>
+ <thead>
+ <tr>
+ <th>Component</th>
+ <th>Purpose</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><code>Mapper.of()</code></td>
+ <td>Factory for standard mappings</td>
+ </tr>
+ <tr>
+ <td><code>MapperBuilder</code></td>
+ <td>Fluent API for custom mappings</td>
+ </tr>
+ <tr>
+ <td><code>TypeConverter</code></td>
+ <td>Type transformation</td>
+ </tr>
+ <tr>
+ <td><code>automap()</code></td>
+ <td>Automatic mapping using annotations</td>
+ </tr>
+ </tbody>
+ </table>
+ <br />
+ <h2>Complete Example</h2>
+ <br />
+ <p>Here's a complete example using both packages. The POJO
becomes your single source of truth for schema, distribution strategy, and
field mappings:</p>
+ <pre><code class="java">// Single POJO defines everything:
schema, zone, indexes, and column mappings
+// This class is the source of truth for both DDL generation and runtime
mapping
@Table(
- zone = @Zone(value = "MusicStore", storageProfiles = "default"),
- colocateBy = @ColumnRef("ArtistId"),
- indexes = @Index(value = "IFK_AlbumArtistId", columns = {
- @ColumnRef("ArtistId") })
+ value = "orders",
+ zone = @Zone(value = "orders_zone", storageProfiles = "default"),
+ colocateBy = @ColumnRef("CUSTOMER_ID"),
+ indexes = @Index(value = "idx_order_date", columns = {
+ @ColumnRef(value = "ORDER_DATE", sort = SortOrder.DESC)
+ })
)
-public class Album {
+public class Order {
@Id
- private Integer AlbumId;
-
+ @Column(value = "ORDER_ID", nullable = false)
+ Integer orderId;
@Id
- private Integer ArtistId;
-
- @Column(value = "Title", length = 160, nullable = false)
- private String Title;
-
+ @Column(value = "CUSTOMER_ID", nullable = false)
+ String customerId;
+ @Column(value = "ORDER_DATE", nullable = false)
+ LocalDate orderDate;
+ @Column(value = "TOTAL_AMOUNT", precision = 10, scale = 2)
+ BigDecimal totalAmount;
+ @Column(value = "STATUS", length = 20)
+ String status;
// Constructors, getters, setters...
}
</code></pre>
- <p>The <code>colocateBy</code> annotation ensures that albums
are stored on the same nodes as their corresponding artists, eliminating
distributed join overhead and enabling local processing.</p>
- <h3>Multiple APIs, Single Schema</h3>
- <p>Ignite 3 provides different API views into the same schema,
eliminating impedance mismatch between operational and analytical workloads:</p>
- <pre><code>// RecordView for structured operations
-RecordView<Artist> artists = ignite.tables()
- .table("Artist")
- .recordView(Artist.class);
-
-// KeyValueView for high-performance access patterns
-KeyValueView<Long, Album> albums = ignite.tables()
- .table("Album")
- .keyValueView(Long.class, Album.class);
-
-// SQL for analytics using Apache Calcite engine
-SqlStatement analytics = ignite.sql()
- .statementBuilder()
- .query("SELECT a.Name, COUNT(al.AlbumId) as AlbumCount " +
- "FROM Artist a JOIN Album al ON a.ArtistId = al.ArtistId " +
- "GROUP BY a.Name");
-
-// Collocated compute jobs
-ComputeJob<String> job = ComputeJob.colocated("Artist", 42,
- RecommendationJob.class);
-JobExecution<String> recommendation = ignite.compute()
- .submit(ignite.clusterNodes(), job, "rock");
+ <p>Create the table from your annotated class:</p>
+ <pre><code class="java">// Schema generated from annotations,
table created in one call
+Table ordersTable = client.catalog().createTable(Order.class);
</code></pre>
- <p>This approach eliminates the typical data serialization and
movement overhead between different systems while maintaining type safety and
schema evolution capabilities.</p>
- <blockquote>
- <p>
- This represents a fundamental architectural shift from
Ignite 2.x, that accessed data as key-value operations using the cache API.
Ignite 3 puts an evolvable schema first and uses memory-centric storage to
deliver
- microsecond latencies for all operations, not just cache
hits.
- </p>
- </blockquote>
- <h3>Memory-First Storage Architecture</h3>
- <p>Unlike disk-first distributed databases, Ignite 3 uses a
memory-first storage model with configurable persistence options:</p>
+ <p>Use the data with a mapper that reads those same
annotations:</p>
+ <pre><code class="java">// RecordView uses your POJO directly
+// Mapper reads @Column annotations through automap()
+RecordView<Order> orders = ordersTable.recordView(Mapper.of(Order.class));
+Order order = new Order(1001, "cust-001", LocalDate.now(),
+ new BigDecimal("299.99"), "PENDING");
+orders.upsert(null, order);
+Order retrieved = orders.get(null, order);
+</code></pre>
+ <br />
+ <h2>Beyond Annotations</h2>
+ <br />
+ <p>
+ The examples use annotations for both schema definition (
<code>@Table</code>, <code>@Zone</code>, <code>@Index</code>) and data mapping
( <code>@Column</code>). This is the simplest approach when you control both
schema
+ and code, but both APIs can also work independently.
+ </p>
+ <p><strong>Alternative schema definition:</strong></p>
<ul>
- <li>
- <strong><code>aimem</code></strong
- >: Pure in-memory storage for maximum performance
- </li>
- <li>
- <strong><code>aipersist</code></strong
- >: Memory-first with persistence for durability
- </li>
- <li>
- <strong><code>RocksDB</code></strong
- >: Disk-based storage for write-heavy workloads
- </li>
+ <li>Use SQL DDL instead of
<code>catalog.createTable(Class)</code> if your schema already exists or your
team prefers SQL</li>
+ <li>Use the Java Catalog DSL for programmatic schema
creation without annotations</li>
+ <li>These approaches support schema evolution with
<code>ALTER TABLE</code> operations</li>
</ul>
- <p>The memory-first approach delivers microsecond response
times for hot data while providing flexible cost-performance trade-offs through
configurable memory-to-disk ratios.</p>
- <h4>Storage Engine Characteristics</h4>
- <table style="border-collapse: collapse; margin: 20px 0;
width: 100%">
+ <p><strong>Alternative data mapping:</strong></p>
+ <ul>
+ <li>Use <code>Mapper.builder().map("field", "COLUMN")</code>
for explicit control without annotations</li>
+ <li>Mix explicit mapping with <code>automap()</code> for
type conversions or special cases</li>
+ <li>Create custom <code>Mapper</code> implementations for
advanced scenarios</li>
+ <li>The mapper works regardless of how you created the
table</li>
+ </ul>
+ <p><strong>Schema evolution consideration:</strong></p>
+ <p>
+ Annotations provide convenience for the create-once pattern.
The <code>createTable(Class)</code> method generates schema from your POJO, but
it doesn't support altering existing tables. If your application needs to evolve
+ the schema over time, define your tables with SQL DDL. You
can still use <code>@Column</code> annotations on your POJOs for mapping, even
if the table was created with DDL.
+ </p>
+ <p>The key is that annotations bridge the two APIs when you
want them to, but you're not locked into using them for both. Pick the approach
that fits your workflow.</p>
+ <br />
+ <h2>Summary</h2>
+ <br />
+ <table>
<thead>
<tr>
- <th style="border: 1px solid #ddd; padding: 12px;
background-color: #f5f5f5; text-align: left">Engine</th>
- <th style="border: 1px solid #ddd; padding: 12px;
background-color: #f5f5f5; text-align: left">Primary Use Case</th>
- <th style="border: 1px solid #ddd; padding: 12px;
background-color: #f5f5f5; text-align: left">Latency Profile</th>
- <th style="border: 1px solid #ddd; padding: 12px;
background-color: #f5f5f5; text-align: left">Durability</th>
+ <th>Want to...</th>
+ <th>Use this</th>
</tr>
</thead>
<tbody>
<tr>
- <td style="border: 1px solid #ddd; padding:
12px">aimem</td>
- <td style="border: 1px solid #ddd; padding:
12px">Ultra-low latency</td>
- <td style="border: 1px solid #ddd; padding:
12px">Microseconds</td>
- <td style="border: 1px solid #ddd; padding:
12px">Volatile</td>
+ <td>Define column name for a field</td>
+ <td><code>@Column("COLUMN_NAME")</code></td>
</tr>
<tr>
- <td style="border: 1px solid #ddd; padding:
12px">aipersist</td>
- <td style="border: 1px solid #ddd; padding:
12px">Balanced performance</td>
- <td style="border: 1px solid #ddd; padding:
12px">Microseconds (memory)</td>
- <td style="border: 1px solid #ddd; padding:
12px">Persistent</td>
+ <td>Map fields automatically</td>
+
<td><code>Mapper.builder(Class).automap().build()</code></td>
</tr>
<tr>
- <td style="border: 1px solid #ddd; padding:
12px">RocksDB</td>
- <td style="border: 1px solid #ddd; padding:
12px">Write-heavy workloads</td>
- <td style="border: 1px solid #ddd; padding:
12px">Variable</td>
- <td style="border: 1px solid #ddd; padding:
12px">Persistent</td>
+ <td>Map a specific field manually</td>
+ <td><code>.map("fieldName", "COLUMN_NAME")</code></td>
+ </tr>
+ <tr>
+ <td>Convert types during mapping</td>
+ <td><code>.map("field", "COLUMN", new
TypeConverter())</code></td>
+ </tr>
+ <tr>
+ <td>Create table from annotations</td>
+
<td><code>client.catalog().createTable(Order.class)</code></td>
</tr>
</tbody>
</table>
- <h3>Consistency and Concurrency Model</h3>
- <p>Ignite 3 implements Raft consensus for strong consistency
and MVCC (Multi-Version Concurrency Control) for transaction isolation:</p>
- <ul>
- <li><strong>Raft consensus</strong>: Ensures data
consistency across replicas without split-brain scenarios</li>
- <li><strong>MVCC transactions</strong>: Provides snapshot
isolation and deadlock-free concurrency</li>
- <li><strong>ACID compliance</strong>: Full transactional
guarantees across distributed operations</li>
- </ul>
- <p>This consistency model applies uniformly across all APIs,
whether you're using RecordView operations, SQL queries, or compute jobs.</p>
- <h3>Collocated Processing: Compute-to-Data Architecture</h3>
- <p>One of Ignite 3's key architectural advantages is
collocated processing, which brings computation to where data is stored rather
than moving data to compute resources:</p>
- <pre><code>// Traditional approach: data movement overhead
-// 1. Query data from database
-// 2. Move data to compute cluster
-// 3. Process data remotely
-// 4. Return results
-
-// Ignite 3 approach: compute colocation
-ComputeJob<Result> job = ComputeJob.colocated("Customer", customerId,
- RiskAnalysisJob.class);
-CompletableFuture<Result> result = ignite.compute()
- .submitAsync(job, parameters);
-</code></pre>
<p>
- This compute-to-data pattern eliminates network
serialization overhead and enables processing of large datasets without data
movement. Instead of moving terabytes of data to processing nodes, you move
kilobytes of code to
- where the data lives.
+ Database conventions and Java conventions don't naturally
align, and Ignite's identifier normalization adds another consideration. The
<code>catalog.annotations</code> package lets you declare the mapping at the
source,
+ right on your fields. The <code>table.mapper</code> package
reads those declarations through <code>automap()</code>, turning what could be
scattered configuration into a single point of truth.
</p>
- <h3>System Consolidation Benefits</h3>
- <p>Traditional distributed architectures typically require
separate systems for different workloads:</p>
- <p><strong>Traditional Multi-System Architecture:</strong></p>
- <ul>
- <li>Transactional database (PostgreSQL, MySQL) - millisecond
latencies</li>
- <li>Analytics database (ClickHouse, Snowflake) - batch
processing</li>
- <li>Caching layer (Redis, Hazelcast) - separate consistency
model</li>
- <li>Compute cluster (Spark, Flink) - data movement
overhead</li>
- <li>Message queue (Kafka, RabbitMQ) - separate operational
model</li>
- <li>Stream processing (Kafka Streams, Pulsar) - additional
complexity</li>
- </ul>
- <p><strong>Ignite 3 Unified Platform:</strong></p>
- <ul>
- <li>Schema-driven storage with multiple storage engines -
microsecond latencies</li>
- <li>SQL analytics through Apache Calcite - real-time
processing</li>
- <li>Collocated compute processing - zero data movement</li>
- <li>Built-in streaming with flow control - integrated
backpressure</li>
- <li>ACID transactions across all operations - single
consistency model</li>
- <li>One operational model and consistency guarantee</li>
- </ul>
- <h4>Operational Advantages</h4>
- <ul>
- <li><strong>Unified Schema Evolution</strong>: Schema
changes propagate automatically across all access patterns</li>
- <li><strong>Single Consistency Model</strong>: ACID
guarantees across transactions, analytics, and compute</li>
- <li><strong>Reduced Operational Complexity</strong>: One
system to monitor, tune, and scale</li>
- <li><strong>Eliminated Data Movement</strong>: Processing
happens where data lives</li>
- <li><strong>Cost-Elastic Scaling</strong>: Adjust
memory-to-disk ratios based on performance requirements</li>
- </ul>
- <h3>Streaming and Flow Control</h3>
- <p>Ignite 3 includes built-in streaming capabilities with
configurable backpressure mechanisms:</p>
- <pre><code>// Publisher with flow control configuration
-StreamingOptions options = StreamingOptions.builder()
- .pageSize(1000)
- .autoFlushFrequency(Duration.ofMillis(100))
- .retryLimit(3)
- .build();
-
-// Handle millions of events with automatic backpressure
-CompletableFuture<Void> streaming = ignite.sql()
- .streamAsync("INSERT INTO events VALUES (?, ?, ?)",
- eventStream,
- options);
-</code></pre>
- <p>The streaming API provides automatic flow control through
configurable page sizes, flush intervals, and retry policies, preventing system
overload without data loss.</p>
- <h3>Performance Characteristics</h3>
- <p>Ignite 3's memory-first architecture delivers significantly
different performance characteristics compared to disk-based distributed
databases:</p>
- <ul>
- <li><strong>Latency</strong>: Microsecond response times for
memory-resident data vs. millisecond latencies for disk-based systems</li>
- <li><strong>Throughput</strong>: Handles millions of
operations per second per node</li>
- <li><strong>Scalability</strong>: Linear scaling through
data partitioning and colocation</li>
- <li><strong>Consistency</strong>: ACID transactions with
minimal overhead due to memory speeds</li>
- </ul>
- <p>The 10-1000x performance improvement comes from eliminating
disk I/O bottlenecks and data movement overhead through collocated
processing.</p>
- <h3>Migration and Adoption Strategy</h3>
- <p>For technical teams considering Ignite 3:</p>
- <h4>Assessment Phase</h4>
- <ul>
- <li><strong>Workload Analysis</strong>: Identify
performance-critical paths requiring microsecond latencies</li>
- <li><strong>Data Model Mapping</strong>: Design colocation
strategies for your entities</li>
- <li><strong>Integration Points</strong>: Plan API migration
from current multi-system architecture</li>
- <li><strong>Performance Benchmarking</strong>: Compare
memory-first vs. disk-first performance for your workloads</li>
- </ul>
- <h4>Implementation Approach</h4>
- <ul>
- <li><strong>Start with New Features</strong>: Use Ignite 3
for new development requiring low latency</li>
- <li><strong>Gradual Migration</strong>: Move
performance-critical workloads first</li>
- <li><strong>Schema Design</strong>: Leverage colocation for
optimal data locality</li>
- <li><strong>Operational Integration</strong>: Integrate
monitoring and deployment pipelines</li>
- </ul>
- <h3>Technical Considerations</h3>
- <h4>Schema Design Best Practices</h4>
- <ul>
- <li>Use <code>colocateBy</code> annotations to ensure
related data stays together</li>
- <li>Design partition keys to distribute load evenly across
nodes</li>
- <li>Consider query patterns when defining indexes and
colocation strategies</li>
- <li>Plan for schema evolution with backward-compatible
changes</li>
- </ul>
- <h4>Performance Optimization</h4>
- <ul>
- <li>Size memory regions appropriately for your working
set</li>
- <li>Use collocated compute jobs to minimize data
movement</li>
- <li>Leverage appropriate storage engines for different
workload patterns</li>
- <li>Monitor memory usage and adjust disk ratios as
needed</li>
- </ul>
- <h4>Operational Requirements</h4>
- <ul>
- <li>Plan for Raft consensus network requirements
(low-latency, reliable connectivity)</li>
- <li>Design backup and recovery procedures for persistent
storage engines</li>
- <li>Implement monitoring for memory usage, query
performance, and compute job execution</li>
- <li>Establish capacity planning procedures for memory-first
architecture</li>
- </ul>
- <h3>Summary</h3>
- <p>
- Apache Ignite 3 represents a schema-driven distributed
computing platform that consolidates transaction processing, analytics, and
compute workloads into a single memory-first architecture. Key architectural
elements
- include:
- </p>
- <ul>
- <li><strong>Schema-driven design</strong>: Single schema
definition drives data placement, query optimization, and compute
colocation</li>
- <li><strong>Memory-first storage</strong>: Multiple storage
engines with microsecond latency characteristics</li>
- <li><strong>Collocated processing</strong>: Compute-to-data
architecture that eliminates data movement overhead</li>
- <li><strong>Unified APIs</strong>: Multiple access patterns
(RecordView, KeyValueView, SQL, Compute) for the same schema</li>
- <li><strong>ACID consistency</strong>: Raft consensus and
MVCC transactions across all operations</li>
- <li><strong>Built-in streaming</strong>: Flow control and
backpressure mechanisms for high-velocity data ingestion</li>
- </ul>
+ <p>The result is code that your IDE can help you navigate and
that future you can understand without archaeology.</p>
<p>
- The platform addresses scenarios where traditional
multi-system architectures create operational complexity and performance
bottlenecks through data movement between separate databases, compute clusters,
and analytics
- systems.
+ For more details, see the <a
href="https://ignite.apache.org/docs/ignite3/latest/developers-guide/java-to-tables">Creating
Tables from Java Classes</a> and
+ <a
href="https://ignite.apache.org/docs/ignite3/latest/developers-guide/table-api">Table
API</a> documentation.
</p>
- <p>Explore the <a
href="https://ignite.apache.org/docs/ignite3/latest/">Ignite 3
documentation</a> for detailed implementation guides and API references.</p>
</div>
</article>
<section class="blog__footer">
<ul class="pagination post_page">
- <li><a href="/blog/apache">apache</a></li>
- <li><a href="/blog/ignite">ignite</a></li>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
</ul>
</section>
</section>
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/schema-design-for-distributed-systems-ai3.html
b/blog/schema-design-for-distributed-systems-ai3.html
index a27eb542a2..4b85659eed 100644
--- a/blog/schema-design-for-distributed-systems-ai3.html
+++ b/blog/schema-design-for-distributed-systems-ai3.html
@@ -614,6 +614,9 @@ albums.upsert(null, abbeyRoad); // Automatically colocated
with artist
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/whats-new-in-apache-ignite-3-0.html
b/blog/whats-new-in-apache-ignite-3-0.html
index 4762e18721..700c508e84 100644
--- a/blog/whats-new-in-apache-ignite-3-0.html
+++ b/blog/whats-new-in-apache-ignite-3-0.html
@@ -441,6 +441,9 @@
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>
diff --git a/blog/whats-new-in-apache-ignite-3-1.html
b/blog/whats-new-in-apache-ignite-3-1.html
index c7a4116a4f..e33a30eff9 100644
--- a/blog/whats-new-in-apache-ignite-3-1.html
+++ b/blog/whats-new-in-apache-ignite-3-1.html
@@ -588,6 +588,9 @@ node config update ignite.network.nodeFinder.type=MULTICAST
</main>
<aside class="blog__sidebar">
<ul>
+ <li><a href="/blog/technical">technical</a></li>
+ <li><a href="/blog/ignite3">ignite3</a></li>
+ <li><a href="/blog/java">java</a></li>
<li><a href="/blog/apache">apache</a></li>
<li><a href="/blog/ignite">ignite</a></li>
<li><a href="/blog/release">release</a></li>