<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Apache Fluss™ Blog</title>
        <link>https://fluss.apache.org/blog/</link>
        <description>Apache Fluss™ Blog</description>
        <lastBuildDate>Thu, 06 Aug 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Apache Fluss Graduates to a Top Level Project]]></title>
            <link>https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/</link>
            <guid>https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/</guid>
            <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Apache Fluss Graduates to a Top Level Project]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Apache Fluss Graduates to a Top Level Project" src="https://fluss.apache.org/assets/images/banner-fdd438e281137099b1f4fc9bf5e62543.png" width="3840" height="1440" class="img_ev3q"></p>
<p>As <a href="https://news.apache.org/foundation/entry/the-apache-software-foundation-announces-new-top-level-projects-5" target="_blank" rel="noopener noreferrer" class="">officially announced by the Apache Software Foundation</a>, we are thrilled to share that <strong>Apache Fluss</strong> has graduated from the Apache Incubator to become a <strong>Top Level Project (TLP)</strong>.</p>
<p>The project's <a href="https://lists.apache.org/thread/kltvfrklyoqm9dj6dgwdzf82sm097427" target="_blank" rel="noopener noreferrer" class="">graduation proposal</a> received unanimous approval from the Apache Incubator Project Management Committee (IPMC) and was subsequently approved by the ASF Board of Directors. This milestone not only marks a new stage in Fluss's journey, but also further advances the convergence of streaming storage, the real-time Lakehouse, and AI data infrastructure, opening a new chapter for real-time data infrastructure.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-we-got-here">How We Got Here<a href="https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/#how-we-got-here" class="hash-link" aria-label="Direct link to How We Got Here" title="Direct link to How We Got Here" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="The Apache Fluss journey from launch to top-level project" src="https://fluss.apache.org/assets/images/apache-fluss-journey-en-a025e58718bf92be8e9f69302aeb51f0.png" width="3812" height="1360" class="img_ev3q"></p>
<p>Fluss was initiated by the Flink team at Alibaba Cloud in July 2023 to address long-standing challenges in streaming storage for analytical workloads, including unified streaming and batch storage, Flink state management, and complex data pipelines. Its goal was to build a unified streaming storage system for real-time analytics in the Lakehouse era. The name “Fluss” comes from <strong>Flink Unified Streaming Storage</strong> and also means “river” in German, reflecting the vision of data flowing continuously like a river before eventually joining the open Lakehouse.</p>
<p>After more than a year of development and large-scale production use within Alibaba, Fluss was officially <a href="https://fluss.apache.org/blog/fluss-open-source/" target="_blank" rel="noopener noreferrer" class="">open-sourced at Flink Forward Asia 2024</a> in Shanghai in November 2024. In June 2025, <a href="https://fluss.apache.org/blog/fluss-joins-asf/" target="_blank" rel="noopener noreferrer" class="">Fluss entered the Apache Incubator</a>, evolving from a technology initiative originating at Alibaba into an open source project built by developers around the world.</p>
<p>During incubation, both the contributor community and interest in the project continued to grow. Today, the community includes <strong>157 contributors</strong>, and the project has earned <strong>2,000+ stars on GitHub</strong>, merged <strong>1700+ pull requests</strong>, bringing together developers from different countries and organizations in an active open source community.</p>
<p>At the same time, Fluss has been deployed in production at <strong>Alibaba, Xiaohongshu (RedNote), Fresha, JD.com, Ant Group, iQIYI, and other companies</strong>. It is used across log collection and analytics, real-time data warehousing, search and recommendation, indexing pipelines, and real-time feature serving. These deployments help organizations reduce cross-system data copies, lower real-time data processing and storage costs, and improve Lakehouse data freshness and analytical efficiency.</p>
<p>"When we started Fluss, I believed strongly in the problem we were solving, but the project has grown faster and reached further than I expected," said <strong>Jark Wu, PMC Chair of Apache Fluss</strong>. "The energy of the community and the growing number of companies running Fluss in production have been especially encouraging. Graduation is a new beginning, and I am confident Fluss will become a foundational real-time data layer for lakehouse architectures and AI applications that depend on fresh, continuously updated data."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-lakehouse-to-lakestream-unify-stream-and-lakehouse-for-the-agentic-era">From Lakehouse to Lakestream: Unify Stream and Lakehouse for the Agentic Era<a href="https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/#from-lakehouse-to-lakestream-unify-stream-and-lakehouse-for-the-agentic-era" class="hash-link" aria-label="Direct link to From Lakehouse to Lakestream: Unify Stream and Lakehouse for the Agentic Era" title="Direct link to From Lakehouse to Lakestream: Unify Stream and Lakehouse for the Agentic Era" translate="no">​</a></h2>
<p>In the Agentic era, AI is evolving from chatbots that answer questions into agents capable of making autonomous decisions, using tools, and executing tasks. Data is no longer just an input to analytics; it is the real-time context agents rely on to reason and act. That context must span the full historical record while accurately reflecting the latest state of the business. Traditional Lakehouse architectures excel at storing and governing massive volumes of historical data, but struggle to continuously meet data-freshness requirements measured in seconds or even milliseconds.</p>
<p>As an open source pioneer of the Lakestream architecture, Apache Fluss adds a Lakehouse-native, real-time streaming storage layer on top of the data lake. It unifies continuously updated, real-time data with long-term history, giving AI agents fresh and complete context from the past through the present and enabling production-grade, real-time decisions and actions.</p>
<p><img decoding="async" loading="lazy" alt="Apache Fluss streaming storage architecture" src="https://fluss.apache.org/assets/images/lakestream-arch-c92d432b6f0105b410e333d88fe312c2.jpg" width="3240" height="1430" class="img_ev3q"></p>
<p>Apache Fluss brings the following core capabilities to the traditional Lakehouse, creating a unified Lakestream foundation that makes both historical and real-time data readily available:</p>
<ul>
<li class=""><strong>Stream &amp; Lakehouse Unification:</strong> Long-term historical data resides in open data lake formats such as Apache Paimon, Apache Iceberg, Apache Hudi, and Lance, while Fluss serves the latest data in real time. With Union Read, agents can access everything from historical data to the latest state through a unified table view, avoiding fragmented context.</li>
<li class=""><strong>Columnar Streaming Storage:</strong> Built on the Apache Arrow columnar format, Fluss supports server-side column pruning, predicate pushdown, and partition pruning, reducing data reads and transfers for efficient streaming and real-time analytics.</li>
<li class=""><strong>Real-time Updates and Point Queries:</strong> Primary-key tables natively support streaming updates, partial updates, changelogs, key-value lookups, and Delta Join, allowing the same real-time data to serve both stream processing and point queries.</li>
<li class=""><strong>Real-time Context for Agents:</strong> Fluss unifies continuously updated real-time state with long-term historical data, giving AI agents fresh, complete, low-latency, and trustworthy context for real-time decisions and actions.</li>
<li class=""><strong>Open Ecosystem with Multi-Language Access:</strong> Fluss works with compute engines such as Apache Flink, Apache Spark, and StarRocks and provides Java, Rust, Python, and C++ clients, making it easy for real-time analytics and AI applications to connect.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="growing-community-and-adoption">Growing Community and Adoption<a href="https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/#growing-community-and-adoption" class="hash-link" aria-label="Direct link to Growing Community and Adoption" title="Direct link to Growing Community and Adoption" translate="no">​</a></h2>
<p>In the year since joining the Apache Software Foundation, Fluss has built an active global contributor community and received broad interest and support from leading companies and open source ecosystem partners.</p>
<p><strong>Feng Wang, Head of Open Data Platform at Alibaba Cloud:</strong> “Apache Fluss’s graduation reflects a mature community shaped by open, collaborative governance at the ASF. Deployed and proven at scale in Alibaba’s core e-commerce production workloads, Fluss’s Lakestream architecture unifies streams with Lakehouse data, making the Lakehouse real-time while reducing duplication and pipeline complexity. This led us to open-source and donate Fluss to the ASF. I look forward to Fluss becoming the open data foundation for the real-time Lakehouse, advancing analytics and AI across the open data ecosystem.”</p>
<p><strong>Yu Li, ASF Member and Mentor in the Incubator Program:</strong> “Congratulations to the Apache Fluss community on reaching this important milestone and becoming an ASF Top-Level Project. Serving as the project’s champion, I have been delighted to see Fluss grow into an open and collaborative community guided by the Apache Way. During incubation, the community delivered substantial releases, advanced streaming storage for real-time analytics, and steadily welcomed new contributors. I look forward to seeing Fluss build on this strong foundation in its next chapter.”</p>
<p><strong>Igor Kersic, CTO at Ververica:</strong> “I'm truly excited to see Fluss graduate and become a top-level ASF project. It marks the beginning of next evolution of streaming landscape, but it's real power is in unlocking orders of magnitude more business value in streaming analytics. The design choices are just a perfect mix for one stop native streaming storage combined in just one project. My expectations is for it be de-facto foundation streaming storage go-to standard in real-time AI era. I see quicker than expected enterprise adoption due to it's power to collapse costs of complex lakehouse landscapes, thus enterprise maturity is important.”</p>
<p><strong>Jiangjie Qin, Principal Staff Software Engineer at LinkedIn:</strong> “Congratulations to Fluss on its successful graduation! As a unified storage layer that brings together streaming, lakehouse, and key-value store capabilities, Fluss has demonstrated unique and significant value in the data infrastructure ecosystem. Over the past year in the Apache Incubator, the Fluss community has grown rapidly and healthily, and the project has demonstrated remarkable momentum. Its graduation to an Apache Top-Level Project is a well-deserved milestone. Wishing Fluss continued success as it grows into another flagship project in the data infrastructure ecosystem.”</p>
<p><strong>Jingsong Li, Apache Paimon PMC Chair:</strong> “Apache Paimon's original vision included a serving and acceleration layer that would keep data moving, but for a long time that part of the vision remained unrealized. With the launch and continued development of Apache Fluss, this acceleration layer now brings sub-second data freshness to the Lakehouse, making it truly real-time and unified. Congratulations to Fluss on its graduation!”</p>
<p><strong>Han Liu, Head of Fluss and Kafka at Xiaohongshu:</strong> “As an early adopter and contributor to Fluss, Xiaohongshu is delighted to have witnessed and participated in the community's journey from incubation to graduation. Fluss's columnar reads and writes, hot and cold data tiering, and Stream-Lakehouse unification have delivered significant value in core workloads such as Xiaohongshu's indexing data pipelines, substantially reducing real-time pipeline costs while improving data build and online consumption efficiency. We look forward to the Apache Fluss community continuing to thrive, creating value in more enterprise real-time data scenarios, and establishing Fluss as an important open source project for the next generation of real-time data infrastructure.”</p>
<p><strong>Emiliano Mancuso, VP of Architecture and Data Engineering at Fresha:</strong> "At Fresha, Apache Fluss is deployed in production as part of our real-time data platform. Its combination of low-latency streaming access and open Lakehouse integration helps us simplify data movement and build fresher analytical and operational data products, while capabilities such as Delta Join make our Apache Flink pipelines simpler to operate. We are proud to contribute directly to the project and look forward to helping shape its next chapter as an Apache Top-Level Project."</p>
<p><strong>Peibin Wang, Senior Big Data Expert at Taobao Instant Commerce:</strong> “Fluss has redefined the paradigm of real-time data infrastructure. Innovations such as Delta Join and Stream-Lakehouse unification mean that streaming storage is no longer simply an alternative to message queues, but an indispensable real-time data layer in the Lakehouse architecture. As an early and deeply involved adopter of Fluss, Taobao Instant Commerce is proud to have used Fluss to systematically address state growth and pipeline complexity at the scale of hundreds of billions of events, while reliably supporting core real-time decision-making pipelines during major campaigns such as the 618 shopping festival. We look forward to Fluss unlocking even greater value in the AI × Data era!”</p>
<p><strong>Fei Han, Head of the Real-Time Data Platform at JD Retail:</strong> “By starting with streaming storage, Fluss opens up new possibilities for data infrastructure in the era of real-time analytics and AI. JD.com is combining its broad range of business scenarios with active participation in Fluss's technical development and community building, helping its capabilities take root in enterprise environments. Becoming a Top-Level Project is a new beginning. We look forward to Fluss continuing to embrace open innovation, growing its global developer ecosystem, and helping more organizations unlock the value of their data!”</p>
<p><strong>Xin Wang, ASF Member and Head of Real-Time Intelligence at Ant Group:</strong> “Fluss has steadily evolved around streaming storage into an important open source project connecting stream processing and the Lakehouse ecosystem, earning increasingly broad industry adoption. Ant Group is also actively exploring and deploying Fluss. We look forward to Apache Fluss continuing to foster an open, transparent, and collaborative community guided by the Apache Way and becoming a stronger, more trusted foundation for real-time data and AI.”</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next-for-fluss">What’s Next for Fluss?<a href="https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/#whats-next-for-fluss" class="hash-link" aria-label="Direct link to What’s Next for Fluss?" title="Direct link to What’s Next for Fluss?" translate="no">​</a></h2>
<p>As an Apache Top Level Project, Fluss will continue to advance Lakehouse-native streaming storage: providing stream processing with a more efficient and reusable storage foundation, bringing true real-time capabilities to the Lakehouse, and offering an open data foundation for AI applications that depend on real-time features, dynamic state, and continuous context.</p>
<p>Thank you to every contributor who has submitted code, improved documentation, participated in discussions, reported issues, shared their experience, or helped newcomers. Thank you as well to all the users and partners who chose Fluss for their production environments and helped move the project forward by bringing real-world challenges to the community. We would also like to extend our special thanks to the Apache Fluss incubation mentors: Yu Li (Champion), Zili Chen, Jingsong Li, Jiangjie Qin, and Jean-Baptiste Onofré, whose continued guidance and support throughout incubation helped the community better embrace the Apache Way and successfully complete the journey from incubation to graduation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="join-the-apache-fluss-community">Join the Apache Fluss Community<a href="https://fluss.apache.org/blog/apache-fluss-graduates-to-top-level-project/#join-the-apache-fluss-community" class="hash-link" aria-label="Direct link to Join the Apache Fluss Community" title="Direct link to Join the Apache Fluss Community" translate="no">​</a></h2>
<p>Whether you want to use Fluss for real-time analytics and AI or learn more and contribute to the project, now is a great time to join the community:</p>
<ul>
<li class="">Website: <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/</a></li>
<li class="">GitHub: <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">https://github.com/apache/fluss</a> (⭐ stars and contributions are welcome!)</li>
<li class="">Contribution guide: <a href="https://fluss.apache.org/community/how-to-contribute/overview/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/community/how-to-contribute/overview/</a></li>
</ul>
<p>A new journey has begun. Let this open river of data keep flowing. 🌊</p>]]></content:encoded>
            <category>Apache Fluss</category>
            <category>lakestream</category>
            <category>Real-time Analytics</category>
        </item>
        <item>
            <title><![CDATA[Tiering Service Deep Dive Part 3: In Production]]></title>
            <link>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/</link>
            <guid>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/</guid>
            <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-17c29e54cf3b1975445992b335cce286.png" width="1514" height="498" class="img_ev3q"></p>
<p><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/">Part 1</a> and <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a> built up everything you need to know about how tiering behaves: the mental model, the dials, the queue dynamics, the scale-out story. This part is about what to do with all of that. What breaks at runtime, and which of those failures self-heal versus need operator action. The design mistakes that look fine on day one but come back to bite you on day two. And the operator's daily view: which five numbers tell you whether tiering is healthy on a Tuesday afternoon, where each one comes from, and why two of them can only come from your Flink-side dashboards.</p>
<p><strong>Tiering Service Deep Dive, 3-parts:</strong></p>
<ul>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/">Part 1 - The Mental Model</a>:</strong> how one tiering round actually works, from timer fire to lake commit.</li>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2 - Tuning</a>:</strong> per-table dials, multi-table dynamics, and scaling out.</li>
<li class=""><strong>Part 3 - In Production:</strong> failure modes, design pitfalls, and the dashboard that tells you everything is fine.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="failure-modes-what-self-heals-and-what-doesnt">Failure Modes: What Self-Heals And What Doesn't<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#failure-modes-what-self-heals-and-what-doesnt" class="hash-link" aria-label="Direct link to Failure Modes: What Self-Heals And What Doesn't" title="Direct link to Failure Modes: What Self-Heals And What Doesn't" translate="no">​</a></h2>
<p>The tiering service has a small number of well-defined failure modes, and the design absorbs most of them gracefully. The point of this section isn't to enumerate every edge case. It's to give you a mental model for what self-heals and what doesn't, so when something goes wrong you know whether to wait, restart, or escalate.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig1-f24aad3f6cf6272bfd2f6c0e6f86f158.png" width="1178" height="593" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-tiering-job-dies-mid-round">The Tiering Job Dies Mid-Round<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-tiering-job-dies-mid-round" class="hash-link" aria-label="Direct link to The Tiering Job Dies Mid-Round" title="Direct link to The Tiering Job Dies Mid-Round" translate="no">​</a></h3>
<p>This is the most common failure and the easiest to reason about. Job A is halfway through tiering <code>orders</code>: readers have written some Parquet files, but the writer hasn't completed the lake commit yet. The Flink TaskManager crashes, or the JobManager loses leadership.</p>
<p>From Fluss's perspective, heartbeats stop arriving. Roughly two minutes later (the 2-minute liveness threshold from <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a>, plus up to one 15-second checker tick, so detection lands at about 2 to 2.25 minutes), the coordinator declares the job dead, fences its in-flight assignment, and returns <code>orders</code> to the back of the pending queue, where it waits behind whatever else is already queued. The next tiering job to heartbeat picks up the table at the front of the queue, which may or may not be the fenced table, depending on what else was waiting. The interrupted writer's Parquet files in the lake are orphaned; they're committed to neither Fluss nor the lake catalog, so no reader will ever see them. They sit until an external lake-side garbage collector cleans them up.</p>
<p>The new job starts the round from the last committed lake offset, not from where the dead job left off. <strong>That's the cost:</strong> all the work since the last commit is redone. <strong>The benefit:</strong> you never get a half-committed table.</p>
<p>Three invariants hold throughout this recovery:</p>
<ul>
<li class=""><strong>The lake never sees a partial commit.</strong> Readers can't observe a half-written snapshot.</li>
<li class=""><strong>The redo cannot be double-counted into Fluss.</strong> The epoch fences the heartbeat path: the dead attempt's finish, fail, and heartbeat messages carry a stale epoch and are rejected by the coordinator (<code>validateTieringServiceRequest</code>). This fences the bookkeeping, not the lake write itself; the lake commit and the offset-advance RPC are not epoch-gated. Exactly-once into the lake is instead protected by per-snapshot atomicity plus a reconciliation check on the next round (<code>getMissingLakeSnapshot</code>), which detects and repairs the case where the lake advanced but Fluss didn't.</li>
<li class=""><strong>The orphaned Parquet files are lake-side garbage.</strong> They rely on an external lake-side garbage collector to clean them up; as of the Fluss version this series tracks, there is no Fluss-side reaper for orphaned lake files.</li>
</ul>
<p>These invariants assume the dead job stopped before its lake commit. A job that is fenced but still alive (a JobManager GC pause, or a JobManager-to-coordinator partition that doesn't affect the committer) can still complete its lake commit after the table was reassigned, because that write carries no fencing token derived from the 2-minute lease. In that narrow case, exactly-once depends on the lake committer's own idempotency.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig2-7d90672adbb338a3df3758b414f21109.png" width="1149" height="598" class="img_ev3q"></p>
<p><strong>Call to action:</strong> If it's Kubernetes or any restart-on-failure scheduler there is no required action. The job comes back, registers, and pulls from the queue normally. The 2-minute fencing window is your only real cost.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-reader-fails-inside-a-healthy-job">A Reader Fails Inside A Healthy Job<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#a-reader-fails-inside-a-healthy-job" class="hash-link" aria-label="Direct link to A Reader Fails Inside A Healthy Job" title="Direct link to A Reader Fails Inside A Healthy Job" translate="no">​</a></h3>
<p>A single TaskManager dies while the JobManager keeps running. This isn't a localized restart: the tiering job runs with a full-restart failover strategy, so one failed task restarts the entire job, readers, enumerator, and committer together. The in-flight round's partial work, which lives only in memory, is thrown away.</p>
<p>After the restart, the enumerator re-registers with the coordinator and asks for fresh work; it does not resume the table it was tiering. That table stays in <code>Tiering</code> on the coordinator until the same ~2-minute liveness timeout from the job-death case fences it (<code>Failed</code> to <code>Pending</code>, epoch bumped) and re-queues it. So a reader failure collapses into the same recovery path as a dead job: full restart, then the interrupted table waits out the ~2-minute fence before it is picked up again. What the surviving JobManager buys you is a faster restart of the job process, not faster recovery of that one table. No progress is lost and nothing double-commits, because the durable record of completed rounds lives in Fluss metadata (committed lake offsets).</p>
<p><strong>Call to action:</strong> nothing; it self-heals. Watch the job's restart count and the Fluss-side <code>tierLag</code> and queue depth: a healthy recovery is a brief restart followed by <code>tierLag</code> settling back down.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fluss-coordinator-restarts">The Fluss Coordinator Restarts<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-fluss-coordinator-restarts" class="hash-link" aria-label="Direct link to The Fluss Coordinator Restarts" title="Direct link to The Fluss Coordinator Restarts" translate="no">​</a></h3>
<p>The coordinator holds the queue and the per-table assignment state in memory. When it restarts, the queue empties and the assignments are dropped. The durable truth lives in Fluss's persistent metadata (ZooKeeper for table-level configuration, with committed lake offsets recorded through the lake-snapshot commit path). On restart, <code>initWithLakeTables</code> rebuilds the in-memory state from cluster metadata and, importantly, resets every table's epoch counter to 0 as part of <code>registerLakeTable</code>.</p>
<p>Right after restart, any job that was mid-round holds a non-zero epoch, so its next heartbeat is fenced with <code>FencedTieringEpochException</code> (returned as a per-table error in the heartbeat response, which the enumerator treats as "throw away in-flight state and ask for fresh work"). One caveat worth knowing: this epoch lives only in the coordinator's memory and restarts from 0, so it is not monotonic across restarts. Fencing here relies on the stale job being detected and reassigned before the per-table epoch happens to climb back to the same value: in practice fine, but it's a lease-style guarantee, not an absolute one.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig3-7230b41fad053b096c458e16f67c7bf2.png" width="1195" height="453" class="img_ev3q"></p>
<p>Practically, any tiering job that was mid-round when the coordinator restarted loses its assignment, and the next freshness firing re-enqueues the affected tables. That can mean a freshness lag of up to the per-table target on the worst-affected table, but no data loss: the writes that were in flight just hadn't been committed to the lake yet, and they'll be re-tiered on the next round.</p>
<p><strong>Call to action:</strong> nothing on the tiering side. Coordinator availability is the broader story; in HA mode another coordinator takes over. The tiering jobs reconnect to the new leader through the same metadata path as everything else.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-asymmetric-failure-two-jobs-one-healthy">The Asymmetric Failure: Two Jobs, One Healthy<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-asymmetric-failure-two-jobs-one-healthy" class="hash-link" aria-label="Direct link to The Asymmetric Failure: Two Jobs, One Healthy" title="Direct link to The Asymmetric Failure: Two Jobs, One Healthy" translate="no">​</a></h3>
<p>You're running two tiering jobs (the scale-out pattern from <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a>). Job A is healthy; Job B is in a crash loop because of some error. What happens?</p>
<p>From the coordinator's perspective, Job B's heartbeats stop arriving. After roughly two minutes (the same 2-minute threshold plus up to one 15-second checker tick), the coordinator fences whatever Job B was holding. Job A picks up the slack: everything Job B would have done now flows through Job A. Effective throughput drops from two-job back to one-job behavior, with the queue-starvation pattern returning if your workload has the mixed-size shape.</p>
<p>This is the silent failure mode worth watching for. If you scaled out specifically to unblock a small table from a large one, and one of your jobs goes dark, the small table's freshness gets worse without anything obviously broken on the Fluss side. The challenge is that Fluss itself doesn't track tiering-job identity (as we saw when scaling out), so the coordinator cannot tell you <strong>"I expected 2 jobs and only see 1"</strong>. The monitoring signal has to come from your Flink-side dashboards (per-JobManager liveness, checkpoint health) combined with the Fluss-side queue and lag metrics from the operations section. If your Flink dashboard shows two healthy JobManagers but <code>pendingTablesCount</code> is climbing and <code>tierLag</code> on small tables is degrading, you have evidence that one of the jobs has effectively gone dark even if its process is alive.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig4-039060397c25edc27689fdaf91037472.png" width="1205" height="650" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pattern-what-self-heals-and-what-doesnt">The Pattern: What Self-Heals And What Doesn't<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-pattern-what-self-heals-and-what-doesnt" class="hash-link" aria-label="Direct link to The Pattern: What Self-Heals And What Doesn't" title="Direct link to The Pattern: What Self-Heals And What Doesn't" translate="no">​</a></h3>
<p>Anything that's transient and respects the 2-minute liveness window self-heals: job crashes, network blips, reader restarts, coordinator failover. Anything structural (a bad catalog credential, a missing Iceberg table, a misconfigured lake bucket) will crash-loop your tiering job and not affect Fluss's hot tier at all. The hot tier keeps accepting writes regardless of lake health. <strong>Your cluster doesn't break when tiering breaks; it just stops aging out.</strong> That's the design's most important safety property, and also the reason monitoring the tiering side separately from the Fluss side matters.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-mistakes-what-to-avoid-in-production">Common Mistakes: What To Avoid In Production<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#common-mistakes-what-to-avoid-in-production" class="hash-link" aria-label="Direct link to Common Mistakes: What To Avoid In Production" title="Direct link to Common Mistakes: What To Avoid In Production" translate="no">​</a></h2>
<p>This section is the failure-modes section's sibling. Not <strong>"what breaks when the system is healthy"</strong> but <strong>"what looks fine but is actually about to break"</strong>. None of these will crash your cluster. All of them produce slow, expensive, or surprising behavior down the road. Worth catching at design time rather than three months in.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mistake-1-sizing-buckets-for-the-wrong-dimension">Mistake 1: Sizing Buckets For The Wrong Dimension<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#mistake-1-sizing-buckets-for-the-wrong-dimension" class="hash-link" aria-label="Direct link to Mistake 1: Sizing Buckets For The Wrong Dimension" title="Direct link to Mistake 1: Sizing Buckets For The Wrong Dimension" translate="no">​</a></h3>
<p>The most common one. You pick bucket count based on ingest throughput: <strong>"we have 200K writes/sec, give it 32 buckets"</strong>. That sizes hot-path throughput just fine. What you didn't size for is the tiering round, which is per-bucket parallelism on a single Flink job. With 32 buckets, your tiering job needs 32 reader slots to run in parallel, otherwise the round serializes. And with 32 readers, the Flink-side write to the lake fans out to 32 concurrent writers, which means 32 concurrent S3 PUTs per round.</p>
<p>And bucket count isn't something you can walk back: in current Fluss there's no online ALTER path to change a table's bucket count at all. It's part of the table's distribution, fixed at create time, not an alterable <code>table.*</code> property, so for both log and PK tables you're committing to the count you pick at creation. PK tables make this constraint conceptually sharper still: the hash function that assigns rows to buckets depends on the count, so even if a future release exposed a reshard, changing the count would invalidate every existing key's placement.</p>
<p><strong>The practical takeaway:</strong> pick a bucket count at table creation time that reflects your steady-state volume, not your peak burst. Eight buckets handle a lot of throughput when the tiering round is healthy, and you'll thank yourself later when the round time stays predictable. For most tables, fewer buckets is better, until you have measured evidence that ingest is bucket-bound.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mistake-2-one-tiering-job-for-everything">Mistake 2: One Tiering Job For Everything<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#mistake-2-one-tiering-job-for-everything" class="hash-link" aria-label="Direct link to Mistake 2: One Tiering Job For Everything" title="Direct link to Mistake 2: One Tiering Job For Everything" translate="no">​</a></h3>
<p>Covered at length in the multi-table and scale-out sections of <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a>, but worth repeating as a discrete anti-pattern: deploying one tiering job and pointing all 50 of your tables at it. It works, for a while. Then one table grows, its round duration extends, and every other table's effective freshness silently degrades because the queue gets longer. The fix when it bites is to scale out to multiple jobs, but the better play is to think about workload mix at the design stage. If you know you have a heavy table and a hot table, plan for two jobs from day one. The cost is two Flink deployments instead of one, which is marginal on most clusters. The benefit is that one bad week of growth on the heavy table doesn't take out your latency SLO on the hot one.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mistake-3-confusing-freshness-target-with-freshness-guarantee">Mistake 3: Confusing Freshness Target With Freshness Guarantee<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#mistake-3-confusing-freshness-target-with-freshness-guarantee" class="hash-link" aria-label="Direct link to Mistake 3: Confusing Freshness Target With Freshness Guarantee" title="Direct link to Mistake 3: Confusing Freshness Target With Freshness Guarantee" translate="no">​</a></h3>
<p>The <code>table.datalake.freshness</code> config is the <strong>"the maximum amount of time that the datalake table's content should lag behind updates"</strong>, but it's also a <strong>"target freshness"</strong>. Those aren't the same thing. The coordinator schedules the next round as a re-enqueue delay (<code>freshness − (now − last_tiered)</code>), so freshness controls how often a table becomes eligible to tier again, not a hard ceiling on how stale the lake can get. <strong>Under queue contention, the <strong>"maximum"</strong> is aspirational, not enforced.</strong> If the queue ahead of your table is empty and the round is fast, you'll get something close to 1 minute. If the queue is full of bigger tables, you'll get whatever you get; the multi-table walkthrough in <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a> goes through this case in detail.</p>
<p>This can look confusing when users, set up dashboards that read directly from the lake and expect <strong>"1-minute freshness"</strong> to mean <strong>"data is never more than 1 minute behind real time"</strong>. It can mean that under light load.</p>
<p>Under realistic load it means <strong>"this table is allowed to be re-tiered as often as every minute, queue permitting"</strong>. If you genuinely need bounded freshness on a lake-side read, your options are: read from Fluss directly, or scale out tiering jobs to make sure your table never queues.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mistake-4-enabling-lake-tiering-on-a-tiny-table">Mistake 4: Enabling Lake Tiering On A Tiny Table<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#mistake-4-enabling-lake-tiering-on-a-tiny-table" class="hash-link" aria-label="Direct link to Mistake 4: Enabling Lake Tiering On A Tiny Table" title="Direct link to Mistake 4: Enabling Lake Tiering On A Tiny Table" translate="no">​</a></h3>
<p>This one isn't so much a mistake as a waste. You have a <code>dim_country</code> table with 200 rows, updated once a month. Someone enables <code>table.datalake.enabled = true</code> on it because <strong>"we tier everything"</strong>. Now your tiering job is scheduling rounds on this 200-row table at the configured freshness cadence, each round writing a Parquet file that's mostly metadata overhead, each commit allocating a snapshot in the lake catalog. The lake-side Parquet directory fills up with thousands of tiny files. Compaction will eventually clean them up, but in the meantime your S3 LIST operations are paying for them and your catalog has thousands of unnecessary snapshot entries.</p>
<p>For small, slow-changing reference tables, the correct play is usually to keep them in Fluss only (no lake tiering) and let lake-side queries do a join through the union-read path. Or, if they really need to be in the lake, materialize them once via a batch job and refresh on a much longer cadence. The tiering service is the wrong tool for low-velocity data.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mistake-5-ignoring-the-cross-table-effects-of-compaction">Mistake 5: Ignoring The Cross-Table Effects Of Compaction<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#mistake-5-ignoring-the-cross-table-effects-of-compaction" class="hash-link" aria-label="Direct link to Mistake 5: Ignoring The Cross-Table Effects Of Compaction" title="Direct link to Mistake 5: Ignoring The Cross-Table Effects Of Compaction" translate="no">​</a></h3>
<p>The Fluss-specific hook is simple: because tiering parallelism is per-bucket, each round writes at least one Parquet file per bucket. So a 16-bucket table tiering at 1-minute freshness produces on the order of 16 small files a minute, roughly 960 an hour and 23,040 a day, per table. That count compounds fast.</p>
<p>Everything past that point is standard lakehouse behavior, not a Fluss bug: small files degrade reads and bloat catalog metadata, and both Iceberg and Paimon lean on compaction to claw it back, which isn't free and isn't always automatic. The takeaway for a tiering deployment is just to size and schedule compaction deliberately rather than discover it reactively.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pattern-a-production-readiness-checklist-for-tiering">The Pattern: A Production-Readiness Checklist For Tiering<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-pattern-a-production-readiness-checklist-for-tiering" class="hash-link" aria-label="Direct link to The Pattern: A Production-Readiness Checklist For Tiering" title="Direct link to The Pattern: A Production-Readiness Checklist For Tiering" translate="no">​</a></h3>
<p>Before you call a tiering deployment production-ready, walk through:</p>
<ol>
<li class="">Bucket counts are sized for round duration, not just ingest throughput, and you've explicitly defined that at table creation time.</li>
<li class="">Tables of similar round duration are grouped onto the same tiering job; tables of dissimilar round duration are split across jobs.</li>
<li class="">Freshness targets reflect what you actually need at the lake-tier read path, not <strong>"we just tier everything"</strong>.</li>
<li class="">Compaction is configured on the lake side and you have a dashboard showing file count and snapshot count growth.</li>
<li class="">You have Flink-side liveness monitoring on every tiering job you deployed (see the operations section on why Fluss itself can't tell you a job has gone dark).</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-to-monitor-the-operators-view">What To Monitor: The Operator's View<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#what-to-monitor-the-operators-view" class="hash-link" aria-label="Direct link to What To Monitor: The Operator's View" title="Direct link to What To Monitor: The Operator's View" translate="no">​</a></h2>
<p>The previous sections covered design-time decisions and failure-mode reasoning. This one is about the running system: what to look at on a Tuesday afternoon when you're trying to figure out if tiering is healthy, slow, or quietly broken. The tiering service exposes its state through a combination of Flink metrics, Fluss coordinator metrics, and the lake catalog itself.
It's worth knowing which metrics matter.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-fluss-coordinator-actually-exposes">What The Fluss Coordinator Actually Exposes<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#what-the-fluss-coordinator-actually-exposes" class="hash-link" aria-label="Direct link to What The Fluss Coordinator Actually Exposes" title="Direct link to What The Fluss Coordinator Actually Exposes" translate="no">​</a></h3>
<p>A short list of the metrics actually registered in <code>LakeTableTieringManager</code>, so you know what you can build alarms against without inventing things.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-level">Cluster-level:<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#cluster-level" class="hash-link" aria-label="Direct link to Cluster-level:" title="Direct link to Cluster-level:" translate="no">​</a></h4>
<ul>
<li class=""><strong>pendingTablesCount:</strong> how many tables are waiting in the pending queue right now.</li>
<li class=""><strong>runningTablesCount:</strong> how many tables are currently in <code>Tiering</code> (that is, assigned to some tiering job).</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="per-table">Per-table:<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#per-table" class="hash-link" aria-label="Direct link to Per-table:" title="Direct link to Per-table:" translate="no">​</a></h4>
<ul>
<li class=""><strong>tierLag:</strong> milliseconds since the last successful tiering of this table.</li>
<li class=""><strong>tierDuration:</strong> wall-clock duration of the last completed tiering round.</li>
<li class=""><strong>pendingTime:</strong> how long this table has been waiting in the pending queue right now.</li>
<li class=""><strong>failuresTotal:</strong> a counter of total tiering failures observed for this table.</li>
<li class=""><strong>fileSize / recordCount:</strong> cumulative lake-side size and record count after the last round.</li>
<li class=""><strong>freshness:</strong> the per-table configured freshness, in milliseconds.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="round-duration">Round Duration<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#round-duration" class="hash-link" aria-label="Direct link to Round Duration" title="Direct link to Round Duration" translate="no">​</a></h3>
<p>If you only watch one number, watch per-table <code>tierDuration</code> against the table's configured freshness. This is the metric that tells you whether your freshness targets are realistic. If <code>orders</code>'s configured freshness is 2 minutes and its observed <code>tierDuration</code> is 90 seconds, you have headroom. If <code>tierDuration</code> is 110 seconds and creeping up week over week, you have a problem coming.</p>
<p>Build a per-table dashboard. The pattern you want to see is <code>tierDuration</code> &lt; <code>freshness</code>, with stable variance round over round.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="queue-depth">Queue Depth<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#queue-depth" class="hash-link" aria-label="Direct link to Queue Depth" title="Direct link to Queue Depth" translate="no">​</a></h3>
<p><code>pendingTablesCount</code> is the leading indicator of starvation. In a healthy system, the queue is usually short (0 to 2 tables): tables enter, get assigned, get committed, exit. When the queue starts growing (5 tables pending, then 10, then 15), you're past the point where adding tables can keep up with tiering throughput. Either tiering rounds are slowing down, or tables are being added to the cluster faster than they're being drained, or one of your tiering jobs has effectively gone dark.</p>
<p>This is your cue to scale out tiering jobs (the multi-job pattern), or revisit bucket counts, or investigate a sick job. By the time freshness misses are showing up in the lake, queue depth has been climbing for hours. Watch it ahead of the symptom.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="remote-tier-storage-growth">Remote-Tier Storage Growth<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#remote-tier-storage-growth" class="hash-link" aria-label="Direct link to Remote-Tier Storage Growth" title="Direct link to Remote-Tier Storage Growth" translate="no">​</a></h3>
<p>It's tempting to assume a broken lake tier shows up as hot-tier (local) disk filling on the tablet servers. It usually doesn't. Local disk is bounded by <code>table.log.tiered.local-segments</code> (default 2): older local segments are continuously moved to the remote log tier no matter what the lakehouse tiering service is doing. The remote-log tier and the lakehouse tier are two independent mechanisms.</p>
<p>What actually grows when lake tiering stalls is the remote log tier. With <code>table.datalake.enabled</code>, Fluss won't delete a TTL-expired remote segment until it has been tiered to the lake: cleanup only frees segments whose log end offset is at or below the lake-synced offset. So a stuck lake tier overrides <code>table.log.ttl</code>, expired segments pile up in remote storage, and your object-store footprint for that table's log climbs even though local disk looks fine.</p>
<p>So the signal to watch is remote-tier storage size, not local disk. Remote log storage that keeps growing and never drops after <code>table.log.ttl</code> should have expired it, combined with a climbing <code>tierLag</code>, is the most direct "the lake side is failing" sign you have. For a table with no lake tiering enabled, <code>table.log.ttl</code> alone bounds remote growth and there's nothing lake-specific to watch here.</p>
<p>The operator's dashboard is five tiles, each answering one question. If all five are green, tiering is healthy. If any is red, the section it points to tells you where to look.</p>
<table><thead><tr><th>Tile</th><th>Question</th><th>Where it points if red</th></tr></thead><tbody><tr><td>1. <code>tierDuration</code> / freshness</td><td>Is any round time near its target?</td><td>Freshness and queue dynamics</td></tr><tr><td>2. <code>pendingTablesCount</code></td><td>Is the queue growing?</td><td>Scale out, queue starvation</td></tr><tr><td>3. Remote-tier storage growth</td><td>Is TTL cleanup blocked by a stalled lake tier?</td><td>Lake outage, stuck commits</td></tr><tr><td>4. Lake-side file count</td><td>Is compaction keeping up?</td><td>Compaction misconfigured</td></tr><tr><td>5. Flink JobManager liveness</td><td>Are all deployed jobs still up?</td><td>Asymmetric failure pattern</td></tr></tbody></table>
<p><strong>The rule that ties it together:</strong> four of these tiles come from Fluss and the lake catalog. Tiles 1 and 2 are Fluss coordinator metrics, tile 3 is the Fluss remote-log tier's storage footprint, and tile 4 comes from the lake catalog. Only tile 5 has to come from your Flink-side dashboard: because Fluss has no notion of tiering-job identity, no Fluss metric can answer "are all my deployed jobs still up?" That's why tile 5 lives outside Fluss, and why monitoring tiering means wiring both sides into one view.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pattern-the-five-number-daily-check">The Pattern: The Five-Number Daily Check<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#the-pattern-the-five-number-daily-check" class="hash-link" aria-label="Direct link to The Pattern: The Five-Number Daily Check" title="Direct link to The Pattern: The Five-Number Daily Check" translate="no">​</a></h3>
<p>A complete daily-operations view of the tiering service is five numbers:</p>
<ol>
<li class="">Per-table <code>tierDuration</code> vs freshness: is anything trending toward the limit?</li>
<li class=""><code>pendingTablesCount</code>: is it growing?</li>
<li class="">Remote-tier storage growth: is TTL cleanup stuck behind a stalled lake tier?</li>
<li class="">Lake-side file count growth: is compaction keeping up?</li>
<li class="">Flink-side JobManager liveness for every tiering job you provisioned: does it match what you deployed?</li>
</ol>
<p>If all five are green, the tiering service is healthy. If any of them are red, the previous sections tell you where to look next. That's the whole operator's playbook for this subsystem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-to-go-from-here">Where To Go From Here<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/#where-to-go-from-here" class="hash-link" aria-label="Direct link to Where To Go From Here" title="Direct link to Where To Go From Here" translate="no">​</a></h2>
<p>This three-part walkthrough was deliberately scoped to the tiering service, the mechanism that moves data from Fluss's hot tier into the lake. There are three adjacent topics worth exploring next, in roughly increasing order of depth.</p>
<p><strong>Reading from the lake side.</strong> The whole point of tiering is to make data queryable from Flink batch, Spark, Trino, or any other engine that speaks Paimon or Iceberg. The union-read path, which seamlessly stitches together lake-side historical data with Fluss-side hot data, is what makes this useful. It's separate machinery from the tiering service, but it depends on it.</p>
<p><strong>Compaction on the lake side.</strong> Mentioned several times across this series as a downstream concern. The actual mechanics of Paimon's compaction (full vs minor, the role of the dedicated compaction job) and Iceberg's (rewrite manifests, expire snapshots) are their own topics. If you're going to operate tiering in production, you need to operate compaction alongside it; they're a pair.</p>
<p><strong>Schema evolution.</strong> The tiering service handles schema changes by carrying the Fluss-side schema through to the lake on the next round. The lake-side catalog needs to accept the new schema; Paimon and Iceberg both support evolution, but with different rules. The interaction between Fluss schema changes (<code>ALTER TABLE</code> on the streaming side) and lake-side schema (the corresponding Paimon or Iceberg evolution) has its own corner cases and is worth a separate walkthrough.</p>
<p>The tiering service is the kind of subsystem that's invisible when it works and confusing when it doesn't. The goal of this walkthrough was to give you the mental model that lets you reason about it without having to dig back through the source every time something looks off.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tiering Service Deep Dive Part 2: Tuning]]></title>
            <link>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/</link>
            <guid>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/</guid>
            <pubDate>Tue, 09 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-3e1640cd3dda615e4db8e4f78c20076e.png" width="1617" height="679" class="img_ev3q"></p>
<p><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/">Part 1</a> built the mental model. What tiering is, who does what, how the round runs end-to-end.</p>
<p>This part adds the dials. Buckets and splits determine how a round parallelizes.
Log and PK tables behave so differently on round one that the difference deserves its own treatment.
The freshness setting, the one knob most users actually touch, does two different jobs that share the same value.
Once a single job is handling many tables, queue position starts to dominate effective freshness more than any per-table setting.
And once that happens, you have a deployment-shape decision: stay with one job, or scale out. By the end, you'll know which levers matter most and how to use them.</p>
<p><strong>Tiering Service Deep Dive, 3-parts:</strong></p>
<ul>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/">Part 1 - The Mental Model</a>:</strong> how one tiering round actually works, from timer fire to lake commit.</li>
<li class=""><strong>Part 2 - Tuning:</strong> per-table dials, multi-table dynamics, and scaling out.</li>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3 - In Production</a>:</strong> failure modes, design pitfalls, and monitoring.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="buckets-splits-and-how-the-work-gets-divided">Buckets, Splits, And How The Work Gets Divided<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#buckets-splits-and-how-the-work-gets-divided" class="hash-link" aria-label="Direct link to Buckets, Splits, And How The Work Gets Divided" title="Direct link to Buckets, Splits, And How The Work Gets Divided" translate="no">​</a></h2>
<p>In <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/">Part 1</a>, we had a table with 4 buckets that ended up with 4 splits.
That's not accidental: the rule is one split per bucket.
Let's slow down and look at the parallelism story end-to-end, because this is where bucket count actually starts to matter.</p>
<p>The mental model is straightforward: <strong>buckets</strong> are how the data is physically partitioned, <strong>splits</strong> are the chunks of work the tiering job hands out, and <strong>readers</strong> are the workers that consume splits.
The number of buckets is fixed at table creation time. The number of readers is your Flink job parallelism. The number of splits per round equals the number of buckets.</p>
<p><img decoding="async" loading="lazy" alt="Buckets map one-to-one to splits, which are handed out to reader tasks" src="https://fluss.apache.org/assets/images/fig1-87dd446c63e84baaf5f06611225fff01.png" width="1185" height="581" class="img_ev3q"></p>
<p>What happens when the number of readers doesn't match the number of buckets? Two cases:</p>
<p><strong>More readers than buckets</strong>. A table with 4 buckets and 16 readers means only 4 readers can work on <em>that</em> table. Bucket count caps how many readers a single table's round can use. The other 12 don't necessarily sit idle, though: if other tables are waiting in the queue, the enumerator hands those spare readers the next table's splits, and they start reading ahead (we'll see exactly how in the multi-table section). They only truly idle when there's no other work queued.</p>
<p><strong>Fewer readers than buckets</strong>. A table with 16 buckets and 4 readers means each reader will process 4 splits sequentially. Reader 0 takes one split, finishes it, asks for the next, takes another, and so on. The whole round takes roughly 4× longer than it would with 16 readers. This is the common production case, and it's fine, just slower.</p>
<p>One more thing: the splits are shuffled (randomized) before being assigned. This is a small but important detail. Without it, reader 0 would always get bucket 0, reader 1 would always get bucket 1, and so on. If bucket 0 is consistently the heaviest (because of a bad key distribution), reader 0 would consistently be the bottleneck. Shuffling spreads the bad luck around: over many rounds, every reader gets stuck with the heavy bucket about equally often.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-slow-bucket-effect">The Slow-Bucket Effect<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-slow-bucket-effect" class="hash-link" aria-label="Direct link to The Slow-Bucket Effect" title="Direct link to The Slow-Bucket Effect" translate="no">​</a></h2>
<p>In the normal commit path, the commit operator waits for every split of a table before committing.
If 3 readers finish quickly and the 4th is still processing a much larger bucket, the table's commit can't happen until that last bucket lands. The 3 fast readers move on to other queued tables (or idle, if there's nothing else queued), but this table's round isn't done until its slowest bucket is.
The lake commit itself is always atomic. Readers can't see a half-written snapshot, and most of the time every bucket of a round lands in the same commit.</p>
<p><strong>The end result:</strong> the slowest bucket determines how long a round takes. Skewed bucket keys can hurt you here. (There's one exception: when freshness fires and the round force-finishes, whatever each bucket has tiered so far gets committed, and the unread remainder rolls to the next round. We'll cover this in the freshness section.)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-kinds-of-tables-log-vs-primary-key-tables">Two Kinds of Tables: Log vs. Primary-Key Tables<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#two-kinds-of-tables-log-vs-primary-key-tables" class="hash-link" aria-label="Direct link to Two Kinds of Tables: Log vs. Primary-Key Tables" title="Direct link to Two Kinds of Tables: Log vs. Primary-Key Tables" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="log-tables-the-simple-case">Log Tables: The Simple Case<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#log-tables-the-simple-case" class="hash-link" aria-label="Direct link to Log Tables: The Simple Case" title="Direct link to Log Tables: The Simple Case" translate="no">​</a></h3>
<p>A log table is exactly what it sounds like: an append-only stream. Records arrive, they get assigned an offset, and they sit there. You can't update or delete a record; you can only append. Examples: a clickstream, an event bus, an audit log.</p>
<p>Tiering a log table is straightforward. Every round, the Flink job reads "everything since the last commit" (just the new offsets) and writes those records as Parquet files in the lake.
The lake table grows monotonically. The work per round is proportional to how much you wrote since the last round.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="primary-key-tables-the-trickier-case">Primary-Key Tables: The Trickier Case<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#primary-key-tables-the-trickier-case" class="hash-link" aria-label="Direct link to Primary-Key Tables: The Trickier Case" title="Direct link to Primary-Key Tables: The Trickier Case" translate="no">​</a></h3>
<p>A primary-key (PK) table is like a row-keyed materialized view.
Each record has a primary key and the latest value for that key replaces any previous value.
Internally, Fluss stores PK tables in two parts: <strong>a changelog</strong> (a log of all the upserts and deletes that have happened) and a <strong>KV state</strong> (the current value for each key, derived from the changelog). It's similar to how a database has a write-ahead log plus the actual table.</p>
<p>The changelog records every event using four operation codes you'll see throughout the Flink and Fluss world: <code>+I</code> for an insert, <code>-U</code> and <code>+U</code> together for an update (the old row going out, the new row coming in), and <code>-D</code> for a delete.
Apply all of those to an empty state in order and you get the current row for every key; that's the KV state. Here's a small example to make this concrete:</p>
<p><img decoding="async" loading="lazy" alt="Applying a changelog of +I, -U/+U and -D events to derive the current KV state" src="https://fluss.apache.org/assets/images/fig2-886635d014288e1ff64345e7b6dfe1a5.png" width="1190" height="710" class="img_ev3q"></p>
<p>Now think about what <strong>"tier this to the lake"</strong> means for a PK table.
The lake needs the current state of every row, not just the log of what changed.
So the very first round of a PK table has to copy the entire KV state to the lake.
That can be huge: think 200 GB if you have a big customer-profile table.
After that first round, subsequent rounds only need the changelog records since the last snapshot offset, because the lake's merge engine knows how to apply upserts and deletes to existing rows.</p>
<p><img decoding="async" loading="lazy" alt="A PK table&amp;#39;s first round copies the full KV state; later rounds tier only the changelog since the last snapshot offset" src="https://fluss.apache.org/assets/images/fig3-10c1755bfa263041a434f8ccd8719fa8.png" width="1193" height="527" class="img_ev3q"></p>
<p>The numbers here are illustrative. A 200 GB PK table might take 10+ minutes to read on its first round (because all 200 GB must be copied).
The second round, where only the upserts and deletes from the last few minutes need to be applied, might take 30 seconds.
This is an important difference, and the single most important thing to understand about PK tiering: the first round is in a different category from every round that follows.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-summary-table">The Summary Table<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-summary-table" class="hash-link" aria-label="Direct link to The Summary Table" title="Direct link to The Summary Table" translate="no">​</a></h3>
<table><thead><tr><th>Aspect</th><th>Log Table</th><th>Primary-Key Table</th></tr></thead><tbody><tr><td><strong>Round 1 reads</strong></td><td>Whatever offsets exist so far</td><td>Full KV state, up to 100s of GB</td></tr><tr><td><strong>Round 2+ reads</strong></td><td>New offsets since round 1</td><td>Changelog since round 1's snapshot offset</td></tr><tr><td><strong>What the lake stores</strong></td><td>Append-only Parquet</td><td>Snapshot rows + applied upserts/deletes</td></tr><tr><td><strong>Cost stability</strong></td><td>Roughly constant per round</td><td>Round 1 huge, rest tiny</td></tr><tr><td><strong>Partial progress useful?</strong></td><td>Yes: committed buckets advance the lake; the rest roll to the next round</td><td>Round 1 snapshot: no (force-finish is ignored for snapshot splits, so they run to completion rather than committing partially). Round 2+: yes</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-freshness-knob">The Freshness Knob<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-freshness-knob" class="hash-link" aria-label="Direct link to The Freshness Knob" title="Direct link to The Freshness Knob" translate="no">​</a></h2>
<p>Every table that's enabled for tiering has a setting called <code>table.datalake.freshness</code>.
It's a duration, something like <code>5min</code> or <code>30s</code>, and the default is 3 minutes.
This single number is the most common source of confusion in the tiering service, because it does two different jobs that share the same value.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="job-1-how-often-rounds-start">Job 1: How Often Rounds Start<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#job-1-how-often-rounds-start" class="hash-link" aria-label="Direct link to Job 1: How Often Rounds Start" title="Direct link to Job 1: How Often Rounds Start" translate="no">​</a></h3>
<p>The obvious meaning.
After a round completes, the coordinator schedules the next round one full freshness interval later, measured from the moment that round <em>finished</em>. So with 5-minute freshness, the next round becomes eligible 5 minutes after the previous round committed. The round's own duration is <em>not</em> subtracted.</p>
<p>Under the hood the scheduler computes <code>freshness − (now − last_completion_time)</code>, which looks like it deducts elapsed time. But <code>last_completion_time</code> is reset to the instant the round just finished, so the subtracted term is ~0 for back-to-back rounds. That subtraction only bites after a coordinator restart, where the last completion can be well in the past and the next round may fire immediately.</p>
<p>The practical consequence: the effective start-to-start cadence is <code>round_duration + freshness</code>, not <code>freshness</code> on its own. A table with 5-minute freshness and 90-second rounds runs a round roughly every 6.5 minutes, not every 5.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="if-youre-coming-from-a-flink-streaming-background">If You're Coming From a Flink Streaming Background<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#if-youre-coming-from-a-flink-streaming-background" class="hash-link" aria-label="Direct link to If You're Coming From a Flink Streaming Background" title="Direct link to If You're Coming From a Flink Streaming Background" translate="no">​</a></h4>
<p>Tiering cadence is driven by freshness; it is <strong>not</strong> tied to Flink checkpoints.
A common mental model from Flink-CDC-style pipelines is "data lands in the sink when a checkpoint completes".
That's not quite how the tiering service works. A tiering round commits to the lake when all the round's bucket results have arrived at the commit operator, which is driven by the round's own progress rather than by external checkpoint cadence.
Whatever checkpoint interval the Flink tiering job has configured doesn't bound when the lake sees new data.
The round itself does.</p>
<p>The correctness story has two layers worth separating.
Atomicity comes from the lake's own commit primitive (Paimon's snapshot commit, Iceberg's metadata swap). Consistency across attempts comes from the epoch fencing mechanism the coordinator uses, introduced in <strong>Part 1</strong>, in the <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-heartbeat">heartbeat section</a>, which rejects any commit from a stale attempt (an epoch-fencing error).
So you don't need to tune Flink checkpoint settings to get correct tiering; the defaults are fine.</p>
<p>What you do still need is a healthy checkpoint cycle.
The Flink tiering job is configured with a full-restart strategy because the commit operator is stateless: it holds the per-table bucket results it has collected so far only in memory (nothing is checkpointed), so if it fails those in-flight committables are lost and the whole job restarts to collect them fresh.</p>
<p>That's deliberate. It keeps the all-or-nothing commit semantics simple. So checkpoints aren't your tuning knob, but they do need to complete; treat the tiering job's checkpoint cycle as load-bearing infrastructure rather than something you can ignore.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="job-2-the-ceiling-on-a-rounds-wall-clock-duration">Job 2: The Ceiling On A Round's Wall-clock Duration<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#job-2-the-ceiling-on-a-rounds-wall-clock-duration" class="hash-link" aria-label="Direct link to Job 2: The Ceiling On A Round's Wall-clock Duration" title="Direct link to Job 2: The Ceiling On A Round's Wall-clock Duration" translate="no">​</a></h3>
<p>The less-obvious meaning. The same value also caps how much wall-clock time a single round of this table is allowed to consume before something gives. When freshness elapses mid-round, the Flink enumerator fires a force-finish: every log split that has started commits whatever it has read so far (advancing that bucket's offset), and buckets that hadn't started yet are skipped. The skipped buckets, and the remaining tail of any partially-read bucket, get re-read in the next round (which starts immediately, since the table is re-queued straight away).</p>
<p>This is an important nuance and worth slowing down on: <strong>force-finish is not a failure</strong>. It's a partial commit. On the coordinator side, the table transitions <code>Tiering</code> → <code>Tiered</code> (with a <code>force_finished</code> flag) → <code>Pending</code>. It is not marked <code>Failed</code>. It does take a fresh tiering epoch on the way back into the queue: the <code>Tiered</code> → <code>Pending</code> transition bumps the epoch exactly as any normal re-enqueue does, but that's ordinary re-attempt bookkeeping, not a failure recorded against the table.
The lake snapshot that lands contains every bucket that tiered any data this round, each up to wherever it reached, and that snapshot is still atomic. Readers never see a partial commit. Buckets that never started just roll forward to the next round.</p>
<p><strong>One exception to highlight:</strong> for the first round of a PK table, assuming a KV snapshot already exists (the normal case), that round reads the snapshot, and force-finish is suppressed for snapshot splits. (If no KV snapshot has been taken yet, the first round instead reads the changelog from the earliest offset as ordinary log splits, and force-finish applies as usual.)
The reason force-finish is suppressed is that a snapshot split has to be read in full to yield the log offset that the snapshot ends at, and that offset is what anchors the next incremental round.
Truncate the snapshot read part-way and you lose that offset. There's nothing for the following round to start from.
So when freshness fires during a PK snapshot round, the force-finish signal is sent but the snapshot reader simply ignores it; the splits keep running until the snapshot finishes naturally.
The lake commit lands whenever that happens, no matter how much longer it takes. Force-finish only meaningfully truncates work on log splits, not snapshot splits.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-freshness-gives-you-and-what-it-doesnt">What The Freshness Gives You And What It Doesn't<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#what-the-freshness-gives-you-and-what-it-doesnt" class="hash-link" aria-label="Direct link to What The Freshness Gives You And What It Doesn't" title="Direct link to What The Freshness Gives You And What It Doesn't" translate="no">​</a></h3>
<p>Freshness is a target scheduling cadence, not a guarantee of how stale the lake is.
The actual lake lag is the time between when a record is written to Fluss and when it appears in the lake, and it oscillates in a sawtooth. The floor is one <code>round_duration</code>: a record written just before a round's cutoff lands in the lake about one round-length later. The peak is roughly <code>freshness + 2 × round_duration</code>: a record written just <em>after</em> a round's cutoff has to wait a full freshness interval for the next round to start, then that round's duration to commit. So a table with 5-minute freshness and 90-second tiering rounds gives you a lake that's between ~90 seconds and ~8 minutes behind.</p>
<p><img decoding="async" loading="lazy" alt="Lake-lag sawtooth: lag drops to one round duration after each commit, then climbs until the next round commits" src="https://fluss.apache.org/assets/images/fig4-c98d93542bfcc365541390330187aef0.png" width="1188" height="465" class="img_ev3q"></p>
<p>So the rule of thumb to take away is: floor lag ≈ <code>round_duration</code>, and peak lag ≈ <code>freshness + ~2 × round_duration</code>, meaningfully more than the configured freshness once rounds get long.
If your stakeholders say "the lake must never be more than 10 minutes behind", work backwards from the peak: with 90-second rounds, a freshness around 7 minutes keeps peak lag near 10 minutes (7 min + ~3 min). Leave yourself margin.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-other-timeout-you-should-know-about">The Other Timeout You Should Know About<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-other-timeout-you-should-know-about" class="hash-link" aria-label="Direct link to The Other Timeout You Should Know About" title="Direct link to The Other Timeout You Should Know About" translate="no">​</a></h3>
<p>There's a second timeout, set on the coordinator side and applied to every table regardless of its freshness. It's a 2-minute window, but the thing it measures is easy to misread. It's <em>not</em> a ceiling on how long a round can run; rather, it's a ceiling on how long the coordinator will wait between heartbeats from the Flink job about a given in-flight table.</p>
<p>The coordinator runs a background sweep every 15 seconds.
For every table currently in <code>Tiering</code>, it checks <code>currentTime - lastHeartbeat</code>.
If that delta exceeds 2 minutes, the table is fenced and transitioned through <code>Failed</code> back to <code>Pending</code>, epoch bumped, any uncommitted lake files orphaned.
The <code>lastHeartbeat</code> timestamp gets refreshed on every heartbeat from the Flink job that mentions the table, which by default is every 30 seconds (controlled by <code>tiering.poll.table.interval</code>).</p>
<p>So in a healthy Flink job, the 2-minute timer never fires for round duration.
The job heartbeats four times in every 2-minute window, each heartbeat lists the in-flight tables, the coordinator's <code>lastHeartbeat</code> stays fresh, and a single round can keep running for an hour without the coordinator ever raising an eyebrow.
The 2-minute window only catches Flink jobs that have genuinely stopped heartbeating; GC pause long enough to look like unavailable, network partition, JobManager crash, that kind of thing.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-two-ceilings-side-by-side">The Two Ceilings, Side By Side<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-two-ceilings-side-by-side" class="hash-link" aria-label="Direct link to The Two Ceilings, Side By Side" title="Direct link to The Two Ceilings, Side By Side" translate="no">​</a></h3>
<p><strong>Freshness</strong> is set per table, fully tunable, and triggers a force-finish on log splits when it fires mid-round.
For PK snapshot splits, force-finish is ignored and those splits run to completion regardless, because the snapshot read has to finish to produce the log offset that anchors the next incremental round.
<strong>The 2-minute coordinator window</strong> is hardcoded and applies to every table, but it measures heartbeat liveness, not round duration.
A healthy Flink job that heartbeats every 30 seconds can spend hours on a single round without ever triggering it.
The 2-minute window is the coordinator's safety net for a job that has actually stopped responding, which is the only failure mode that needs it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multiple-tables-how-the-queue-actually-works">Multiple Tables: How The Queue Actually Works<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#multiple-tables-how-the-queue-actually-works" class="hash-link" aria-label="Direct link to Multiple Tables: How The Queue Actually Works" title="Direct link to Multiple Tables: How The Queue Actually Works" translate="no">​</a></h2>
<p>Up until now we've talked about one table. Real deployments have many. So what happens when you have, say, three tables (two log tables and one PK table) all sharing a single tiering Flink job?</p>
<p>The coordinator keeps one FIFO queue of pending tables. When a Flink job asks for work via heartbeat, the coordinator pops the table at the front of the queue and hands it over.
The Flink job works that table's buckets in parallel and asks for the next table once its splits are all handed out. The coordinator gives out work one table per request. As we'll see, a job's spare readers can start a second table's reads before the first table's commit lands; what stays strictly one-at-a-time is the commit.</p>
<p>This means the effective freshness for any single table is not just its own configured freshness.
It's also a function of where it sits in the queue and how long the tables ahead of it take.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-concrete-walkthrough-three-tables-one-job">A Concrete Walkthrough: Three Tables, One Job<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#a-concrete-walkthrough-three-tables-one-job" class="hash-link" aria-label="Direct link to A Concrete Walkthrough: Three Tables, One Job" title="Direct link to A Concrete Walkthrough: Three Tables, One Job" translate="no">​</a></h2>
<p>Here's the setup. Three tables sharing one Flink tiering job that's running with parallelism 16 (so up to 16 readers can run in parallel at any moment).
Each table has its own bucket count, freshness target, and expected per-round duration:</p>
<table><thead><tr><th>Table</th><th>Type</th><th>Buckets</th><th>Freshness</th><th>Round duration</th></tr></thead><tbody><tr><td>clicks</td><td>Log</td><td>16</td><td>1 min</td><td>~30 s</td></tr><tr><td>orders</td><td>Log</td><td>8</td><td>2 min</td><td>~90 s</td></tr><tr><td>customers</td><td>PK</td><td>12</td><td>5 min</td><td>~100 s first round, ~20 s after</td></tr></tbody></table>
<p>Each table generates one split per bucket per round, so a clicks round produces 16 splits, an orders round produces 8 splits, and a customers round produces 12 splits.
When the Flink job processes clicks, all 16 of its readers are busy on it at once.
orders has only 8 buckets, so at most 8 readers can work on orders itself, but the other 8 don't idle: once orders' splits are all assigned and none are left pending, the enumerator pulls the next queued table and hands its splits to those readers, which start reading ahead. Same with customers (12 buckets): the 4 spare readers pick up whatever is next in the queue.
The bucket count is a per-table cap on <em>intra-table</em> parallelism (how many readers one table's round can use), not a cap on how busy the job is. The spare readers pipeline onto the next table (detailed in <em>Reads Pipeline: Commits Serialize</em>, below). What stays strictly one-table-at-a-time is the <em>commit</em>, not the reads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-determines-round-duration">What Determines Round Duration?<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#what-determines-round-duration" class="hash-link" aria-label="Direct link to What Determines Round Duration?" title="Direct link to What Determines Round Duration?" translate="no">​</a></h3>
<p>Before we walk through the example, let's pin down what "30 seconds" or "100 seconds" actually means, because those numbers aren't magic.
They emerge from three concrete inputs that every round has to negotiate with:</p>
<ol>
<li class=""><strong>How much data the round has to read</strong>. For a log table, this is the volume of writes since the last commit, roughly <code>write_rate × time_since_last_commit</code>. The longer the gap between rounds, the more data each round has to move. For a PK table's first round, the data is the entire KV state; every key in the table, regardless of how long it's been since the last anything. For a PK table's incremental rounds (round 2 and beyond), it's just the changelog records since the last snapshot offset, which is typically much smaller than the full state.</li>
<li class=""><strong>How parallel the work is</strong>. Bucket count bounds the parallelism within a single round. With 16 buckets, the work is split 16 ways. With 4 buckets, it's split 4 ways, no matter how many Flink readers you have. The Flink job's parallelism setting only matters up to the bucket count of the table currently being tiered for that table's own splits; any extra readers don't wait around: they pick up the next queued table's splits (more on that in a moment).</li>
<li class=""><strong>How fast each reader can move data</strong>. Bounded by network throughput from the Fluss tablet servers, deserialization speed, and write bandwidth to the lake (S3, GCS, etc.). Real-world numbers are usually a few tens of MB/s per reader.</li>
</ol>
<p><strong>Putting it together:</strong> <code>round_duration ≈ data_to_read / (bucket_count × per_reader_throughput)</code>.
For our clicks table, which has been collecting writes for ~1 minute, has 16 buckets, and each reader pulls maybe 30 MB/s, a round of a few hundred MB of new clicks events takes roughly 30 seconds.
For customers' first round, the full KV state, let's say a few GB, 12 readers at 30 MB/s would take around 100 seconds. The exact numbers depend on your workload, but the shape of the formula is what matters: more buckets shortens a round, more data lengthens it.</p>
<p><strong>One important thing this formula leaves out:</strong> the slowest bucket determines when a round can commit, because the commit operator waits for all of them.
If your bucketing key has bad distribution and one bucket holds 3× the data of the others, that one bucket sets the round duration: the other 15 readers finish their splits early and move on to the next queued table, but this table's commit still waits on that one straggler.
The <code>shuffle</code> on split assignment doesn't fix this; it just makes the bad bucket land on a different reader each round.</p>
<p>A newly created table doesn't tier immediately; it waits one freshness interval before its first round (the coordinator schedules it: <code>New</code> → <code>Scheduled</code> → <code>Pending</code>). So picture <strong>T=0</strong> as the moment all three tables have passed that initial wait and are sitting in the coordinator's pending queue, each awaiting its first-ever round. They entered the queue as each table's first scheduling timer fired, in ascending freshness order, so it reads <code>[clicks, orders, customers]</code>.</p>
<p>The Flink job's first heartbeat asks for work and gets clicks.
The enumerator generates 16 splits (one per bucket), shuffles them, and assigns one to each of the 16 readers.
All readers work in parallel; clicks finishes at <strong>T=30s</strong>.</p>
<p>At <strong>T=30s</strong>, the Flink job asks for more work. It gets orders. The enumerator generates 8 splits and assigns them to 8 readers.
The other 8 readers don't sit idle: with orders' 8 splits assigned and nothing left pending, the enumerator immediately pulls the next table and those readers begin reading ahead (this is the read pipelining we detail below). What we're tracking in this walkthrough is the <em>commit</em> order, and orders is the table committing next: it tiers for about 90 seconds and its commit lands at <strong>T=120s</strong>, comfortably inside its 2-minute freshness window.</p>
<p>Now customers is next in the commit order. The enumerator generates 12 splits (since this is round 1 of a PK table, these are snapshot splits reading the full KV state). 12 readers can work on its splits at once; the 4 spare readers, again, pick up whatever's next in the queue rather than idling. The round takes roughly 100 seconds, and customers' commit lands at <strong>T=220s</strong>, well inside the configured 5-minute freshness.</p>
<p>But notice one thing here. clicks committed at <strong>T=30s</strong>; with a 1-minute freshness, its next round was scheduled for <strong>T=90s</strong>, so it's been waiting in the queue since <strong>T=90s</strong>. Its reads may well start earlier (spare readers can pick up clicks round 2 while customers is still being read), but its <em>commit</em> can't land until orders and customers have committed ahead of it.
So clicks' round-2 commit doesn't land until around <strong>T=220s</strong>, putting its actual lake lag near 3.2 minutes despite being configured for 1-minute freshness.
The fact that clicks uses all 16 readers when it does run didn't help. The constraint isn't how fast any one table reads; it's that the job dispatches one table at a time off the FIFO queue and commits one table at a time through a single-parallelism operator. Here clicks round 2 is re-queued only at <strong>T=90s</strong>, behind orders and customers, so it's both dispatched and committed last.
The configured freshness value is the target; the queue is the constraint.</p>
<p><strong>There's also a sizing lesson here</strong>, but it's subtler than "don't over-provision." It's tempting to think that setting Flink parallelism to 16 wastes slots whenever a table has fewer than 16 buckets (8 for orders, 12 for customers). It mostly doesn't: because spare readers pipeline onto the next queued table, those slots get spent reading ahead as long as there's a backlog. Parallelism above your largest table's bucket count isn't automatically wasted: with several tables queued, the extra readers stay busy on other tables' reads. It only goes to waste when you rarely have more than one table's worth of work in flight at once.</p>
<p>What parallelism <em>can't</em> fix is the commit path. The commit operator runs at parallelism 1 and serializes one table at a time, so no amount of extra parallelism speeds up the rate at which lake snapshots actually land.</p>
<p><strong>A reasonable rule of thumb:</strong> set Flink parallelism at least as high as the largest bucket count of any single table (so your biggest table can use all its buckets in one round), and add more only if you routinely have several tables queued and want their reads to overlap. Size bucket counts at table-creation time based on each table's data volume and freshness target. And if you need more <em>commit</em> throughput than one job can deliver, that's the cue to run multiple tiering jobs (next section), not to crank up parallelism.</p>
<p><img decoding="async" loading="lazy" alt="Commit timeline for three tables sharing one tiering job, showing how queue position inflates effective freshness" src="https://fluss.apache.org/assets/images/fig5-a29e6ed8df2bbe7924a4e6aa37aee170.png" width="1216" height="500" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reads-pipeline-commits-serialize">Reads Pipeline: Commits Serialize<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#reads-pipeline-commits-serialize" class="hash-link" aria-label="Direct link to Reads Pipeline: Commits Serialize" title="Direct link to Reads Pipeline: Commits Serialize" translate="no">​</a></h3>
<p>The diagram above shows when each table commits.
The reads underneath those commits don't line up the same way; when a table doesn't have enough splits to keep all 16 readers busy, the Flink enumerator pulls the next table from the queue and the spare readers immediately start chewing through its splits.
So reads overlap across tables. Commits don't.</p>
<p><img decoding="async" loading="lazy" alt="Reads overlap across tables while commits stay strictly serialized" src="https://fluss.apache.org/assets/images/fig6-5462b5199c6951f0dbe42913e3f04682.png" width="1353" height="630" class="img_ev3q"></p>
<p>The mechanism is simple. The enumerator maintains one shared <code>pendingSplits</code> list across all in-flight tables plus a set of <code>readersAwaitingSplit</code>. It asks the coordinator for the next table only when both <code>pendingSplits</code> is empty and at least one reader is idle. While clicks (16 splits) is running, that pair of conditions never holds simultaneously: all readers stay busy until the splits drain together at <strong>T=30s</strong>. But the moment orders (8 splits) is assigned, 8 readers are idle and <code>pendingSplits</code> is empty, so the enumerator immediately pulls customers from the queue, and those 8 idle readers start chewing through customers' snapshot splits while orders is still being read. The same pattern repeats when orders finishes: the freed readers pick up the remaining customers splits and then clicks round 2 (which has entered the queue by then), so customers and clicks round 2 overlap during phase 3.</p>
<p>The commits, however, can't overlap. The commit operator runs at parallelism 1 by design, processing one table's commit at a time. It commits a table the moment it has collected all of that table's bucket write results for the round, so commits land in completion order, not coordinator-queue order: a later-dispatched but faster table can reach the committer first and commit before an earlier, slower one when their reads overlap. What is guaranteed is that commits never run concurrently, lake snapshots land strictly one after another. In our example the rounds happen to complete in queue order (clicks saturates all 16 readers and finishes first, then orders, then customers), but that ordering is a property of this workload, not a promise the committer makes.</p>
<p>That's why queue position still dominates effective freshness, even with read overlap. clicks' round-2 reads can start while customers is still being read, but every commit is serialized through the single committer subtask, so a table's commit can't land until whatever the committer is currently working on is done. The lake-side appearance of "the round committed" is bottlenecked by the commit operator, not the readers, which is the exact mechanism the commit timeline was measuring.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-general-rule">The General Rule<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-general-rule" class="hash-link" aria-label="Direct link to The General Rule" title="Direct link to The General Rule" translate="no">​</a></h3>
<p>For N tables sharing one tiering job, the effective worst-case freshness of any single table is roughly the sum of all the tiering round durations, not the configured value of that one table. Configured freshness is more of a target; the queue is the constraint.</p>
<p>This is fine when your tables are similar in size and freshness. It's a disaster when you mix tiny fast tables with huge slow ones. The little ones get starved while the big one finishes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-escape-hatch">The Escape Hatch<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-escape-hatch" class="hash-link" aria-label="Direct link to The Escape Hatch" title="Direct link to The Escape Hatch" translate="no">​</a></h3>
<p>There's a clean fix: run more than one tiering job. The coordinator's pending queue is a shared resource, and any Flink tiering job that's registered with the cluster can pull from it. Two jobs, two tables tier at once. Three jobs, three. The starvation pattern softens as soon as the number of concurrent jobs is at least as large as the number of latency tiers you actually care about. The next section is the operational walkthrough.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scaling-out-running-multiple-tiering-jobs">Scaling Out: Running Multiple Tiering Jobs<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#scaling-out-running-multiple-tiering-jobs" class="hash-link" aria-label="Direct link to Scaling Out: Running Multiple Tiering Jobs" title="Direct link to Scaling Out: Running Multiple Tiering Jobs" translate="no">​</a></h2>
<p><strong>The previous section left us with a clean problem statement:</strong> one Flink tiering job serializes work across all tables, so freshness for any one table is bounded by the queue depth ahead of it.
The available knob for scaling out is to run multiple Flink tiering jobs against the same Fluss cluster.
It works, but it's a blunter instrument than you might hope.
The current implementation gives you concurrency, not isolation, and the section below walks through exactly what's in the code and what scaling out can and can't do.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architectural-approach">The Architectural Approach<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-architectural-approach" class="hash-link" aria-label="Direct link to The Architectural Approach" title="Direct link to The Architectural Approach" translate="no">​</a></h3>
<p>Up to <strong>Fluss 0.6</strong>, the tiering service was a single stateful Flink job.
The per-table sync offset, the last lake-committed offset for each bucket, was stored inside Flink's checkpointed state.
Practically, that meant you could scale the one job up (more parallelism, more slots) but you couldn't run two jobs; they would have stomped on each other's state.</p>
<p><strong>Fluss 0.7</strong> <a href="https://cwiki.apache.org/confluence/display/FLUSS/FIP-1%3A+Fluss+Lakehouse+Storage+Design" target="_blank" rel="noopener noreferrer" class="">re-architected the service to be stateless</a>.
The sync offset moved from Flink state into Fluss metadata. The coordinator owns it, durably, regardless of which Flink job most recently tiered a given table.
The <code>TieringSourceReader</code> is now explicitly stateless and its <code>snapshotState</code> returns an empty list by design.
All the truth about "where is this table in its tiering history" lives in Fluss, not in any particular Flink job.
That single change is what makes horizontal scaling safe at all. Two jobs can both ask the coordinator "what's next?", and neither holds state that the other could clobber.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-mechanics-one-queue-undifferentiated-workers">The Mechanics: One Queue, Undifferentiated Workers<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-mechanics-one-queue-undifferentiated-workers" class="hash-link" aria-label="Direct link to The Mechanics: One Queue, Undifferentiated Workers" title="Direct link to The Mechanics: One Queue, Undifferentiated Workers" translate="no">​</a></h3>
<p>The coordinator maintains <strong>exactly one in-memory FIFO queue</strong> of pending tables; <code>pendingTieringTables</code> in <code>LakeTableTieringManager</code>.
When a heartbeat comes in carrying <code>request_table=true</code>, the coordinator's <code>requestTable()</code> just pops the head: <code>Long tableId = pendingTieringTables.poll()</code>.
There's no filter parameter, no caller identity, no routing logic.
The protobuf <code>LakeTieringHeartbeatRequest</code> doesn't carry any "which job am I" field, because the concept doesn't exist on the coordinator side.</p>
<p>So from the coordinator's perspective, every Flink tiering job is indistinguishable.
There's no job ID, no database filter, no notion of "this table belongs to that job".
All registered jobs are undifferentiated workers reaching into the same queue, and the head of the queue goes to whoever happens to heartbeat first. If you have two jobs, two tables can be in <code>Tiering</code> at the same time. If you have five, five can. But you cannot pin clicks to job A; the coordinator wouldn't know how to honor that pin even if you asked.</p>
<p>The epoch mechanism from <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-heartbeat">Part 1's heartbeat section</a> keeps this safe regardless of how many jobs are running. Each table assignment carries a <code>tiering_epoch</code> stamped on it. If two jobs somehow ended up working on the same table (a rare edge case during coordinator failover, mainly), the coordinator only accepts the commit whose epoch matches its current record; the other is rejected with an epoch-fencing error. So multiple jobs running concurrently can never produce duplicate commits or corrupt lake state; the worst case is some wasted reader work that doesn't get committed.</p>
<p><img decoding="async" loading="lazy" alt="Multiple tiering jobs pulling work from the coordinator&amp;#39;s single shared FIFO queue" src="https://fluss.apache.org/assets/images/fig7-33212796ffed8f29039eabb7706b9cde.png" width="1077" height="482" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-earlier-example-with-two-jobs">The Earlier Example, With Two Jobs<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#the-earlier-example-with-two-jobs" class="hash-link" aria-label="Direct link to The Earlier Example, With Two Jobs" title="Direct link to The Earlier Example, With Two Jobs" translate="no">​</a></h2>
<p>Let's revisit the previous walkthrough with two tiering jobs instead of one. The setup is unchanged. Three tables: clicks (1-min target, 30s round), orders (2-min target, 90s round), and customers (5-min target, 100s first PK round). At <strong>T=0</strong> all three are sitting in the queue: <code>[clicks, orders, customers]</code>.</p>
<p><strong>T=0s</strong>. Both jobs heartbeat asking for work. Job A's heartbeat happens to land first and gets clicks. Job B's request gets orders (now head of queue). Both jobs work in parallel; the queue contains just customers.</p>
<p><strong>T=30s</strong>. Job A finishes clicks, asks for next, and gets customers. Job B is still working orders.</p>
<p><strong>T=90s</strong>. Job B finishes orders, asks for next. Queue is empty, so Job B is briefly idle. At the same moment, clicks' 1-minute freshness timer fires (last commit at <strong>T=30s</strong>). It enters the queue. Job B picks it up on its next heartbeat (≤30s later).</p>
<p><strong>T=120s</strong>. Job B finishes clicks round 2. Effective freshness gap for clicks: about 90 seconds, against a 1-min target. Compare with ~3.2 min on one job.</p>
<p><strong>T=130s</strong>. Job A finishes customers first round. Cold tier idle until the next freshness firing 5 minutes later.</p>
<table><thead><tr><th>Table</th><th>Configured</th><th>Effective (1 job)</th><th>Effective (2 jobs)</th></tr></thead><tbody><tr><td>clicks</td><td>1 min</td><td>~3.2 min</td><td>~1.5 min</td></tr><tr><td>orders</td><td>2 min</td><td>~2 min</td><td>~2 min</td></tr><tr><td>customers</td><td>5 min</td><td>~5 min</td><td>~5 min</td></tr></tbody></table>
<p>The improvement is real, but notice it's stochastic, not architectural. Job A got clicks first only because its heartbeat happened to arrive first at <strong>T=0</strong>. You cannot promise "clicks always lands on the responsive job."</p>
<p>In practice the pairing tends to be sticky: once a job has been running short rounds it keeps becoming idle first and keeps picking up the next short round, so over time you do see something close to "fast job handles fast tables." But it's an emergent property of round timing, not a configured guarantee: if the workload shape shifts (a previously fast table grows, a slow table is dropped, a new heavy table is enabled), the pairing reshuffles on its own. Don't lean on it as if it were a guarantee.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-scaling-out-fixes-and-what-it-doesnt">What Scaling Out Fixes And What It Doesn't<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#what-scaling-out-fixes-and-what-it-doesnt" class="hash-link" aria-label="Direct link to What Scaling Out Fixes And What It Doesn't" title="Direct link to What Scaling Out Fixes And What It Doesn't" translate="no">​</a></h3>
<p><strong>Scales:</strong> queue throughput (with N jobs, up to N tables can be in <code>Tiering</code> simultaneously); failure-domain separation (if one job crashes, the others keep tiering against the same Fluss cluster); and capacity headroom (bursts of "many tables freshly ready at once" get absorbed across multiple jobs rather than stacking up serially behind one).</p>
<p><strong>Does not scale:</strong> any single table's round duration. If customers' first PK round takes 100 seconds with 12 buckets and one job, it still takes 100 seconds with two jobs, because only one job is working on customers at a time. Multiple jobs give concurrency across tables, not within one. The lever for a single round is bucket count and per-job parallelism, not job count.</p>
<p><strong>Does not give you:</strong> deterministic routing. There is no current Fluss configuration that lets you say "this table goes to Job A, that one to Job B." The coordinator cannot tell jobs apart; they're identical to it. If you need hard isolation between latency tiers, the current options are limited:</p>
<p><strong>Does not bypass:</strong> the 2-minute heartbeat-liveness window from the freshness section. Every job is subject to the same hardcoded check: if any one job stops heartbeating, the coordinator fences its in-flight tables and another job picks them up. Scaling out doesn't change this; it just spreads the heartbeat burden across multiple JobManagers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's Next?<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/#whats-next" class="hash-link" aria-label="Direct link to What's Next?" title="Direct link to What's Next?" translate="no">​</a></h2>
<p>You now know all the dials, from per-table settings like bucket count and freshness, through the multi-table queue dynamics, to the deployment-shape choice between one tiering job and several.
Everything you've read so far has been about how the system behaves. <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3</a> is about what you do with it.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tiering Service Deep Dive Part 1: The Mental Model]]></title>
            <link>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/</link>
            <guid>https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/</guid>
            <pubDate>Thu, 04 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-98666bf14fe08041b4b05bd8eb130e11.png" width="1636" height="512" class="img_ev3q"></p>
<p>If you're new to Fluss, the lake-tiering story is one of those topics where every explanation seems to assume you already know how it works.
This three-part walkthrough aims to bring some clarity to the confusing parts of the system, and to help you understand how it works in practice.</p>
<p><strong>Part 1</strong> builds the mental model from scratch and by the end of it you'll be able to describe, step by step, what happens between the moment a tiering timer fires and the moment a lake snapshot is committed.</p>
<p><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a> and <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3</a></strong> take that mental model and add the dials (parallelism, table kinds, freshness, multi-table behavior, scale-out) and then put it into a real production deployment (failures, pitfalls, monitoring).</p>
<p><strong>Tiering Service Deep Dive, 3-parts:</strong></p>
<ul>
<li class=""><strong>Part 1 - The Mental Model:</strong> how one tiering round actually works, from timer fire to lake commit.</li>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2 - Tuning</a>:</strong> per-table dials, multi-table dynamics, and scaling out.</li>
<li class=""><strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3 - In Production</a>:</strong> failure modes, design pitfalls, and monitoring.</li>
</ul>
<p>Fluss is bounded in capacity by design and that's the entire reason the tiering service exists.
Its speed comes from keeping data on local disks attached to a small number of machines. Local disk is finite, costs money to keep spinning, and is sized for the working set, not for a year of history.
Fluss isn't your archive, it's the freshest layer of your platform.
So old data has to leave, on a schedule, into storage designed for the long haul. <strong>That's the job of the tiering service</strong>.</p>
<p>Meanwhile, you have analysts and batch jobs and ML/AI pipelines <strong>who don't need millisecond freshness</strong>, but they need to query a year of orders, run a join over a quarter of clicks, train a model on six months of sessions.
That's the job of the data Lakehouse. Parquet files on S3, organized as open table formats (Iceberg, Paimon, Hudi, Lance), queryable by <code>Spark</code>, <code>StarRocks</code>, <code>DuckDB</code>, <code>Trino</code>, <code>Flink-batch</code> or even just <code>Python</code>.</p>
<p><strong>So you have two storage layers:</strong> the hot one (Fluss) and the cold one (the Lakehouse), <strong>exposed as one single table abstraction with different freshness layers</strong>, and you need something that moves data from one to the other on a regular schedule, in a way that's correct, atomic, and survives failures.
That something is the tiering service.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig1-3f37d3905d612d6fe9aeaa4ea1aa2aad.png" width="1185" height="710" class="img_ev3q"></p>
<p>The tiering service does three things at once.</p>
<ol>
<li class=""><strong>It keeps the Lakehouse fresh:</strong> within some configurable target like <strong>"never more than two minutes behind"</strong>.</li>
<li class=""><strong>It lets Fluss reclaim space:</strong> once data is safely in the Lakehouse, Fluss can delete its own copy.</li>
<li class=""><strong>And it does both atomically:</strong> the Lakehouse either sees a complete update or no update at all, never a half-written mess.</li>
</ol>
<p>The tiering service is just a stateless Flink <strong>(Spark support is also WIP)</strong> streaming job that runs continuously. It reads from Fluss, writes Parquet files to S3, and tells the Fluss coordinator <strong>"I committed this offset range, you can release it now."</strong></p>
<p>Everything else in this article is about the details, but if you remember <strong>"it's a stateless Flink job that copies data, on a schedule, in atomic batches,"</strong> you've got the shape of it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-processes-and-what-each-one-does">The Three Processes And What Each One Does<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-three-processes-and-what-each-one-does" class="hash-link" aria-label="Direct link to The Three Processes And What Each One Does" title="Direct link to The Three Processes And What Each One Does" translate="no">​</a></h2>
<p>Three processes are involved in tiering. They live in different places, they have different responsibilities, and they almost never overlap.
Knowing which is which is half the battle of debugging anything.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig2-83362ecdf4c606f1417c0dbd214c94dd.png" width="1189" height="588" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-coordinator">The Coordinator<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-coordinator" class="hash-link" aria-label="Direct link to The Coordinator" title="Direct link to The Coordinator" translate="no">​</a></h3>
<p>The Fluss coordinator is the brain of the cluster that handles <code>metadata</code>, <code>leader election</code>, and <code>cluster state</code>. Tiering is one of its many responsibilities.
It keeps a list of which tables need to be tiered, when each was tiered last, and which one should go next.
It does not move any data itself; it decides what runs and tracks what's in flight.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-flink-tiering-job">The Flink Tiering Job<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-flink-tiering-job" class="hash-link" aria-label="Direct link to The Flink Tiering Job" title="Direct link to The Flink Tiering Job" translate="no">​</a></h3>
<p>This is a stateless Flink streaming job that you deploy. It runs forever. Every so often, it calls the coordinator and asks <strong>"what should I work on next?"</strong>.
When the coordinator hands it a table, the Flink job reads that table's data from the Fluss tablet servers, writes it out as Parquet files in the lake, and commits a lake snapshot.
Then it reports back, <strong>"done, you can mark this table as tiered"</strong> and asks for the next one.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fluss-tablet-servers">The Fluss Tablet Servers<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-fluss-tablet-servers" class="hash-link" aria-label="Direct link to The Fluss Tablet Servers" title="Direct link to The Fluss Tablet Servers" translate="no">​</a></h3>
<p>These are the Fluss processes that actually hold your data.
The tiering job reads from them but never writes to them. They mostly don't know they're being tiered, they just serve reads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-data-lakehouse">The Data Lakehouse<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-data-lakehouse" class="hash-link" aria-label="Direct link to The Data Lakehouse" title="Direct link to The Data Lakehouse" translate="no">​</a></h3>
<p>S3, GCS, or whatever object store you're using. The open table format (Paimon, Iceberg, Hudi or Lance) defines how files and metadata are laid out so that query engines can read them.
The tiering job writes here. Nobody else touches it during tiering.</p>
<blockquote>
<p><strong>Why Split It This Way?</strong>
Separating the <strong>"who decides"</strong> from the <strong>"who moves"</strong> is a deliberate design choice. The coordinator already manages cluster state. Making it also responsible for scheduling tiering is a small addition. Putting the actual data movement in Flink means you get all of Flink's benefits for free, parallelism, checkpointing, restart-from-failure, without having to build any of it from scratch.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-heartbeat">The Heartbeat<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-heartbeat" class="hash-link" aria-label="Direct link to The Heartbeat" title="Direct link to The Heartbeat" translate="no">​</a></h2>
<p>The coordinator and the Flink tiering job are different processes on different machines.
They need a way to talk. They have exactly one channel: <strong>a single RPC called the heartbeat</strong>, sent by the Flink job to the coordinator on a configured interval (<code>tiering.poll.table.interval, default 30 seconds</code>).
Every piece of communication between them happens inside this one call.</p>
<p>That makes the heartbeat a four-purpose message:
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig3-b2b5ea80cb3b2060ab0c911e3e48a76c.png" width="1185" height="575" class="img_ev3q"></p>
<p>The Flink job's heartbeat carries:</p>
<ol>
<li class=""><strong>What it's currently working on:</strong> the list of tables it's tiering right now</li>
<li class=""><strong>What it just finished:</strong> tables whose Lakehouse commits succeeded since the last heartbeat</li>
<li class=""><strong>What failed:</strong> tables whose tiering blew up for some reason, and</li>
<li class=""><strong>"Give me another":</strong> a boolean flag asking for the next table from the queue. The coordinator's response carries either a new table assignment or a <strong>"nothing for you right now"</strong> message.</li>
</ol>
<p>Two things from this design that are worth remembering.</p>
<p><strong>First, the coordinator only learns about in-progress work at heartbeat boundaries</strong>. The heartbeat cadence is for polling, <strong>"anything new for me?</strong>"
Completion is different: when a table finishes (or fails), the enumerator triggers an immediate heartbeat rather than waiting for the next tick.
So the worst-case lag of <strong>"up to 30 seconds"</strong> applies to mid-round status, not to commit announcements.</p>
<p><strong>Second, every message has an "epoch" number stamped on it</strong>. Each time a table starts a new tiering attempt, its epoch goes up by one.
If a Flink job tries to report success with a stale epoch (because the coordinator already gave up on that attempt and handed the work to someone else), the coordinator just ignores the message.
This is how the system stays consistent even when things go sideways and we'll come back to it in <strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3</a></strong>, in the failure-modes section.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-life-of-a-table-four-states-walked-through">The Life Of A Table: Four States, Walked Through<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#the-life-of-a-table-four-states-walked-through" class="hash-link" aria-label="Direct link to The Life Of A Table: Four States, Walked Through" title="Direct link to The Life Of A Table: Four States, Walked Through" translate="no">​</a></h2>
<p>Every table that's enabled for lake tiering goes through the same lifecycle.
The coordinator tracks each table's current state. For our purposes, four states are enough to keep in your head:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig4-08d80fb474cb83b4601a82bb67c86236.png" width="1201" height="456" class="img_ev3q"></p>
<blockquote>
<p><strong>Note:</strong> The actual source code uses seven state names: <code>NEW</code>, <code>INITIALIZED</code>, <code>SCHEDULED</code>, <code>PENDING</code>, <code>TIERING</code>, <code>TIERED</code>, <code>FAILED</code>. Here we collapse them into four pedagogical states to keep the mental model small.</p>
</blockquote>
<p><strong>NEW</strong> is only used for the very first time a lake-enabled table is created, and <code>INITIALIZED</code> is only used for tables the coordinator rediscovers after a restart. Both transition into <code>SCHEDULED</code> immediately, so we ignore them here. <code>FAILED</code> is the unhappy-path state we'll come back to in <strong><a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part3/">Part 3</a></strong>.</p>
<p><strong>WAITING.</strong> The table has been tiered recently and the freshness timer is counting down. Nothing to do.</p>
<p><strong>READY.</strong> The timer has fired. The table is now in the coordinator's pending queue, waiting for a Flink job to pick it up on the next heartbeat. It might wait a few seconds, or several minutes if there are many other tables ahead of it.</p>
<p><strong>TIERING.</strong> A Flink job has picked the table up and is reading its data, writing Parquet files to the Lakehouse, and aiming for a commit. The coordinator has started a clock; if this takes too long, it'll time out.</p>
<p><strong>DONE.</strong> The Lakehouse commit landed. The coordinator records <strong>"this table was last tiered at time T"</strong> and immediately schedules the next round. The table goes back into <code>WAITING</code>.</p>
<p>The happy path is just <code>WAITING → READY → TIERING → DONE → WAITING →</code> ... forever. The unhappy path, which we'll cover in <strong>Part 3</strong>, in the failure-modes section, is when TIERING ends in failure (timeout, reader crash, anything) and the table cycles back to <code>READY</code> without ever reaching <code>DONE</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-complete-tiering-round-step-by-step">A Complete Tiering Round, Step By Step<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#a-complete-tiering-round-step-by-step" class="hash-link" aria-label="Direct link to A Complete Tiering Round, Step By Step" title="Direct link to A Complete Tiering Round, Step By Step" translate="no">​</a></h2>
<p>Three concepts are important to understand when you're reading this:</p>
<ul>
<li class=""><strong>Bucket:</strong> A horizontal partition of a Fluss table. If a table has 16 buckets, every record is hashed into one of the 16 by some key. Buckets are the unit of parallelism for both writes (different writers can land in different buckets) and reads.</li>
<li class=""><strong>Split:</strong> A unit of work for the Flink tiering job. Each split says <strong>"read offsets X to Y of bucket B"</strong>. During a tiering round, the planner generates one split per bucket of the table being tiered.</li>
<li class=""><strong>Reader:</strong> A Flink subtask that processes splits. If you set the Flink job's parallelism to 16, you have 16 readers. Each reader processes one split at a time and asks for another when it's done.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="lets-walk-through-a-single-tiering-round-with-a-concrete-example">Let's walk through a single tiering round with a concrete example.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#lets-walk-through-a-single-tiering-round-with-a-concrete-example" class="hash-link" aria-label="Direct link to Let's walk through a single tiering round with a concrete example." title="Direct link to Let's walk through a single tiering round with a concrete example." translate="no">​</a></h3>
<p>We have one table, called <code>orders</code>, with four buckets. It's a log table (we'll talk about what that means in <a class="" href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part2/">Part 2</a>, when we cover table kinds. For now, just think <strong>"an append-only stream of records"</strong>). Freshness is configured to five minutes. The Flink tiering job is up and running.</p>
<p>Here's what happens:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t0s-the-timer-fires">T+0s the timer fires.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t0s-the-timer-fires" class="hash-link" aria-label="Direct link to T+0s the timer fires." title="Direct link to T+0s the timer fires." translate="no">​</a></h4>
<p>Five minutes have passed since the last tiering round for <code>orders</code>. The coordinator transitions the table from WAITING to READY and pushes it onto the back of the pending queue. The act of entering the pending queue is also what increments the table's tiering epoch, to (say) 7, so every fresh attempt is stamped at enqueue time, before any job has picked it up. Right now the queue contains just <code>orders</code>.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t0s-the-flink-job-sends-its-heartbeat">T+0s the Flink job sends its heartbeat.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t0s-the-flink-job-sends-its-heartbeat" class="hash-link" aria-label="Direct link to T+0s the Flink job sends its heartbeat." title="Direct link to T+0s the Flink job sends its heartbeat." translate="no">​</a></h4>
<p>The job is idle, there is no work in progress, so its heartbeat says <strong>"give me something"</strong>. The coordinator pops <code>orders</code> off the queue, transitions it to <code>TIERING</code>, and replies <strong>"your next table is <code>orders</code>, epoch 7"</strong>. The epoch was already set when the table entered the queue. <code>requestTable()</code> just reads it and hands it back, it doesn't bump it again.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t0s-the-flink-job-plans-the-work">T+0s the Flink job plans the work.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t0s-the-flink-job-plans-the-work" class="hash-link" aria-label="Direct link to T+0s the Flink job plans the work." title="Direct link to T+0s the Flink job plans the work." translate="no">​</a></h4>
<p>The job's enumerator (the part of a Flink source that decides what each parallel reader will do) asks the Fluss tablet servers: <strong>"what's the latest offset for each bucket of <code>orders</code> right now?"</strong> Let's say the answers come back as <code>[bucket-0: 1,000; bucket-1: 1,200; bucket-2: 950; bucket-3: 1,100]</code>. The enumerator also looks up where the last tiering round left off; the <strong>"last committed lake offset"</strong>, let's say <code>[bucket-0: 990; bucket-1: 1,190; bucket-2: 945; bucket-3: 1,090]</code>.
The difference is the work for this round.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t0s-splits-are-created">T+0s splits are created.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t0s-splits-are-created" class="hash-link" aria-label="Direct link to T+0s splits are created." title="Direct link to T+0s splits are created." translate="no">​</a></h4>
<p>The enumerator builds four splits, one per bucket. Each split says <strong>"read from offset X to offset Y of bucket B"</strong>. It shuffles them randomly (so no reader always gets the heaviest bucket) and assigns one split to each of the four Flink readers running in parallel.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t0s-to-t60s-the-readers-work">T+0s to T+60s the readers work.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t0s-to-t60s-the-readers-work" class="hash-link" aria-label="Direct link to T+0s to T+60s the readers work." title="Direct link to T+0s to T+60s the readers work." translate="no">​</a></h4>
<p>Each reader streams its assigned offset range from a Fluss tablet server and writes the records as Parquet files into a staging area in the lake. The buckets process independently and at different speeds. When a reader finishes its split, it emits a small message saying <strong>"bucket B is done, here are the files I wrote, the last offset I read was Y"</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig5-52cf53687f29879728f45ad5d03f9d85.png" width="1184" height="715" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t60s-the-commit-operator-collects-all-four-results">T+60s the commit operator collects all four results.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t60s-the-commit-operator-collects-all-four-results" class="hash-link" aria-label="Direct link to T+60s the commit operator collects all four results." title="Direct link to T+60s the commit operator collects all four results." translate="no">​</a></h4>
<p>A dedicated Flink operator called the commit operator sits at the end of the job. In the normal path it waits for results from all four buckets, then commits them together as <strong>a single atomic lake snapshot</strong>. This gives you a clean, all-or-nothing update to the lake table. Once it has all four, it calls into the open table format and writes a snapshot that includes all the new Parquet files plus the updated per-bucket offsets. (Force-finish, which we'll meet in Part 2, in the freshness section, is the one exception; the commit operator still waits to hear back from every bucket, but readers that ran out of time emit an empty result, and the commit only includes the buckets that did produce data. Anything skipped is picked up in the next round.)</p>
<blockquote>
<p><strong>One detail that's easy to miss:</strong> the lake snapshot is durable at <code>T+60s</code>, but the coordinator hasn't transitioned the table to <code>DONE</code> yet. That happens on the next heartbeat from the enumerator.</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t60s-the-commit-operator-records-the-lake-snapshot-back-to-fluss">T+60s the commit operator records the lake snapshot back to Fluss.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t60s-the-commit-operator-records-the-lake-snapshot-back-to-fluss" class="hash-link" aria-label="Direct link to T+60s the commit operator records the lake snapshot back to Fluss." title="Direct link to T+60s the commit operator records the lake snapshot back to Fluss." translate="no">​</a></h4>
<p>Now that the lake snapshot is durable, the commit operator (<code>TieringCommitOperator</code>) records the new snapshot offsets into Fluss metadata through the coordinator. It delegates this to a helper, <code>FlussTableLakeSnapshotCommitter</code>, which implements a two-phase commit (prepare + commit) against the coordinator gateway rather than RPC'ing tablet servers directly. This lets the cluster know that data up to those offsets is safely in the lake and can be aged out of Fluss's hot storage when the time comes. Tablet servers learn about the new tier point through the standard metadata-propagation path, not via a direct RPC from the committer.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="t60s-the-commit-operator-notifies-the-enumerator">T+60s the commit operator notifies the enumerator.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#t60s-the-commit-operator-notifies-the-enumerator" class="hash-link" aria-label="Direct link to T+60s the commit operator notifies the enumerator." title="Direct link to T+60s the commit operator notifies the enumerator." translate="no">​</a></h4>
<p>Immediately after the commit, the commit operator sends a <code>FinishedTieringEvent</code> back to the source enumerator. As we said earlier, the enumerator doesn't wait for the next 30-second poll tick. It fires an out-of-band heartbeat right away, carrying <code>orders</code> in the <code>finished_tables</code> list.
The coordinator validates the epoch (still 7, all good), records the completion time, transitions <code>orders</code> to <code>DONE</code>, and immediately schedules the next round.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="scheduling-the-next-round">Scheduling the next round.<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#scheduling-the-next-round" class="hash-link" aria-label="Direct link to Scheduling the next round." title="Direct link to Scheduling the next round." translate="no">​</a></h4>
<p>The freshness timer is computed from when the round <strong>completed</strong>, not from when it started. The coordinator does <code>delay = freshness − (now − lastTieredTime)</code>, and <code>lastTieredTime</code> is set to the moment the completion heartbeat is processed. So with a five-minute freshness, the next round fires roughly five minutes after the commit, i.e. at about T+360s. This is the (intentional) reason the lake always lags by at least one full freshness interval plus one round duration.</p>
<p>That's it. Round complete. The lake now contains <code>orders</code> data up to the offsets that were current at T+0s. In another ~five minutes, the whole process repeats.</p>
<blockquote>
<p><strong>The most subtle point in this section: The "stopping offset" is decided at planning time, not at reading time.</strong> When the enumerator asks the tablet servers "what's your latest offset?" at T+0s, those answers freeze. Any records written to Fluss after T+0s but before the readers actually start working are not part of this round; they'll be picked up in the next one. This is correct behavior, but it means the lake always lags by at least one round, even if your freshness is configured aggressively.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-up">Next Up<a href="https://fluss.apache.org/blog/fluss-tiering-service-deep-dive-part1/#next-up" class="hash-link" aria-label="Direct link to Next Up" title="Direct link to Next Up" translate="no">​</a></h2>
<p>You've now got the mental model, the processes, the heartbeat conversation, the lifecycle, and what happens during one full round.
The next part takes that round and shows what changes when you start tuning the dials, and then what changes again when you stop thinking about one table at a time.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Storage Hierarchy: Hot, Remote, and Lake]]></title>
            <link>https://fluss.apache.org/blog/fluss-storage-hierarchy/</link>
            <guid>https://fluss.apache.org/blog/fluss-storage-hierarchy/</guid>
            <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-a5e97edbce82f646cbf1f9d118fd18d0.png" width="1673" height="770" class="img_ev3q"></p>
<p><strong>Apache Fluss stores data in three places:</strong> local disk on the tablet server, remote object storage like S3, and the lakehouse. Which place holds which data at any given moment, and what is responsible for moving it between them, is the foundation everything else rests on. Your capacity plan depends on it. Your latency targets depend on it. Your disaster-recovery story depends on it. So does your ability to predict, in advance, that a particular configuration change is going to fill up local disk a week later.</p>
<p>This post walks through that layering. We'll cover what each tier holds, the two background tasks that move data between them, what changes for primary-key tables, and how recovery actually works when a tablet server loses its disk.</p>
<p>By the end you should be able to look at a Fluss deployment and say, for any given record, where it lives right now and where it will live in an hour.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-tier-storage-hierarchy">The Three-Tier Storage Hierarchy<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#the-three-tier-storage-hierarchy" class="hash-link" aria-label="Direct link to The Three-Tier Storage Hierarchy" title="Direct link to The Three-Tier Storage Hierarchy" translate="no">​</a></h2>
<p><strong>Tier 1 is local disk on the tablet server.</strong> It holds the hot data: recent log segments, the full live RocksDB state for every primary-key table, and a staging view of the most recent KV snapshots (hard links to live SST files while uploads are in flight). Reads from this tier are in milliseconds.</p>
<p><strong>Tier 2 is remote object storage</strong> (S3, GCS, or similar), used for two distinct purposes that share the same <code>remote.data.dir</code> filesystem.</p>
<ul>
<li class=""><strong>First:</strong> older log segments uploaded by the <code>remote-log tiering task</code> in Fluss's native binary format, which extends local retention without growing local disk.</li>
<li class=""><strong>Second:</strong> durable KV snapshots for every primary-key table, uploaded periodically so that a tablet server can recover after disk loss.</li>
</ul>
<p>Remote log storage is <strong>enabled by default</strong>. It's controlled by <code>remote.log.task-interval-duration</code> (default <code>1min</code>), and is only disabled when that value is set to <code>0</code>. KV snapshot upload is independent of remote-log tiering and is governed by <code>kv.snapshot.interval</code> (default <code>10min</code>).</p>
<p><strong>Tier 3 is the lakehouse.</strong> Paimon, Iceberg, or Lance are holding data in analytical file formats queryable by any engine. Hudi support is in active development under <a href="https://github.com/apache/fluss/issues/3254" target="_blank" rel="noopener noreferrer" class="">FIP-24</a>. Reads from the lakehouse cost seconds.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig1-cb4800d380e8873b15e06e34d197f768.png" width="925" height="774" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-note-on-single-copy-storage">A Note On Single-Copy Storage<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#a-note-on-single-copy-storage" class="hash-link" aria-label="Direct link to A Note On Single-Copy Storage" title="Direct link to A Note On Single-Copy Storage" translate="no">​</a></h3>
<p><strong>A Fluss table is a single logical abstraction across all three tiers, each holding data at a different freshness level.</strong> Local disk has the hot, most recent data; remote object storage extends retention beyond what fits locally; the lakehouse holds the analytical projection. Across those tiers, each record has one home at a time and no tier permanently holds a second copy of what another tier already owns.</p>
<p><strong>There is one temporary exception</strong>: when lakehouse tiering is enabled, a remote log segment is only deleted once <strong>both</strong> its TTL has expired <strong>and</strong> the lakehouse has ingested it. That's a safety net against lakehouse lag, and it creates a bounded transition window where the same data exists in both Tier 2 and Tier 3, governed by <code>table.log.ttl</code> (default 7 days). Shorten the TTL if minimizing that overlap matters more to you than a long lakehouse catch-up window.</p>
<p>Once the lakehouse catches up, the remote copy is removed and the overlap closes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="log-tables-on-local-disk">Log Tables on Local Disk<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#log-tables-on-local-disk" class="hash-link" aria-label="Direct link to Log Tables on Local Disk" title="Direct link to Log Tables on Local Disk" translate="no">​</a></h2>
<p>A log table on disk is a sequence of log segments. Each segment is a <code>.log</code> file holding raw records alongside a small set of companion files: a <code>.index</code> offset index and a <code>.timeindex</code> time index for fast seek, plus per-segment writer-state snapshots used for idempotent producers. The active segment is open for appends; every other segment is immutable and named by its starting offset.</p>
<p>What happens to those sealed segments is governed by two retention controls.</p>
<ul>
<li class=""><code>table.log.ttl</code> (default 7 days) is the global retention contract for the log. It defines the maximum age of any log data in the table, regardless of which tier it currently lives on.</li>
<li class=""><code>table.log.tiered.local-segments</code> (default 2) is a count-based floor for local disk, only meaningful when remote-log tiering is on. The remote-log task keeps at least this many recent segments on local disk after upload, so consumers reading near the head don't pay an S3 round-trip for the freshest data.</li>
</ul>
<p>A segment becomes a candidate for upload <strong>the moment it is sealed and its records are below the high watermark</strong> (i.e., committed/acked). Sealing happens when the active segment hits its size threshold, fills the offset or time index, or can no longer encode records as relative offsets, and then rolls over: Fluss closes the current active segment (which becomes immutable) and opens a new one for subsequent writes. The freshly-closed segment is now something the remote-log task can pick up on its next pass. This is the same model Kafka uses for tiered storage, and it has the same operational consequence: <strong>data sitting in the active segment lives only on the local Fluss server until rollover</strong>, which means the active segment's size threshold sets an upper bound on how fresh remote-tier data can be. Anything newer than the current rollover is local-only.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-remote-log-tiering-actually-does">What remote-log Tiering Actually Does<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#what-remote-log-tiering-actually-does" class="hash-link" aria-label="Direct link to What remote-log Tiering Actually Does" title="Direct link to What remote-log Tiering Actually Does" translate="no">​</a></h3>
<p>By default, remote-log tiering is on (<code>remote.log.task-interval-duration=1min</code>) and TTL is 7 days. The remote-log task does three things on each pass:</p>
<ol>
<li class="">Uploads newly-sealed segments to S3.</li>
<li class="">Advances the local log's <code>remoteLogEndOffset</code>, which causes the local log to trim every sealed segment now in S3, keeping at least <code>table.log.tiered.local-segments</code> recent ones.</li>
<li class="">Deletes S3 segments past TTL.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig2-0c0ce637b9af3ee44ba66e1c8829a860.png" width="1058" height="553" class="img_ev3q"></p>
<p>Local disk is bounded primarily by the count-based floor, usually a handful of recent segments. The TTL value applies most visibly on the S3 side, because S3 is where data lives the longest.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="disabling-remote-tiering">Disabling Remote Tiering<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#disabling-remote-tiering" class="hash-link" aria-label="Direct link to Disabling Remote Tiering" title="Direct link to Disabling Remote Tiering" translate="no">​</a></h3>
<p>Setting <code>remote.log.task-interval-duration=0</code> opts out of Tier 2 entirely, but this comes with an additional consequence: it also disables the scheduled cleanup task itself, because that task is what runs both the upload and the segment-deletion paths. With the task disabled, <strong>nothing trims local segments</strong>. <strong>There is no automatic fallback to the lakehouse on the write path.</strong></p>
<p>The end result is <strong>unbounded local-disk growth</strong>. Eventually the tablet server runs out of disk and write batches start failing with storage exceptions.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig3-560d43bf3a05f3a854d5151695e74142.png" width="1145" height="557" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="remote-tiering-and-lakehouse-tiering-are-different-features">Remote Tiering and Lakehouse Tiering Are Different Features<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#remote-tiering-and-lakehouse-tiering-are-different-features" class="hash-link" aria-label="Direct link to Remote Tiering and Lakehouse Tiering Are Different Features" title="Direct link to Remote Tiering and Lakehouse Tiering Are Different Features" translate="no">​</a></h2>
<p>These two features are frequently conflated, which is fair because the names suggest a relationship, but they solve different problems and produce different output.</p>
<p><strong>Remote tiering</strong> is about disk economics on the tablet server. It copies raw log segments in Fluss's native binary format to S3, extending local retention without growing local disk. The tablet server can then read from S3 when a consumer requests an offset that has been trimmed locally. It's managed entirely server-side, by a background task. As a side effect, it's also <strong>the only mechanism that trims local log segments</strong>.</p>
<p><strong>Lakehouse tiering</strong> is about analytical access. It converts Fluss data into lakehouse-native formats, like ORC, Parquet, Lance and writes them to the lakehouse via an external Flink job (the Tiering Service). The output is queryable by Spark, Trino, and Flink independently of Fluss.</p>
<p>These are complementary layers. You can run any combination of them. When both are enabled, the lakehouse confirmation acts as an additional safety gate on top of TTL-based S3 deletion: a remote log segment is not expired until TTL has passed and the lake has confirmed it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="primary-key-tables">Primary-Key Tables<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#primary-key-tables" class="hash-link" aria-label="Direct link to Primary-Key Tables" title="Direct link to Primary-Key Tables" translate="no">​</a></h2>
<p>A log table has one thing on disk: the log. A primary-key table has three. They serve different roles, they live in different places, and they fail in different ways. <strong>Operating primary-key tables without seeing them as three distinct structures is one of the faster routes to a confusing production incident.</strong></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig4-67251d8ee99221e41e40e136912b7a15.png" width="1205" height="504" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="structure-1-live-rocksdb-store">Structure 1: Live RocksDB Store<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#structure-1-live-rocksdb-store" class="hash-link" aria-label="Direct link to Structure 1: Live RocksDB Store" title="Direct link to Structure 1: Live RocksDB Store" translate="no">​</a></h3>
<p>This is the current state of the table. One entry per primary key, always up to date, sitting on the tablet server's local disk inside a RocksDB instance. Every point lookup reads from here. Every upsert merges into here. The live store is created when the tablet opens and deleted only when the table is dropped.</p>
<p>Nothing moves the live store. There is no setting that puts it on S3, in the lakehouse, or anywhere else. <strong>RocksDB on local disk is where the work happens, and that's the only place it can happen.</strong></p>
<p>The role to understand here is "what serves traffic," not "what is durably stored." <strong>The live store is what serves traffic. What survives a disk loss is the snapshot in remote storage</strong>, which is Structure 2. Two roles, two copies, related data. You need local disk for the full merged state of every bucket the tablet server is responsible for; <strong>the lakehouse cannot stand in for this</strong>, and the tablet server doesn't read PK state from the lake under any circumstance.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="structure-2-kv-snapshots">Structure 2: KV Snapshots<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#structure-2-kv-snapshots" class="hash-link" aria-label="Direct link to Structure 2: KV Snapshots" title="Direct link to Structure 2: KV Snapshots" translate="no">​</a></h3>
<p>Every ten minutes by default (<code>kv.snapshot.interval=10min</code>), the tablet server takes a snapshot of the live RocksDB and writes it to remote storage. <strong>This is the system's only durable record of the table's merged state at a point in time.</strong> If the tablet server's local disk evaporates, recovery begins from the most recent snapshot and then replays the changelog forward from that snapshot's offset to reach the present. The two-stage process described in the Recovery section below.</p>
<p><strong>Step one</strong> happens locally and completes immediately. The tablet server hard-links the current RocksDB SST files into a staging directory. <strong>No bytes are copied, just new pointers to existing files.</strong> This is what lets a snapshot start instantly regardless of how large the table is, because nothing is being duplicated on disk.</p>
<p><strong>Step two</strong> is the one that actually moves data. Those files, plus a bit of metadata, get uploaded to remote storage. The remote copy is the durable one; the local staging directory is there so the uploader sees a frozen, consistent view of the files while RocksDB keeps writing and compacting underneath it. The snapshot is considered durable once the upload finishes.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig5-72683eae98d83ef5de0366b7ac6c14c4.png" width="1256" height="600" class="img_ev3q"></p>
<p>Fluss keeps the last two snapshots in remote storage by default. When a new snapshot supersedes an old one, the old one is deleted, with one guard: if anything (most commonly a long-running lakehouse tiering job on its first round) is still reading the older snapshot, a lease prevents the cleanup from removing it underneath the reader. This sounds like a detail, and it is most of the time. It becomes load-bearing the first time a large primary-key table takes longer to tier than the gap between snapshots, and the lease is what keeps the system from racing itself.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="structure-3-the-changelog">Structure 3: The Changelog<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#structure-3-the-changelog" class="hash-link" aria-label="Direct link to Structure 3: The Changelog" title="Direct link to Structure 3: The Changelog" translate="no">​</a></h3>
<p>Every upsert and every delete also gets appended to a log, in the order it happened. This log behaves exactly like a regular log table on disk, same retention rules, same tiering to remote storage, same handoff to the lakehouse.</p>
<p>Two things make the changelog different from the rest of the primary-key table.</p>
<p><strong>It grows with the number of writes, not the number of unique keys.</strong> A primary-key table that updates the same 100 keys ten million times has a small live store and an enormous changelog. RocksDB collapses by key; the log does not. This is what makes the changelog useful as a CDC feed · downstream consumers see every change in order, not just the latest value.</p>
<p><strong>Deleting old changelog segments has no effect on the live store.</strong> The live store is complete on its own; it doesn't need the log to know the current value of any key. The log is there for replay (when a tablet needs to recover) and for downstream feed (when something is reading change events). <strong>It is not a place where state lives.</strong></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig6-dede59a6b48ee15f9523f79d8b76b225.png" width="1055" height="620" class="img_ev3q"></p>
<blockquote>
<p><strong>Note:</strong> This is a simplified version of the changelog for illustrative purposes.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="recovery-independent-tracks-coupled-outcomes">Recovery: Independent Tracks, Coupled Outcomes<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#recovery-independent-tracks-coupled-outcomes" class="hash-link" aria-label="Direct link to Recovery: Independent Tracks, Coupled Outcomes" title="Direct link to Recovery: Independent Tracks, Coupled Outcomes" translate="no">​</a></h2>
<p>The snapshot upload track and the log upload track look independent from a configuration standpoint. Separate settings, separate schedulers, separate remote subdirectories. <strong>They are not independent when you actually need to recover from disk loss.</strong></p>
<p>Recovery on a fresh tablet server works in two stages. The snapshot brings the live state up to whatever point it was taken at. The changelog then replays every change since that point to catch up to the current moment.</p>
<p>If remote-log tiering is off, that changelog tail lives only on the failed tablet server's local disk, which is the disk you just lost. The snapshot, however durably stored, can only restore the state as of its own offset. Everything written since then is gone.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig7-0102a2680b8ed5bf83886b063dcfb941.png" width="898" height="547" class="img_ev3q"></p>
<p><strong>The two upload tracks are independent on the way in. The recovery story stitches them back together on the way out, and breaks if either piece is missing.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="standby-replicas">Standby Replicas<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#standby-replicas" class="hash-link" aria-label="Direct link to Standby Replicas" title="Direct link to Standby Replicas" translate="no">​</a></h2>
<p>Standby replicas are a new feature. The election machinery that designates a follower as the standby has landed (<a href="https://github.com/apache/fluss/issues/2828" target="_blank" rel="noopener noreferrer" class="">#2828</a>). The <strong>hot-standby path</strong> that keeps the standby's RocksDB current with the leader, and the bootstrap path that downloads the latest KV snapshot when a fresh server becomes a standby, are tracked by PR <a href="https://github.com/apache/fluss/pull/2835" target="_blank" rel="noopener noreferrer" class="">#2835</a>. The description below reflects the target behavior once those land.</p>
<p>Everything described so far is the cold-start path; the one that runs when no other copy of a bucket is still alive. Most production recoveries aren't cold restarts.</p>
<p><strong>Fluss replicates each bucket across multiple tablet servers</strong>: one leader handling writes, plus followers continuously tailing the same log. One of those followers is the designated <strong>standby</strong>, the replica the controller will promote on leader failure, and the one that maintains a live RocksDB kept current with the leader in near real time.</p>
<p>When the leader fails, the controller promotes the standby. <strong>The standby's live RocksDB is already current, so traffic resumes in seconds, with no S3 download and no log replay.</strong> The snapshot path still matters, it's the safety net when an entire replica set is lost at once, when a bucket gets reassigned to a brand-new tablet server, or when a fresh follower is bootstrapping into the cluster. But that path is the fallback, not the everyday failure handler.</p>
<p>This refines the framing of remote storage. Calling it the recovery substrate and the durability floor was accurate. It just isn't the recovery path you exercise most often in healthy production. <strong>The everyday path is one replica picking up where another left off</strong>, which is precisely why <strong>running with replication factor 1 in production is a bad idea, however durable your snapshots are</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig8-94c23f93ef6e10759ccd7412b6ce46cf.png" width="1056" height="647" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="combining-tiers">Combining Tiers<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#combining-tiers" class="hash-link" aria-label="Direct link to Combining Tiers" title="Direct link to Combining Tiers" translate="no">​</a></h2>
<p>There are four ways to combine <strong>remote-log tiering</strong> and <strong>Lakehouse tiering</strong>. Three are useful; one isn't.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fig9-adec46cdb170cb4f95a53dfe6e80104c.png" width="991" height="898" class="img_ev3q"></p>
<table><thead><tr><th>Remote</th><th>Lakehouse</th><th>What you get</th><th>When to use</th></tr></thead><tbody><tr><td><strong>Off</strong></td><td><strong>Off</strong></td><td>Local disk only. Bounded by physical local-disk size.</td><td><strong>Don't run this configuration in production :)</strong></td></tr><tr><td><strong>On</strong></td><td><strong>Off</strong></td><td>Production-grade log retention via S3. No analytical projection.</td><td>The most common starting point. Sensible when Fluss is the durable log for streaming consumers, not yet a streaming lakehouse. A good first step when adopting Fluss.</td></tr><tr><td><strong>Off</strong></td><td><strong>On</strong></td><td>Lakehouse works normally; local disk grows until writes start failing. The remote-log task is what trims local segments, and disabling it means nothing trims them.</td><td><strong>Don't run this configuration in production :)</strong></td></tr><tr><td><strong>On</strong></td><td><strong>On</strong></td><td>Full streaming-lakehouse setup. Logs are tiered to S3, snapshots are uploaded to S3, the Tiering Service produces the lakehouse projection, and the lake-confirmation gate stacks on top of TTL.</td><td>The configuration Fluss is designed around.</td></tr></tbody></table>
<p>The primary-key snapshot track is orthogonal to all of this. It runs on its own cadence (<code>kv.snapshot.interval</code>, default 10 minutes), writes to its own remote subdirectory (<code>/kv</code>), and is what makes primary-key tables recoverable after disk loss. <strong>Disabling remote-log tiering does not disable KV snapshot upload.</strong> Three independent tracks, three independent config keys · the configuration vocabulary does not make this obvious, but the runtime behavior does.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="closing-thoughts">Closing Thoughts<a href="https://fluss.apache.org/blog/fluss-storage-hierarchy/#closing-thoughts" class="hash-link" aria-label="Direct link to Closing Thoughts" title="Direct link to Closing Thoughts" translate="no">​</a></h2>
<p>Fluss's storage layer is structurally simple -- three tiers, two background tasks -- and the simplicity is what makes it easy to misread.</p>
<ul>
<li class=""><strong>Tier 1</strong> looks like the tier that matters, because it's the only one on the live query path.</li>
<li class=""><strong>Tier 2</strong> looks like an implementation detail, because it's <strong>"just S3"</strong>.</li>
<li class=""><strong>Tier 3</strong> looks like a destination, because it's the lakehouse.</li>
</ul>
<p>Each shortcut is wrong in a way that only becomes visible after you've configured something based on it.</p>
<p>The model here is: three tiers with three different jobs, two background tasks that should be reasoned about independently, a small set of defaults deliberately tuned for production.</p>
<p>Disabling those defaults is almost always the wrong move. Tuning them to your workload is almost always the right one. 🌊</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Apache Fluss Achieves True Pruning in Streaming Storage]]></title>
            <link>https://fluss.apache.org/blog/column-pruning-streaming-storage/</link>
            <guid>https://fluss.apache.org/blog/column-pruning-streaming-storage/</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-e29da45a50e48f984bd05e9ad72f4a67.png" width="1099" height="623" class="img_ev3q"></p>
<p><strong>TL;DR:</strong></p>
<blockquote>
<p>Apache Kafka's "column pruning" is actually pseudo-pruning. All fields still cross the network, and clients discard unwanted ones after the fact.
Apache Fluss redesigns the storage format, server-side read path, and write-side batching strategy from the ground up with Arrow IPC columnar storage, zero-copy server-side pruning, and client-side pre-shuffle batching.
The result: pruning 90% of columns yields a 10x read throughput improvement, with performance scaling linearly with the pruning ratio.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-column-pruning-why-does-it-matter">What Is Column Pruning? Why Does It Matter?<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#what-is-column-pruning-why-does-it-matter" class="hash-link" aria-label="Direct link to What Is Column Pruning? Why Does It Matter?" title="Direct link to What Is Column Pruning? Why Does It Matter?" translate="no">​</a></h2>
<p>In real-time big data processing, data often contains dozens or even hundreds of fields, yet downstream applications typically only need a small subset. <strong>Column pruning</strong> means reading only the columns a query requires, skipping irrelevant ones to reduce disk I/O, network transfer, and computation overhead.</p>
<p>For example, consider a table with columns <code>a</code>, <code>b</code>, <code>c</code>, <code>d</code> and the following query:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> a </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> t </span><span class="token keyword" style="color:#194670">WHERE</span><span class="token plain"> b </span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>This query only uses columns <code>a</code> and <code>b</code>. Columns <code>c</code> and <code>d</code> are entirely unnecessary. If the storage engine can skip <code>c</code> and <code>d</code> during reads, it dramatically reduces the data that needs to be read from disk and sent over the network. That is column pruning.</p>
<p>Column pruning has long been widely adopted in batch processing systems (e.g., Hive, Spark SQL with Parquet or ORC) with significant results. But in the streaming domain, it has always been a hard problem because mainstream messaging systems like Kafka have no native concept of columnar storage.</p>
<p><img decoding="async" loading="lazy" alt="Wide table with hundreds of columns, showing that downstream queries typically access only a small subset" src="https://fluss.apache.org/assets/images/figure1-956a79cf73c8f35dd7d1b8e13e4578fd.png" width="1400" height="576" class="img_ev3q"></p>
<p>In production, a wide table may have hundreds of columns, while a downstream query typically uses only 5–10. In such cases, column pruning can reduce data transfer by an order of magnitude.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-cant-apache-kafka-do-column-pruning">Why Can't Apache Kafka Do Column Pruning?<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#why-cant-apache-kafka-do-column-pruning" class="hash-link" aria-label="Direct link to Why Can't Apache Kafka Do Column Pruning?" title="Direct link to Why Can't Apache Kafka Do Column Pruning?" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-kafka-broker-has-no-schema-awareness">The Kafka Broker Has No Schema Awareness<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#the-kafka-broker-has-no-schema-awareness" class="hash-link" aria-label="Direct link to The Kafka Broker Has No Schema Awareness" title="Direct link to The Kafka Broker Has No Schema Awareness" translate="no">​</a></h3>
<p>Apache Kafka's broker treats records as opaque byte arrays and has no understanding of the data structure within. Schema parsing relies on client-specified formats such as JSON or Avro. This means that even if a consumer tells the broker "I only need columns <code>a</code> and <code>b</code>," the broker cannot understand or act on that request.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kafka-uses-row-based-storage">Kafka Uses Row-Based Storage<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#kafka-uses-row-based-storage" class="hash-link" aria-label="Direct link to Kafka Uses Row-Based Storage" title="Direct link to Kafka Uses Row-Based Storage" translate="no">​</a></h3>
<p>Kafka stores each message as a contiguous byte sequence containing all fields. Because all fields are packed together, there is no way to skip a subset of columns during a fetch. The entire message must be read and transferred.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pseudo-pruning-pruning-happens-on-the-client-not-the-server">"Pseudo-Pruning": Pruning Happens on the Client, Not the Server<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#pseudo-pruning-pruning-happens-on-the-client-not-the-server" class="hash-link" aria-label="Direct link to &quot;Pseudo-Pruning&quot;: Pruning Happens on the Client, Not the Server" title="Direct link to &quot;Pseudo-Pruning&quot;: Pruning Happens on the Client, Not the Server" translate="no">​</a></h3>
<p>The current Flink and Kafka column pruning implementation runs entirely at the Flink Source (client side). The Kafka broker still sends the full record to the consumer, which then discards irrelevant columns through field mapping during deserialization.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing that Kafka sends full records to the client, which then filters columns during deserialization" src="https://fluss.apache.org/assets/images/figure2-8562bd1a9b36168c2d5f6ba400495ebd.png" width="1400" height="456" class="img_ev3q">
<em>Figure 1: Kafka's "Pseudo-Pruning" -- pruning happens on the client side, not saving any network I/O</em></p>
<p>As shown above, the entire pruning process can be summarized as: the Kafka broker sends full records to the Flink Source, which prunes fields row by row during deserialization, then emits the pruned output.</p>
<p>The most expensive step, the network transfer from broker to consumer, is not optimized at all. All columns are still transmitted over the network; pruning merely filters them out at the destination.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Summary</div><div class="admonitionContent_BuS1"><p>Kafka's column pruning is pseudo-pruning. It operates on the client side and saves no broker-to-client network I/O, which is typically the dominant cost. Combined with the row-oriented storage model, even client-side pruning requires parsing every record in full before discarding irrelevant fields, making it highly inefficient for wide-table workloads.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-challenges-of-column-pruning-in-streaming-storage">Core Challenges of Column Pruning in Streaming Storage<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#core-challenges-of-column-pruning-in-streaming-storage" class="hash-link" aria-label="Direct link to Core Challenges of Column Pruning in Streaming Storage" title="Direct link to Core Challenges of Column Pruning in Streaming Storage" translate="no">​</a></h2>
<p>Introducing column pruning into streaming storage is fundamentally different from doing so in traditional OLAP or data lake systems. OLAP systems process bounded data and can perform extensive sorting and encoding optimizations after writes are complete. Streaming storage, by contrast, faces continuous-append and real-time consumption workloads with strict latency and throughput requirements. This creates three distinct challenges.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenge-1-column-pruning-vs-streaming-writes-a-fundamental-storage-layout-conflict">Challenge 1: Column Pruning vs. Streaming Writes, a Fundamental Storage Layout Conflict<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#challenge-1-column-pruning-vs-streaming-writes-a-fundamental-storage-layout-conflict" class="hash-link" aria-label="Direct link to Challenge 1: Column Pruning vs. Streaming Writes, a Fundamental Storage Layout Conflict" title="Direct link to Challenge 1: Column Pruning vs. Streaming Writes, a Fundamental Storage Layout Conflict" translate="no">​</a></h3>
<p>Efficient column pruning requires columnar storage, where data for each column is stored contiguously so that irrelevant columns can be skipped entirely during reads. But streaming storage systems are inherently row-append-based: data arrives record by record, each containing all fields, and must be persisted promptly. This creates a fundamental contradiction: <strong>data arrives in rows but must ideally be stored in columns</strong>.</p>
<p>During Fluss's evolution, we explored a compromise: the <strong>Indexed Row</strong> format. It is a row-based format with field-offset indexes in each record's header, allowing direct jumps to target fields without parsing the entire record. The results were disappointing. Indexed Row is still fundamentally row storage: all fields within each record are packed together, so reads still require row-by-row scanning. Its performance gains in column pruning scenarios were minimal, and this approach was ultimately a dead end.</p>
<p>The real solution is to adopt a true columnar storage format. The most widely adopted columnar file format in batch analytics is Parquet. However, Parquet is designed entirely for offline batch processing and poses a fundamental problem for streaming reads. Parquet requires all row groups to be written before the file footer (which contains the column offset index) can be emitted, meaning a Parquet file can only be consumed after it is completely closed. This makes sub-second streaming latency impossible.</p>
<p><img decoding="async" loading="lazy" alt="Diagram illustrating the streaming read dilemma with Parquet: large batch writes are needed before the file can be read" src="https://fluss.apache.org/assets/images/figure3-9ad9e3d45b0f05667f04a63a528c29bb.png" width="1400" height="548" class="img_ev3q">
<em>Figure 2: The Streaming Read Dilemma of Parquet -- batch write vs. frequent close</em></p>
<p>This is the first fundamental challenge of column pruning in streaming storage: <strong>existing columnar formats are designed for batch processing and cannot simultaneously satisfy low-latency streaming consumption and efficient columnar storage</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenge-2-eliminating-server-side-pruning-overhead">Challenge 2: Eliminating Server-Side Pruning Overhead<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#challenge-2-eliminating-server-side-pruning-overhead" class="hash-link" aria-label="Direct link to Challenge 2: Eliminating Server-Side Pruning Overhead" title="Direct link to Challenge 2: Eliminating Server-Side Pruning Overhead" translate="no">​</a></h3>
<p>How can the server perform column pruning without incurring additional computation and memory overhead?</p>
<p>The traditional approach is a full deserialize-prune-reserialize cycle on the server: upon receiving a read request, the server deserializes all data from disk into memory, performs column pruning in memory, then reserializes and sends the pruned data to the consumer. This is feasible for low-frequency query systems, but a streaming storage server is a shared resource serving many concurrent consumers. Each read request that triggers a full decode-prune-encode cycle adds 2–5x memory amplification under load.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing the overhead of traditional server-side column pruning: deserialize, prune, then re-serialize" src="https://fluss.apache.org/assets/images/figure4-c1ed88c935387c8b3dd4798d47dec4a3.png" width="1400" height="664" class="img_ev3q">
<em>Figure 3: Overhead of Traditional Server-Side Column Pruning -- deserialize, prune, re-serialize</em></p>
<p>This is the second core challenge: <strong>a streaming storage server needs a zero-overhead column pruning approach: no data parsing, no extra memory allocation, just metadata and byte-offset manipulation</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenge-3-the-tension-between-low-latency-writes-and-columnar-batching">Challenge 3: The Tension Between Low-Latency Writes and Columnar Batching<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#challenge-3-the-tension-between-low-latency-writes-and-columnar-batching" class="hash-link" aria-label="Direct link to Challenge 3: The Tension Between Low-Latency Writes and Columnar Batching" title="Direct link to Challenge 3: The Tension Between Low-Latency Writes and Columnar Batching" translate="no">​</a></h3>
<p>Columnar storage efficiency depends heavily on batch size. Larger batches mean more contiguous data per column, better compression ratios, and more efficient bulk reads. But streaming storage has strict write latency requirements (typically sub-100 ms), making indefinite batching impractical.</p>
<p>The problem is compounded by data distribution. In a typical Flink write scenario, multiple Sink operator instances distribute records across all Buckets via round-robin. With N Sink instances and M Buckets, this creates N×M connections, each carrying a small fraction of the total data.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing how round-robin distribution fragments data into small per-bucket batches" src="https://fluss.apache.org/assets/images/figure5-f1519edd528b601957c49309e7e0b0ad.png" width="1400" height="412" class="img_ev3q">
<em>Figure 4: Round-Robin distribution causes fragmented small batches</em></p>
<p>Such fragmented small batches are devastating for columnar storage: too few rows per column render compression algorithms nearly useless, column contiguity is lost, and storage efficiency can degrade below even row storage. This is the third core challenge: <strong>the fundamental tension between streaming storage's low-latency write requirements and columnar storage's dependence on large batches, further amplified by round-robin data distribution</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-does-fluss-implement-column-pruning">How Does Fluss Implement Column Pruning?<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#how-does-fluss-implement-column-pruning" class="hash-link" aria-label="Direct link to How Does Fluss Implement Column Pruning?" title="Direct link to How Does Fluss Implement Column Pruning?" translate="no">​</a></h2>
<p>With the above challenges understood, let's see how Apache Fluss addresses them. Column pruning is designed as a core feature from the ground up, with end-to-end solutions spanning the storage format, the server-side read path, and the client-side write pipeline. Compared to Kafka's pseudo-pruning, Fluss achieves three key breakthroughs:</p>
<p><img decoding="async" loading="lazy" alt="Overview diagram of Fluss&amp;#39;s three key design breakthroughs for column pruning" src="https://fluss.apache.org/assets/images/figure6-5d9969ae43adcf64b140bb2c2e6cfad6.png" width="1400" height="799" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-1-streaming-columnar-storage-format-with-apache-arrow">Key Design 1: Streaming Columnar Storage Format with Apache Arrow<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#key-design-1-streaming-columnar-storage-format-with-apache-arrow" class="hash-link" aria-label="Direct link to Key Design 1: Streaming Columnar Storage Format with Apache Arrow" title="Direct link to Key Design 1: Streaming Columnar Storage Format with Apache Arrow" translate="no">​</a></h3>
<p>Challenge 1 surfaced two requirements: the storage format must be columnar (unlike Kafka's row storage), and column metadata must be inlined with each batch (unlike Parquet's file-level footer). Fluss chose the <strong>Apache Arrow IPC Streaming Format</strong> as the on-disk format for log segments, satisfying both requirements simultaneously.</p>
<p>A Fluss Log Segment consists of sequentially appended RecordBatches, each ranging from 100 KB to several MB. Each RecordBatch has two parts:</p>
<ul>
<li class=""><strong>Fluss Header:</strong> contains <code>magic</code>, <code>schemaId</code>, <code>batchLen</code>, <code>rowCount</code>, and other fields for quick location and validation.</li>
<li class=""><strong>Arrow RecordBatch:</strong> follows the standard Arrow IPC format, with a leading metadata section (FlatBuffers-encoded, recording each column's byte offset, length, and FieldNode) followed by contiguous column Buffer data.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Diagram of the RecordBatch layout in a Fluss Log Segment showing the Fluss Header and Arrow RecordBatch structure" src="https://fluss.apache.org/assets/images/figure7-710c2c9b9253c3f73f1ef59be5f7e189.png" width="1400" height="664" class="img_ev3q">
<em>Figure 5: RecordBatch Layout in Fluss Log Segment</em></p>
<p>This design addresses both challenges directly. Because data for the same column is stored contiguously within each RecordBatch, reading a specific column can skip all other columns' disk regions entirely, with no row-by-row scanning. And because each RecordBatch carries its own complete Arrow metadata, it can be consumed independently as soon as it is written, with no need to wait for file closure. Streaming consumers can read batch by batch with millisecond-level latency.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-2-end-to-end-zero-copy-column-pruning">Key Design 2: End-to-End Zero-Copy Column Pruning<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#key-design-2-end-to-end-zero-copy-column-pruning" class="hash-link" aria-label="Direct link to Key Design 2: End-to-End Zero-Copy Column Pruning" title="Direct link to Key Design 2: End-to-End Zero-Copy Column Pruning" translate="no">​</a></h3>
<p>Fluss leverages Arrow's buffer layout to achieve true end-to-end zero-copy column pruning: target column data travels from disk to the network without ever entering user space.</p>
<p>When the server receives a column-pruning read request, the processing flow is:</p>
<ol>
<li class=""><strong>Read Arrow metadata:</strong> The RecordBatch header records each column's byte offset and length within the file.</li>
<li class=""><strong>Reconstruct the buffer descriptor:</strong> Build new metadata referencing only the target columns' FieldNodes and Buffers, without reading any column data from disk.</li>
<li class=""><strong>Send directly to the network:</strong> Transfer the new metadata and the target columns' raw byte ranges from the page cache to the network interface card (NIC), bypassing user space entirely.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Diagram of Fluss zero-copy column pruning showing data flowing directly from disk page cache to NIC without user-space processing" src="https://fluss.apache.org/assets/images/figure8-67f878be44347ae258843cbb69d86f04.png" width="1400" height="669" class="img_ev3q">
<em>Figure 6: Fluss Zero-Copy Column Pruning -- from disk to NIC without user-space data parsing</em></p>
<p>Throughout this process, the server neither deserializes nor reserializes any data. It reassembles buffer references based on Arrow metadata and transfers the target columns' bytes directly to the network. This is the fundamental reason Fluss's column pruning performance scales linearly with the pruning ratio.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-design-3-client-side-pre-shuffle-for-large-batches">Key Design 3: Client-Side Pre-Shuffle for Large Batches<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#key-design-3-client-side-pre-shuffle-for-large-batches" class="hash-link" aria-label="Direct link to Key Design 3: Client-Side Pre-Shuffle for Large Batches" title="Direct link to Key Design 3: Client-Side Pre-Shuffle for Large Batches" translate="no">​</a></h3>
<p>To address the batching dilemma from Challenge 3, Fluss performs a pre-shuffle (client-side data partitioning) that consolidates records destined for the same bucket before they are sent to the server, transforming fragmented small batches into large contiguous ones. Fluss implements three strategies for this:</p>
<p><strong>Sticky Bucket Assigner:</strong> For data without an explicit Bucket Key, Sinks can freely choose which Bucket to write to. The Sticky Bucket Assigner keeps writing to one Bucket until the batch is full (default: 2 MB), then switches to the next Bucket. This maximizes the number of rows per batch before any Bucket transitions.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing the Sticky Bucket Assigner filling one bucket completely before moving to the next" src="https://fluss.apache.org/assets/images/figure9-814a33b620eb27b991efaad0230542c0.png" width="1400" height="553" class="img_ev3q">
<em>Figure 7: Sticky Bucket Assigner -- fill one Bucket before switching to the next</em></p>
<p><strong>Dynamic Shuffle Sink:</strong> For partitioned tables with many partitions (e.g., hundreds), the write-side buffer is spread thinly across all partitions, causing each partition's batch to hold as little as a single row. Dynamic Shuffle Sink analyzes partition traffic at runtime and allocates Sink parallelism proportionally, so each Sink instance writes to as few partitions as possible, maximizing per-partition batch size.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing Dynamic Shuffle Sink allocating sink instances proportionally by partition traffic weight" src="https://fluss.apache.org/assets/images/figure10-7688c8245dc96e0f2c13777dc1907e4a.png" width="1400" height="559" class="img_ev3q">
<em>Figure 8: Dynamic Shuffle Sink -- allocate Sinks by partition traffic weight</em></p>
<p><strong>Bucket Shuffle:</strong> When a table defines a Bucket Key, each record must be routed to its corresponding Bucket, forcing Sinks to fan out to all Buckets simultaneously. Bucket Shuffle performs the partitioning on the Flink side using the Bucket Key, concentrating data for the same Bucket at a single Sink operator instance before it reaches Fluss, eliminating the fan-out fragmentation.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing Bucket Shuffle pre-partitioning records by bucket key on the Flink side before writing to Fluss" src="https://fluss.apache.org/assets/images/figure11-b6ec17d87f7c7119a31ef856f62516c0.png" width="1400" height="562" class="img_ev3q">
<em>Figure 9: Bucket Shuffle -- each record must be written to its corresponding Bucket</em></p>
<p>Together, these strategies ensure that within the target write latency (default: 100 ms), each batch contains enough rows that same-column data is stored contiguously on disk and can be fetched in efficient bulk reads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="putting-it-all-together">Putting It All Together<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#putting-it-all-together" class="hash-link" aria-label="Direct link to Putting It All Together" title="Direct link to Putting It All Together" translate="no">​</a></h3>
<p>Fluss's column pruning is not a single-point optimization but an end-to-end solution spanning the on-disk storage format, the server-side read path, and the client-side write pipeline.</p>
<p><img decoding="async" loading="lazy" alt="Summary diagram showing how Fluss&amp;#39;s three key designs address each of the three core column pruning challenges" src="https://fluss.apache.org/assets/images/figure12-8ddd8d78876f695e248ee27a955da5a9.png" width="1400" height="569" class="img_ev3q">
<em>Figure 10: How Fluss solves the Core Challenges of Column Pruning in Streaming Storage</em></p>
<p>Because each layer addresses exactly its corresponding challenge, column pruning performance scales linearly with the pruning ratio. Pruning 50% of columns yields approximately 2x read throughput improvement; pruning 90% of columns yields approximately 10x.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="performance">Performance<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#performance" class="hash-link" aria-label="Direct link to Performance" title="Direct link to Performance" translate="no">​</a></h2>
<p>Read throughput improves linearly with the column pruning ratio. When 90% of columns are pruned, read throughput increases by 10x. Because pruning happens server-side, the pruned columns are never read from disk and never transmitted over the network, so the throughput gain is roughly proportional to the pruning ratio. Kafka's throughput, by contrast, remains constant regardless of how many columns the consumer requests.</p>
<p><img decoding="async" loading="lazy" alt="Chart showing Fluss column pruning streaming read performance scaling linearly compared to Kafka&amp;#39;s constant throughput" src="https://fluss.apache.org/assets/images/figure13-1c6c246506dc28d30ff45ea8a01077aa.png" width="1400" height="728" class="img_ev3q">
<em>Figure 11: Column Pruning Streaming Read Performance -- Fluss vs. Kafka</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="test-setup">Test Setup<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#test-setup" class="hash-link" aria-label="Direct link to Test Setup" title="Direct link to Test Setup" translate="no">​</a></h3>
<p>The benchmark uses the <a href="https://github.com/openmessaging/benchmark" target="_blank" rel="noopener noreferrer" class="">Open Message Benchmark (OMB)</a> dataset, which is widely used in the messaging systems community. The schema has 20 columns covering Integer, Long, and String types, representing a typical wide-table read scenario. Each record is 2 KB. For the test, 100 GB of data was pre-loaded onto the server with writes stopped, and remote tiering was disabled, so all reads are served entirely from the TabletServer's local disk.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Bar chart comparing Fluss vs Kafka read TPS across different column pruning ratios, showing Fluss performance increasing linearly" src="https://fluss.apache.org/assets/images/figure14-110f0c9bdeba52908eda8b25b6c58c1f.png" width="1400" height="1228" class="img_ev3q">
<em>Figure 12: Fluss vs. Kafka -- Column Pruning Read TPS Comparison</em></p>
<p>Kafka's throughput remains flat at 117K records/s across all pruning ratios because the broker performs no server-side column pruning. Fluss's throughput improves approximately linearly with the pruning ratio, confirming the effectiveness of zero-copy server-side pruning.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://fluss.apache.org/blog/column-pruning-streaming-storage/#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">Apache Fluss</a></li>
<li class=""><a href="https://arrow.apache.org/docs/format/IPC.html" target="_blank" rel="noopener noreferrer" class="">Apache Arrow IPC Streaming Format</a></li>
<li class=""><a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">Sticky Bucket Assigner (Source Code)</a></li>
<li class=""><a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">Dynamic Shuffle Sink for Partitioned Tables</a></li>
<li class=""><a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">Bucket Shuffle PR</a></li>
<li class=""><a href="https://github.com/openmessaging/benchmark" target="_blank" rel="noopener noreferrer" class="">Open Message Benchmark (OMB)</a></li>
</ul>
<hr>
<p>If you found this interesting, consider exploring the <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">Apache Fluss documentation</a> or giving the project a star on <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">GitHub</a>. Community contributions, feedback, and bug reports are always welcome.</p>]]></content:encoded>
            <category>fluss</category>
            <category>column-pruning</category>
            <category>Arrow</category>
            <category>streaming</category>
            <category>kafka</category>
            <category>performance</category>
        </item>
        <item>
            <title><![CDATA[Taobao Instant Commerce: Real-Time Decisions at Scale with Apache Fluss]]></title>
            <link>https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/</link>
            <guid>https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/</guid>
            <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Every autumn in China, social media floods with posts about "The First Cup of Milk Tea in Autumn." With a tap on their phone, consumers expect their order delivered within 30 minutes. That effortless experience is no accident: it is the result of Taobao Instant Commerce making thousands of data-driven decisions every second.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/banner-8d98ad1a7ee1c477949be609a3f8ae97.jpg" width="1376" height="768" class="img_ev3q"></p>
<p>Every autumn in China, social media floods with posts about "The First Cup of Milk Tea in Autumn." With a tap on their phone, consumers expect their order delivered within 30 minutes. That effortless experience is no accident: it is the result of Taobao Instant Commerce making thousands of data-driven decisions every second.</p>
<p>Taobao Instant Commerce has scaled from a single-category food delivery service into a high-frequency platform spanning fresh produce, consumer electronics (3C), and beauty products. It operates under two very different modes: steady high-frequency daily transactions, and explosive traffic surges during promotional events where order volumes can multiply within minutes. Both demand the same thing: real-time responsiveness across hundreds of millions of SKUs.</p>
<p><strong>Real-time</strong> is not a nice-to-have here; it is the lifeline for three critical functions:</p>
<ul>
<li class=""><strong>Operations:</strong> Refresh conversion rates and funnels within 30 seconds.</li>
<li class=""><strong>Algorithms:</strong> Order prediction models must iterate at minute-level granularity.</li>
<li class=""><strong>Quality Assurance:</strong> Canary release anomalies must be detected within seconds and trigger instant alerts.</li>
</ul>
<p>The existing pipeline (built on Kafka, Flink, Paimon, and StarRocks) handled this at one scale.</p>
<blockquote>
<p><strong>Note:</strong> In Alibaba's internal infrastructure, <strong>TT (TimeTunnel)</strong> is the internal equivalent of Apache Kafka — a high-throughput distributed message queue. Throughout this post, "Kafka" refers to TT in the Taobao Instant Commerce context. But as the business grew, three fundamental bottlenecks emerged: unbounded state growth from stream joins, mounting complexity in building multi-stream denormalized tables, and excessive resource consumption from lakehouse synchronization. Together they formed an <strong>impossible triangle</strong>: no matter how the team tuned the system, latency, consistency, and cost could not all be optimized at once.</p>
</blockquote>
<p>Fluss broke this impasse. By replacing the fragmented stream-batch architecture with a unified storage layer, its features (Delta Join, Partial Update, Streaming-Lakehouse Unification, Column Pruning, and Auto-Increment Columns) systematically eliminated all three bottlenecks and fundamentally reshaped how Taobao Instant Commerce handles real-time decision-making at scale.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-impossible-triangle-dilemma-business-pain-points--technical-challenges">The "Impossible Triangle" Dilemma: Business Pain Points &amp; Technical Challenges<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#the-impossible-triangle-dilemma-business-pain-points--technical-challenges" class="hash-link" aria-label="Direct link to The &quot;Impossible Triangle&quot; Dilemma: Business Pain Points &amp; Technical Challenges" title="Direct link to The &quot;Impossible Triangle&quot; Dilemma: Business Pain Points &amp; Technical Challenges" translate="no">​</a></h2>
<p>The real-time data system of Taobao Instant Commerce must reliably process ultra-large-scale, high-concurrency data streams.
It must also build real-time denormalized tables (wide tables) that aggregate metrics across multiple business domains, and support core pipelines such as associating page view streams with order streams, as well as canary release monitoring.
This imposes extreme requirements on latency, consistency, cost-efficiency, and system scalability.</p>
<p>Under the traditional technology stack, challenges across these four dimensions were deeply intertwined, ultimately forming the same trilemma of latency, consistency, and cost that the business had already identified.</p>
<p>The root cause lies in the stream-batch separation at the storage layer: Kafka serving as the message queue, represents the "stream" abstraction, while Paimon, as the lakehouse format, represents the "batch" abstraction.
Bridging these two relies heavily on numerous Flink ETL jobs acting as a <strong>glue layer.</strong>
Each additional layer of glue compounds latency, increases costs, and heightens the risk to data consistency.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="four-core-business-issues">Four Core Business Issues<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#four-core-business-issues" class="hash-link" aria-label="Direct link to Four Core Business Issues" title="Direct link to Four Core Business Issues" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/issues-8f1bfcdd61f876151104cb8ad07c72a9.png" width="1988" height="922" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-core-pain-points-of-the-traditional-tech-stack">Three Core Pain Points of the Traditional Tech Stack<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#three-core-pain-points-of-the-traditional-tech-stack" class="hash-link" aria-label="Direct link to Three Core Pain Points of the Traditional Tech Stack" title="Direct link to Three Core Pain Points of the Traditional Tech Stack" translate="no">​</a></h3>
<p>The traditional pipeline based on Kafka, Flink and Paimon exposed three critical pain points under Taobao Instant Commerce’s massive scale, high concurrency, and strict real-time requirements.
These issues became the core focus areas for the subsequent Fluss implementation:</p>
<ul>
<li class=""><strong>Surging Memory Pressure from Dual-Stream Joins:</strong> Kafka lacks native support for dimension table lookups. Consequently, order information had to be fully loaded into Flink <code>State</code>. During promotional events, over <strong>100 million</strong> orders caused the state size of single jobs to surge to <strong>hundreds of GBs</strong>, leading to <code>Checkpoint</code> timeouts and frequent task failures.</li>
<li class=""><strong>Fan-Out Pipeline Complexity in Wide Table Construction:</strong> The product-store analysis wide table depended on 5+ upstream systems. Using Kafka required writing to a new Topic after every Join operation, resulting in a sprawling, tightly coupled pipeline with high operational and maintenance costs.</li>
<li class=""><strong>Core Resource Drain from Lakehouse Synchronization:</strong> Each table synced to Paimon required maintaining an independent Flink consumption job. During peak events, dozens of these "data transfer" jobs ran simultaneously, competing with core computing tasks for resources and causing overall performance degradation.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/arch-0290889aab197b1d65f26605420ae7e4.png" width="1224" height="746" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-selection-logic-from-compute-layer-stitching-to-storage-layer-unification">Architecture Selection Logic: From "Compute-Layer Stitching" to "Storage-Layer Unification"<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-selection-logic-from-compute-layer-stitching-to-storage-layer-unification" class="hash-link" aria-label="Direct link to Architecture Selection Logic: From &quot;Compute-Layer Stitching&quot; to &quot;Storage-Layer Unification&quot;" title="Direct link to Architecture Selection Logic: From &quot;Compute-Layer Stitching&quot; to &quot;Storage-Layer Unification&quot;" translate="no">​</a></h3>
<p>Guided by the <strong>impossible triangle</strong> dilemma described above, we established a core principle for our technology selection: the breakthrough lies not in the compute layer, but in the storage layer.
The traditional architecture, relying on the combination of Kafka, Flink and Paimon as the open table format, was essentially a <strong>stitched</strong> model characterized by stream-batch separation.
As the compute engine, Flink was forced to assume numerous <strong>glue</strong> responsibilities: maintaining <strong>TB-scale</strong> <code>State</code>, scheduling dozens of data transfer jobs, and handling memory bloat from dual-stream joins.
This not only drove up resource costs but also made data consistency difficult to guarantee.</p>
<p>Fluss’s design philosophy directly addresses this pain point through Stream-Batch Unification at the storage layer.
It is not merely another message queue; rather, it converges streaming consumption and batch querying into a single distributed storage kernel.
Streaming and batch operations require no data replication, no format conversion, and no additional ETL jobs for data movement.
This fundamental shift in underlying architecture directly eliminates the three primary sources of latency, cost, and consistency risks inherent in traditional pipelines, providing a unified foundation for the implementation of subsequent core features.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fluss-core-scenario-implementation">Fluss Core Scenario Implementation<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#fluss-core-scenario-implementation" class="hash-link" aria-label="Direct link to Fluss Core Scenario Implementation" title="Direct link to Fluss Core Scenario Implementation" translate="no">​</a></h2>
<p>Taobao Instant Commerce designed a three-layer architecture underpinned by five core Fluss capabilities, each mapped to a specific business need. By establishing Fluss as the unified data foundation, this architecture covers the entire pipeline, from data ingestion and stream-batch processing to lakehouse storage and service delivery, eliminating the pain points described above.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-layer-overall-architecture">Three-Layer Overall Architecture<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#three-layer-overall-architecture" class="hash-link" aria-label="Direct link to Three-Layer Overall Architecture" title="Direct link to Three-Layer Overall Architecture" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/3_layer_arch-e9939c0960c89e5fbccbf9806418db5f.png" width="1080" height="605" class="img_ev3q">
The real-time data system of Taobao Instant Commerce, powered by Fluss, is structured into three layers: the Data Source Layer, the Fluss Storage &amp; Compute Layer, and the Business Service Layer. Built on the core philosophy of Stream-Batch Unification, this architecture enables <strong>Write Once, Read Anywhere</strong> data reuse, completely eliminating the stream-batch separation and glue-layer ETL jobs inherent in traditional pipelines.</p>
<ul>
<li class=""><strong>Data Source Layer:</strong> Integrates all business data sources (including order streams, visit streams, product dimension tables, and inventory/price streams) and uniformly writes them into Fluss’s Log Tables or KV Tables, adapting to the specific ingestion characteristics of each data source.</li>
<li class=""><strong>Fluss Storage &amp; Compute Layer:</strong> As the core layer, it achieves unified data storage via Fluss’s Log/KV Tables. It leverages features like Delta Join, Partial Update, and Streaming-Lakehouse Unification (Auto-Tiering) to handle real-time computation, multi-table joins, wide table construction, and lakehouse synchronization. Crucially, it provides stateless computing support for Flink, significantly reducing state management overhead.</li>
<li class=""><strong>Business Service Layer:</strong> Leveraging a unified data view from both Fluss and lakehouse storage (Paimon), this layer delivers data services to operational real-time dashboards, algorithmic order prediction models, canary monitoring systems, and product-store analysis systems, enabling decision support within seconds.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="five-core-scenarios-and-feature-combination-matrix">Five Core Scenarios and Feature Combination Matrix<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#five-core-scenarios-and-feature-combination-matrix" class="hash-link" aria-label="Direct link to Five Core Scenarios and Feature Combination Matrix" title="Direct link to Five Core Scenarios and Feature Combination Matrix" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/matrix1-06469c8619fc13548b69ef3d251c4358.png" width="1968" height="1048" class="img_ev3q">
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/matrix2-ff8075a31b6f7ff2aef3ad6348c06f82.png" width="1970" height="1360" class="img_ev3q"></p>
<p>The following sections walk through five production scenarios where Fluss was applied, covering the architectural decisions, implementation details, and measured results for each.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-1-real-time-conversion-rate-and-order-attribution-solving-unbounded-state-growth-in-dual-stream-joins-with-delta-join">Scenario 1: Real-Time Conversion Rate and Order Attribution: Solving Unbounded State Growth in Dual-Stream Joins with Delta Join<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#scenario-1-real-time-conversion-rate-and-order-attribution-solving-unbounded-state-growth-in-dual-stream-joins-with-delta-join" class="hash-link" aria-label="Direct link to Scenario 1: Real-Time Conversion Rate and Order Attribution: Solving Unbounded State Growth in Dual-Stream Joins with Delta Join" title="Direct link to Scenario 1: Real-Time Conversion Rate and Order Attribution: Solving Unbounded State Growth in Dual-Stream Joins with Delta Join" translate="no">​</a></h2>
<p>Conversion Rate (defined as the ratio of purchasing users to visiting users) is a core metric for measuring conversion efficiency in Taobao Instant Commerce.
Its calculation relies on the real-time join of user visit streams with order streams, directly enabling operational decisions within seconds.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pain-points-of-traditional-solutions">Pain Points of Traditional Solutions<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#pain-points-of-traditional-solutions" class="hash-link" aria-label="Direct link to Pain Points of Traditional Solutions" title="Direct link to Pain Points of Traditional Solutions" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/painpoints-a628951c0368b0e5fa21c23f79b85603.png" width="1080" height="554" class="img_ev3q">
In the legacy architecture, a Flink Regular Join must continuously accumulate all unexpired events in its local state to match orders with browsing records.
This creates an architectural dilemma: extending the conversion observation window incurs linearly growing storage costs.</p>
<ul>
<li class=""><strong>Unbounded State Growth &amp; Compaction Pressure:</strong> During promotional events, hundreds of billions of access logs must be retained within long time windows. The total state for a single job grew past <strong>TB levels</strong>, leading to severe <code>RocksDB</code> compaction backlogs and extremely long checkpoint durations.</li>
<li class=""><strong>I/O Saturation &amp; Latency Degradation:</strong> Amplified <code>State</code> read/write operations saturated disk I/O. Real-time attribution latency degraded from seconds to minutes.</li>
<li class=""><strong>Checkpoint Failures &amp; High RTO:</strong> <code>Checkpoint</code> durations deteriorated from minutes to <strong>10–15 minutes</strong>, frequently causing timeouts and failures.</li>
<li class=""><strong>Limited Attribution Window:</strong> The effective association time window was constrained by local storage capacity, making it impossible to support long-cycle "Browse-to-Order" conversion attribution.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-based-on-fluss-delta-join">Architecture Based on Fluss Delta Join<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-based-on-fluss-delta-join" class="hash-link" aria-label="Direct link to Architecture Based on Fluss Delta Join" title="Direct link to Architecture Based on Fluss Delta Join" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/delta-a1f381c3d99174f7fe1af1a8d525ee67.png" width="1758" height="682" class="img_ev3q">
To address the pain points of traditional architectures, our solution upgrades the conventional Flink dual-stream join to a Delta Join. This approach offloads the full volume of dual-stream data from Flink <code>State</code> to Fluss KV Tables (backed by <code>RocksDB</code>). By implementing bidirectional Prefix Lookup (where incoming records from either stream trigger a lookup in the opposite KV Table), we achieve a near-stateless dual-stream join. Furthermore, query performance is enhanced through <code>MurmurHash</code>-based bucket routing and column pruning.</p>
<ul>
<li class=""><strong>Dual KV Table Ingestion with Composite Keys:</strong> The real-time order stream is written to a Fluss KV Table with <code>PRIMARY KEY (user_id, ds, order_id)</code> and <code>bucket.key = ‘user_id’</code>. Simultaneously, real-time traffic visit logs are written to a Fluss KV Table with <code>PRIMARY KEY (user_id, ds)</code> and <code>bucket.key = ‘user_id’</code>. Both tables use a composite primary key design where the join key (<code>user_id</code>) serves as the <code>bucket.key</code> and is a strict prefix of the primary key, enabling efficient Prefix Lookup operations.</li>
<li class=""><strong>Stateless Bidirectional Join via Delta Join Operator:</strong> Leveraging the Delta Join operator in Flink 2.1+, we implement bidirectional association: incoming orders trigger lookups in the visit log table, and incoming visits trigger lookups in the order table. Flink calculates the target <code>bucketId</code> using <code>MurmurHash(bucket.key)</code> to route RPC requests precisely to the single Fluss TabletServer holding that specific bucket’s <code>RocksDB</code> instance. This eliminates the need for broadcast scans or full-table scans. The Flink operator itself retains only minimal transient state (for asynchronous lookup queues), completely removing dependency on large-scale Join <code>State</code>.</li>
<li class=""><strong>Real-Time Attribution &amp; Lakehouse Sync:</strong> The joined results are written to a Fluss wide table and automatically synchronized to Paimon via the Streaming-Lakehouse Unification mechanism. The data is finally served to StarRocks and real-time BI dashboards, achieving metric refresh latency within 30 seconds.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-ddl-and-join-sql">Core DDL and Join SQL<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#core-ddl-and-join-sql" class="hash-link" aria-label="Direct link to Core DDL and Join SQL" title="Direct link to Core DDL and Join SQL" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} represents environment-specific parameters. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Actual deployment values should be evaluated based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Order Stream (Right Table in Join)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    order_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    user_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    order_time STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    pay_amt STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> order_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Visit Stream (Left Table in Join)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_page_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    user_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    first_page_time STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    pv </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Sink Table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_page_ord_detail_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_id</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">       STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'User ID'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_id</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">      STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order ID'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">first_page_time</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'First Visit Time'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_time</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">    STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order Placement Time'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">pv</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">            </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Page Views (Visit Count)'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">pay_amt</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">       STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Payment Amount'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">is_matched</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">    STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Match Status'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">ds</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain">            STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Date Partition'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> order_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Attribution Result Table'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_id,order_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable Streaming-Lakehouse Unification</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'3min'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Controls Lakehouse Table Freshness</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Subsequently, the Lakehouse Tiering Service continuously syncs data from Fluss to Paimon.
The <code>table.datalake.freshness</code> parameter controls how frequently Fluss writes data to the Paimon table.
By default, the data freshness is set to <strong>3 minutes</strong>.</p>
<p>The implementation of the Delta Join is as follows:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VIEW</span><span class="token plain"> matched_page_ord_detail </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">first_page_time</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">pv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    o</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    o</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_time</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">CASE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WHEN</span><span class="token plain"> o</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_time </span><span class="token operator" style="color:#475569">&gt;=</span><span class="token plain"> l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">first_page_time </span><span class="token keyword" style="color:#194670">THEN</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">ELSE</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'0'</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">END</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> is_matched</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_page_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> l</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> o</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> o</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">AND</span><span class="token plain"> l</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> o</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><br></div></code></pre></div></div>
<p>Fluss supports two TTL (Time-To-Live) mechanisms:</p>
<ul>
<li class=""><strong>Changelog Expiration:</strong> Controlled by <code>table.log.ttl</code> (default: 7 days).</li>
<li class=""><strong>Data Partition Expiration:</strong> Controlled by <code>table.auto-partition.num-retention</code> (default: 7 partitions).</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="measured-results">Measured Results<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#measured-results" class="hash-link" aria-label="Direct link to Measured Results" title="Direct link to Measured Results" translate="no">​</a></h3>
<p>The Fluss Delta Join solution enables stateless dimension table joins, completely eliminating the unbounded state growth problem and delivering order-of-magnitude improvements in core metrics:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/results-ddca1b55491dee5fc1e51ac3903466e4.png" width="2014" height="1052" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-2-real-time-product-store-wide-table-efficient-column-level-updates-via-partial-update">Scenario 2: Real-Time Product-Store Wide Table: Efficient Column-Level Updates via Partial Update<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#scenario-2-real-time-product-store-wide-table-efficient-column-level-updates-via-partial-update" class="hash-link" aria-label="Direct link to Scenario 2: Real-Time Product-Store Wide Table: Efficient Column-Level Updates via Partial Update" title="Direct link to Scenario 2: Real-Time Product-Store Wide Table: Efficient Column-Level Updates via Partial Update" translate="no">​</a></h2>
<p>In Taobao Instant Commerce’s traffic operations ecosystem, the Real-time Product-Store Wide Table (a denormalized table that combines metrics from multiple behavioral data sources into a single queryable record) is the most critical data asset in the traffic domain.
This table must aggregate four core behavioral streams (impressions, clicks, visits, and orders) in real time, producing key metrics across both "Product" and "Store" dimensions. These include UV/PV counts for each event type (where UV = unique users and PV = total event count), unique orders, and GMV. The table directly powers real-time traffic dashboards, ROI analysis, algorithmic feature engineering, and decision-making during live promotional events.</p>
<p>However, under extreme scale and concurrency, traditional real-time aggregation models based on multi-stream joins face severe challenges: excessive state accumulation, high pipeline coupling, and poor fault tolerance.</p>
<p>Fluss addresses these issues by leveraging Partial Update. It refactors the complex <strong>multi-stream real-time join aggregation</strong> model into a streamlined architecture of <strong>independent multi-stream writes with automatic server-side column-level merging</strong>. This transformation simplifies the real-time wide table construction pipeline and delivers a leap in performance.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pain-points-of-traditional-solutions-1">Pain Points of Traditional Solutions<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#pain-points-of-traditional-solutions-1" class="hash-link" aria-label="Direct link to Pain Points of Traditional Solutions" title="Direct link to Pain Points of Traditional Solutions" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/painpoints2-ba43f0007d6299aaa4eb7b42f30bac90.png" width="1400" height="617" class="img_ev3q"></p>
<p>The legacy solution constructed the wide table using multi-stream Flink joins over Kafka topics, resulting in a tightly coupled fan-in architecture. Its core pain points were as follows:</p>
<ul>
<li class=""><strong>Full-Row Write I/O Waste:</strong> Even if only a single behavior metric (e.g., "clicks" or "impressions") is updated, the system must write all 100+ columns of the full row. This causes severe redundancy in both network and storage I/O.</li>
<li class=""><strong>High Operational Complexity:</strong> The architecture resembles a fan-out pipeline, requiring N upstream jobs and M join jobs to be maintained in parallel. Adding a new field requires modifying multiple jobs, significantly increasing operational overhead.</li>
<li class=""><strong>Latency Coupling (Straggler Problem):</strong> The write latency of the wide table is determined by the slowest upstream stream. A delay in any single data stream causes the entire row update to be delayed, leading to inconsistent real-time visibility.</li>
<li class=""><strong>Unbounded State Growth and No Live Schema Updates:</strong> To match data across different behavioral streams for conversion rate calculations, Flink must cache large amounts of intermediate state locally. This leads to <strong>TB-scale</strong> unbounded state growth, <code>Checkpoint</code> timeouts, and risk of out-of-memory (OOM) errors. Furthermore, the tightly coupled join logic means the system cannot be updated while running: any schema change, scaling operation, or model update requires a full service restart and data reprocessing.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-based-on-fluss-partial-update">Architecture Based on Fluss Partial Update<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-based-on-fluss-partial-update" class="hash-link" aria-label="Direct link to Architecture Based on Fluss Partial Update" title="Direct link to Architecture Based on Fluss Partial Update" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/solution2-7a7d1ba42b3406116debb25bc674a427.png" width="1200" height="687" class="img_ev3q"></p>
<p>To address the aforementioned pain points, our solution utilizes Fluss KV Tables as the unified storage for a multi-dimensional wide table (spanning Product and Store dimensions). By leveraging Partial Update, we achieve column-level updates that completely decouple the ingestion of diverse behavior streams such as impressions, clicks, visits, and orders. Each Flink job is responsible solely for aggregating its specific behavioral metrics and writing to the target columns, eliminating the need for complex multi-stream joins. The field-level merging is automatically handled by the <code>RowMerger</code> on the Fluss server side. The core architectural workflow is as follows:</p>
<ul>
<li class=""><strong>Independent Aggregation &amp; Ingestion of Multiple Streams:</strong> Five or more upstream behavior streams (impressions, clicks, visits, add-to-cart, orders) run as independent Flink jobs. Each job consumes only a single type of behavior log, pre-aggregates data by "Product" or "Store" dimension, and writes exclusively to the corresponding metric columns in the wide table. This approach thoroughly eliminates inter-stream dependencies.</li>
<li class=""><strong>Server-Side Intelligent Column-Level Merging:</strong> Fluss leverages the Partial Update feature, utilizing <code>BitSet</code> bitmap technology to identify target columns in write requests. Within the storage engine, only the target columns are read and updated (via accumulation or overwrite), while non-target columns retain their historical values in <code>RocksDB</code>. This avoids transmitting redundant data over the network or in memory, achieving microsecond-level merging.</li>
<li class=""><strong>Streaming-Lakehouse Unification with Auto-Tiering:</strong> The merged, up-to-date wide table data provides low-latency point queries and streaming consumption capabilities in real-time. Simultaneously, the Auto-Tiering mechanism asynchronously syncs data to the Paimon lakehouse. This supports offline T+1 (next-day batch) validation, historical backtracking, and AI model training without requiring additional ETL development for data movement.</li>
<li class=""><strong>Online Schema Evolution:</strong> When new statistical metrics are required, business teams simply execute an <code>ALTER TABLE ADD COLUMN</code> DDL statement and deploy a new write job for that specific column. This takes effect immediately without downtime or restarting existing jobs (e.g., for impressions or orders), enabling agile iteration even during peak promotional events.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-ddl-and-per-job-write-implementation">Core DDL and Per-Job Write Implementation<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#core-ddl-and-per-job-write-implementation" class="hash-link" aria-label="Direct link to Core DDL and Per-Job Write Implementation" title="Direct link to Core DDL and Per-Job Write Implementation" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> shop_stat_wide </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shop_id    </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">NULL</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_date  STRING </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">NULL</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_hour  STRING </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">NULL</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    exp_cnt    </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- Exposure Count</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    page_cnt   </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- Page View Count</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_hour</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'shop_id'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Write Exposure Data</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> shop_stat_wide </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_hour</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> exp_cnt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_hour</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">exp_cnt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> exp_cnt</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> exposure_log</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_hour</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Write Visit Data</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> shop_stat_wide </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_hour</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> page_cnt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    stat_hour</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">page_cnt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> page_cnt</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> page_log</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> shop_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_date</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> stat_hour</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="measured-results-1">Measured Results<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#measured-results-1" class="hash-link" aria-label="Direct link to Measured Results" title="Direct link to Measured Results" translate="no">​</a></h3>
<p>The Fluss Partial Update solution enables column-level updates and decoupled writes for wide tables, delivering significant improvements in core metrics while reducing operational costs.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/results2-c5f2378db8d4e2a01de812a9fb3d607f.png" width="1994" height="1154" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-3-real-time-order-estimation-efficient-data-fusion-via-fluss-kv-lake-stream-unification">Scenario 3: Real-Time Order Estimation: Efficient Data Fusion via Fluss KV Lake-Stream Unification<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#scenario-3-real-time-order-estimation-efficient-data-fusion-via-fluss-kv-lake-stream-unification" class="hash-link" aria-label="Direct link to Scenario 3: Real-Time Order Estimation: Efficient Data Fusion via Fluss KV Lake-Stream Unification" title="Direct link to Scenario 3: Real-Time Order Estimation: Efficient Data Fusion via Fluss KV Lake-Stream Unification" translate="no">​</a></h2>
<p>Real-time order forecasting serves as a critical support pillar in Taobao Instant Commerce’s algorithmic decision-making framework.
During promotional events, the system iterates the prediction model every minute to accurately project the total daily Gross Merchandise Volume (GMV), thereby enabling dynamic adjustments to subsidy strategies and inventory allocation.
This scenario imposes extreme challenges on the data pipeline: model inputs must deeply integrate real-time cumulative order streams (high-frequency incremental data) with historical baseline data from corresponding periods (massive static data), demanding exceptionally high standards for both data consistency and low latency.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/scenario3-956a82e94cd7e6c4b8ce0606207ee19f.png" width="1206" height="942" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-based-on-fluss-lake-stream-unification">Architecture Based on Fluss Lake-Stream Unification<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-based-on-fluss-lake-stream-unification" class="hash-link" aria-label="Direct link to Architecture Based on Fluss Lake-Stream Unification" title="Direct link to Architecture Based on Fluss Lake-Stream Unification" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/arch3-fff6f1c8fbf2246cfd476cc947d6aca6.png" width="1572" height="520" class="img_ev3q"></p>
<p>The current implementation leverages Fluss KV Tables as a unified storage layer for both streaming and batch processing. By integrating Streaming-Lakehouse Unification, it enables automated synchronization to the data lake. Since both streaming and batch operations rely on the same KV Table, data consistency is inherently guaranteed. This approach eliminates the need for independent data migration jobs, ensuring low-latency synchronization of prediction results.</p>
<p>Core Architecture Workflow:</p>
<ul>
<li class=""><strong>Real-time Accumulation:</strong> The real-time order stream is written into the Fluss KV Table, enabling the calculation of rolling cumulative order counts on a per-minute basis via stream reads.</li>
<li class=""><strong>Efficient Stream-Batch Fusion:</strong> A Flink UDF directly reads both real-time data (via stream read) and historical baseline data (via batch read / Snapshot Lookup) from the Fluss KV Table, achieving efficient fusion of streaming and batch data.</li>
<li class=""><strong>Low-Latency Lake Sync:</strong> Model prediction results are written back to the Fluss KV Table. With automatic Tiering enabled, these results are synchronized to Paimon with low latency (with configurable snapshot intervals).</li>
<li class=""><strong>Unified Data Consistency:</strong> Downstream algorithm models and operational dashboards read real-time prediction results from the Fluss KV Table and historical prediction results from Paimon, ensuring end-to-end data consistency across the entire pipeline.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-ddl-and-write-implementation">Core DDL and Write Implementation<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#core-ddl-and-write-implementation" class="hash-link" aria-label="Direct link to Core DDL and Write Implementation" title="Direct link to Core DDL and Write Implementation" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} represents environment-specific parameters. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Actual deployment values should be evaluated based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- 1. Real-time Order Minute-Accumulation Stream</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_dtm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">ds</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Day Partition'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">mm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Minute'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_source</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order Source'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_cnt</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order Count'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> order_source</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Real-time Order Minute-Accumulation Stream'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'mm,order_source'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'0'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Number of future partitions to pre-create.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Retain the last N partitions; automatically delete older ones.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.replication.factor'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${REPLICATION_FACTOR}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'zstd'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- 2. Order Forecast Stream (Fluss Streaming-Lakehouse Unification)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_forecast_mm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">ds</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Date Partition (Format: yyyymmdd)'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">mm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Minute (Format: HH:mm or HHmm)'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_source</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order Source'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">cumulative_order_cnt</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Cumulative Order Count up to the current minute'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">forecast_daily_order_cnt</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Predicted Total Daily Order Volume (24h) based on historical ratios'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> order_source</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Order Forecast Stream'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'mm,order_source'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'0'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.replication.factor'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${REPLICATION_FACTOR}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'zstd'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable Streaming-Lakehouse Unification</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'30s'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Controls Lakehouse Table Freshness</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- 3. Real-time Incremental &amp; Offline Feature Fusion: </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Uses Flink Temporal Join to associate the Fluss real-time stream with the Paimon historical baseline table, </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- and performs prediction calculations via a UDF.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FUNCTION</span><span class="token plain"> predict_hour_gmv </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'com.example.flink.udf.FutureHourlyForecastUDF'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VIEW</span><span class="token plain"> trd_item_order </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    order_source</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    order_cnt</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    PROCTIME</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> proc_time</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_dtm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">order_forecast_mm</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_source</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_cnt </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> cumulative_order_cnt</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    predict_hour_gmv</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">COALESCE</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_cnt</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">minute_ratio_feat</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> forecast_hour_order_cnt</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    trd_item_order t1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> paimon_catalog</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_mm_his_str </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS(</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'scan.partitions' = 'max_pt()',</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.dynamic-partition.refresh-interval' = '1 h'</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">) */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">OF</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">proc_time </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> t2 </span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">order_source </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">channel_id</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="measured-results-2">Measured Results<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#measured-results-2" class="hash-link" aria-label="Direct link to Measured Results" title="Direct link to Measured Results" translate="no">​</a></h3>
<p>The Fluss KV Streaming-Lakehouse Unification solution achieves unified source integration for streaming and batch data, eliminating independent data migration jobs and delivering significant improvements in core metrics:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/results3-61913d100d2167c5013e22186a0b51fa.png" width="1964" height="1554" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-4-canary-monitoring-efficient-log-processing-via-log-tables-and-column-pruning">Scenario 4: Canary Monitoring: Efficient Log Processing via Log Tables and Column Pruning<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#scenario-4-canary-monitoring-efficient-log-processing-via-log-tables-and-column-pruning" class="hash-link" aria-label="Direct link to Scenario 4: Canary Monitoring: Efficient Log Processing via Log Tables and Column Pruning" title="Direct link to Scenario 4: Canary Monitoring: Efficient Log Processing via Log Tables and Column Pruning" translate="no">​</a></h2>
<p>In the context of Taobao Instant Commerce’s high-frequency iteration cycle, frontend telemetry data covering the full user journey (from app launch, site entry, impressions, traffic redirection, store visits, cart additions, and orders through to fulfillment) serves as the core basis for evaluating release quality, monitoring marketing effectiveness, and optimizing search and recommendation strategies. To mitigate release risks, canary releases have become the standard procedure, with canary monitoring acting as the critical "safety valve." This process aims to detect data collection anomalies or business logic defects within seconds by real-time analysis of discrepancies between canary traffic and baseline traffic. However, facing TB-scale log throughput and millisecond-level alerting requirements, the traditional "Kafka + Flink" architecture has gradually revealed bottlenecks such as severe I/O waste, redundant pipelines, and data silos across multiple sources.</p>
<p>Fluss has refactored the data foundation for canary monitoring by leveraging:</p>
<ul>
<li class="">High-throughput writes via Log Tables;</li>
<li class="">Server-side column pruning for I/O optimization;</li>
<li class="">Automatic archiving through Streaming-Lakehouse Unification.</li>
</ul>
<p>This transformation upgrades the architecture from <strong>reactive firefighting</strong> to <strong>proactive governance</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pain-points-of-traditional-solutions-2">Pain Points of Traditional Solutions<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#pain-points-of-traditional-solutions-2" class="hash-link" aria-label="Direct link to Pain Points of Traditional Solutions" title="Direct link to Pain Points of Traditional Solutions" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/painpoints3-be364f89be387994e0475b0162116cc5.png" width="1200" height="515" class="img_ev3q"></p>
<p>The legacy architecture utilized Kafka as the message queue, Flink for real-time consumption and computation, and Paimon for storing detailed data. However, as data volumes surged and business complexity increased, this architecture exposed several core pain points:</p>
<table><thead><tr><th><strong>Dimension</strong></th><th><strong>Specific Technical Challenge</strong></th></tr></thead><tbody><tr><td>I/O Resource Waste</td><td>Kafka carries complete message payloads with no server-side column filtering. Monitoring tasks require only ~30 core fields, yet must deserialize entire messages containing large fields like <code>args</code> (90+ columns).</td></tr><tr><td>Redundant &amp; Complex Pipeline</td><td>Two independent Flink jobs must be maintained: one for real-time monitoring and alerting, and another for data cleaning and ingestion into Paimon.</td></tr><tr><td>Multi-Source Data Silos</td><td>Canary logs from multiple client endpoints are processed in isolation with scattered logic.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-based-on-fluss-log-table-and-column-pruning">Architecture Based on Fluss Log Table and Column Pruning<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-based-on-fluss-log-table-and-column-pruning" class="hash-link" aria-label="Direct link to Architecture Based on Fluss Log Table and Column Pruning" title="Direct link to Architecture Based on Fluss Log Table and Column Pruning" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/arch4-1a1c321ddce7027f6bb89f14d3007939.png" width="1080" height="476" class="img_ev3q"></p>
<p>To address the aforementioned pain points, Taobao Instant Commerce underwent a three-stage architectural evolution, ultimately establishing a unified log governance solution centered on Fluss:</p>
<ul>
<li class=""><strong>Phase 1:</strong> Introduced Fluss for a specific client’s data to validate the feasibility of cost optimization and improvements in data freshness.</li>
<li class=""><strong>Phase 2:</strong> Integrated data from all client endpoints. While this achieved unified data ingestion, computational tasks remained redundant due to difficulties in reusing the intermediate layer.</li>
<li class=""><strong>Phase 3:</strong> Leveraged Fluss’s server-side column pruning to eliminate invalid I/O, and used automatic Tiering to merge monitoring and synchronization pipelines. This completely resolves the issue of redundant computations.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/arch5-ae5729b648a47e8a4e8ed804cd0f2c87.png" width="1600" height="526" class="img_ev3q"></p>
<p>The new architecture establishes Fluss Log Tables as the single source of truth, creating an efficient <strong>write once, multi-dimensional reuse</strong> data loop:</p>
<ul>
<li class=""><strong>Unified High-Throughput Ingestion:</strong> Multi-source canary logs are uniformly written into Fluss Log Tables. Leveraging the Append-Only nature of Log Tables, the system perfectly accommodates high-concurrency write scenarios for massive volumes of logs.</li>
<li class=""><strong>Server-Side Column Pruning:</strong> During Flink consumption, Projection Pushdown pushes the required columns down to the Fluss server side. The server reads and returns only the specified <strong>30 core fields</strong>, blocking <strong>67%</strong> of invalid network transmission at the source.</li>
<li class=""><strong>Automated Archiving via Streaming-Lakehouse Unification:</strong> By simply enabling the <code>table.datalake.enabled</code> configuration, the system automatically and asynchronously synchronizes real-time data to Paimon. This eliminates the need for deploying separate Flink archiving jobs. Real-time monitoring and offline analysis share the same data pipeline, achieving seamless stream-batch data fusion.</li>
<li class=""><strong>Multi-Client Reuse &amp; Within-Seconds Alerting:</strong> Real-time monitoring tasks directly consume the lightweight, pruned data stream, feeding BI dashboards to achieve within-seconds anomaly alerting. Offline analysis queries Paimon tables directly, ensuring inherent consistency between streaming and batch data.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-ddl-and-write-implementation-1">Core DDL and Write Implementation<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#core-ddl-and-write-implementation-1" class="hash-link" aria-label="Direct link to Core DDL and Write Implementation" title="Direct link to Core DDL and Write Implementation" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} represents environment-specific parameters. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Actual deployment values should be evaluated based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_page_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">col1</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">col2</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">col3</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">col4</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">-- ....... Intermediate fields omitted</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">ds STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Page View Log Stream'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Partition key defined above</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'0'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'1'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.ttl'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${LOG_TTL}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.replication.factor'</span><span class="token operator" style="color:#475569">=</span><span class="token string" style="color:#0E7C66">'${REPLICATION_FACTOR}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'zstd'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable Streaming-Lakehouse Unification</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'3min'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Control lake table data freshness</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Monitoring Query (Reads only 30 core columns to trigger server-side column pruning)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_gray_monitoring_ri </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS('client.writer.enable-idempotence'='false') */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">-- ......... Intermediate fields omitted</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss-ali-log</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_page_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WHERE</span><span class="token plain"> KEYVALUE</span><span class="token punctuation" style="color:#475569">(</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">args</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">','</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'='</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'release_type'</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'grey'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Canary release flag</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Flink Cube Aggregation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_gray_monitoring_permm_ri </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS('client.writer.enable-idempotence'='false') */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    col4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token function" style="color:#7C3AED">count</span><span class="token punctuation" style="color:#475569">(</span><span class="token operator" style="color:#475569">*</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- ......... Intermediate fields omitted</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="measured-results-3">Measured Results<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#measured-results-3" class="hash-link" aria-label="Direct link to Measured Results" title="Direct link to Measured Results" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/results4-5656ef59df33ac93eb438b3cfc94e98a.png" width="1990" height="1166" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-5-global-real-time-uv-statistics-auto-increment-columns-and-aggregation-tables-for-unique-user-counting">Scenario 5: Global Real-Time UV Statistics: Auto-Increment Columns and Aggregation Tables for Unique User Counting<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#scenario-5-global-real-time-uv-statistics-auto-increment-columns-and-aggregation-tables-for-unique-user-counting" class="hash-link" aria-label="Direct link to Scenario 5: Global Real-Time UV Statistics: Auto-Increment Columns and Aggregation Tables for Unique User Counting" title="Direct link to Scenario 5: Global Real-Time UV Statistics: Auto-Increment Columns and Aggregation Tables for Unique User Counting" translate="no">​</a></h2>
<p>In Taobao Instant Commerce’s refined operational framework, unique visitor (UV) counts across the full user journey (covering impressions, visits, clicks, and orders) are core metrics for measuring traffic quality and conversion efficiency. In the original architecture, a hybrid stack of Flink + Hologres + Paimon + StarRocks was used:</p>
<ul>
<li class="">Hologres (Alibaba Cloud’s real-time OLAP database) stored user mapping tables to handle ID resolution;</li>
<li class="">Flink constructed Bitmaps via custom user-defined functions (UDFs) and wrote them to Paimon;</li>
<li class="">StarRocks accelerated queries using materialized views.</li>
</ul>
<p>However, under ultra-large-scale data volumes, this architecture exposed three critical pain points:</p>
<ul>
<li class="">Lookup hotspots and latency spikes in Hologres;</li>
<li class="">Backpressure from unbounded Flink state growth;</li>
<li class="">High data synchronization latency from Paimon’s compaction model.</li>
</ul>
<p>These issues made it impossible to achieve within-seconds real-time monitoring during promotional events. The introduction of Fluss marks a key iteration of this architecture:</p>
<ul>
<li class="">It replaces Hologres with Fluss KV Tables for low-latency ID mapping;</li>
<li class="">It replaces Flink state with Fluss Aggregation Tables for stateless local aggregation;</li>
<li class="">It replaces the independent Paimon synchronization pipeline with Auto Tiering for within-seconds sync.</li>
</ul>
<p>This solution achieves an architectural upgrade from a fragmented, high-latency multi-component stack to a unified, low-latency Streaming-Lakehouse architecture.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pain-point-of-the-old-approach">Pain Point of the Old Approach<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#pain-point-of-the-old-approach" class="hash-link" aria-label="Direct link to Pain Point of the Old Approach" title="Direct link to Pain Point of the Old Approach" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/painpoints4-2ab1271cf7963066a01571e44fced03b.png" width="1200" height="485" class="img_ev3q">
The pre-iteration architecture suffered from the following critical bottlenecks:</p>
<ul>
<li class=""><strong>Hologres Lookup Hotspots and Network Bottlenecks:</strong> Flink issued massive volumes of RPC requests to Hologres. Under high concurrency, this led to data skew and hotspots, causing lookup latency to degrade to several seconds.</li>
<li class=""><strong>Unbounded Flink State Growth and Checkpoint Failures:</strong> Flink UDFs were required to maintain <strong>TB-scale</strong> Bitmap state. This resulted in excessively long <code>Checkpoint</code> durations (<strong>&gt;10 minutes</strong>), frequent timeouts, and poor job stability.</li>
<li class=""><strong>Paimon Synchronization Latency:</strong> Paimon <code>compaction</code> introduced minute-level latency, leaving downstream StarRocks Materialized Views with stale data. This made it impossible to meet the requirements for within-seconds monitoring.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-based-on-fluss-auto-increment-columns-and-aggregation-tables">Architecture Based on Fluss Auto-Increment Columns and Aggregation Tables<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#architecture-based-on-fluss-auto-increment-columns-and-aggregation-tables" class="hash-link" aria-label="Direct link to Architecture Based on Fluss Auto-Increment Columns and Aggregation Tables" title="Direct link to Architecture Based on Fluss Auto-Increment Columns and Aggregation Tables" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/arch6-5394a5c2d2e041c53218b004d30e308b.png" width="1400" height="514" class="img_ev3q"></p>
<p>To address the three major pain points of the traditional architecture, we leveraged Fluss’s core design with a strategy centered on "mapping sparse business IDs to dense integer IDs for bitmap computation." Through techniques such as full pre-loading, dense integer ID generation, sharded parallelism, and 32-bit bitmap optimization, we achieved high-performance real-time unique visitor counting.</p>
<ul>
<li class=""><strong>Auto-Increment KV Mapping Layer:</strong> Using Flink’s built-in dimension table lookup, we built real-time ID resolution capabilities:<!-- -->
<ul>
<li class=""><strong>Lock-Free Segment Allocation:</strong> Fluss requests ID segments in batches and caches them locally. This lock-free, thread-level allocation avoids lock contention and GC pauses during concurrent writes, significantly improving ID generation throughput.</li>
<li class=""><strong>Query-Then-Insert:</strong> In dimension table Lookup scenarios, if a business ID is not found, Fluss automatically triggers an <code>INSERT</code> and returns the new auto-increment ID. This mechanism merges the traditional "check-then-write" two-step interaction into a single request, effectively reducing network traffic and cluster pressure.</li>
<li class=""><strong>Batching &amp; Pipeline Optimization:</strong> By combining Flink’s asynchronous Lookup batch request mechanism with Fluss client-side Pipeline writes, massive discrete RPCs are aggregated and processed in parallel. This design reduces interaction overhead and further optimizes overall system throughput.</li>
<li class=""><strong>Full Pre-loading &amp; Dense ID Generation:</strong> Before going live, billions of historical business IDs were batch-imported into the Fluss KV Table to generate unique 64-bit integer IDs using the auto-increment column. This eliminates cross-cluster RPC latency for existing users (<strong>100% cache hit rate</strong>), while new users are assigned IDs on first appearance via <code>INSERT</code>.</li>
</ul>
</li>
<li class=""><strong>Sharded Parallelism:</strong> By taking the modulus of the physical ID, all users are uniformly distributed across N logical shards, enabling localized parallelism for global deduplication tasks. Since behaviors from the same user strictly fall into the same shard, data volume across shards remains absolutely balanced, completely avoiding hotspot skew caused by patterns in business ID ranges.</li>
<li class=""><strong>32-Bit ID Compression:</strong> When the total number of physical IDs is below 4 billion, 64-bit IDs can be downcast to 32-bit integers, enabling the use of the <code>rbm32</code> (32-bit RoaringBitmap) aggregation function. Compared to <code>rbm64</code>, this reduces memory usage by over <strong>50%</strong> and significantly improves bitwise operation efficiency and CPU cache utilization.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="dual-path-aggregation-architecture">Dual-Path Aggregation Architecture<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#dual-path-aggregation-architecture" class="hash-link" aria-label="Direct link to Dual-Path Aggregation Architecture" title="Direct link to Dual-Path Aggregation Architecture" translate="no">​</a></h4>
<p>Based on the aforementioned preprocessing, the team implemented two parallel data pipelines:</p>
<ul>
<li class=""><strong>Option A (Fluss Aggregation Table Pre-Aggregation):</strong> The server side performs local Bitmap merging keyed by <code>(ds, hh, mm, shard_id)</code>, allowing Flink to consume only the already-merged results. This offloads the computational work to the storage layer and is ideal for extreme real-time scenarios such as live event war rooms, achieving sub-second latency.</li>
<li class=""><strong>Option B (Fluss Auto Tiering + StarRocks Native Deduplication):</strong> Detailed data is synchronized to Paimon within seconds, with StarRocks handling the final deduplication. This approach suits flexible multi-dimensional analysis (such as daily operational dashboards) and supports ad-hoc queries across arbitrary dimensions.</li>
</ul>
<p>Both paths share the same ID mapping and sharding logic, allowing the team to freely switch between them based on business requirements for real-time performance versus query flexibility.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-ddl-and-write-implementation-2">Core DDL and Write Implementation<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#core-ddl-and-write-implementation-2" class="hash-link" aria-label="Direct link to Core DDL and Write Implementation" title="Direct link to Core DDL and Write Implementation" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="user-mapping-table-with-auto-increment-column-enabled">User Mapping Table (with Auto-Increment Column Enabled)<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#user-mapping-table-with-auto-increment-column-enabled" class="hash-link" aria-label="Direct link to User Mapping Table (with Auto-Increment Column Enabled)" title="Direct link to User Mapping Table (with Auto-Increment Column Enabled)" translate="no">​</a></h4>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} represents environment-specific parameters. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Actual deployment values should be evaluated based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_mapping_table</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  user_id </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Original User ID'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  uid_int64 </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Auto-incremented Globally Unique Integer ID'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  update_time </span><span class="token keyword" style="color:#194670">TIMESTAMP</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Update Time'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'auto-increment.fields'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'uid_int64'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable auto-increment column</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'zstd'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="new-user-discovery-and-mapping-ingestion">New User Discovery and Mapping Ingestion<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#new-user-discovery-and-mapping-ingestion" class="hash-link" aria-label="Direct link to New User Discovery and Mapping Ingestion" title="Direct link to New User Discovery and Mapping Ingestion" translate="no">​</a></h4>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_mapping_table</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ REPLICATED_SHUFFLE_HASH(t2) */</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_exp_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> t1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_mapping_table</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS(</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.insert-if-not-exists' = 'true',</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.cache' = 'PARTIAL',</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.partial-cache.max-rows' = '500000',</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.partial-cache.expire-after-write' = '1h',</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">    'lookup.partial-cache.cache-missing-key' = 'false'</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">) */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">OF</span><span class="token plain"> PROCTIME</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> t2 </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-of-solution-a-fluss-aggregation-table--flink-global-merge">Implementation of Solution A: Fluss Aggregation Table + Flink Global Merge<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#implementation-of-solution-a-fluss-aggregation-table--flink-global-merge" class="hash-link" aria-label="Direct link to Implementation of Solution A: Fluss Aggregation Table + Flink Global Merge" title="Direct link to Implementation of Solution A: Fluss Aggregation Table + Flink Global Merge" translate="no">​</a></h4>
<p>Core Idea: Use the Fluss Aggregation Table to perform partial Bitmap merging on the server side, so Flink only needs to consume the pre-aggregated results for a final global merge.</p>
<p><strong>Fluss DDL (Aggregation Table)</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} represents environment-specific parameters. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Actual deployment values should be evaluated based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">IF</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">EXISTS</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">ads_uv_agg</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Date partition yyyyMMdd'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    hh STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Hour HH'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    mm STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Minute mm'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shard_id </span><span class="token keyword" style="color:#194670">INT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Shard ID'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uv_bitmap BYTES </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Minute-level user bitmap'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> shard_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'shard_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${BUCKET_NUM}'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'aggregation'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable aggregation engine; otherwise, Bytes fields cannot be automatically merged</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.uv_bitmap.agg'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'rbm32'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Specify that the Bytes field uses the rbm32 algorithm for Union aggregation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ds'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Fluss DML (Aggregation Table)</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: N is a power of 2 and should be dynamically configured based on cluster scale.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> fluss_catalog</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">hh</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid_int64 </span><span class="token operator" style="color:#475569">%</span><span class="token plain"> N </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">                                    </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> shard_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    BITMAP_TO_BYTES</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">BITMAP_BUILD_AGG</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid_int64 </span><span class="token operator" style="color:#475569">/</span><span class="token plain"> N</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv_bitmap</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_exp_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> t1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_mapping</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS('lookup.cache' = 'PARTIAL', 'lookup.partial-cache.max-rows' = '500000') */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">OF</span><span class="token plain"> PROCTIME</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> t2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">hh</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">mm</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid_int64 </span><span class="token operator" style="color:#475569">%</span><span class="token plain"> N </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-of-solution-b-fluss-auto-tiering--starrocks-native-deduplication">Implementation of Solution B: Fluss Auto Tiering + StarRocks Native Deduplication<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#implementation-of-solution-b-fluss-auto-tiering--starrocks-native-deduplication" class="hash-link" aria-label="Direct link to Implementation of Solution B: Fluss Auto Tiering + StarRocks Native Deduplication" title="Direct link to Implementation of Solution B: Fluss Auto Tiering + StarRocks Native Deduplication" translate="no">​</a></h4>
<p>Core Idea: Fluss acts as a high-throughput data pipeline, synchronizing data to Paimon via Auto Tiering. StarRocks then handles the final deduplication using its native OLAP engine.</p>
<p><strong>Fluss DDL (Log/KV Table with Tiering):</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> fluss_catalog</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_uv_detail </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">-- ....... Intermediate fields omitted</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shard_id </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uid_int32 </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> shard_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> uid_int32</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Enable Streaming-Lakehouse Unification</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10s'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Second-level synchronization</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Fact Table Denormalization (Generating Shard ID and Bitmap Index)</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: N is a power of 2 and should be dynamically configured based on cluster scale.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> fluss_catalog</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_uv_detail</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- ....... Intermediate fields omitted</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid_int64 </span><span class="token operator" style="color:#475569">%</span><span class="token plain"> N </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> shard_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">      </span><span class="token comment" style="color:#64748B;font-style:italic">-- Shard ID, used for parallel aggregation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid_int64 </span><span class="token operator" style="color:#475569">/</span><span class="token plain"> N</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uid_int32   </span><span class="token comment" style="color:#64748B;font-style:italic">-- Compressed ID, used for Bitmap</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">log_exp_ri</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> t1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">LEFT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss_catalog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">db</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">user_mapping</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">/*+ OPTIONS('lookup.cache' = 'PARTIAL','lookup.partial-cache.max-rows' = '500000','lookup.partial-cache.expire-after-access' = '2min') */</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">FOR</span><span class="token plain"> SYSTEM_TIME </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">OF</span><span class="token plain"> PROCTIME</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> t2 </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Downstream StarRocks Precise Deduplication Query</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Note: ${xxx} in the text represents environment-specific parameters. Actual deployment requires evaluation based on cluster scale and data volume.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Create a materialized view to accelerate queries</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> MATERIALIZED </span><span class="token keyword" style="color:#194670">VIEW</span><span class="token plain"> mv_global_uv </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  t</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">t</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">rb_partial</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">as</span><span class="token plain"> rbm_user_count </span><span class="token comment" style="color:#64748B;font-style:italic">-- Globally merge partial Bitmaps from each shard</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    shard_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    BITMAP_UNION_COUNT</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">TO_BITMAP</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uid_int32</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">as</span><span class="token plain"> rb_partial </span><span class="token comment" style="color:#64748B;font-style:italic">-- Partial Bitmap count</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    paimon_catalog</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">db</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ads_uv_detail</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">WHERE</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'${date}'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ds</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> shard_id</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> t</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> t</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ds</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Features: Decoupled architecture; StarRocks bears the computational load and supports flexible ad-hoc queries.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="measured-results-4">Measured Results<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#measured-results-4" class="hash-link" aria-label="Direct link to Measured Results" title="Direct link to Measured Results" translate="no">​</a></h3>
<p>Compared to the legacy solution, both new approaches achieve significant optimizations, but with different focuses:
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/results5-c6d0022743553219299f7d2bb86b9fbe.png" width="1984" height="1244" class="img_ev3q"></p>
<ul>
<li class="">If the business prioritizes extreme real-time performance and has fixed aggregation logic, Solution A is recommended, as it pushes the computational load down to the storage layer to the greatest extent.</li>
<li class="">If the business requires flexible multi-dimensional analysis and can tolerate a few seconds of latency, Solution B is recommended, as it fully leverages StarRocks' OLAP capabilities and results in a simpler, more general-purpose architecture.</li>
</ul>
<p>By adopting these two solutions, Taobao Instant Commerce successfully achieved real-time, accurate, and cost-effective end-to-end unique visitor (UV) metrics at massive scale. This approach retains StarRocks' query strengths while resolving the upstream pipeline performance bottlenecks through Fluss.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fluss-implementation-best-practices-and-future-roadmap">Fluss Implementation Best Practices and Future Roadmap<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#fluss-implementation-best-practices-and-future-roadmap" class="hash-link" aria-label="Direct link to Fluss Implementation Best Practices and Future Roadmap" title="Direct link to Fluss Implementation Best Practices and Future Roadmap" translate="no">​</a></h2>
<p>During the rollout of Fluss, Taobao Instant Commerce progressed from single-feature pilots to full pipeline implementations combining multiple Fluss features. This process has produced practical, reusable patterns while also shaping a clear technical roadmap aligned with both business needs and upstream community development.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-lessons-learned">Key Lessons Learned<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#key-lessons-learned" class="hash-link" aria-label="Direct link to Key Lessons Learned" title="Direct link to Key Lessons Learned" translate="no">​</a></h3>
<ul>
<li class=""><strong>Prioritize DDL Standards:</strong> When creating wide tables, plan the responsible jobs for each column in advance. All non-primary key columns must be declared as <code>NULLABLE</code>, and writers should specify only the target columns. This standard must be integrated into the team’s table creation workflow to prevent update failures caused by improper field configurations.</li>
<li class=""><strong>Precise Bucketing Strategy:</strong> An insufficient number of buckets can lead to excessive data volume in a single bucket, causing read amplification in <code>RocksDB</code>.</li>
<li class=""><strong>Maximize Column Pruning Benefits:</strong> Business SQL queries should explicitly specify the required columns to avoid <code>SELECT *</code>. This is particularly important in scenarios involving log wide tables and dimension table joins, allowing Fluss to fully leverage its I/O savings advantages.</li>
<li class=""><strong>Phased Migration:</strong> There is no need to replace the entire traditional pipeline at once. Start by replacing large-state two-stream joins with Delta Joins, then gradually expand to Partial Update wide tables and lake-stream integration. Each step should deliver independent, quantifiable benefits, and should be promoted broadly only after its effectiveness has been verified.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="future-roadmap">Future Roadmap<a href="https://fluss.apache.org/blog/taobao-instant-commerce-real-time-decision/#future-roadmap" class="hash-link" aria-label="Direct link to Future Roadmap" title="Direct link to Future Roadmap" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/future1-c553b8da51276a5e56c0e3bd70b706d6.png" width="1200" height="640" class="img_ev3q"></p>
<p>In the future, the overall data architecture will gradually unify computational logic through Flink SQL, evolve into a unified lake-stream storage foundation based on Fluss and Paimon, and build a unified query acceleration layer leveraging StarRocks.
The ultimate goal is to achieve <strong>one codebase, one dataset, and one metric definition</strong>, serving both real-time applications and offline analytics simultaneously, thereby thoroughly resolving data inconsistencies and high maintenance costs caused by the separation of stream and batch processing.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/future2-6ade7f5fde156c28259354d068c2422c.png" width="840" height="1041" class="img_ev3q"></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Real-Time Multi-Dimensional Unique Visitor Deduplication in Practice]]></title>
            <link>https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/</link>
            <guid>https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/</guid>
            <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UV (Unique Visitors) measures the count of distinct users who visited a page or triggered an event within a given time window — unlike PV (Page Views), which counts every request regardless of who made it. For any product or platform, accurate real-time UV statistics across dimensions like channel, city, date, and hour are a core analytical requirement.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/image-5c856ebe23a4c28f2e6ef5f74819cd2f.png" width="1792" height="1024" class="img_ev3q"></p>
<p><strong>UV (Unique Visitors)</strong> measures the count of distinct users who visited a page or triggered an event within a given time window — unlike <strong>PV (Page Views)</strong>, which counts every request regardless of who made it. For any product or platform, accurate real-time UV statistics across dimensions like channel, city, date, and hour are a core analytical requirement.
The full combination of four dimensions means <strong>16 grouping methods</strong>; when the dimension count increases to seven, the number of possible groupings reaches <strong>128</strong>.</p>
<p>How can multi-dimensional deduplication be both accurate and flexible while maintaining real-time performance? Behind this challenge lie two very different computing paradigms: direct deduplication of raw data, or set operations based on bitmaps.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="problems-with-traditional-deduplication-schemes">Problems with Traditional Deduplication Schemes<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#problems-with-traditional-deduplication-schemes" class="hash-link" aria-label="Direct link to Problems with Traditional Deduplication Schemes" title="Direct link to Problems with Traditional Deduplication Schemes" translate="no">​</a></h2>
<p>Direct deduplication of raw data (e.g. <code>COUNT(DISTINCT user_id)</code>) is the most intuitive and accurate deduplication method: maintain a <strong>hash set</strong> for each dimension combination and record all user IDs that have appeared under the group.</p>
<p>This approach works well with smaller data volumes and fewer dimensions, but as business scale grows, three core limitations emerge.</p>
<p><strong>Performance increases linearly with the amount of data.</strong> Each dimension combination needs to independently store all user identities that have occurred. When daily page view logs reach billions and distinct users number in the tens or hundreds of millions, each group's hash set demands significant memory and CPU for insertion and lookup. Total compute cost scales with the product of dimension combinations and data volume, which is especially noticeable during peak periods.</p>
<p><strong>Multidimensional extensibility is limited.</strong> If an analyst needs UV by <strong>Channel x City</strong> and <strong>Channel x Date</strong>, the traditional scheme must calculate two separate deduplication results. The existing <strong>Channel x City</strong> result cannot be rolled up to get the <strong>Channel</strong>-level UV, because user sets across cities may overlap and simple summation leads to double counting. With each additional dimension, the computational task multiplies and existing intermediate results cannot be reused.</p>
<p><strong>Insufficient query flexibility.</strong> Pre-aggregation schemes must fix dimension combinations in advance. When a business team needs a new dimension on the fly (such as adding <strong>device type</strong>), adjusting the entire data processing pipeline is often required instead of simply modifying a query.</p>
<p>These three limitations share a root cause: storing and computing on raw user identities at every dimension combination does not scale.</p>
<p>Put it another way: what if instead of storing the raw ID of each user, we represented the entire user set as a compact structure — one bit per user?</p>
<p>In the bitmap scheme, deduplication count equals counting the number of 1s in the bitmap (<code>popcount</code>), merging two user sets equals a bitwise <code>OR</code>, and cross-dimensional analysis equals a bitwise <code>AND</code>. All operations are CPU-native bit operations, blisteringly fast.</p>
<p>More importantly, bitmaps naturally support roll-up: the bitmap of <code>Channel = app, City = Shanghai</code> combined with <code>OR</code> with the bitmap of <code>Channel = app, City = Beijing</code> yields the user set for <code>Channel = app</code> — no need to re-scan the original data.</p>
<p>This is a fundamental difference between the two computing paradigms: <strong>direct deduplication</strong> requires independent calculations for each dimension combination, while the intermediate results of <strong>bitmap deduplication</strong> are naturally composable.</p>
<p>But the raw bitmap has a problem: if the user ID space is sparse (such as <code>UUID</code> or hash values), the bitmap will be very large and waste memory. A compressed bitmap format that works efficiently on sparse data is needed. This is where <strong>RoaringBitmap</strong> comes in.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/figure1-ffd30828fe581b5510e87190be869ada.png" width="1376" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-roaringbitmap-works">How RoaringBitmap Works<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#how-roaringbitmap-works" class="hash-link" aria-label="Direct link to How RoaringBitmap Works" title="Direct link to How RoaringBitmap Works" translate="no">​</a></h2>
<p>The core idea of a bitmap is simple: use an array of bits to represent a collection of integers. If the user ID is <code>k</code>, set the <code>k</code>-th position to 1. The deduplication count is the number of 1s, and set merging is a bitwise <code>OR</code>. These operations map directly to CPU instructions, which is extremely efficient.</p>
<p>However, the size of a raw bitmap depends on the maximum ID value. If the ID space is a <strong>32-bit integer</strong> (about 2.1 billion), a full bitmap takes <strong>256 MB</strong>. When the ID distribution is sparse, most bits are 0, and almost all memory is wasted.</p>
<p><strong>RoaringBitmap</strong> solves this with a layered, adaptive compression strategy. It divides the 32-bit integer space into <strong>65,536 partitions</strong> by the upper 16 bits, and each partition holds up to 65,536 values (the lower 16 bits), called a <strong>Container</strong>. Each container automatically selects the optimal storage format based on its data density:</p>
<ul>
<li class=""><strong>ArrayContainer:</strong> When the number of elements is less than 4,096, values are stored in a sorted array of 16-bit integers. For sparse data, this saves tens of times more space than a raw bitmap.</li>
<li class=""><strong>BitmapContainer:</strong> When the number of elements reaches 4,096 or more, the container switches to a raw 8 KB bitmap. For dense data, bitmaps are more compact than arrays.</li>
<li class=""><strong>RunContainer:</strong> When data presents continuous interval characteristics (such as IDs 100–500), values are stored using run-length encoding, recording only the start and end of each run.</li>
</ul>
<p>This adaptive strategy allows RoaringBitmap to remain efficient across a variety of data distributions: as compact as an array when sparse, as fast as a bitmap when dense, and as economical as run-length encoding when continuous. In practice, its memory footprint is usually only one-tenth to one-hundredth of the original hash set.</p>
<p>For deduplication scenarios, RoaringBitmap set operations map directly to analytical requirements: <code>OR</code> merges user sets, <code>AND</code> performs cross-dimensional analysis, <code>ANDNOT</code> performs exclusion analysis, and cardinality returns the deduplication count. All operations are accelerated by <strong>SIMD instructions</strong> and can be completed in milliseconds even on hundreds of millions of records.</p>
<p>One prerequisite: the input to RoaringBitmap must be an integer, and the denser the ID space, the better the compression and efficiency. If the original user ID is a string (such as a <code>UUID</code> or phone number), a dictionary table is needed to map sparse IDs to dense integer IDs.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/figure2-956356036536ec3cd2e69a133b65db09.png" width="1376" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-apache-fluss-enables-roaringbitmap-deduplication">How Apache Fluss Enables RoaringBitmap Deduplication<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#how-apache-fluss-enables-roaringbitmap-deduplication" class="hash-link" aria-label="Direct link to How Apache Fluss Enables RoaringBitmap Deduplication" title="Direct link to How Apache Fluss Enables RoaringBitmap Deduplication" translate="no">​</a></h2>
<p>A RoaringBitmap-based deduplication scheme requires three things: <strong>a dense integer ID space</strong>, <strong>bitmap merging at the storage layer</strong>, and <strong>native bitmap aggregation functions</strong>. Apache Fluss provides all three as built-in capabilities.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="auto-increment-columns-zero-code-dictionary-tables">Auto-Increment Columns: Zero-Code Dictionary Tables<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#auto-increment-columns-zero-code-dictionary-tables" class="hash-link" aria-label="Direct link to Auto-Increment Columns: Zero-Code Dictionary Tables" title="Direct link to Auto-Increment Columns: Zero-Code Dictionary Tables" translate="no">​</a></h3>
<p>The auto-increment column feature of Fluss lets you declare a field directly in the table definition. When a new user is written for the first time, Fluss automatically assigns an incrementing integer ID. No external ID service or historical data migration is required.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> uid_dictionary </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    user_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uid </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'auto-increment.fields'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'uid'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>A single <code>INSERT</code> statement is all that is needed to map sparse identities to dense integers. <code>int32</code> covers about 2.1 billion users, which is sufficient for most business scenarios. The generated ID is unique and monotonically increasing, which is naturally suited to RoaringBitmap's compression strategy.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="aggregate-merge-engine-merge-on-write">Aggregate Merge Engine: Merge on Write<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#aggregate-merge-engine-merge-on-write" class="hash-link" aria-label="Direct link to Aggregate Merge Engine: Merge on Write" title="Direct link to Aggregate Merge Engine: Merge on Write" translate="no">​</a></h3>
<p>The <strong>Aggregation Merge Engine</strong> of Fluss allows you to define aggregation rules for each column. When rows with the same primary key arrive, Fluss automatically merges them on the server side instead of overwriting or appending.</p>
<p>This means the compute layer only needs to send raw events and does not need to maintain any aggregated intermediate state. With <strong>exactly-once semantics</strong> based on Flink Checkpoint, the write path is compact and reliable.</p>
<p>For append-only aggregation operations such as bitmap union (UV deduplication), merging is naturally <strong>idempotent</strong> during writing. Adding the same <code>uid</code> to a bitmap multiple times does not change the result. In such scenarios, Flink jobs are only responsible for forwarding — no complex <code>UDAFs</code> or retraction messages are needed. For scenarios involving data retraction (such as order cancellation), rollback support for the aggregation merge engine is currently under development and is expected in a future release.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="built-in-rbm32--rbm64-aggregate-functions">Built-in <code>rbm32</code> / <code>rbm64</code> Aggregate Functions<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#built-in-rbm32--rbm64-aggregate-functions" class="hash-link" aria-label="Direct link to built-in-rbm32--rbm64-aggregate-functions" title="Direct link to built-in-rbm32--rbm64-aggregate-functions" translate="no">​</a></h3>
<p>Fluss supports RoaringBitmap as a first-class aggregation type. <code>rbm32</code> (32-bit) and <code>rbm64</code> (64-bit) perform native bitmap union on write.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> uv_agg </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    channel   STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    city      STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ymd       STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- format: YYYYMMDD</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    hh        STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- format: HH (00-23)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uv_bitmap BYTES</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    pv        </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token plain">    </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'aggregation'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.uv_bitmap.agg'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'rbm32'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.pv.agg'</span><span class="token plain">        </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sum'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<blockquote>
<p>Note: the quickstart section extends this table with <code>'table.datalake.enabled' = 'true'</code> and <code>'table.datalake.freshness' = '30s'</code> to enable lake tiering for batch queries. The core deduplication behaviour is identical.</p>
</blockquote>
<p>Each event carries a single-element bitmap, which Fluss merges into the existing bitmap of the corresponding primary key on the server side. The deduplication logic is entirely handled by the storage layer without requiring any custom merge code.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="putting-it-together-the-full-data-flow">Putting It Together: The Full Data Flow<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#putting-it-together-the-full-data-flow" class="hash-link" aria-label="Direct link to Putting It Together: The Full Data Flow" title="Direct link to Putting It Together: The Full Data Flow" translate="no">​</a></h3>
<p>These three capabilities are interlinked: the auto-increment column provides a <strong>dense ID</strong> (the prerequisite for bitmap deduplication), the aggregation engine merges on write (deduplication is completed in the storage layer), and <code>rbm32</code> makes bitmap merging a native storage operation (no custom logic needed).</p>
<p>The data flow is straightforward: when an event arrives, Flink obtains the dense <code>uid</code> by querying the dictionary table via a lookup join, calls <code>RB_BUILD(uid)</code> to build a single-element bitmap, and writes it to the aggregation table. Fluss automatically executes bitmap union on the server side. At query time, <code>RB_CARDINALITY()</code> extracts the UV count directly.</p>
<p>The responsibilities are clearly separated: <strong>Flink</strong> handles mapping, joining, and forwarding without holding long-lived state; <strong>Fluss</strong> holds the persistent merged result; queries read the merged bitmap and extract the count. Deduplication happens at write time — the query side requires no aggregation at all.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="streaming-lakehouse-unification">Streaming Lakehouse Unification<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#streaming-lakehouse-unification" class="hash-link" aria-label="Direct link to Streaming Lakehouse Unification" title="Direct link to Streaming Lakehouse Unification" translate="no">​</a></h3>
<p>When data volumes grow to require analysis across longer time windows, this architecture can be seamlessly extended through Fluss's <strong>Lake-Stream integration</strong> capabilities. In a daily partitioning scenario, each day's UV data is stored as a bitmap partition. Historical partitions (e.g., older than 30 days) can be offloaded to <strong>Data Lake</strong> storage (Paimon or Iceberg), while recent partitions remain in Fluss for real-time updates.</p>
<p>To count UV for an entire year, you only need to <code>OR</code> the 365 daily bitmap partitions and compute the cardinality. Regardless of whether data is in Fluss or the data lake, the bitmap union semantics are identical. This architecture balances storage cost and query flexibility, enabling accurate deduplication analysis across any time span.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/figure3-86043d69fa7c6dc4276644c8d648d5af.png" width="1376" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="end-to-end-quickstart">End-to-End Quickstart<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#end-to-end-quickstart" class="hash-link" aria-label="Direct link to End-to-End Quickstart" title="Direct link to End-to-End Quickstart" translate="no">​</a></h2>
<p>This section walks through the complete workflow with a minimal working example you can run locally.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="environment-preparation">Environment Preparation<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#environment-preparation" class="hash-link" aria-label="Direct link to Environment Preparation" title="Direct link to Environment Preparation" translate="no">​</a></h3>
<p>Requirements: <strong>Docker</strong> and <strong>Docker Compose</strong>.</p>
<p><strong>1. Create a working directory and download dependencies</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">mkdir</span><span class="token plain"> fluss-rbm-quickstart </span><span class="token operator" style="color:#475569">&amp;&amp;</span><span class="token plain"> </span><span class="token builtin class-name" style="color:#7C3AED">cd</span><span class="token plain"> fluss-rbm-quickstart</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">mkdir</span><span class="token plain"> lib</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic"># Download Fluss Flink connector</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/fluss-flink-1.20-0.9.0-incubating.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://repo1.maven.org/maven2/org/apache/fluss/fluss-flink-1.20/0.9.0-incubating/fluss-flink-1.20-0.9.0-incubating.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic"># Download RoaringBitmap UDF (provides RB_BUILD, RB_CARDINALITY, RB_OR_AGG, etc.)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/flink-roaringbitmap-0.2.0.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://github.com/flink-extended/flink-roaringbitmap/releases/download/v0.2.0/flink-roaringbitmap-0.2.0.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic"># Download Paimon and Fluss Lakehouse dependencies (for data tiering and batch queries)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/paimon-flink-1.20-1.3.1.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://repo1.maven.org/maven2/org/apache/paimon/paimon-flink-1.20/1.3.1/paimon-flink-1.20-1.3.1.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/fluss-lake-paimon-0.9.0-incubating.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://repo1.maven.org/maven2/org/apache/fluss/fluss-lake-paimon/0.9.0-incubating/fluss-lake-paimon-0.9.0-incubating.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/fluss-flink-tiering-0.9.0-incubating.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://repo1.maven.org/maven2/org/apache/fluss/fluss-flink-tiering/0.9.0-incubating/fluss-flink-tiering-0.9.0-incubating.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token function" style="color:#7C3AED">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-fL</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-o</span><span class="token plain"> lib/flink-shaded-hadoop-2.8.3-10.0.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  https://repo1.maven.org/maven2/org/apache/flink/flink-shaded-hadoop-2-uber/2.8.3-10.0/flink-shaded-hadoop-2-uber-2.8.3-10.0.jar</span><br></div></code></pre></div></div>
<p>The UDF JAR is a self-contained fat JAR. Placing it in Flink's <code>lib/</code> directory makes all functions available without additional dependencies. The source code is available in the <a href="https://github.com/flink-extended/flink-roaringbitmap" target="_blank" rel="noopener noreferrer" class="">flink-roaringbitmap</a> repository. Paimon-related JARs enable the <strong>Lakehouse Tiering</strong> feature of Fluss, where data automatically tiers from Fluss into Paimon, supporting <code>ORDER BY</code> and <code>GROUP BY</code> aggregated batch queries.</p>
<p><strong>2. Create <code>docker-compose.yml</code></strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">docker-compose.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token key atrule" style="color:#194670">services</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">zookeeper</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">restart</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> always</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">image</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> zookeeper</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">3.9.2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">coordinator-server</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">image</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> apache/fluss</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">0.9.0</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">incubating</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">entrypoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">[</span><span class="token string" style="color:#0E7C66">"sh"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"-c"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"umask 0000 &amp;&amp; exec /docker-entrypoint.sh coordinatorServer"</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">depends_on</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> zookeeper</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">environment</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">|</span><span class="token scalar string" style="color:#0E7C66"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        FLUSS_PROPERTIES=</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        zookeeper.address: zookeeper:2181</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        bind.listeners: FLUSS://coordinator-server:9123</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        remote.data.dir: /tmp/fluss-remote-data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.format: paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.paimon.metastore: filesystem</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.paimon.warehouse: /tmp/paimon</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">volumes</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">tablet-server</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">image</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> apache/fluss</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">0.9.0</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">incubating</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">entrypoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">[</span><span class="token string" style="color:#0E7C66">"sh"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"-c"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"umask 0000 &amp;&amp; exec /docker-entrypoint.sh tabletServer"</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">depends_on</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> coordinator</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">environment</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">|</span><span class="token scalar string" style="color:#0E7C66"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        FLUSS_PROPERTIES=</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        zookeeper.address: zookeeper:2181</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        bind.listeners: FLUSS://tablet-server:9123</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        data.dir: /tmp/fluss/data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        kv.snapshot.interval: 30s</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        remote.data.dir: /tmp/fluss-remote-data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.format: paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.paimon.metastore: filesystem</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        datalake.paimon.warehouse: /tmp/paimon</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">volumes</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">jobmanager</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">image</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> flink</span><span class="token punctuation" style="color:#475569">:</span><span class="token number" style="color:#B45309">1.20</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">ports</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"8083:8081"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">environment</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">|</span><span class="token scalar string" style="color:#0E7C66"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        FLINK_PROPERTIES=</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        jobmanager.rpc.address: jobmanager</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">entrypoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">[</span><span class="token string" style="color:#0E7C66">"sh"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"-c"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"cp -v /tmp/lib/*.jar /opt/flink/lib &amp;&amp; exec /docker-entrypoint.sh jobmanager"</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">volumes</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> ./lib</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/lib</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">taskmanager</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">image</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> flink</span><span class="token punctuation" style="color:#475569">:</span><span class="token number" style="color:#B45309">1.20</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">depends_on</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> jobmanager</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">environment</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">|</span><span class="token scalar string" style="color:#0E7C66"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        FLINK_PROPERTIES=</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        jobmanager.rpc.address: jobmanager</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        taskmanager.numberOfTaskSlots: 10</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        taskmanager.memory.process.size: 4096m</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token scalar string" style="color:#0E7C66">        taskmanager.memory.framework.off-heap.size: 256m</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">entrypoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">[</span><span class="token string" style="color:#0E7C66">"sh"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"-c"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"cp -v /tmp/lib/*.jar /opt/flink/lib &amp;&amp; exec /docker-entrypoint.sh taskmanager"</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token key atrule" style="color:#194670">volumes</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> ./lib</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/lib</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">-</span><span class="token plain"> shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">/tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">volumes</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token key atrule" style="color:#194670">shared-warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  shared</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><span class="token punctuation" style="color:#475569">:</span><br></div></code></pre></div></div>
<p><strong>3. Start the environment</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">docker</span><span class="token plain"> compose up </span><span class="token parameter variable" style="color:#12325C">-d</span><br></div></code></pre></div></div>
<p><strong>4. Start the Lakehouse Tiering Service</strong></p>
<p>The tiering service continuously transfers data from Fluss to Paimon to support batch queries. Start it in the background before writing data:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">docker</span><span class="token plain"> compose </span><span class="token builtin class-name" style="color:#7C3AED">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:#12325C">-d</span><span class="token plain"> jobmanager </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  /opt/flink/bin/flink run </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">-D</span><span class="token plain"> </span><span class="token assign-left variable" style="color:#12325C">execution.checkpointing.interval</span><span class="token operator" style="color:#475569">=</span><span class="token plain">30s </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    /opt/flink/lib/fluss-flink-tiering-0.9.0-incubating.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.bootstrap.servers</span><span class="token plain"> coordinator-server:9123 </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.format</span><span class="token plain"> paimon </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.metastore</span><span class="token plain"> filesystem </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.warehouse</span><span class="token plain"> /tmp/paimon</span><br></div></code></pre></div></div>
<p><strong>5. Open the SQL Client</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">docker</span><span class="token plain"> compose </span><span class="token builtin class-name" style="color:#7C3AED">exec</span><span class="token plain"> jobmanager ./bin/sql-client.sh</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-a-catalog-and-register-the-udfs">Create a Catalog and Register the UDFs<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#create-a-catalog-and-register-the-udfs" class="hash-link" aria-label="Direct link to Create a Catalog and Register the UDFs" title="Direct link to Create a Catalog and Register the UDFs" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> CATALOG fluss_catalog </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'type'</span><span class="token plain">              </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'fluss'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bootstrap.servers'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'coordinator-server:9123'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">USE</span><span class="token plain"> CATALOG fluss_catalog</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Register the RoaringBitmap UDFs. The JAR contains three functions: <code>RB_BUILD</code> wraps a single integer as a bitmap; <code>RB_CARDINALITY</code> extracts the deduplication count from a bitmap; <code>RB_OR_AGG</code> executes bitmap <code>OR</code> aggregation for roll-up queries. The deduplication merge at write time is performed by Fluss's <code>rbm32</code> aggregation — these UDFs handle bitmap construction on the Flink side and analysis on the query side.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FUNCTION</span><span class="token plain"> RB_BUILD</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'org.apache.flink.udfs.bitmap.RbBuildFunction'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FUNCTION</span><span class="token plain"> RB_CARDINALITY</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'org.apache.flink.udfs.bitmap.RbCardinalityFunction'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FUNCTION</span><span class="token plain"> RB_OR_AGG</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'org.apache.flink.udfs.bitmap.RbOrAggFunction'</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<blockquote>
<p><strong>Note:</strong> These functions are currently provided by the external <a href="https://github.com/flink-extended/flink-roaringbitmap" target="_blank" rel="noopener noreferrer" class="">flink-roaringbitmap</a> library. Once <a href="https://cwiki.apache.org/confluence/display/FLUSS/FIP-37%3A+Native+RoaringBitmap+Integration+for+Apache+Fluss" target="_blank" rel="noopener noreferrer" class="">FIP-37: Native RoaringBitmap Integration for Apache Fluss</a> is completed, they will be built directly into Fluss — no external JAR or manual UDF registration will be required.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-dictionary-and-aggregation-tables">Create Dictionary and Aggregation Tables<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#create-dictionary-and-aggregation-tables" class="hash-link" aria-label="Direct link to Create Dictionary and Aggregation Tables" title="Direct link to Create Dictionary and Aggregation Tables" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Dictionary table: sparse user_id -&gt; dense uid</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> uid_dictionary </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  user_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  uid     </span><span class="token keyword" style="color:#194670">INT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'auto-increment.fields'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'uid'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Aggregation table: bitmap is automatically merged on write</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> uv_agg </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  channel    STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  city       STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  ymd        STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- format: YYYYMMDD</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  hh         STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- format: HH (00-23)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  uv_bitmap  BYTES</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv         </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token plain">       </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'aggregation'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'fields.uv_bitmap.agg'</span><span class="token plain">    </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'rbm32'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'fields.pv.agg'</span><span class="token plain">           </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sum'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain">  </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'30s'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="write-sample-data">Write Sample Data<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#write-sample-data" class="hash-link" aria-label="Direct link to Write Sample Data" title="Direct link to Write Sample Data" translate="no">​</a></h3>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Simulate page browsing events: users appear across channels, cities, and time periods</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TEMPORARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VIEW</span><span class="token plain"> page_views </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_1'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_1'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'14'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_3'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Beijing'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_1'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Beijing'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'14'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_3'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'web'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Beijing'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_4'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'web'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260301'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'14'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260302'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'09'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_5'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'app'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">          </span><span class="token string" style="color:#0E7C66">'Beijing'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'20260302'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'09'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_4'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'mini_program'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Beijing'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'20260302'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'11'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user_1'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'mini_program'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Shanghai'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'20260302'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'11'</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> t</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Populate the dictionary table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_dictionary </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">DISTINCT</span><span class="token plain"> user_id </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> page_views</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Build bitmaps and write to the aggregation table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">hh</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_BUILD</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">d</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">uid</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv_bitmap</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  CAST</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> page_views </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">JOIN</span><span class="token plain"> uid_dictionary </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> d </span><span class="token keyword" style="color:#194670">ON</span><span class="token plain"> pv</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> d</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Each event is written as one row carrying a single-element bitmap. Fluss merges it server-side via <code>rbm32</code> union into the existing bitmap of the corresponding primary key. Flink jobs are only responsible for mapping and forwarding — they hold no aggregation state, require no <code>GROUP BY</code>, and do not process retraction messages.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/figure4-db29debe9adb92cf124773bcb8528a08.png" width="1376" height="768" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="query-deduplication-results">Query Deduplication Results<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#query-deduplication-results" class="hash-link" aria-label="Direct link to Query Deduplication Results" title="Direct link to Query Deduplication Results" translate="no">​</a></h3>
<p>After writing the data, wait approximately 60 seconds for the data to tier into Paimon. For PK tables, the tiering service uses the <strong>KV snapshot</strong> as a synchronization checkpoint — it reads the snapshot state first, then replays subsequent <strong>CDC events</strong> from that point. The wait covers one KV snapshot period (30 seconds) plus one tiering checkpoint period. Switch to <strong>batch mode</strong> to query the tiered data. Batch mode supports <code>ORDER BY</code> and does not involve streaming retraction semantics.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'execution.runtime-mode'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'batch'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sql-client.execution.result-mode'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'tableau'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_CARDINALITY</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uv_bitmap</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> city</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> hh</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Result:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----------+----+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| channel      | city     | ymd      | hh | uv | pv |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----------+----+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Beijing  | 20260301 | 10 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Beijing  | 20260301 | 14 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Beijing  | 20260302 | 09 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Shanghai | 20260301 | 10 |  2 |  3 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Shanghai | 20260301 | 14 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | Shanghai | 20260302 | 09 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| mini_program | Beijing  | 20260302 | 11 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| mini_program | Shanghai | 20260302 | 11 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| web          | Beijing  | 20260301 | 10 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| web          | Shanghai | 20260301 | 14 |  1 |  1 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----------+----+----+----+</span><br></div></code></pre></div></div>
<p>Note the <code>(app, Shanghai, 20260301, 10)</code> row: <code>user_1</code> appeared twice and <code>user_2</code> appeared once, yielding <code>uv = 2</code>. The three single-element bitmaps were merged by Fluss into a deduplicated set containing two distinct users. <code>pv = 3</code> is the raw event count from the sum aggregation. The query side requires no aggregation — results are ready at write time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-dimensional-roll-up-queries">Multi-Dimensional Roll-Up Queries<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#multi-dimensional-roll-up-queries" class="hash-link" aria-label="Direct link to Multi-Dimensional Roll-Up Queries" title="Direct link to Multi-Dimensional Roll-Up Queries" translate="no">​</a></h3>
<p>The preceding query reads the finest-granularity pre-aggregation results (<code>channel × city × ymd × hh</code>). The real power of bitmaps is supporting flexible roll-up: performing <code>OR</code> operations across fine-grained bitmaps yields coarse-grained deduplication counts without returning to the original data.</p>
<p>Roll up by channel, overall UV across all cities, dates, and hours:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_CARDINALITY</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">RB_OR_AGG</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uv_bitmap</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">pv</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> channel</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| channel      | uv | pv |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          |  4 |  8 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| mini_program |  2 |  2 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| web          |  2 |  2 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----+----+</span><br></div></code></pre></div></div>
<p><code>user_1</code> visited the <code>app</code> channel from both Shanghai and Beijing at multiple times. <code>RB_OR_AGG</code> combines all fine-grained bitmaps under the same channel into a single deduplicated set — each user is counted only once. The <code>app</code> channel has four distinct users (1, 2, 3, 5), not a simple sum of per-group UV counts.</p>
<p>Roll up by date, daily UV across all channels and cities:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_CARDINALITY</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">RB_OR_AGG</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uv_bitmap</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">pv</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| ymd      | uv | pv |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| 20260301 |  4 |  8 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| 20260302 |  4 |  4 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----+----+</span><br></div></code></pre></div></div>
<p>Active users are users 1–4 on March 1 and users 1, 2, 4, and 5 on March 2. Simply summing per-group UV counts would result in double counting, but the bitmap <code>OR</code> operation gives accurate results.</p>
<p>Roll up by channel x date, a common dashboard view:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  ymd</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_CARDINALITY</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">RB_OR_AGG</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uv_bitmap</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> uv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">pv</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uv_agg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> channel</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> ymd</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| channel      | ymd      | uv | pv |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----+----+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | 20260301 |  3 |  6 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| app          | 20260302 |  2 |  2 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| mini_program | 20260302 |  2 |  2 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| web          | 20260301 |  2 |  2 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+--------------+----------+----+----+</span><br></div></code></pre></div></div>
<p>Global UV, total distinct users across all dimensions:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  RB_CARDINALITY</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">RB_OR_AGG</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">uv_bitmap</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> total_uv</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">pv</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> total_pv</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uv_agg</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----------+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">| total_uv | total_pv |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----------+</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">|        5 |       12 |</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">+----------+----------+</span><br></div></code></pre></div></div>
<p>Five distinct users generated a total of 12 page views. One query, one full bitmap <code>OR</code> — no need to re-scan the original events.</p>
<p>All roll-up queries follow the same pattern: <code>GROUP BY</code> the target dimensions, apply <code>RB_OR_AGG</code> on the bitmap column, and use <code>RB_CARDINALITY</code> to extract the count. The finest-granularity pre-aggregated bitmap is the building block for all coarser-grained analysis.</p>
<blockquote>
<p>In a production environment, replace the bounded <code>page_views</code> view with a streaming data source and add the <code>/*+ OPTIONS('lookup.insert-if-not-exists' = 'true') */</code> hint to automatically register new users and enable a real-time bitmap write pipeline. The storage-side schema remains identical.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="clean-up-the-environment">Clean Up the Environment<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#clean-up-the-environment" class="hash-link" aria-label="Direct link to Clean Up the Environment" title="Direct link to Clean Up the Environment" translate="no">​</a></h3>
<p>After exiting the SQL Client, run the following to stop and remove all containers:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">docker</span><span class="token plain"> compose down </span><span class="token parameter variable" style="color:#12325C">-v</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="beyond-uv-other-use-cases">Beyond UV: Other Use Cases<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#beyond-uv-other-use-cases" class="hash-link" aria-label="Direct link to Beyond UV: Other Use Cases" title="Direct link to Beyond UV: Other Use Cases" translate="no">​</a></h2>
<p>This pattern is not limited to UV counting. It extends naturally to a variety of analytical scenarios. The core structure is consistent: choose the appropriate primary key granularity, and bitmap merging is handled by the storage layer at write time.</p>
<p><strong>Real-time multi-dimensional UV analysis.</strong> The main scenario in this post. Accurate UV statistics based on any combination of dimensions such as channel, city, date, and hour, with flexible dimension roll-up via bitmap union. Applicable to traffic dashboards, advertising effectiveness analysis, and activity monitoring.</p>
<p><strong>User retention analysis.</strong> Store active user bitmaps at the granularity of <code>user × date</code>. The retained user set is obtained by <code>AND</code>-ing bitmaps from different dates. For example, the cardinality of <code>Day1_bitmap AND Day7_bitmap</code> gives the 7-day retention count.</p>
<p><strong>Funnel analysis.</strong> Store an aggregation table for each funnel step, with bitmaps recording the set of users who reached that step. Conversion rates are computed by <code>AND</code>-ing bitmaps of adjacent steps. Since bitmaps support set difference (<code>ANDNOT</code>), you can further analyze the characteristics of users who dropped off at each step.</p>
<p><strong>Audience segmentation and user profiling.</strong> Build user attribute labels (such as gender, province, and preference category) as bitmaps. Multi-label combination queries are converted into bitmap <code>AND</code>/<code>OR</code>/<code>ANDNOT</code> operations, enabling selection of tens of millions of users in milliseconds. This is a high-frequency scenario in advertising <strong>DMP</strong> and recommendation systems.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/figure5-24b03a4c2f1d9ecc825f241d757937e4.png" width="1376" height="768" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting Started<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started" translate="no">​</a></h2>
<p>If you are evaluating real-time multi-dimensional exact deduplication schemes, Fluss's RoaringBitmap capability is a worthwhile starting point. The architecture follows a clear three-step pattern:</p>
<ol>
<li class=""><strong>Map</strong> sparse user identities to dense integers using an auto-increment <code>uid_dictionary</code> table.</li>
<li class=""><strong>Deduplicate at write time</strong> using an <code>rbm32</code> aggregation table — no stateful Flink jobs, no complex UDAFs.</li>
<li class=""><strong>Query directly</strong> using <code>RB_CARDINALITY()</code> for point reads or <code>RB_OR_AGG()</code> + <code>RB_CARDINALITY()</code> for any roll-up combination.</li>
</ol>
<p>The result is a system where deduplication is handled entirely by the storage layer, and the query side reads pre-computed results with no additional aggregation overhead.</p>
<p><code>rbm32</code> / <code>rbm64</code> aggregation and the auto-increment dictionary scheme are available from <strong>Apache Fluss 0.9</strong>. See the <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">Fluss documentation</a> for the full configuration reference.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="join-the-community">Join the Community<a href="https://fluss.apache.org/blog/roaringbitmap-uv-deduplication/#join-the-community" class="hash-link" aria-label="Direct link to Join the Community" title="Direct link to Join the Community" translate="no">​</a></h2>
<p>Apache Fluss is an open-source project under active development within the Apache Software Foundation.</p>
<ul>
<li class="">GitHub: <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">github.com/apache/fluss</a></li>
<li class="">Documentation: <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">fluss.apache.org</a></li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Apache Fluss Chose Rust for Its Multi-Language SDK]]></title>
            <link>https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/</link>
            <guid>https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/</guid>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-ff73743992ef46d54308fdd53126d3db.jpg" width="1828" height="1850" class="img_ev3q"></p>
<p>If you maintain a data system that only speaks Java, you will eventually hear from someone who doesn't. A Python team building a feature store. A C++ service that needs sub-millisecond writes. An AI agent that wants to call your system through a tool binding. They all need the same capabilities (writes, reads, lookups) and none of them want to spin up a JVM to get them.</p>
<p>Apache Fluss, streaming storage for real-time analytics and AI, hit this exact inflection point. The <a class="" href="https://fluss.apache.org/blog/fluss-java-client/">Java client</a> works well for Flink-based compute, where the JVM is already the world you live in. But outside that world, asking consumers to run a JVM sidecar just to write a record or look up a key creates friction that compounds across every service, every pipeline, every agent in the stack.</p>
<p>We could have written a separate client for each language. Maintain five copies of the wire protocol, five implementations of the batching logic, five sets of retry semantics and idempotence tracking. That path scales linearly with languages and ends predictably: the Java client gets features first, the Python client gets them six months later with slightly different edge-case behavior, and the C++ client is perpetually "almost done."</p>
<p>We took a different path and tried to leverage the lessons of the great.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-librdkafka-model">The librdkafka Model<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#the-librdkafka-model" class="hash-link" aria-label="Direct link to The librdkafka Model" title="Direct link to The librdkafka Model" translate="no">​</a></h2>
<p>If you've worked with Kafka clients outside of Java, you've probably used <a href="https://github.com/confluentinc/librdkafka" target="_blank" rel="noopener noreferrer" class="">librdkafka</a> without knowing it. It's a single C library that powers <code>confluent-kafka-python</code>, <code>confluent-kafka-go</code>, and others. One core handles the wire protocol, batching, memory management, and delivery semantics. Each language binding is a thin wrapper, a glue on top of a battle-tested engine.</p>
<p>The model is elegant because it inverts the usual maintenance equation. Instead of N full client implementations that diverge over time, each developing its own bugs, its own subtle behavioral differences, its own backlog of features the Java client has but the Python client doesn't yet, you get one implementation and N thin bindings that stay in sync by construction. A bug gets fixed once, and every language picks it up on the next build.</p>
<p><img decoding="async" loading="lazy" alt="N Separate Clients vs Shared Core" src="https://fluss.apache.org/assets/images/n_separate_clients_vs_shared_core-db6c7c3e98273439a2a370d7b118484f.png" width="1536" height="1024" class="img_ev3q"></p>
<p>The deeper benefit is correctness, not just code reuse. When you maintain three separate implementations of a client protocol, behavioral drift is inevitable. Edge cases in retry logic, subtle differences in how backpressure kicks in, inconsistencies in how idempotent writes handle sequence numbers. These are the bugs that don't show up in unit tests but surface in production under load, and they surface differently in each language.</p>
<p>We built fluss-rust on this same idea. A single Rust core implements the full Fluss client protocol (Protobuf-based RPC, record batching with backpressure, background I/O, Arrow serialization, idempotent writes, SASL authentication) and exposes it to three languages:</p>
<ul>
<li class=""><strong>Rust</strong>: directly, as the <code>fluss-rs</code> crate</li>
<li class=""><strong>Python</strong>: via <a href="https://pyo3.rs/" target="_blank" rel="noopener noreferrer" class="">PyO3</a>, the Rust-Python bridge</li>
<li class=""><strong>C++</strong>: via <a href="https://cxx.rs/" target="_blank" rel="noopener noreferrer" class="">CXX</a>, the Rust-C++ bridge</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Bindings Structure" src="https://fluss.apache.org/assets/images/bindings_structure-55b2c3b65549808bef69b95315ffd87d.png" width="1536" height="1024" class="img_ev3q"></p>
<p>To give a sense of proportion: the Rust core is roughly 40k lines, while the Python binding is around 5k and the C++ binding around 6k. The bindings handle type conversion, async runtime bridging, and memory ownership at the language boundary, but all the protocol logic, batching, Arrow codec, and retry handling live in the shared core.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-rust-and-not-c">Why Rust and Not C<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#why-rust-and-not-c" class="hash-link" aria-label="Direct link to Why Rust and Not C" title="Direct link to Why Rust and Not C" translate="no">​</a></h2>
<p>C would have been the obvious choice. librdkafka already proves the model works at enormous scale, and the C ABI is the universal language of foreign function interfaces.</p>
<p>We chose Rust, and the reason is specific rather than philosophical: compile-time safety is a force multiplier for a small team maintaining a shared core that multiple languages depend on.</p>
<p>Getting memory safety right in C means manual lifetime tracking, careful code review, and years of experience knowing where the subtle bugs hide. Rust gives the same zero-overhead profile (no garbage collector, no runtime) but checks those invariants at compile time instead.</p>
<p>To make this concrete: the Fluss write path moves ownership from the caller through a concurrent map, into a background event loop, and back out to futures the caller may or may not still be holding. In C, getting that right is a matter of discipline, and getting it wrong means segfaults that only reproduce under production load. In Rust, the borrow checker and the <code>Send</code>/<code>Sync</code> traits catch those problems before the code ever runs.</p>
<p>We're not alone here. Polars, Apache OpenDAL, and delta-rs all chose Rust as a shared core with language bindings on top. Fluss's Rust SDK sits in that lineage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="relationship-with-the-java-client">Relationship with the Java Client<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#relationship-with-the-java-client" class="hash-link" aria-label="Direct link to Relationship with the Java Client" title="Direct link to Relationship with the Java Client" translate="no">​</a></h2>
<p>The Java client remains the primary integration point for Flink, powering the SQL connector, the DataStream API, and the tightest path for JVM-based streaming compute. If your workload is Flink reading from and writing to Fluss, that's still the right client to use.</p>
<p>fluss-rust isn't trying to replace it. It exists for the consumers the Java client was never designed to serve: Python pipelines, C++ services, Rust applications, and anything else that doesn't want a JVM in the process. Both clients talk the same wire protocol and get the same server-side behavior. Most teams will end up using both, the Java client for Flink and fluss-rust for everything around it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-the-rust-core-covers">What the Rust Core Covers<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#what-the-rust-core-covers" class="hash-link" aria-label="Direct link to What the Rust Core Covers" title="Direct link to What the Rust Core Covers" translate="no">​</a></h2>
<p>The Rust core implements the complete Fluss client protocol. Here's how the pieces fit together.</p>
<p>When you write a record, the call is synchronous: the record gets queued into a per-bucket batch without touching the network. A background sender task picks up ready batches and ships them as RPCs to the responsible TabletServers. This follows the same pattern as both the Fluss Java client and Kafka producers.</p>
<p>The caller gets back a <code>WriteResultFuture</code>. Await it to block until the server confirms, or drop it for fire-and-forget. The write proceeds through the background sender regardless, with the same retries and server-side durability (acks=all by default). In high-throughput pipelines you typically don't await every write individually, but collect futures for a batch of records and await them together before committing your source offset.</p>
<p><img decoding="async" loading="lazy" alt="Write Modes" src="https://fluss.apache.org/assets/images/write_modes-0874b158121a0ff77526fbdea1762a5e.png" width="1536" height="1024" class="img_ev3q"></p>
<p>Batches ship automatically when they fill up or after a short timeout (100ms by default), so <code>flush()</code> isn't needed for data to reach the server. It's there for when you need to confirm that everything in flight has landed. If the write buffer fills up, new writes block until space frees up rather than silently consuming unbounded memory.</p>
<p>Fluss has two table types (primary key tables and log tables), and the Rust core has a writer for each. <code>UpsertWriter</code> handles keyed writes: full upserts, deletes, and partial updates where you send only the columns that changed. <code>AppendWriter</code> handles append-only log writes and can also accept Arrow <code>RecordBatch</code> directly if you already have columnar data. Both support idempotent delivery.</p>
<p>For reads, <code>LogScanner</code> provides streaming consumption with offset tracking. You can push column projection down to the server, so if you only need three columns out of twenty, only those three travel over the network. Each record also carries changelog metadata (offset, timestamp, and change type like <code>AppendOnly</code>, <code>Insert</code>, <code>UpdateBefore</code>, <code>UpdateAfter</code>, <code>Delete</code>), which is how Fluss exposes its CDC semantics to consumers outside of Flink.</p>
<p><code>Lookuper</code> handles the other access pattern: point queries against primary key tables. You encode a key, and the client resolves the bucket, finds the leader, and returns the row. The response is compact binary, and the row is only deserialized when you first access a field.</p>
<p>The core also covers admin operations (creating and dropping databases and tables, schema management) and SASL/PLAIN authentication at the connection level.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="arrow-on-the-wire">Arrow on the Wire<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#arrow-on-the-wire" class="hash-link" aria-label="Direct link to Arrow on the Wire" title="Direct link to Arrow on the Wire" translate="no">​</a></h2>
<p>Arrow deserves its own section because it's the architectural decision that makes the multi-language model work end to end, not just at the API level but down to the bytes on the wire.</p>
<p>Fluss transmits data as Arrow IPC, compressed with ZSTD by default. When you write an Arrow <code>RecordBatch</code>, it goes straight into the wire format. When you scan, the response comes back as Arrow <code>RecordBatch</code>. The data stays in Arrow throughout, which means there's no serialization boundary between the Rust core and the caller.</p>
<p><img decoding="async" loading="lazy" alt="Arrow Data Flow" src="https://fluss.apache.org/assets/images/arrow_data_flow-9f608e93308f65090a2c588f521a7cd8.png" width="1536" height="1024" class="img_ev3q"></p>
<p>This matters most at the language boundary. The Python binding already supports full Arrow interop in both directions: <code>poll_arrow()</code> returns scan results as a PyArrow Table, and <code>write_arrow_batch()</code> accepts a PyArrow RecordBatch for writes. Both cross the Rust-Python boundary without copying, because <code>arrow-pyarrow</code> shares the underlying memory buffers. A scan result goes straight from the Rust core into PyArrow, and from there into Pandas, Polars, or DuckDB with no conversion step.</p>
<p>On the C++ side, the Arrow C Data Interface handles the same zero-copy handoff for callers that export or import Arrow arrays.</p>
<p>Looking ahead, Arrow also makes <a href="https://datafusion.apache.org/" target="_blank" rel="noopener noreferrer" class="">Apache DataFusion</a> integration straightforward. DataFusion's table providers already expect Arrow, so wiring fluss-rust as a data source is a natural extension.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-this-looks-like-in-practice">What This Looks Like in Practice<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#what-this-looks-like-in-practice" class="hash-link" aria-label="Direct link to What This Looks Like in Practice" title="Direct link to What This Looks Like in Practice" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="One Table, Many Languages" src="https://fluss.apache.org/assets/images/one_table_four_languages-e793685b539f90a27f63f9b261c18e81.png" width="1536" height="1024" class="img_ev3q"></p>
<p>Suppose a Flink job consumes CDC from Postgres, computes user features, and writes them into a Fluss primary key table. A Python scoring service needs to look up those features before running a model. With the Python binding, that's a few lines:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Python</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">from</span><span class="token plain"> fluss </span><span class="token keyword" style="color:#194670">import</span><span class="token plain"> FlussConnection</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> Config</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> TablePath</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">conn </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">await</span><span class="token plain"> FlussConnection</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">create</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">Config</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">{</span><span class="token string" style="color:#0E7C66">"bootstrap.servers"</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"fluss:9123"</span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">table </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">await</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">get_table</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">TablePath</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"analytics"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"user_features"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">lookuper </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> table</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">new_lookup</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">create_lookuper</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">result </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">await</span><span class="token plain"> lookuper</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">lookup</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">{</span><span class="token string" style="color:#0E7C66">"user_id"</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> request</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">score </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> model</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">predict</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">result</span><span class="token punctuation" style="color:#475569">)</span><br></div></code></pre></div></div>
<p>The lookup goes through the same Protobuf RPC and hits the same KV store on the TabletServer that the Java client would use. The Python service just doesn't need a JVM to get there.</p>
<p>On the write side, consider an IoT gateway written in C++ that pushes sensor readings into a Fluss log table. It can't afford to block on each record, so it queues them and lets the Rust core handle batching and delivery:</p>
<div class="language-cpp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">C++</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-cpp codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">fluss</span><span class="token double-colon punctuation" style="color:#475569">::</span><span class="token plain">AppendWriter writer</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">table</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">NewAppend</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">CreateWriter</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">writer</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">const</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">auto</span><span class="token operator" style="color:#475569">&amp;</span><span class="token plain"> event </span><span class="token operator" style="color:#475569">:</span><span class="token plain"> events</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    fluss</span><span class="token double-colon punctuation" style="color:#475569">::</span><span class="token plain">GenericRow row</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">// ... populate row from event ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">Append</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain">               </span><span class="token comment" style="color:#64748B;font-style:italic">// queued in Rust, sent automatically</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-the-caller-is-a-machine">When the Caller Is a Machine<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#when-the-caller-is-a-machine" class="hash-link" aria-label="Direct link to When the Caller Is a Machine" title="Direct link to When the Caller Is a Machine" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="AI and Fluss" src="https://fluss.apache.org/assets/images/ai_and_fluss-bd68ba74f8d8441a553e40fb51ce1857.png" width="1536" height="1024" class="img_ev3q"></p>
<p>The examples above involve humans writing Python or C++ code. But increasingly, the caller isn't a human at all. AI agents interact with data infrastructure through tool calls, and the way they use a client library is different from how a developer does.</p>
<p>An agent that needs to check a user's subscription tier or write back a recommendation doesn't read documentation or understand batching internals. It sees a tool definition: <code>lookup(table, key)</code>, <code>upsert(table, row)</code>, <code>flush()</code>. The smaller and more predictable that interface is, the more reliably the agent uses it. If you've worked with LLM tool-calling, you've seen how quickly reliability degrades as the number of functions or the complexity of their signatures grows.</p>
<p>This is where the single-core architecture pays off in a way we didn't originally design for. Because the Rust core hides all the protocol and batching complexity, the Python binding exposes a small set of straightforward functions. An agent calls <code>await lookuper.lookup(key)</code>, gets a dict back, and moves on. No JVM to manage, no sidecar to health-check, just a Python function that happens to run compiled Rust underneath.</p>
<p>More broadly, as we discussed in <a class="" href="https://fluss.apache.org/blog/fluss-for-ai/">What does Apache Fluss mean in the context of AI?</a>, real-time intelligent systems need fresh features, evolving context, and continuously updated state. Fluss fits naturally as a context store for these systems, and the Rust SDK is what makes that context store accessible outside the JVM world.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-we-got-here">How We Got Here<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#how-we-got-here" class="hash-link" aria-label="Direct link to How We Got Here" title="Direct link to How We Got Here" translate="no">​</a></h2>
<p>We didn't build everything at once. The Rust core came first with the protocol, batching, Arrow integration, writes, and scanning. We added <a href="https://pyo3.rs/" target="_blank" rel="noopener noreferrer" class="">PyO3</a> for Python and <a href="https://cxx.rs/" target="_blank" rel="noopener noreferrer" class="">CXX</a> for C++ once those paths were stable, and features like lookups landed later as the Rust API matured. If we'd started the bindings earlier, the FFI boundary would have been designed around a half-finished API, and we'd have spent more time reshaping glue code than building features.</p>
<p><img decoding="async" loading="lazy" alt="Async complexity in the Rust SDK Core" src="https://fluss.apache.org/assets/images/async_complexity_in_core-97572ce099db6a2b799941c580585531.png" width="1536" height="1024" class="img_ev3q"></p>
<p>Async was the most involved design problem. Rust runs on Tokio, Python on asyncio, and C++ callers expect synchronous returns. We decided early that all async complexity would stay inside the Rust core: Python spawns work on the shared Tokio runtime and gets back an asyncio-compatible future, while C++ blocks on Tokio and returns when the operation completes. That meant tricky problems like <code>WriteResultFuture</code> drop semantics (making sure a dropped future doesn't leak memory or leave a batch stuck in the accumulator) only had to be solved once.</p>
<p>We also underestimated how many bugs live at the language boundary rather than in the core. The Rust integration tests against a real Fluss cluster all passed, but when we ran the same scenarios through the Python and C++ bindings, new issues appeared. On the Python side, API errors from the server weren't being propagated through PyO3 at all, so operations would fail silently. On the C++ side, the row access layer had panics that required a significant rework, and the error handling for pointer-returning FFI methods was swallowing server errors instead of surfacing them. As a result, we now run full round-trip integration tests for all three languages in CI.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's Next<a href="https://fluss.apache.org/blog/why-fluss-chose-rust-for-multi-language-sdk/#whats-next" class="hash-link" aria-label="Direct link to What's Next" title="Direct link to What's Next" translate="no">​</a></h2>
<p>The core read and write paths work, but there are gaps to close before the Rust SDK reaches full parity with the Java client. Complex data types (Array, Map, Row) aren't supported yet, which limits what schemas you can work with. Limit scans and batch scanning are in progress, and once those land with Python and C++ bindings, the SDK becomes usable for a wider range of analytical workloads. We also want to support subscribing to primary key table changelogs, which would let non-Flink consumers track how keyed state evolves over time.</p>
<p>On the infrastructure side, we're adding client metrics so operators can monitor the SDK in production, and improving Python ergonomics with async iterator support for log scanning.</p>
<p>Beyond feature parity, there are four directions we're especially excited about.</p>
<p><img decoding="async" loading="lazy" alt="What is next?" src="https://fluss.apache.org/assets/images/what_is_next-74fa44c9a334ea161414fb5680eff778.png" width="1536" height="1024" class="img_ev3q"></p>
<p>The first is <strong>DataFusion integration</strong>. The Rust core already produces Arrow RecordBatches, which is exactly what DataFusion's table provider interface expects. Wiring the two together would let users run SQL queries directly over Fluss data from Rust or Python, without going through Flink.</p>
<p>The second is a <strong>Go client</strong>. The shared-core model extends naturally to Go via CGo or a similar FFI bridge. A Go client would unlock native log ingestion for the Go ecosystem, including integrations with tools like Filebeat, and is already something Fluss users have asked for.</p>
<p>The third is a <strong>CLI for AI agent integration</strong>. A command-line tool built on the Rust core that can look up keys, write records, and scan tables gives AI agents a natural way to interact with Fluss through tool-calling, without importing a library or managing a runtime. It's equally useful for operators debugging in production, shell scripts, and CI/CD pipelines.</p>
<p>The fourth is a <strong>multiprotocol query gateway</strong> built on top of the Rust core. A Rust-based gateway could expose Fluss through Flight SQL (accessible via ADBC), REST, and the PostgreSQL wire protocol. For SQL queries it delegates to DataFusion; for lookups and log writes it calls the Rust core directly.
On the project side, the community is working toward moving fluss-rust into the main Apache Fluss repository. This would unify the release process, simplify cross-repo coordination, and signal the project's long-term commitment to the multi-language SDK as a first-class part of Fluss.</p>
<p>If any of this is interesting to you, we welcome contributions, bug reports, and feedback.</p>
<hr>
<p>And before you go 😊 don't forget to give some ❤️ via ⭐ on GitHub: <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">Apache Fluss</a> and <a href="https://github.com/apache/fluss-rust" target="_blank" rel="noopener noreferrer" class="">Fluss Rust SDK</a></p>]]></content:encoded>
            <category>Fluss Rust</category>
            <category>fluss-rs</category>
            <category>PyFluss</category>
            <category>Fluss C++</category>
            <category>Arrow</category>
        </item>
        <item>
            <title><![CDATA[Announcing Apache Fluss (Incubating) Rust, Python, and C++ Client 0.1.0 Release]]></title>
            <link>https://fluss.apache.org/blog/fluss_rust_client_release/</link>
            <guid>https://fluss.apache.org/blog/fluss_rust_client_release/</guid>
            <pubDate>Mon, 06 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-3a23e44c21af69fe1612d19a3d959ef4.png" width="1409" height="479" class="img_ev3q"></p>
<p>We are excited to announce the release of <a href="https://github.com/apache/fluss-rust" target="_blank" rel="noopener noreferrer" class="">fluss-rust clients</a> 0.1.0, the first official release of the <a href="https://clients.fluss.apache.org/user-guide/rust/installation" target="_blank" rel="noopener noreferrer" class="">Rust</a>, <a href="https://clients.fluss.apache.org/user-guide/python/installation" target="_blank" rel="noopener noreferrer" class="">Python</a>, and <a href="https://clients.fluss.apache.org/user-guide/cpp/installation" target="_blank" rel="noopener noreferrer" class="">C++</a> clients for Apache Fluss. This 0.1.0 release represents the culmination of 210+ commits from the community, delivering a feature-rich multi-language client from the ground up.</p>
<p>Under the hood, all three clients share a single Rust core that handles protocol negotiation, batching, retries, and <a href="https://arrow.apache.org/" target="_blank" rel="noopener noreferrer" class="">Apache Arrow</a>-based data exchange, with thin language-specific bindings on top. This was a deliberate community decision to deliver native performance and feature parity across every language from day one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="highlights">Highlights<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#highlights" class="hash-link" aria-label="Direct link to Highlights" title="Direct link to Highlights" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="support-for-all-fluss-table-types">Support for all Fluss table types<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#support-for-all-fluss-table-types" class="hash-link" aria-label="Direct link to Support for all Fluss table types" title="Direct link to Support for all Fluss table types" translate="no">​</a></h3>
<ul>
<li class="">Log Tables - Append-only streaming ingestion with subscription-based polling. Example uses include clickstreams, IoT sensor data, and audit logs. See <a href="https://clients.fluss.apache.org/user-guide/rust/example/log-tables" target="_blank" rel="noopener noreferrer" class="">log table examples</a>
(<a href="https://clients.fluss.apache.org/user-guide/python/example/log-tables" target="_blank" rel="noopener noreferrer" class="">Python</a>, <a href="https://clients.fluss.apache.org/user-guide/cpp/example/log-tables" target="_blank" rel="noopener noreferrer" class="">C++</a>).</li>
<li class="">Primary Key Tables - Upsert, delete, and point lookups by key, with support for partial column updates. Example uses include product catalogs and real-time dashboards backed by data from multiple sources.
See <a href="https://clients.fluss.apache.org/user-guide/rust/example/primary-key-tables" target="_blank" rel="noopener noreferrer" class="">primary key table examples</a> (<a href="https://clients.fluss.apache.org/user-guide/python/example/primary-key-tables" target="_blank" rel="noopener noreferrer" class="">Python</a>, <a href="https://clients.fluss.apache.org/user-guide/cpp/example/primary-key-tables" target="_blank" rel="noopener noreferrer" class="">C++</a>).</li>
<li class="">Partitioned Tables - Both Log and Primary Key tables support partitioning, with partition-aware reads and writes.
See the <a href="https://clients.fluss.apache.org/user-guide/rust/example/partitioned-tables" target="_blank" rel="noopener noreferrer" class="">partitioned table examples</a> (<a href="https://clients.fluss.apache.org/user-guide/python/example/partitioned-tables" target="_blank" rel="noopener noreferrer" class="">Python</a>, <a href="https://clients.fluss.apache.org/user-guide/cpp/example/partitioned-tables" target="_blank" rel="noopener noreferrer" class="">C++</a>).</li>
</ul>
<p>All table types support idempotent writes and memory-bounded backpressure for reliable production use. The scanner includes parallel, prioritized remote segment fetching for efficient reads over tiered storage. The Python client additionally provides Polars and Pandas specific APIs for seamless integration with dataframe workflows.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="zero-copy-arrow-integration">Zero-copy Arrow integration<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#zero-copy-arrow-integration" class="hash-link" aria-label="Direct link to Zero-copy Arrow integration" title="Direct link to Zero-copy Arrow integration" translate="no">​</a></h3>
<p>Leverage your existing Apache Arrow ecosystem. Whether you use Polars, Pandas, DuckDB, DataFusion, or Arrow directly, the clients speak Arrow natively. Log records are represented as Arrow RecordBatches throughout the stack, so data arriving from Fluss can be handed directly to your tools without serialization or conversion.</p>
<p>A batch scanner mode makes this practical at scale: it returns complete RecordBatches to the caller, ready for immediate use. Python users can pass batches straight to Polars or Pandas, C++ applications can feed them into DuckDB, and Rust tools benefit from the same zero-copy path. The Java client does not offer this today because Flink and Spark convert data into their own internal row formats on ingestion. For the native ecosystem, skipping that conversion is where the performance wins are.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="direct-reads-from-tiered-storage">Direct reads from tiered storage<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#direct-reads-from-tiered-storage" class="hash-link" aria-label="Direct link to Direct reads from tiered storage" title="Direct link to Direct reads from tiered storage" translate="no">​</a></h3>
<p>Fluss tiers older log segments to remote storage. When you need to replay or backfill historical data, fluss-rust reads those segments directly from the object store rather than routing them back through the server. This keeps replay workloads off the serving path and avoids paying for data to round-trip through the cluster.</p>
<p>Supported storage backends (enabled via feature flags):</p>
<ul>
<li class=""><strong>storage-fs</strong> — Local filesystem (default)</li>
<li class=""><strong>storage-s3</strong> — <a href="https://aws.amazon.com/s3/" target="_blank" rel="noopener noreferrer" class="">Amazon S3</a></li>
<li class=""><strong>storage-oss</strong> — <a href="https://www.alibabacloud.com/product/object-storage-service" target="_blank" rel="noopener noreferrer" class="">Alibaba Object Storage Service</a></li>
</ul>
<p>A priority-queue-based prefetching system with configurable concurrent downloads keeps sequential scans from stalling on object store round trips.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="other-features">Other Features<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#other-features" class="hash-link" aria-label="Direct link to Other Features" title="Direct link to Other Features" translate="no">​</a></h3>
<p>The release also includes SASL/PLAIN authentication across all three clients, a comprehensive admin API for database and table management, fire-and-forget write batching with configurable bucket assignment strategies, column projection, and more.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting Started<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started" translate="no">​</a></h3>
<ul>
<li class="">Rust: fluss-rs <a href="https://clients.fluss.apache.org/user-guide/rust/installation" target="_blank" rel="noopener noreferrer" class="">installation guide</a>.</li>
<li class="">Python: pyfluss <a href="https://clients.fluss.apache.org/user-guide/python/installation" target="_blank" rel="noopener noreferrer" class="">installation guide</a>.</li>
<li class="">C++: fluss-cpp <a href="https://clients.fluss.apache.org/user-guide/cpp/installation" target="_blank" rel="noopener noreferrer" class="">installation guide</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's Next<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#whats-next" class="hash-link" aria-label="Direct link to What's Next" title="Direct link to What's Next" translate="no">​</a></h3>
<p>This is the first release of fluss-rs, pyfluss and fluss-cpp, and the community is actively working on expanding capabilities. Areas of future development include additional language bindings, additional storage backends, enhanced compression support, and expanded ecosystem integrations.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="thank-you-contributors">Thank You, Contributors<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#thank-you-contributors" class="hash-link" aria-label="Direct link to Thank You, Contributors" title="Direct link to Thank You, Contributors" translate="no">​</a></h3>
<p>This release would not have been possible without the efforts of our contributors. Thank you to everyone who submitted code, reported issues, reviewed pull requests, and helped shape the project:</p>
<p><a href="https://github.com/zhaohaidao" target="_blank" rel="noopener noreferrer" class="">AlexZhao</a>, <a href="https://github.com/AndreaBozzo" target="_blank" rel="noopener noreferrer" class="">Andrea Bozzo</a>, <a href="https://github.com/fresh-borzoni" target="_blank" rel="noopener noreferrer" class="">Anton Borisov</a>, <a href="https://github.com/Arnav-panjla" target="_blank" rel="noopener noreferrer" class="">Arnav Panjla</a>, <a href="https://github.com/cnaples79" target="_blank" rel="noopener noreferrer" class="">Chase Naples</a>, <a href="https://github.com/binary-signal" target="_blank" rel="noopener noreferrer" class="">Evan</a>, <a href="https://github.com/wuchong" target="_blank" rel="noopener noreferrer" class="">Jark Wu</a>, <a href="https://github.com/beryllw" target="_blank" rel="noopener noreferrer" class="">Junbo Wang</a>, <a href="https://github.com/zuston" target="_blank" rel="noopener noreferrer" class="">Junfan Zhang</a>, <a href="https://github.com/charlesdong1991" target="_blank" rel="noopener noreferrer" class="">Kaiqi Dong</a>, <a href="https://github.com/KaranPradhan266" target="_blank" rel="noopener noreferrer" class="">Karan Pradhan</a>, <a href="https://github.com/leekeiabstraction" target="_blank" rel="noopener noreferrer" class="">Keith Lee</a>, <a href="https://github.com/Kelvinyu1117" target="_blank" rel="noopener noreferrer" class="">Kelvin Wu</a>, <a href="https://github.com/lemorage" target="_blank" rel="noopener noreferrer" class="">Miao</a>, <a href="https://github.com/niknegi" target="_blank" rel="noopener noreferrer" class="">Nikhil Negi</a>, <a href="https://github.com/pavlospt" target="_blank" rel="noopener noreferrer" class="">Pavlos-Petros Tournaris</a>, <a href="https://github.com/Prajwal-banakar" target="_blank" rel="noopener noreferrer" class="">Prajwal Banakar</a>, <a href="https://github.com/linguoxuan" target="_blank" rel="noopener noreferrer" class="">SkylerLin</a>, <a href="https://github.com/gyang94" target="_blank" rel="noopener noreferrer" class="">Yang Guo</a>, <a href="https://github.com/luoyuxia" target="_blank" rel="noopener noreferrer" class="">Yuxia Luo</a>, <a href="https://github.com/naivedogger" target="_blank" rel="noopener noreferrer" class="">naivedogger</a>, and <a href="https://github.com/tisonkun" target="_blank" rel="noopener noreferrer" class="">tison</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-involved">Getting Involved<a href="https://fluss.apache.org/blog/fluss_rust_client_release/#getting-involved" class="hash-link" aria-label="Direct link to Getting Involved" title="Direct link to Getting Involved" translate="no">​</a></h3>
<p>The Apache Fluss community welcomes contributions!</p>
<ul>
<li class="">Client GitHub: <a href="https://github.com/apache/fluss-rust" target="_blank" rel="noopener noreferrer" class="">https://github.com/apache/fluss-rust</a></li>
<li class="">Client Documentation: <a href="https://clients.fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">https://clients.fluss.apache.org</a></li>
<li class="">Website: <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/</a></li>
<li class="">Mailing List: <a href="https://fluss.apache.org/community/welcome/#mailing-lists" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/community/welcome/#mailing-lists</a></li>
<li class="">Slack: <a href="https://apache-fluss.slack.com/" target="_blank" rel="noopener noreferrer" class="">https://apache-fluss.slack.com</a></li>
</ul>]]></content:encoded>
            <category>releases</category>
            <category>Fluss Rust</category>
            <category>fluss-rs</category>
            <category>PyFluss</category>
            <category>Fluss C++</category>
            <category>Arrow</category>
        </item>
        <item>
            <title><![CDATA[What does Apache Fluss mean in the context of AI?]]></title>
            <link>https://fluss.apache.org/blog/fluss-for-ai/</link>
            <guid>https://fluss.apache.org/blog/fluss-for-ai/</guid>
            <pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The Data Foundation for Real-Time Intelligent Systems]]></description>
            <content:encoded><![CDATA[<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-data-foundation-for-real-time-intelligent-systems">The Data Foundation for Real-Time Intelligent Systems<a href="https://fluss.apache.org/blog/fluss-for-ai/#the-data-foundation-for-real-time-intelligent-systems" class="hash-link" aria-label="Direct link to The Data Foundation for Real-Time Intelligent Systems" title="Direct link to The Data Foundation for Real-Time Intelligent Systems" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/banner-a3fa3c1c723340acae7046f86be1de82.png" width="1024" height="1024" class="img_ev3q">
<strong>Apache Fluss (Incubating) started as streaming storage for real-time analytics</strong>, built to work closely with stream processors like Apache Flink.
Its focus has always been on freshness, efficient analytical access, and continuous data, making fast-changing streams directly usable without forcing them
through batch-oriented systems or log-only pipelines.</p>
<p>Over the last year, Fluss has expanded beyond this original framing. You’ll now see it described as streaming storage for <strong>real-time analytics and AI</strong>. This change reflects how data systems are being used today: more workloads depend on continuously updated data, low-latency access to evolving state, and the ability to reason over context as it changes.</p>
<p>In this context, “AI” does not mean training or serving models inside Fluss. It refers to the class of intelligent systems that rely on fresh features, evolving context, and real-time state to make decisions continuously. Whether those systems use traditional machine learning models, newer AI techniques, or a combination of both, they all depend on the same data foundations.</p>
<p>This shift explains the recent evolution of Apache Fluss. Investments in stateless compute, richer data types with zero-copy schema evolution, and vector support through Lance were driven by a single question:</p>
<blockquote>
<p>What does a data foundation need to look like to support real-time intelligent systems reliably at scale?</p>
</blockquote>
<p>The rest of this post answers that question. We’ll explain what AI means when viewed through the lens of Apache Fluss, and why a streaming-first foundation for features, context, and state is central to building the next generation of intelligent systems.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-we-mean-by-real-time-intelligent-systems">What We Mean by “Real-Time Intelligent Systems”<a href="https://fluss.apache.org/blog/fluss-for-ai/#what-we-mean-by-real-time-intelligent-systems" class="hash-link" aria-label="Direct link to What We Mean by “Real-Time Intelligent Systems”" title="Direct link to What We Mean by “Real-Time Intelligent Systems”" translate="no">​</a></h3>
<p>In this post, when we talk about real-time intelligent systems, we are not referring to a specific class of models or a particular AI technique. We are describing a category of systems defined by how they behave over time and how they interact with data that is constantly changing.</p>
<p>At a minimum, these systems continuously ingest live data: events, updates, interactions, signals, from the world around them. The data never really “stops,” and the system is expected to keep up, processing information as it arrives rather than waiting for periodic batch windows.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/intelligence-7746ed279c3e98e4416e91fce7af2e4c.png" width="1414" height="801" class="img_ev3q"></p>
<p>They also maintain state that evolves over time. This state might represent user profiles, counters, risk scores, preferences, embeddings, or derived features. Crucially, this state is not static; it is updated incrementally as new data arrives and as past information becomes less relevant or is refined.</p>
<p>On top of that, real-time intelligent systems make decisions repeatedly, not once per day or once per job run. They score, rank, filter, recommend, or trigger actions continuously, and their outputs are expected to adapt as conditions change. The feedback loop between data, state, and decisions is tight and ongoing.</p>
<p>From the perspective of a streaming storage system, this framing matters. The intelligence may come from ML models, rules, LLMs, or agents, but the system itself is fundamentally about data plus state over time. Fresh data, historical context, and continuously updated state are the real inputs and enabling that combination efficiently is where the data foundation becomes critical.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-fluss-came-from-streaming-storage-for-real-time-analytics">Where Fluss Came From: Streaming Storage for Real-Time Analytics<a href="https://fluss.apache.org/blog/fluss-for-ai/#where-fluss-came-from-streaming-storage-for-real-time-analytics" class="hash-link" aria-label="Direct link to Where Fluss Came From: Streaming Storage for Real-Time Analytics" title="Direct link to Where Fluss Came From: Streaming Storage for Real-Time Analytics" translate="no">​</a></h3>
<p>To understand why Apache Fluss is evolving in the direction it is today, it helps to look at the architecture patterns it was originally designed to address. Traditional streaming systems were excellent at moving events quickly, but far less effective at making those events easily queryable once they were in motion.</p>
<p>A common architecture emerged as a result: producers write to Kafka, stream processors like Flink consume and process the data, state lives inside the compute layer, and results are pushed out to multiple downstream systems. Analytical queries usually happen later, against an OLAP system or a lakehouse populated asynchronously.</p>
<p>Over time, this led to a familiar but complex stack. Kafka handled transport, Flink handled computation, an operational database stored the latest state, an OLAP engine supported analytics, and a lakehouse stored historical data. Each component solved a real problem, but the same data had to be copied, transformed, and re-ingested at every layer.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/log_based-a5054a838d4fedd63211acd322b4bf0e.png" width="1191" height="511" class="img_ev3q"></p>
<p>This duplication came at a cost. Pipelines became harder to reason about, data freshness varied across systems, and infrastructure costs grew as every
layer maintained its own storage and indexing strategy. Querying <strong>what’s happening now</strong> often meant stitching together multiple systems that were never designed to work as a single whole.</p>
<p>Apache Fluss started as a response to this fragmentation. The core idea was to ingest streaming data once, store it durably, and make it directly queryable in near real time using analytical access patterns. Instead of treating streams as transient transport, Fluss treats them as a durable dataset, one that can serve both continuous processing and real-time analytics without constant data movement.
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss-ad3d70a3c20dd0c7cd6fa2fa95309ef6.png" width="1502" height="753" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-last-year-three-big-investments-that-changed-what-fluss-can-be">The Last Year: Three Big Investments That Changed What Fluss Can Be<a href="https://fluss.apache.org/blog/fluss-for-ai/#the-last-year-three-big-investments-that-changed-what-fluss-can-be" class="hash-link" aria-label="Direct link to The Last Year: Three Big Investments That Changed What Fluss Can Be" title="Direct link to The Last Year: Three Big Investments That Changed What Fluss Can Be" translate="no">​</a></h3>
<p>Apache Fluss is an open-source project, and its evolution over the last year reflects very deliberate architectural choices rather than a collection of disconnected features. The work we invested in during this period was guided by a single question:</p>
<blockquote>
<p>What kind of data foundation is needed for systems that are continuous, stateful, and expected to evolve over time?</p>
</blockquote>
<p>Those investments converged around three major areas. Each one addressed a recurring limitation we observed in real-world streaming architectures, and each one pushed Fluss beyond its original role as “just” streaming storage for real-time analytics. Together, they define the next stage of what Fluss can support.</p>
<p>The first investment focused on how compute and state interact. The second addressed how real systems evolve at the data model level. The third extended Fluss into the vector domain, which has become foundational for many intelligent workloads. While these areas may seem independent at first glance, they reinforce each other at the system level.</p>
<p>Importantly, these changes also influenced how Fluss fits into the broader lakehouse ecosystem. We strengthened its role as a durable, queryable layer that can interoperate with engines like Flink, Spark, DuckDB and soon more; and integrate cleanly with open table formats such as Apache Iceberg, Paimon, and Lance, rather than sitting outside the lakehouse as a special-purpose system.</p>
<p>What follows is a closer look at each of these investments, why they matter individually, and how they collectively move Fluss toward becoming a long-term foundation for real-time intelligent systems.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stateless-compute-zero-state-streaming">Stateless Compute (Zero-State Streaming)<a href="https://fluss.apache.org/blog/fluss-for-ai/#stateless-compute-zero-state-streaming" class="hash-link" aria-label="Direct link to Stateless Compute (Zero-State Streaming)" title="Direct link to Stateless Compute (Zero-State Streaming)" translate="no">​</a></h4>
<p>One of the clearest directions in modern streaming architectures is the separation of compute and state. Over the last year, we invested heavily in stateless stream processing, driven by the idea that <strong>compute should be lightweight and replaceable, while state should be durable and externalized</strong>.</p>
<p>In many traditional streaming systems, state lives inside the stream processor itself. This tightly couples long-lived state with the compute runtime, making recovery slow, scaling expensive, and operational complexity high. Restarting or resizing a job often means rebuilding large amounts of state before the system becomes useful again.</p>
<p>With stateless compute, stream processors focus purely on processing, while durable state is managed externally in Fluss. Compute becomes disposable, while state remains stable and queryable, independent of any single job or runtime.</p>
<p>This separation enables faster recovery, elastic scaling, and significantly simpler operations. It also leads to stronger RTO and RPO characteristics for stateful pipelines, because state is no longer trapped inside ephemeral compute containers.</p>
<p>For real-time intelligent systems, this matters deeply. These systems are continuous by nature and depend on ever-evolving state. Making compute stateless allows teams to evolve logic, scale workloads, and recover from failures without destabilizing the intelligence built on top of that state.</p>
<p>You can find out more <a href="https://www.ververica.com/blog/introducing-the-era-of-zero-state-streaming-joins?hs_preview=cdhHvcIE-199898654106" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="complex-data-types-and-zero-copy-schema-evolution">Complex Data Types and Zero-Copy Schema Evolution<a href="https://fluss.apache.org/blog/fluss-for-ai/#complex-data-types-and-zero-copy-schema-evolution" class="hash-link" aria-label="Direct link to Complex Data Types and Zero-Copy Schema Evolution" title="Direct link to Complex Data Types and Zero-Copy Schema Evolution" translate="no">​</a></h4>
<p>Real systems do not stand still, and neither do their data models. Over time, new fields are added, existing structures become more nested, and records that started simple accumulate richer context. If schema evolution is painful, innovation slows or technical debt piles up.</p>
<p>To address this, we invested in support for complex data types and zero-copy schema evolution. This allows Fluss to handle rich, nested structures without forcing users to flatten their data or redesign pipelines every time requirements change.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/schema-40f1ea728824e67fcde426172547526b.png" width="1536" height="1024" class="img_ev3q"></p>
<p>Zero-copy schema evolution means schemas can evolve without rewriting existing data or triggering large-scale migrations. A record can grow from a few scalar fields to include nested structures, contextual attributes, or even embeddings, while older data remains valid and accessible.</p>
<p>This capability is especially important in environments where Fluss acts as both a streaming store and part of a broader lakehouse architecture. By aligning with open formats and improving interoperability with systems like Apache Iceberg, Fluss can participate in analytical workflows without imposing rigid schema constraints.</p>
<p>For intelligent systems, this flexibility is essential. Features, profiles, and contextual records evolve rapidly, and the data foundation must keep up without breaking pipelines or forcing expensive reprocessing.</p>
<p><strong>Note:</strong> Schema changes are also propagated downstream to open table formats like Apache Iceberg, Paimon, and Lance, ensuring that data remains accessible and usable across different systems and use cases.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="vectors-and-lance">Vectors and Lance<a href="https://fluss.apache.org/blog/fluss-for-ai/#vectors-and-lance" class="hash-link" aria-label="Direct link to Vectors and Lance" title="Direct link to Vectors and Lance" translate="no">​</a></h4>
<p>Modern intelligent systems increasingly rely on vector embeddings to represent unstructured data such as text, images, audio, and video. These embeddings enable similarity-based queries that are central to use cases like semantic search, recommendation, and content discovery.</p>
<p>Traditionally, vectors are stored in specialized vector databases, separate from streaming systems and lakehouses. That separation introduces yet another silo, along with additional ingestion pipelines and consistency challenges.</p>
<p>Over the last year, we invested in bringing vector support closer to Fluss through integration with Lance. This allows vector data to live alongside structured and streaming data, rather than being isolated in a separate system.</p>
<p>By colocating vectors with the rest of the data foundation, Fluss can support hybrid workloads where structured attributes, streaming signals, and embeddings are all part of the same logical dataset. This also aligns with ongoing lakehouse work, making it easier to integrate vector-aware workloads with analytical engines and table formats like Iceberg.</p>
<p>For real-time intelligent systems, this reduces friction significantly. When vectors are no longer a separate silo, building end-to-end pipelines, from ingestion to feature generation to retrieval, becomes simpler, more consistent, and easier to operate at scale.</p>
<p>You can find a quickstart tutorial <a href="https://lancedb.com/blog/fluss-integration/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-aha-moment-real-time-feature-store-and-zero-drift">The “Aha” Moment: Real-Time Feature Store and Zero Drift<a href="https://fluss.apache.org/blog/fluss-for-ai/#the-aha-moment-real-time-feature-store-and-zero-drift" class="hash-link" aria-label="Direct link to The “Aha” Moment: Real-Time Feature Store and Zero Drift" title="Direct link to The “Aha” Moment: Real-Time Feature Store and Zero Drift" translate="no">​</a></h3>
<p>As these capabilities came together, we started noticing a recurring pattern: teams were increasingly asking for real-time feature store behavior, even when they weren’t explicitly building a “feature store.” The need kept emerging organically once streaming data, durable state, and low-latency access converged in a single system.</p>
<p>At its core, a feature store exists to compute and serve features, the structured inputs consumed by machine learning models. These features might represent recent activity, aggregated behavior, derived metrics, or continuously updated scores that capture how an entity changes over time.</p>
<p>The reason feature stores exist at all is a well-known failure mode in production ML systems. Training data is often computed offline using batch pipelines, while inference data is computed online using streaming or request-time logic. Over time, these two paths drift apart in logic, timing, or semantics.</p>
<p>This divergence is commonly referred to as <strong>training–serving skew</strong>. It’s subtle, hard to detect early, and is responsible for a large number of models underperforming in production despite looking correct during training and evaluation.</p>
<p>Once you view the problem through a data foundation lens, the implication becomes clear: eliminating drift is less about adding more tooling and
more about ensuring that <strong>training and serving are built on the same underlying data, with the same semantics</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-fluss-as-a-feature-store">Why Fluss as a Feature Store<a href="https://fluss.apache.org/blog/fluss-for-ai/#why-fluss-as-a-feature-store" class="hash-link" aria-label="Direct link to Why Fluss as a Feature Store" title="Direct link to Why Fluss as a Feature Store" translate="no">​</a></h3>
<p>Because Fluss stores streaming data durably, it can act as both a historical record and a source of continuously updated state, without splitting the data across separate systems.</p>
<p>From the same dataset, Fluss can support historical scans used for training, as well as latest-state access used for online inference. The data does not need to be duplicated, re-materialized, or reshaped into two different pipelines.</p>
<p>This unification is the critical shift. When training and serving operate on the same dataset, with the same schema and update semantics, drift becomes dramatically easier to avoid. The system enforces consistency by construction rather than by convention.</p>
<p>In practice, this means feature computation logic can be shared or reused, and the gap between <strong><code>offline</code></strong> and <strong><code>online</code></strong> feature views shrinks. The distinction still exists at the access level, but not at the data foundation level.</p>
<p>This is why feature-store-like use cases naturally emerge once you build a shared, durable streaming foundation. Fluss doesn’t start as a feature store, but it removes the architectural reasons that feature stores had to exist as separate systems in the first place.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-feature-stores-to-context-stores">From Feature Stores to Context Stores<a href="https://fluss.apache.org/blog/fluss-for-ai/#from-feature-stores-to-context-stores" class="hash-link" aria-label="Direct link to From Feature Stores to Context Stores" title="Direct link to From Feature Stores to Context Stores" translate="no">​</a></h3>
<p>Traditional feature stores focus narrowly on model inputs, structured, derived attributes designed to be stable and predictable.
That framing works well for many ML pipelines, <strong>but it only captures part of what real-time intelligent systems actually need</strong>.</p>
<p>In practice, these systems depend on context, not just features.</p>
<blockquote>
<p>Context includes structured features, but also the latest entity state, recent event sequences, historical data for reconstruction, and increasingly, embeddings for semantic understanding.</p>
</blockquote>
<p>A context store is therefore broader than a feature store. It provides multiple perspectives on the same underlying data, depending on whether the system is training a model, making a real-time decision, or debugging past behavior.</p>
<p>Apache Fluss fits this model naturally because it can expose multiple views over the same durable streaming dataset. The same data can serve as a feature table, a state store, an event log, or a historical archive, depending on how it is accessed.</p>
<p>This flexibility matters because real-time intelligent systems rarely draw a clean line between features and context. They blend both, and the data foundation needs to support that blend without forcing artificial separation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-modal-data-and-unified-access-patterns">Multi-Modal Data and Unified Access Patterns<a href="https://fluss.apache.org/blog/fluss-for-ai/#multi-modal-data-and-unified-access-patterns" class="hash-link" aria-label="Direct link to Multi-Modal Data and Unified Access Patterns" title="Direct link to Multi-Modal Data and Unified Access Patterns" translate="no">​</a></h3>
<p>Modern intelligent systems work with more than just rows in a table. They combine structured records, semi-structured events, unstructured content, and vector representations derived from that content.</p>
<p>A single real-time decision might depend on a user’s latest profile, their recent actions, the text they just submitted, and embeddings representing both the query and previously consumed content. In many architectures, each of these elements lives in a different system.</p>
<p>Fluss aims to make this feel like a single, coherent foundation by supporting multiple access patterns over the same data. These patterns align with how intelligent systems actually consume information.</p>
<p>Row-oriented access retrieves the latest state for a specific key and is essential for real-time decisions. Column-oriented access scans attributes across many entities and underpins analytics and model training. Vector access enables semantic similarity search and retrieval.</p>
<p>By converging row, column, and vector access on a shared foundation, Apache Fluss reduces system sprawl and conceptual overhead. The intelligence still lives in the models and applications, but the data they depend on finally lives in one place.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtual-tables-for-decision-tracking-and-auditability">Virtual Tables for Decision Tracking and Auditability<a href="https://fluss.apache.org/blog/fluss-for-ai/#virtual-tables-for-decision-tracking-and-auditability" class="hash-link" aria-label="Direct link to Virtual Tables for Decision Tracking and Auditability" title="Direct link to Virtual Tables for Decision Tracking and Auditability" translate="no">​</a></h3>
<p>One of the most underestimated requirements of real-time intelligent systems is auditability. When systems make decisions continuously and automatically, it’s no longer enough to know what happened; you need to understand why it happened and what the system knew at the time.</p>
<p>In practice, this means being able to answer questions such as what decision was made, what the system’s state looked like at that moment, which signals influenced the outcome, and how that state evolved leading up to the decision. These questions matter for debugging, trust, and increasingly for regulatory and compliance reasons.</p>
<p>This is where changelogs and virtual tables become foundational. Instead of treating state as something hidden inside a running system, they make state transitions explicit and durable. Every update becomes data that can be queried, replayed, and inspected.</p>
<p>Virtual tables built on changelogs allow systems to treat decisions and state evolution as first-class citizens. Rather than capturing only final outcomes, the system records how it arrived there, step by step, as the data changed.</p>
<p>For real-time intelligent systems, this shifts auditability from an afterthought to a built-in property of the architecture.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="changelogs-in-action-tracking-ai-decisions-over-time">Changelogs in Action: Tracking AI Decisions Over Time<a href="https://fluss.apache.org/blog/fluss-for-ai/#changelogs-in-action-tracking-ai-decisions-over-time" class="hash-link" aria-label="Direct link to Changelogs in Action: Tracking AI Decisions Over Time" title="Direct link to Changelogs in Action: Tracking AI Decisions Over Time" translate="no">​</a></h4>
<p>Consider a real-time system that decides whether to approve or decline transactions. For each user, it maintains a continuously updated decision context containing fields such as risk score, recent activity velocity, total spend, and account state.</p>
<p>Applications compute this context and emit it as a changelog table, where each update is expressed as a semantic change rather than a full overwrite. Changelog events follow a simple model: inserts, updates expressed as retractions plus new values, and deletions.</p>
<p>For example, imagine the following changelog sequence for user_id = 123:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token operator" style="color:#475569">+</span><span class="token plain">I  user_id</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">123</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> risk_score</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">0.12</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> velocity_10m</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> total_spend_24h</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">35</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">-</span><span class="token plain">U  user_id</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">123</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> risk_score</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">0.12</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> velocity_10m</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> total_spend_24h</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">35</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token plain">U  user_id</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">123</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> risk_score</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">0.47</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> velocity_10m</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> total_spend_24h</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">120</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">-</span><span class="token plain">U  user_id</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">123</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> risk_score</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">0.47</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> velocity_10m</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> total_spend_24h</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">120</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token plain">U  user_id</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">123</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> risk_score</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">0.83</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> velocity_10m</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> total_spend_24h</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">240</span><br></div></code></pre></div></div>
<p>Each update captures how the user’s risk profile evolves as new events arrive, rather than hiding those transitions behind a mutable row.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="reconstructing-the-decision">Reconstructing the Decision<a href="https://fluss.apache.org/blog/fluss-for-ai/#reconstructing-the-decision" class="hash-link" aria-label="Direct link to Reconstructing the Decision" title="Direct link to Reconstructing the Decision" translate="no">​</a></h4>
<p>At a certain point, the system evaluates a transaction and produces a decision.
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/changelogs-e93f6836870d9e94511c89f1cd9d1faf.png" width="1377" height="752" class="img_ev3q"></p>
<p>Because the changelog is stored durably, this decision is no longer opaque. You can reconstruct exactly what the system’s state was at decision time and trace how it reached that state through prior updates.</p>
<p>This makes it possible to answer concrete questions: which signals pushed the risk score higher, when thresholds were crossed, and whether the decision logic behaved as expected under changing conditions.</p>
<p>Importantly, this reconstruction does not rely on logs or ad-hoc instrumentation. It falls naturally out of the data model itself.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="changelogs-as-an-audit-trail">Changelogs as an Audit Trail<a href="https://fluss.apache.org/blog/fluss-for-ai/#changelogs-as-an-audit-trail" class="hash-link" aria-label="Direct link to Changelogs as an Audit Trail" title="Direct link to Changelogs as an Audit Trail" translate="no">​</a></h3>
<p>Once changelogs are treated as immutable, append-only records, they become a powerful audit trail. Every change is captured, timestamped, and associated with its update semantics and lineage.</p>
<p>The same approach can be applied beyond the decision state. Teams can track changes to training data versions, model predictions, feature definitions, and user profile evolution using the same underlying mechanism.</p>
<p>Instead of asking “why did the system do this?” and hoping logs still exist, the answer lives in the data. What changed, when it changed, how it changed, and which job or model produced the update are all preserved.</p>
<p>For real-time intelligent systems operating at scale, this level of transparency is not just good engineering; it is quickly becoming a baseline requirement.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-were-going-next">Where We’re Going Next<a href="https://fluss.apache.org/blog/fluss-for-ai/#where-were-going-next" class="hash-link" aria-label="Direct link to Where We’re Going Next" title="Direct link to Where We’re Going Next" translate="no">​</a></h3>
<p>Apache Fluss started as streaming storage for real-time analytics, with a narrow and intentional focus on making continuously changing data queryable as it arrived. That foundation shaped the system’s core assumptions around freshness, durability, and tight integration with stream processing engines.</p>
<p>Over the last year, we invested in capabilities that extend Fluss beyond analytics without abandoning that original focus. Stateless compute and externalized state changed how systems scale and recover. Support for complex data types and zero-copy schema evolution made it possible for data models to grow without constant rewrites. Vector support and Lance integration brought unstructured and semantic data into the same foundation.</p>
<p>At the same time, the direction of the broader industry has become increasingly clear. Intelligent systems depend on shared, real-time context that spans features, state, history, and embeddings. Feature-store-like capabilities are no longer a niche requirement—they are becoming a central building block for systems that operate continuously and adapt over time.</p>
<p>These threads converge on a direction we are committing to: Apache Fluss as a streaming-first foundation that supports real-time analytics, feature and context access, multi-modal data, decision tracking, and explainability. All of this is built on stateless, resilient streaming applications that can evolve without destabilizing the system.</p>
<p>That is what “AI” means in the context of Apache Fluss. Not a model platform, and not a framework, but the data and state foundation that real-time intelligent systems depend on to operate reliably, transparently, and at scale.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Apache Fluss (Incubating) 0.9 Release Announcement]]></title>
            <link>https://fluss.apache.org/blog/releases/0.9/</link>
            <guid>https://fluss.apache.org/blog/releases/0.9/</guid>
            <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-b4e3815d84fc459897c2d04c7e90ec37.png" width="1536" height="1024" class="img_ev3q"></p>
<p>🌊 We are excited to announce the official release of <strong>Apache Fluss (Incubating) 0.9</strong>!</p>
<p>This release marks a major milestone for the project. Fluss 0.9 significantly expands Fluss’s capabilities as a <strong>streaming storage system for real-time analytics, AI, and state-heavy streaming workloads</strong>, with a strong focus on:</p>
<ul>
<li class="">Richer and more flexible data models</li>
<li class="">Safe, zero-downtime schema evolution</li>
<li class="">Storage-level optimizations (aggregations, CDC, formats)</li>
<li class="">Stronger operational guarantees and scalability</li>
<li class="">A more mature ecosystem and developer experience</li>
</ul>
<p>Whether you’re building <strong>unified stream &amp; lakehouse architectures</strong>, <strong>real-time analytics</strong>, <strong>feature/context stores</strong>, or <strong>long-running stateful pipelines</strong>, Fluss 0.9 introduces powerful new primitives that make these systems easier, safer, and more efficient to operate at scale.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tldr-what-fluss-09-unlocks">TL;DR: What Fluss 0.9 Unlocks<a href="https://fluss.apache.org/blog/releases/0.9/#tldr-what-fluss-09-unlocks" class="hash-link" aria-label="Direct link to TL;DR: What Fluss 0.9 Unlocks" title="Direct link to TL;DR: What Fluss 0.9 Unlocks" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Features" src="https://fluss.apache.org/assets/images/features-b6569c7b33e37a6ff8daeb38e0fcde7c.png" width="2658" height="1622" class="img_ev3q"></p>
<ul>
<li class=""><strong>Zero-copy schema evolution</strong> for evolving streaming jobs</li>
<li class=""><strong>Storage-level aggregations</strong> that further enhances zero-state processing</li>
<li class=""><strong>Change data feed</strong> for CDC, audit trails, point-in-time recovery, and ML reproducibility</li>
<li class=""><strong>Safer snapshot-based reads</strong> with consumer-aware lifecycle management</li>
<li class=""><strong>Operationally robust clusters</strong> with automatic rebalancing and safer maintenance workflows</li>
<li class=""><strong>Apache Spark integration</strong>, enabling unified batch and streaming analytics on Fluss</li>
<li class=""><strong>First-class Azure support</strong>, allowing Fluss to tier and operate seamlessly on Azure Blob Storage and ADLS Gen2</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-richer-data-models--schema-evolution">1. Richer Data Models &amp; Schema Evolution<a href="https://fluss.apache.org/blog/releases/0.9/#1-richer-data-models--schema-evolution" class="hash-link" aria-label="Direct link to 1. Richer Data Models &amp; Schema Evolution" title="Direct link to 1. Richer Data Models &amp; Schema Evolution" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="support-for-complex-data-types">Support for Complex Data Types<a href="https://fluss.apache.org/blog/releases/0.9/#support-for-complex-data-types" class="hash-link" aria-label="Direct link to Support for Complex Data Types" title="Direct link to Support for Complex Data Types" translate="no">​</a></h3>
<p>Apache Fluss 0.9 strengthens and extends support for <strong>complex data types</strong> with a focus on <strong>deep nesting</strong>, <strong>safe schema evolution</strong>, and new <strong>ML-oriented use cases</strong>.</p>
<p>Supported types now include <code>ARRAY</code>, <code>MAP</code>, <code>ROW</code>, and deeply nested structures. For example, complex schemas such as:</p>
<blockquote>
<p><code>ARRAY&lt;MAP&lt;STRING, ROW&lt;values ARRAY&lt;FLOAT&gt;, ts TIMESTAMP_LTZ(3)&gt;&gt;&gt;</code></p>
</blockquote>
<p>are now fully supported in production. These nested structures are handled as <strong>schema-aware</strong> rows, not opaque payloads, ensuring <strong>data correctness</strong> and <strong>type safety</strong>.</p>
<p>Additionally, adding new columns does not affect existing jobs after clients upgrade to 0.9. Moreover, with support for the <strong>Lance format</strong>, Fluss can be used for the ingestion of <strong>multi-modal data</strong> and <strong>vector storage</strong>. Users can now store <strong>embeddings</strong> directly in tables using <code>ARRAY&lt;FLOAT&gt;</code> or <code>ARRAY&lt;DOUBLE&gt;</code>:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> documents </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  doc_id </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  embedding ARRAY</span><span class="token operator" style="color:#475569">&lt;</span><span class="token keyword" style="color:#194670">FLOAT</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>This enables new use cases where Fluss acts as the <strong>source of truth for vector embeddings</strong>, which can then be incrementally consumed by vector engines to maintain <strong>ANN indexes</strong>.</p>
<p>You can find the complete feature umbrella <a href="https://github.com/apache/fluss/issues/816" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="schema-evolution-with-zero-copy-semantics">Schema Evolution with Zero-Copy Semantics<a href="https://fluss.apache.org/blog/releases/0.9/#schema-evolution-with-zero-copy-semantics" class="hash-link" aria-label="Direct link to Schema Evolution with Zero-Copy Semantics" title="Direct link to Schema Evolution with Zero-Copy Semantics" translate="no">​</a></h3>
<p><strong>Schema evolution</strong> is critical for evolving systems and use cases like <strong>feature calculations</strong>, and Fluss 0.9 delivers a major step forward in this area.</p>
<p>The release adds support for <strong>altering table schemas</strong> by appending new columns, fully integrated with <strong>Flink SQL</strong>. For example:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Add a single column at the end of the table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> my_table </span><span class="token keyword" style="color:#194670">ADD</span><span class="token plain"> user_email STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'User email address'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Add multiple columns at the end of the table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> MyTable </span><span class="token keyword" style="color:#194670">ADD</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    user_email STRING </span><span class="token keyword" style="color:#194670">COMMENT</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'User email address'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    order_quantity </span><span class="token keyword" style="color:#194670">INT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>For more information, see <a href="https://fluss.apache.org/docs/0.9/engine-flink/ddl/#alter-table" target="_blank" rel="noopener noreferrer" class="">Flink DDL support</a>.</p>
<p><strong>Zero-copy schema evolution</strong> means that existing data files are <strong>not rewritten</strong> when a schema changes. Instead, only <strong>metadata is updated</strong>.</p>
<p>Existing records simply do not contain the new column, and readers interpret missing fields as <code>NULL</code>. New records immediately include the new column without impacting historical data.</p>
<p>This approach <strong>avoids downtime</strong>, <strong>eliminates expensive backfills</strong>, and ensures <strong>predictable performance</strong> during schema changes. It is especially important for streaming pipelines that are expected to run continuously over long periods of time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-storage-level-processing--semantics">2. Storage-Level Processing &amp; Semantics<a href="https://fluss.apache.org/blog/releases/0.9/#2-storage-level-processing--semantics" class="hash-link" aria-label="Direct link to 2. Storage-Level Processing &amp; Semantics" title="Direct link to 2. Storage-Level Processing &amp; Semantics" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="aggregation-merge-engine">Aggregation Merge Engine<a href="https://fluss.apache.org/blog/releases/0.9/#aggregation-merge-engine" class="hash-link" aria-label="Direct link to Aggregation Merge Engine" title="Direct link to Aggregation Merge Engine" translate="no">​</a></h3>
<p>Apache Fluss now supports <strong>storage-level aggregations</strong> via a new <strong>Aggregation Merge Engine</strong>, enabling real-time aggregation to be pushed down from the compute layer into the <strong>Fluss storage layer</strong>.</p>
<p>Traditionally, real-time aggregations are maintained in Flink state, which can lead to:</p>
<ul>
<li class=""><strong>Large and growing state size</strong></li>
<li class=""><strong>Slower checkpoints and recovery</strong></li>
<li class=""><strong>Limited scalability</strong> for high-cardinality aggregations</li>
</ul>
<p>With the <strong>Aggregation Merge Engine</strong>, aggregation state is <strong>externalized to Fluss</strong>, allowing Flink jobs to remain <strong>nearly stateless</strong> while Fluss efficiently maintains aggregated results.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> campaign_uv </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    campaign_id STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uv_bitmap BYTES</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    total_events </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    last_event_time </span><span class="token keyword" style="color:#194670">TIMESTAMP</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">campaign_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'aggregation'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.uv_bitmap.agg'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'rbm64'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.total_events.agg'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sum'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'fields.last_event_time.agg'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'last_value_ignore_nulls'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Maintain <strong>continuously updated metrics</strong> (for example, order counts or model aggregated features) directly in Fluss tables, while Flink focuses only on event ingestion and lightweight processing.</p>
<p>The Aggregation Merge Engine is designed for <strong>production use</strong> and supports <strong>end-to-end exactly-once semantics</strong> when integrated with Flink.
In the event of either a Flink job or Fluss cluster failover, aggregation results remain <strong>eventually consistent</strong>, providing the same consistency guarantees as regular Primary Key Tables.
To achieve this, Fluss <strong>does not</strong> introduce complex distributed transactions for primary key tables, which would otherwise come <strong>at the cost of throughput and latency performance</strong>.
Instead, it effectively combines Flink checkpoints with the <strong>changelog capability</strong> of Fluss tables to implement an <strong>undo log</strong> mechanism during Flink Job failover.
This approach ensures <strong>end-to-end exactly-once</strong> processing while maintaining the <strong>same read/write throughput and latency</strong> as regular primary key tables.</p>
<p>The aggregation merge engine is another step towards Fluss’s <a href="https://www.ververica.com/blog/introducing-the-era-of-zero-state-streaming-joins?hs_preview=cdhHvcIE-199898654106" target="_blank" rel="noopener noreferrer" class="">compute-storage separation</a>.</p>
<p>You can find more instructions about how to use the aggregation merge engine <a href="https://fluss.apache.org/docs/0.9/table-design/merge-engines/aggregation/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="auto-increment-columns-for-dictionary-tables">Auto-Increment Columns for Dictionary Tables<a href="https://fluss.apache.org/blog/releases/0.9/#auto-increment-columns-for-dictionary-tables" class="hash-link" aria-label="Direct link to Auto-Increment Columns for Dictionary Tables" title="Direct link to Auto-Increment Columns for Dictionary Tables" translate="no">​</a></h3>
<p>This release introduces <code>AUTO_INCREMENT</code> columns in Fluss, enabling <strong>Dictionary Tables</strong>, a simple pattern for mapping long identifiers (such as strings or UUIDs) to <strong>compact numeric IDs</strong> in real-time systems.</p>
<p><code>AUTO_INCREMENT</code> columns automatically assign a <strong>unique numeric ID</strong> when a row is inserted and no value is provided. The assigned ID is <strong>stable</strong> and never changes. In distributed setups, IDs may not appear strictly sequential due to parallelism and bucketing, but they are guaranteed to be <strong>unique and monotonically increasing</strong> per allocation range.</p>
<p>A <strong>Dictionary Table</strong> is a regular Fluss table that uses an <code>AUTO_INCREMENT</code> column to map long business identifiers to compact integer IDs.
In simple terms, it gives every unique value a short number and always returns the same number for the same value.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uid STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    uid_int64 </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">uid</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'auto-increment.fields'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'uid_int64'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user1'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user2'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user3'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user4'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> uid_mapping </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'user5'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> uid_mapping</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> uid   </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> uid_int64 </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token comment" style="color:#64748B;font-style:italic">-------|-----------|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> user1 </span><span class="token operator" style="color:#475569">|</span><span class="token plain">     </span><span class="token number" style="color:#B45309">1</span><span class="token plain">     </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> user2 </span><span class="token operator" style="color:#475569">|</span><span class="token plain">     </span><span class="token number" style="color:#B45309">2</span><span class="token plain">     </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> user3 </span><span class="token operator" style="color:#475569">|</span><span class="token plain">     </span><span class="token number" style="color:#B45309">3</span><span class="token plain">     </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> user4 </span><span class="token operator" style="color:#475569">|</span><span class="token plain">     </span><span class="token number" style="color:#B45309">4</span><span class="token plain">     </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> user5 </span><span class="token operator" style="color:#475569">|</span><span class="token plain">     </span><span class="token number" style="color:#B45309">5</span><span class="token plain">     </span><span class="token operator" style="color:#475569">|</span><br></div></code></pre></div></div>
<p>Dictionary Tables are commonly used to answer operational questions such as:</p>
<ul>
<li class=""><strong>Unique Counting</strong>: How many unique users, devices, or sessions have we seen so far?</li>
<li class=""><strong>First-Seen Detection</strong>: Is this the first time we are seeing this identifier?</li>
<li class=""><strong>Event Deduplication</strong>: Have we already processed this event?</li>
<li class=""><strong>Active Entity Tracking</strong>: What is the current set of active entities/sessions?</li>
</ul>
<p>They also help keep <strong>identity-related state</strong> manageable over long periods and provide a <strong>shared, consistent ID mapping</strong> that can be reused across systems.</p>
<p>By combining <strong>auto-increment columns</strong> with the <strong>Aggregation Merge Engine</strong> and <strong>RoaringBitmap-based aggregation functions</strong> such as <code>rbm32</code> and <code>rbm64</code>,
you can maintain real-time counts of unique users or sessions without managing large state in Flink.
A typical usage pattern involves creating a dictionary table that maps raw identifiers, such as strings or sparse IDs, to compact dense integer IDs via an auto-increment column.
These dense IDs are then aggregated into a <strong>RoaringBitmap</strong> using <code>rbm32</code> for 32-bit IDs or <code>rbm64</code> for 64-bit IDs within the Aggregation Merge Engine.
Finally, end users can efficiently compute cardinality directly from RoaringBitmap results and perform union, intersection, and difference operations during roll-up aggregations.
This enables <strong>highly efficient count-distinct computations</strong> both at the storage layer and during query execution.</p>
<p>You can find more instructions about how to use the auto-increment feature and dictionary tables <a href="https://fluss.apache.org/docs/0.9/table-design/table-types/pk-table/#auto-increment-column" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="change-data-feed">Change Data Feed<a href="https://fluss.apache.org/blog/releases/0.9/#change-data-feed" class="hash-link" aria-label="Direct link to Change Data Feed" title="Direct link to Change Data Feed" translate="no">​</a></h3>
<p>Apache Fluss 0.9 introduces virtual tables <code>$changelog</code> and <code>$binlog</code> for change data feed, providing access to <strong>metadata</strong> and <strong>change data</strong> without storing additional data. By simply appending <code>$changelog</code> to any table name, users can access a complete <strong>audit trail</strong> of every data modification.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-the-changelog-of-any-table">Access the changelog of any table<a href="https://fluss.apache.org/blog/releases/0.9/#access-the-changelog-of-any-table" class="hash-link" aria-label="Direct link to Access the changelog of any table" title="Direct link to Access the changelog of any table" translate="no">​</a></h4>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">orders$changelog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Virtual Table" src="https://fluss.apache.org/assets/images/cdf_cl-b0ce87ae54c9ba7cb37dbfe62e48a16d.png" width="900" height="169" class="img_ev3q"></p>
<p>Each changelog record includes metadata columns prepended to the original table columns:</p>
<ul>
<li class=""><strong><code>_change_type</code></strong>: The type of change operation (<code>insert</code>, <code>update_before</code>/<code>update_after</code>, <code>delete</code> for Primary Key Tables; only <code>insert</code> for Log Tables).</li>
<li class=""><strong><code>_log_offset</code></strong>: The position in the log for <strong>tracking and replay</strong>.</li>
<li class=""><strong><code>_commit_timestamp</code></strong>: The exact timestamp when the change was committed.</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="flexible-startup-modes">Flexible Startup Modes<a href="https://fluss.apache.org/blog/releases/0.9/#flexible-startup-modes" class="hash-link" aria-label="Direct link to Flexible Startup Modes" title="Direct link to Flexible Startup Modes" translate="no">​</a></h4>
<p>Users can control where reading begins using startup modes: <code>earliest</code> (full history), <code>latest</code> (new changes), or <code>timestamp</code> (specific point in time). These modes are essential for <strong>Point-in-Time Recovery</strong> and <strong>ML Backtesting</strong>.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="binlog-virtual-table">Binlog Virtual Table<a href="https://fluss.apache.org/blog/releases/0.9/#binlog-virtual-table" class="hash-link" aria-label="Direct link to Binlog Virtual Table" title="Direct link to Binlog Virtual Table" translate="no">​</a></h4>
<p>For Primary Key Tables, Fluss also provides a <code>$binlog</code> virtual table that presents change data in a binlog format. Unlike <code>$changelog</code>, which shows individual change records, <code>$binlog</code> provides both <strong>before and after images</strong> in a single record with nested <code>before</code> and <code>after</code> row structures. This format is particularly useful for change data capture (CDC) integrations and systems that need to process both states of a row in a single operation.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-the-binlog-of-pk-table">Access the binlog of pk table<a href="https://fluss.apache.org/blog/releases/0.9/#access-the-binlog-of-pk-table" class="hash-link" aria-label="Direct link to Access the binlog of pk table" title="Direct link to Access the binlog of pk table" translate="no">​</a></h4>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">orders$binlog</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><img decoding="async" loading="lazy" alt="Virtual Table2" src="https://fluss.apache.org/assets/images/cdf_bl-b7ab2aa9f938c9d281a6f5afe6c2708a.png" width="900" height="165" class="img_ev3q"></p>
<p>Each binlog virtual table includes three metadata columns followed by nested before and after row structures:</p>
<ul>
<li class=""><strong><code>_change_type</code></strong>: The type of change operation (insert, update, delete for Primary Key Tables).</li>
<li class=""><strong><code>_log_offset</code></strong>: The position in the log for tracking and replay.</li>
<li class=""><strong><code>_commit_timestamp</code></strong>: The exact timestamp when the change was committed.</li>
<li class=""><strong><code>before	ROW&lt;...&gt;</code></strong>: The row values before the change (NULL for inserts).</li>
<li class=""><strong><code>after	ROW&lt;...&gt;</code></strong>: The row values after the change (NULL for deletes).</li>
</ul>
<p>The <code>$changelog</code> &amp; <code>$binlog</code>  virtual table unlocks critical use cases for regulatory and advanced analytics environments.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="compliance-audit-trails-and-ai-reproducibility">Compliance, Audit Trails, and AI Reproducibility<a href="https://fluss.apache.org/blog/releases/0.9/#compliance-audit-trails-and-ai-reproducibility" class="hash-link" aria-label="Direct link to Compliance, Audit Trails, and AI Reproducibility" title="Direct link to Compliance, Audit Trails, and AI Reproducibility" translate="no">​</a></h4>
<p>For regulatory environments, Fluss provides a complete, atomic audit trail of all data modifications.
Every change, such as a user profile update or a transaction status change is captured with precision, proving <strong>data lineage</strong> and change attribution.</p>
<p>Beyond compliance, this is critical for <strong>AI/ML Reproducibility</strong> and <strong>Decision Tracking</strong>. By providing a high-fidelity record of the exact data state at the moment a model made a prediction or a decision was triggered, Fluss enables <strong>full model audibility</strong> and simplifies debugging complex autonomous systems. It ensures <strong>trustworthy AI</strong> by allowing teams to reconstruct the exact environment behind every automated decision.</p>
<p>Storage-level CDC means no extra compute overhead; changes are already materialized, with just run️:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> orders$changelog</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> orders$binlog</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>You can find more instructions about how to use virtual tables and the changelog feature <a href="https://fluss.apache.org/docs/0.9/table-design/virtual-tables/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="compacted-log-format">Compacted Log Format<a href="https://fluss.apache.org/blog/releases/0.9/#compacted-log-format" class="hash-link" aria-label="Direct link to Compacted Log Format" title="Direct link to Compacted Log Format" translate="no">​</a></h3>
<p>By default, Fluss uses <strong>Apache Arrow–based columnar storage</strong>, which is ideal for analytical workloads with selective column access. However, some workloads do not benefit from columnar layouts, especially when <strong>all columns are read together</strong>.</p>
<p>Fluss 0.9 introduces support for a <strong>Compacted (row-oriented) LogFormat</strong> to address these cases. This format is designed for tables such as <strong>aggregated result tables</strong> and <strong>large vector or embedding tables</strong>, where <strong>full-row reads</strong> are the dominant access pattern. In these scenarios, columnar storage provides limited benefit and can introduce unnecessary overhead.</p>
<p>The <strong>Compacted LogFormat</strong> stores rows in a tightly packed, compact representation on disk, resulting in:</p>
<ul>
<li class=""><strong>Reduced disk footprint</strong> for wide rows</li>
<li class=""><strong>More efficient full-table and wide-row scans</strong></li>
<li class=""><strong>Better storage efficiency</strong> for derived and materialized tables</li>
</ul>
<p>Arrow remains the default and preferred choice for column-pruned analytical workloads, while the <strong>Compacted LogFormat</strong> provides a more efficient option for full-row, compacted datasets.</p>
<p>You can find more information <a href="https://fluss.apache.org/docs/0.9/table-design/data-formats/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dynamic-sink-shuffle-for-partitioned-tables">Dynamic Sink Shuffle for Partitioned Tables<a href="https://fluss.apache.org/blog/releases/0.9/#dynamic-sink-shuffle-for-partitioned-tables" class="hash-link" aria-label="Direct link to Dynamic Sink Shuffle for Partitioned Tables" title="Direct link to Dynamic Sink Shuffle for Partitioned Tables" translate="no">​</a></h3>
<p>Flink Sink supports typical strategies when shuffling data to the Sink node, such as shuffle-by-bucket and round-robin.
While effective, these can encounter bottlenecks or high metadata overhead in certain scenarios, especially with <strong>uneven traffic distribution</strong> in multi-partition tables.</p>
<p>The newly introduced <strong>Dynamic Sink Shuffle</strong> dynamically detects traffic distribution across partitions at runtime. It allocates write nodes proportionally based on traffic levels:</p>
<ul>
<li class=""><strong>High-traffic partitions</strong> are assigned more sink nodes.</li>
<li class=""><strong>Low-traffic partitions</strong> are allocated fewer nodes.</li>
</ul>
<p>This ensures that each sink is responsible for writing to an optimal number of buckets, significantly enhancing <strong>batching efficiency</strong>. Even with skewed traffic, the write load remains balanced, ensuring <strong>optimal performance</strong> and reducing the number of active connections.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-operational-safety--scalability">3. Operational Safety &amp; Scalability<a href="https://fluss.apache.org/blog/releases/0.9/#3-operational-safety--scalability" class="hash-link" aria-label="Direct link to 3. Operational Safety &amp; Scalability" title="Direct link to 3. Operational Safety &amp; Scalability" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kv-snapshot-lease">KV Snapshot Lease<a href="https://fluss.apache.org/blog/releases/0.9/#kv-snapshot-lease" class="hash-link" aria-label="Direct link to KV Snapshot Lease" title="Direct link to KV Snapshot Lease" translate="no">​</a></h3>
<p>Apache Fluss now supports <strong>KV Snapshot Lease</strong>, improving the reliability of <strong>snapshot-based reads</strong> for streaming and batch workloads.</p>
<p>Fluss tables periodically generate <strong>KV snapshots</strong> that are used by readers (e.g., Flink jobs) as a consistent starting point before continuing with incremental changelog consumption. Previously, snapshot cleanup was driven solely by <strong>retention policies</strong> and was unaware of whether a snapshot was actively being read. As a result, snapshots could be deleted while a job was still reading them, leading to <strong>job failures</strong> and making clean restarts impossible.</p>
<p>With <strong>KV Snapshot Leases</strong>, snapshot lifecycle management becomes <strong>consumer-aware</strong>. Readers explicitly acquire a lease when they start reading a snapshot, which prevents that snapshot from being deleted while it is in use. Leases are periodically renewed during long-running reads, and snapshots are only eligible for cleanup once all associated leases have been released or have expired.</p>
<p>This ensures snapshots remain available for the full duration of a read, while still allowing automatic cleanup if a reader crashes. This feature makes snapshot-based reads <strong>safe and predictable</strong>, enabling reliable <strong>large table bootstrapping</strong> and <strong>long-running snapshot scans</strong>.</p>
<p><strong>More Information:</strong> <a href="https://cwiki.apache.org/confluence/display/FLUSS/FIP-22+Support+Kv+Snapshot+Lease" target="_blank" rel="noopener noreferrer" class="">FIP-22: Support Kv Snapshot Lease</a></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cluster-rebalance">Cluster Rebalance<a href="https://fluss.apache.org/blog/releases/0.9/#cluster-rebalance" class="hash-link" aria-label="Direct link to Cluster Rebalance" title="Direct link to Cluster Rebalance" translate="no">​</a></h3>
<p>Apache Fluss now supports <strong>cluster rebalancing</strong>, enabling <strong>automatic redistribution</strong> of buckets and leaders across TabletServers to maintain <strong>balanced load</strong> and efficient resource utilization.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="highlights">Highlights<a href="https://fluss.apache.org/blog/releases/0.9/#highlights" class="hash-link" aria-label="Direct link to Highlights" title="Direct link to Highlights" translate="no">​</a></h4>
<ul>
<li class=""><strong>On-demand rebalancing</strong> for common operational scenarios:<!-- -->
<ul>
<li class=""><strong>Scaling</strong> the cluster up or down</li>
<li class=""><strong>Decommissioning</strong> TabletServers</li>
<li class=""><strong>Planned maintenance</strong></li>
<li class=""><strong>Resolving load imbalance</strong></li>
</ul>
</li>
<li class=""><strong>Goal-driven rebalance</strong> with prioritized objectives:<!-- -->
<ul>
<li class=""><code>REPLICA_DISTRIBUTION</code>: Balances replicas across the cluster.</li>
<li class=""><code>LEADER_DISTRIBUTION</code>: Balances leadership roles to distribute write/read pressure.</li>
</ul>
</li>
<li class=""><strong>Server-aware rebalancing</strong> using tags:<!-- -->
<ul>
<li class=""><code>PERMANENT_OFFLINE</code>: For graceful decommissioning.</li>
<li class=""><code>TEMPORARY_OFFLINE</code>: For maintenance scenarios.</li>
</ul>
</li>
<li class=""><strong>Operational visibility and control</strong>:<!-- -->
<ul>
<li class="">Track rebalance <strong>progress and status</strong>.</li>
<li class=""><strong>Cancel</strong> an in-progress rebalance if needed.</li>
<li class="">Ensures <strong>exclusive execution</strong> (one rebalance at a time per cluster).</li>
</ul>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Cluster Rebalance" src="https://fluss.apache.org/assets/images/cr-cc2da65fd473dad1a8690a0c191b245a.png" width="3418" height="1220" class="img_ev3q"></p>
<p>The first image shows the cluster’s write throughput, demonstrating <strong>stable performance throughout the rebalancing process</strong>.
The other three images display the local disk usage, leader count, and replica count before and after rebalancing.
Following the rebalancing operation, <strong>all metrics are evenly distributed</strong> across the nodes.</p>
<p>This feature simplifies cluster operations, improves <strong>stability</strong> during topology changes, and ensures <strong>consistent performance</strong> as Fluss clusters scale.</p>
<p><strong>Documentation:</strong> <a href="https://fluss.apache.org/docs/0.9/maintenance/operations/rebalance/" target="_blank" rel="noopener noreferrer" class="">Cluster Rebalance Operations</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-ecosystem--developer-experience">4. Ecosystem &amp; Developer Experience<a href="https://fluss.apache.org/blog/releases/0.9/#4-ecosystem--developer-experience" class="hash-link" aria-label="Direct link to 4. Ecosystem &amp; Developer Experience" title="Direct link to 4. Ecosystem &amp; Developer Experience" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-spark-engine-integration">Apache Spark Engine Integration<a href="https://fluss.apache.org/blog/releases/0.9/#apache-spark-engine-integration" class="hash-link" aria-label="Direct link to Apache Spark Engine Integration" title="Direct link to Apache Spark Engine Integration" translate="no">​</a></h3>
<p>Fluss 0.9 significantly matures its integration with the Apache Spark ecosystem. This release introduces support for <strong>Spark Catalogs</strong>, enabling seamless metadata management. Additionally, users can now perform both <strong>stream and batch reads and writes</strong>, allowing Spark to act as a powerful processing engine for Fluss-backed data lakes and real-time streams.</p>
<p>You can get started with the Spark engine <a href="https://fluss.apache.org/docs/0.9/engine-spark/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-flink-22-integration">Apache Flink 2.2 Integration<a href="https://fluss.apache.org/blog/releases/0.9/#apache-flink-22-integration" class="hash-link" aria-label="Direct link to Apache Flink 2.2 Integration" title="Direct link to Apache Flink 2.2 Integration" translate="no">​</a></h3>
<p>Fluss stays ahead with support for <strong>Apache Flink 2.2</strong>. This integration unlocks expanded query patterns for <strong>Delta Join</strong>, and both communities will collaborate to drive further improvements in upcoming versions. In this release, we have also introduced <strong>enhanced DDL capabilities</strong>, such as the ability to dynamically adjust <strong>datalake freshness</strong> settings via <code>ALTER TABLE</code>. This ensures that Fluss remains a first-class citizen in the Flink ecosystem, supporting the latest improvements in streaming SQL and table management.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="native-azure-filesystem-support">Native Azure Filesystem Support<a href="https://fluss.apache.org/blog/releases/0.9/#native-azure-filesystem-support" class="hash-link" aria-label="Direct link to Native Azure Filesystem Support" title="Direct link to Native Azure Filesystem Support" translate="no">​</a></h3>
<p>With the addition of the <strong>Azure File System (Azure FS)</strong> plugin, Fluss extends its cloud-native storage capabilities to Microsoft Azure. Users can now leverage <strong>Azure Blob Storage (WASB/WASBS)</strong> and <strong>Azure Data Lake Storage Gen2 (ABFS/ABFSS)</strong> for tiering data to the lake, ensuring cost-effective and scalable long-term storage across all major cloud providers.</p>
<p>You can find more information <a href="https://fluss.apache.org/docs/0.9/maintenance/filesystems/azure/" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="java-client-pojo-support">Java Client POJO Support<a href="https://fluss.apache.org/blog/releases/0.9/#java-client-pojo-support" class="hash-link" aria-label="Direct link to Java Client POJO Support" title="Direct link to Java Client POJO Support" translate="no">​</a></h3>
<p>To improve the developer experience for Java users, Fluss now supports <strong>Plain Old Java Objects (POJOs)</strong>. This allows for more intuitive data handling by enabling direct mapping between Fluss table rows and Java classes, reducing boilerplate code and making it easier to integrate Fluss into existing Java-based microservices and applications.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token class-name" style="color:#7C3AED">TablePath</span><span class="token plain"> path </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TablePath</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"my_db"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"users_log"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Table</span><span class="token plain"> table </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> conn</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">path</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">TypedAppendWriter</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">User</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> writer </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> table</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newAppend</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createTypedWriter</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">User</span><span class="token punctuation" style="color:#475569">.</span><span class="token keyword" style="color:#194670">class</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">append</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">User</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Alice"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">append</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">User</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Bob"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">25</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">flush</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>You can find more information <a href="https://fluss.apache.org/docs/0.9/apis/java-client/#java-typed-api" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-more-improvements">5. More Improvements<a href="https://fluss.apache.org/blog/releases/0.9/#5-more-improvements" class="hash-link" aria-label="Direct link to 5. More Improvements" title="Direct link to 5. More Improvements" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://fluss.apache.org/docs/0.9/engine-flink/reads/#aggregations" target="_blank" rel="noopener noreferrer" class="">Support <code>COUNT(*)</code> direct on log tables and primary key tables</a></li>
<li class=""><a href="https://fluss.apache.org/docs/0.9/engine-flink/procedures/#set_cluster_configs" target="_blank" rel="noopener noreferrer" class="">Support <code>sys.set_cluster_configs</code> CALL procedure to dynamically update cluster-level configurations</a></li>
<li class=""><a href="https://github.com/apache/fluss/pull/2326" target="_blank" rel="noopener noreferrer" class="">Fix correctness issue in union reads of Paimon tables with deletion vectors</a></li>
<li class=""><a href="https://github.com/apache/fluss/issues/2224" target="_blank" rel="noopener noreferrer" class="">Improve stability for large datalake enabled tables with more than 10K buckets</a></li>
<li class=""><a href="https://fluss.apache.org/docs/0.9/table-design/data-formats/#compacted-with-wal-changelog-image" target="_blank" rel="noopener noreferrer" class="">Support WAL (write-ahead-log) mode changelog images of primary key tables to reduce the changelog footprint</a></li>
<li class=""><a href="https://fluss.apache.org/docs/0.9/maintenance/observability/monitor-metrics/#rocksdb" target="_blank" rel="noopener noreferrer" class="">Report rich RocksDB metrics for debugging and production-ready usage</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="upgrade-notes">Upgrade Notes<a href="https://fluss.apache.org/blog/releases/0.9/#upgrade-notes" class="hash-link" aria-label="Direct link to Upgrade Notes" title="Direct link to Upgrade Notes" translate="no">​</a></h2>
<p>The Fluss community is committed to delivering a smooth upgrade experience. This 0.9 release maintains compatibility at the levels of network protocols and storage formats, with full bidirectional compatibility between clients and servers:</p>
<p>However, we changed some default behavior and added new features that may require adjustments in your applications and operational practices. Please refer to the <a href="https://fluss.apache.org/docs/0.9/maintenance/operations/upgrade-notes-0.9/" target="_blank" rel="noopener noreferrer" class="">upgrade notes</a> for a comprehensive list of adjustments to make and issues to check during the upgrading process.</p>
<p>For a detailed list of all changes in this release, please refer to the <a href="https://github.com/apache/fluss/releases/tag/v0.9.0-incubating" target="_blank" rel="noopener noreferrer" class="">release notes</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="list-of-contributors">List of contributors<a href="https://fluss.apache.org/blog/releases/0.9/#list-of-contributors" class="hash-link" aria-label="Direct link to List of contributors" title="Direct link to List of contributors" translate="no">​</a></h2>
<p>The Apache Fluss community would like to express gratitude to all the contributors who made this release possible:</p>
<blockquote>
<p>Aditya, Anton Borisov, CaoZhen, David, Eduard Tudenhoefner, Evan, ForwardXu, Giannis Polyzos, Giovanny Gutiérrez, HONGGEUN JI, Harsh Mehta, Hongshun Wang, Jackeyzhe, Jacopo Gardini, Jark Wu, Junbo Wang, Junfan Zhang, Keith Lee, Kerwin, Knock.Code, Leonard Xu, Liebing, Madhur Chandran, MehulBatra, Michael Koepf, Muhammet Orazov, Nikhil Negi, Paritosh, Pei Yu, Prajwal banakar, Priya Manjare, Rion Williams, Sergey Nuyanzin, SeungMin, Xuyang, Yang Guo, Yang Wang, Yang Zhang, Yann Byron, Yuxia Luo, Zübeyir Eser, binary-signal, buvb, forwardxu, gkatzioura, nhuantho, ocean.wy, vamossagar12, xiaozhou, xuyang, xx789, yunhong, yuxia, yuxia Luo, zhan7236, zhaomin1423, 白鵺</p>
</blockquote>
<p>Apache Fluss is under active development. Be sure to stay updated on the project, give it a try and if you like it,
don’t forget to give it some ❤️ via ⭐ on <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">GitHub</a>.</p>]]></content:encoded>
            <category>releases</category>
        </item>
        <item>
            <title><![CDATA[A fraud detection pipeline with Streamhouse]]></title>
            <link>https://fluss.apache.org/blog/fluss_fraud_detection/</link>
            <guid>https://fluss.apache.org/blog/fluss_fraud_detection/</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Apache Fluss, Apache Flink and Iceberg.]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-fluss-apache-flink-and-iceberg">Apache Fluss, Apache Flink and Iceberg.<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#apache-fluss-apache-flink-and-iceberg" class="hash-link" aria-label="Direct link to Apache Fluss, Apache Flink and Iceberg." title="Direct link to Apache Fluss, Apache Flink and Iceberg." translate="no">​</a></h2>
<p>Fraud detection is a mission-critical capability for businesses operating in financial services, e-commerce, and digital payments. Detecting suspicious transactions in real time can prevent significant losses and protect customers. This blog demonstrates how to build a streamhouse that processes bank transactions in real time, detects fraud, and serves data seamlessly across hot (sub‑second latency) and cold (minutes‑latency) layers.
Real-time detection and historical analytics are combined, enabling businesses to act quickly while maintaining a complete audit trail.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="streamhouse-architecture">Streamhouse Architecture<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#streamhouse-architecture" class="hash-link" aria-label="Direct link to Streamhouse Architecture" title="Direct link to Streamhouse Architecture" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h3>
<p>A Streamhouse is a modern data architecture that unifies real‑time stream processing and batch processing on top of a data lake, enabling organizations to work seamlessly with both current and historical data in a single system. It bridges the traditional gap between streaming systems and lakehouse storage, delivering near–real‑time insights, cost‑efficiency, and simplified architectures.
A Streamhouse brings several major advantages:</p>
<ul>
<li class=""><strong>Unified architecture:</strong> No need to maintain separate streaming and batch systems; both run on the same storage and compute foundation.</li>
<li class=""><strong>Real‑time analytics:</strong> Stream-native storage (e.g. Apache Fluss) enables sub‑second consumption.</li>
<li class=""><strong>Cost‑efficiency:</strong> Uses a data‑lake foundation, which is cheaper and more scalable than warehouse‑centric architectures.</li>
<li class=""><strong>ACID guarantees:</strong> with streaming updates when built on open table formats like Apache Iceberg.</li>
<li class=""><strong>Improved data freshness:</strong> Hot data is immediately queryable by streaming processors, while cold data is efficiently stored for historical analytics.</li>
</ul>
<p>In simple terms: a Streamhouse = Streaming + Lakehouse</p>
<p>A single architecture that:</p>
<ul>
<li class="">handles real‑time data (streams) through a hot layer</li>
<li class="">handles historical data (batch) through a cold layer</li>
</ul>
<p>The hot layer is responsible for low‑latency ingestion and real‑time processing. It is composed of:</p>
<ol>
<li class=""><strong>Data Producers:</strong> these are the data sources continuously generating live event streams (applications, microservices, sensors, CDC pipelines, etc.).
Supports the overall concept of streaming ingestion into Streamhouses.</li>
<li class=""><strong>Streaming Storage Layer:</strong> this layer stores live streams and enables their immediate consumption by real‑time processors. For example Apache Fluss, which provides ultra‑low‑latency streaming storage optimized for fast ingestion and fast consumption by systems like Apache Flink or microservices.</li>
<li class=""><strong>Streaming Processors Engines:</strong> that consume and process streaming data with low latency, such as Apache Flink, which is core to many Streamhouse implementations. These components together deliver sub‑second end‑to‑end processing latency.</li>
</ol>
<p>The cold layer provides durable, optimized, and cost‑effective storage for large‑scale historical data and is formed by:</p>
<ol>
<li class="">Lakehouse Storage, a data lakehouse built on an open table format (e.g., Apache Iceberg, Paimon, etc.), which provides:<!-- -->
<ul>
<li class="">ACID guarantees: analytical batch queries</li>
<li class="">unified tables for both batch and stream processing</li>
</ul>
</li>
<li class="">Tiering Service, a background synchronization component that:<!-- -->
<ul>
<li class="">continuously moves or materializes data from the streaming storage layer into lakehouse tables</li>
<li class="">ensures the cold layer stays up to date for historical analytics</li>
</ul>
</li>
</ol>
<p>Thanks to its layered architecture, a Streamhouse enables:</p>
<ol>
<li class="">Sub‑second latency from the hot layer
For real‑time:<!-- -->
<ul>
<li class="">dashboards</li>
<li class="">monitoring</li>
<li class="">alerts</li>
<li class="">streaming transformations</li>
<li class="">microservice consumption</li>
<li class="">agentic AI</li>
</ul>
</li>
<li class="">Minutes‑level latency from the cold layer
For:<!-- -->
<ul>
<li class="">historical analytics</li>
<li class="">machine learning feature pipelines</li>
<li class="">complex batch queries</li>
<li class="">long‑term and fault tolerant data retention</li>
</ul>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tech-stack">Tech Stack<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#tech-stack" class="hash-link" aria-label="Direct link to Tech Stack" title="Direct link to Tech Stack" translate="no">​</a></h3>
<p>The Streamhouse architecture enables real‑time processing of bank transactions, fraud detection, and seamless data management across both hot and cold data layers.
I implemented this architecture using the following technology stack:</p>
<ul>
<li class="">Java client application as the producer, continuously generating streams of bank transactions and account updates leveraging the <a href="https://fluss.apache.org/docs/apis/java-client/" target="_blank" rel="noopener noreferrer" class="">Fluss Java Client API</a> .</li>
<li class="">Apache Fluss as the ultra‑low‑latency streaming storage layer, ingesting live event streams and enabling real‑time consumption by microservices, Flink processors, and other streaming systems.</li>
<li class="">Apache Flink as the streaming processor, performing real‑time fraud detection and enabling immediate downstream reactions.</li>
<li class="">Apache Iceberg as the open table format for storing the historical view of detected frauds for OLAP consumers.</li>
<li class="">MinIO, acting as the S3‑compatible object store holding the Iceberg tables.</li>
<li class="">Iceberg REST Catalog to manage Iceberg table metadata.</li>
<li class="">Docker to spin up a lightweight Fluss and Flink cluster.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-flow">Data flow<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#data-flow" class="hash-link" aria-label="Direct link to Data flow" title="Direct link to Data flow" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="architecture" src="https://fluss.apache.org/assets/images/flussfraudarch-fa672f5267601a8969c5d49aee342bed.png" width="1690" height="760" class="img_ev3q"></p>
<p>The Fluss Java Client initializes the Fluss tables on the Fluss cluster and generates live records.
Specifically, three tables are created at startup:</p>
<ol>
<li class="">Transaction Log Table — append-only</li>
<li class="">Account Primary Table — supports INSERT, UPDATE, and DELETE</li>
<li class="">Enriched Fraud Log Table — append-only, with the datalake option enabled</li>
</ol>
<p>The Fluss <a href="https://fluss.apache.org/docs/concepts/architecture/#coordinatorserver" target="_blank" rel="noopener noreferrer" class="">CoordinatorServer</a> of the cluster automatically initializes a corresponding Iceberg table on MinIO through the REST Catalog for historical storage when the corresponding Fluss table is created.
After initialization, the Fluss Java Client continuously generates transaction and account records and writes them to Fluss.
The Flink‑based fraud detection job continuously consumes transactions from the Fluss table and identifies fraudulent records in real time.
When a fraud is detected, Flink enriches the record with the account name by referencing the Fluss Account Primary Table.
The enriched fraud records are then appended to the Fluss Enriched Fraud Log Table, which is tiered into the lakehouse with the Tiering Flink Job provided by the Fluss ecosystem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="repository-structure">Repository structure<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#repository-structure" class="hash-link" aria-label="Direct link to Repository structure" title="Direct link to Repository structure" translate="no">​</a></h2>
<p>You can find the code explained in this blog at the following link: <a href="https://github.com/Lourousa/frauddetection" target="_blank" rel="noopener noreferrer" class="">Github</a></p>
<p>Here's the repository structure, which will be useful as reference for the following paragraphs:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">src/main/java/org/jg/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── FraudDetectionJob.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── config/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── JobConfig.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── utils/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── FlinkEnv.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── Utils.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── FlussManagerRunner.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── FlussManager.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── pipeline/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── FraudPipeline.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── FlinkPipeline.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── strategy/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── EnrichedFraudTransformStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── FraudFlussSinkStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── FraudTransformStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── InitCatalogStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── InitFlinkCatalogStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── SinkStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── SourceStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── TransactionFlussSourceStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── TransformStrategy.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── entity/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── Transaction.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── Account.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     ├── Fraud.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── EnrichedFraud.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├── function/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │     └── FraudDetector.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    └── serde/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          ├── TransactionDeserializationSchema.java</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          ├── TransactionSerializationSchema</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          ├── AccountSerializationSchema</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          └── EnrichedFraudSerializationSchema.java</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fluss-client-tables-and-record-generation">The Fluss client: tables and record generation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#the-fluss-client-tables-and-record-generation" class="hash-link" aria-label="Direct link to The Fluss client: tables and record generation" title="Direct link to The Fluss client: tables and record generation" translate="no">​</a></h2>
<p>A Fluss Java client acts as the data producer. In the codebase, the <code>FlussManager</code> and the <code>FlussManagerRunner</code> are the classes representing the client and its execution. The client’s role is to simulate transaction events and feed them into the Fluss tables. More specifically, the client creates the Fluss database and three tables leveraging the <a href="https://fluss.apache.org/docs/apis/java-client/" target="_blank" rel="noopener noreferrer" class="">Fluss Java Client API</a> . For the sake of simplicity, each table has been <a href="https://fluss.apache.org/docs/table-design/data-distribution/bucketing/" target="_blank" rel="noopener noreferrer" class="">distributed</a> by its ID on four buckets.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="transaction-log-table-append-only">Transaction Log Table (append-only)<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#transaction-log-table-append-only" class="hash-link" aria-label="Direct link to Transaction Log Table (append-only)" title="Direct link to Transaction Log Table (append-only)" translate="no">​</a></h3>
<table><thead><tr><th>Column</th><th>Fluss Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>BIGINT</td><td>Unique identifier of the transaction.</td></tr><tr><td><code>accountId</code></td><td>BIGINT</td><td>Identifier of the account the transaction belongs to.</td></tr><tr><td><code>createdAt</code></td><td>BIGINT</td><td>Creation timestamp in epoch milliseconds.</td></tr><tr><td><code>amount</code></td><td>DECIMAL</td><td>Monetary amount of the transaction.</td></tr></tbody></table>
<p>The table stores transactions with a retention period of 7 days. Since it is a Fluss Log table, only APPEND operations are supported.</p>
<p>Snippet from FlussManager.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createTransactionSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">CREATED_AT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">AMOUNT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DECIMAL</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createTransactionDescriptor</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">// the schema is created by createTransactionSchema()</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">// few buckets for local testing</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="account-primary-table">Account Primary Table<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#account-primary-table" class="hash-link" aria-label="Direct link to Account Primary Table" title="Direct link to Account Primary Table" translate="no">​</a></h3>
<table><thead><tr><th>Column</th><th>Fluss Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>BIGINT</td><td>Unique identifier of the account. Primary key.</td></tr><tr><td><code>name</code></td><td>STRING</td><td>Name of the account holder or entity.</td></tr><tr><td><code>createdAt</code></td><td>BIGINT</td><td>Creation timestamp in epoch milliseconds.</td></tr><tr><td><code>updatedAt</code></td><td>BIGINT</td><td>Last update timestamp in epoch milliseconds.</td></tr></tbody></table>
<p>The table stores accounts of the bank’s customers. Since it is a Fluss Primary table, INSERT, UPDATE, and DELETE operations are supported.</p>
<p>Snippet from FlussManager.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createAccountSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Account</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Account</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">NAME</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Account</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">UPDATED_AT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">primaryKey</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Account</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createAccountDescriptor</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Account</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="enriched-fraud-log-table">Enriched Fraud Log Table<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#enriched-fraud-log-table" class="hash-link" aria-label="Direct link to Enriched Fraud Log Table" title="Direct link to Enriched Fraud Log Table" translate="no">​</a></h3>
<table><thead><tr><th>Column</th><th>Fluss Type</th><th>Description</th></tr></thead><tbody><tr><td><code>transactionId</code></td><td>BIGINT</td><td>Identifier of the transaction flagged as fraud.</td></tr><tr><td><code>accountId</code></td><td>BIGINT</td><td>Account identifier associated with the transaction.</td></tr><tr><td><code>name</code></td><td>STRING</td><td>Name of the account holder.</td></tr></tbody></table>
<p>The table stores enriched transactions detected as fraud, with a retention period of 7 days. Since it is a Fluss Log table, only APPEND operations are supported.
This is the only table that will be tiered to the Iceberg lakehouse.</p>
<p>Snippet from FlussManager.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createFraudSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">TRANSACTION_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">NAME</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createFraudDescriptor</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">TRANSACTION_ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.enabled"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"true"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.freshness"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"30s"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.auto-compaction"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"true"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="records-generation">Records generation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#records-generation" class="hash-link" aria-label="Direct link to Records generation" title="Direct link to Records generation" translate="no">​</a></h3>
<p>The Fluss client needs to serialize transactions and accounts POJOs instances as <code>GenericRow</code> instances. This class implements the <code>InternalRow</code> interface that represents the binary format used to write records from the Fluss client to Fluss tables.
Here is an example of the <code>Transaction</code> POJO and the <code>TransactionSerializationSchema</code>, which extends <code>FlussSerializationSchema&lt;Transaction&gt;</code> to override the serialize method of the <code>FlussSerializationSchema</code> interface, along with how it is used by the client generating records.</p>
<p>Transaction.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">implements</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Serializable</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> serialVersionUID </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1L</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">String</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">ID</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"id"</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">String</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"accountId"</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">String</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">CREATED_AT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"createdAt"</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">String</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">AMOUNT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"amount"</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> id</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> accountId</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> createdAt</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token plain"> amount</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token plain"> kind</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> accountId</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> createdAt</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token plain"> amount</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token plain"> kind</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> id</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">accountId </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> accountId</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">createdAt </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> createdAt</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">amount </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> amount</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">kind </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> kind</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">//...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>TransactionSerializationSchema.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransactionSerializationSchema</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">implements</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FlussSerializationSchema</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> serialVersionUID </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1L</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">open</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">InitializationContext</span><span class="token plain"> context</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">RowWithOp</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">serialize</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token plain"> value</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> value</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> value</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAccountId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> value</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getCreatedAt</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Decimal</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromBigDecimal</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">value</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAmount</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token plain"> rowKind </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> value</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getKind</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">switch</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">rowKind</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token keyword" style="color:#194670">case</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">INSERT</span><span class="token operator" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">RowWithOp</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">OperationType</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">APPEND</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token keyword" style="color:#194670">default</span><span class="token operator" style="color:#475569">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">IllegalArgumentException</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Unsupported row kind: "</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> rowKind</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Generation of transactions from FlussManager.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">InternalRow</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">getTransactions</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">AtomicLong</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">GLOBAL_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Random</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">RANDOM</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">long</span><span class="token punctuation" style="color:#475569">[</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">ACCOUNT_IDS</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token number" style="color:#B45309">1006L</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1007L</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1008L</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1009L</span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> fraudAccountId </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">ACCOUNT_IDS</span><span class="token punctuation" style="color:#475569">[</span><span class="token constant" style="color:#12325C">RANDOM</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">nextInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">ACCOUNT_IDS</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">]</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transactions </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">ArrayList</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  transactions</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">add</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token constant" style="color:#12325C">GLOBAL_ID</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAndIncrement</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          fraudAccountId</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token class-name" style="color:#7C3AED">System</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">currentTimeMillis</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"0.8"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">INSERT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  transactions</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">add</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token constant" style="color:#12325C">GLOBAL_ID</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAndIncrement</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          fraudAccountId</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token class-name" style="color:#7C3AED">System</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">currentTimeMillis</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"1001.00"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">INSERT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">int</span><span class="token plain"> i </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"> i </span><span class="token operator" style="color:#475569">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"> i</span><span class="token operator" style="color:#475569">++</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    transactions</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">add</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token constant" style="color:#12325C">GLOBAL_ID</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAndIncrement</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token function" style="color:#7C3AED">generateRandomAccountId</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">RANDOM</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">System</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">currentTimeMillis</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">valueOf</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">RANDOM</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">nextInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">500</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">INSERT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">TransactionSerializationSchema</span><span class="token plain"> schema </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransactionSerializationSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token keyword" style="color:#194670">open</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">null</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> transactions</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">stream</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">map</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          tx </span><span class="token operator" style="color:#475569">-&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">try</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token class-name" style="color:#7C3AED">RowWithOp</span><span class="token plain"> rowWithOp </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">serialize</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">tx</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> rowWithOp</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">catch</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> e</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token keyword" style="color:#194670">throw</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">RuntimeException</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Serialization failed for transaction: "</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> tx</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> e</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">collect</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Collectors</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toList</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>The <code>getTransaction</code> method is repeatedly called by the client to produce transactions continuously. They are pushed in batches of ten.<br>
<!-- -->The first two transactions of each batch represent a fraudulent pattern for the same account:</p>
<ul>
<li class="">a small transaction with an amount less than 1.</li>
<li class="">a larger transaction with an amount greater than 1000.</li>
</ul>
<p>This means that every second transaction in the batch is a fraud. This logic is very simple, since the purpose of this blog is to show Fluss as a streamhouse pillar, not to demonstrate an advanced fraud detector. The Fluss Transaction table is continuously updated by appending new records. Accounts are also generated by the client in a similar way and then written to the Fluss Account table, which acts as a dimension table, meaning its records are not upserted frequently.
For simplicity, only four accounts are generated, and each transaction belongs to one of them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fraud-detection-job">Fraud detection job<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#fraud-detection-job" class="hash-link" aria-label="Direct link to Fraud detection job" title="Direct link to Fraud detection job" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-pipeline-overview">Flink pipeline overview<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#flink-pipeline-overview" class="hash-link" aria-label="Direct link to Flink pipeline overview" title="Direct link to Flink pipeline overview" translate="no">​</a></h3>
<p>The Fraud detection Job is based on Apache Flink. The orchestration of the streaming pipeline has been modelled by the <code>FrauDetectionJob</code> class, which is the entry point of the Flink job:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudDetectionJob</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">main</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">String</span><span class="token punctuation" style="color:#475569">[</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> args</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">JobConfig</span><span class="token plain"> config </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token class-name" style="color:#7C3AED">JobConfig</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">bootstrapServers</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">FLUSS_BOOTSTRAP_SERVER_INTERNAL</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">database</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">FLUSS_DB_NAME</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">transactionTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">TRANSACTION_LOG</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fraudTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">FRAUD_LOG</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">FlinkEnv</span><span class="token plain"> env </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FlinkEnv</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInstance</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">FraudPipeline</span><span class="token plain"> pipeline </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudPipeline</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">InitFlinkCatalogStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">env</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransactionFlussSourceStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudTransformStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">env</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">EnrichedFraudTransformStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">env</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudFlussSinkStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    pipeline</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">compose</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    pipeline</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">run</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">env</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Each step of the streaming pipeline, such as reading from the source, applying transformations, and writing to the sink, has been modeled as a strategy. These strategies are combined into the <code>FraudPipeline</code> which implements the <code>FlinkPipeline</code> interface. The <code>FlinkPipeline</code> interface is accountable to hold and combine the strategies, exposing the <code>compose</code> and <code>run</code> signatures. The <code>compose</code> method is used to chain the strategies together, while the <code>run</code> method triggers the Flink execution environment.</p>
<p><img decoding="async" loading="lazy" alt="flinkpipe" src="https://fluss.apache.org/assets/images/flinkpipe-4994a4be4abc17e2d909d7bea4e067aa.jpg" width="2780" height="809" class="img_ev3q"></p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudPipeline</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">implements</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FlinkPipeline</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">InitFlinkCatalogStrategy</span><span class="token plain"> initFlinkCatalogStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SourceStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> sourceStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransformStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">,</span><span class="token generics"> </span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transformFraudStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransformStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">,</span><span class="token generics"> </span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transformEnrichedFraudStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SinkStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> sinkStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudPipeline</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">InitFlinkCatalogStrategy</span><span class="token plain"> initStrategy</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">SourceStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> sourceStrategy</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">TransformStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">,</span><span class="token generics"> </span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transformFraudStrategy</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">TransformStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">,</span><span class="token generics"> </span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transformEnrichedFraudStrategy</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">SinkStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> sinkStrategy</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">initFlinkCatalogStrategy </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> initStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">sourceStrategy </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sourceStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">transformFraudStrategy </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> transformFraudStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">transformEnrichedFraudStrategy </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> transformEnrichedFraudStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">sinkStrategy </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sinkStrategy</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">compose</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    initFlinkCatalogStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">init</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">var</span><span class="token plain"> transactionsDs </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sourceStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createSource</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">var</span><span class="token plain"> fraudsDs </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> transformFraudStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">transform</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transactionsDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">var</span><span class="token plain"> enrichedFraudDs </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> transformEnrichedFraudStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">transform</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">fraudsDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    sinkStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createSink</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">enrichedFraudDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">JobExecutionResult</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">run</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">FlinkEnv</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getStreamEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">execute</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"fraud-detection"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>There are 4 strategies interfaces with different accountability explained in the next paragraph.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="strategy-deep-dive">Strategy deep dive<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#strategy-deep-dive" class="hash-link" aria-label="Direct link to Strategy deep dive" title="Direct link to Strategy deep dive" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-initcatalogstrategy-interface-and-initflinkcatalogstrategy-implementation">1. InitCatalogStrategy interface and InitFlinkCatalogStrategy implementation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#1-initcatalogstrategy-interface-and-initflinkcatalogstrategy-implementation" class="hash-link" aria-label="Direct link to 1. InitCatalogStrategy interface and InitFlinkCatalogStrategy implementation" title="Direct link to 1. InitCatalogStrategy interface and InitFlinkCatalogStrategy implementation" translate="no">​</a></h4>
<p>This interface defines the catalog initialization, exposing the <code>init</code> method.</p>
<p><img decoding="async" loading="lazy" alt="catstr" src="https://fluss.apache.org/assets/images/catstr-b31b5c6c1637afad44690e2fac0b4a47.png" width="538" height="724" class="img_ev3q"></p>
<p>The <code>InitFlinkCatalogStrategy</code> implementation, which extends <code>InitCatalogStrategy</code>, overrides the <code>init</code> method to define and activate a Flink catalog, enabling the use of the Fluss tables.</p>
<p>Snippet from InitFlinkCatalogStrategy.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">init</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">executeSql</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token string" style="color:#0E7C66">"CREATE CATALOG fluss_catalog\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"WITH ('type' = 'fluss', 'bootstrap.servers' = 'coordinator-server:9122')"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">executeSql</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"USE CATALOG fluss_catalog"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">executeSql</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"USE fluss"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-sourcestrategy-interface-and-transactionflusssourcestrategy-implementation">2. SourceStrategy interface and TransactionFlussSourceStrategy implementation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#2-sourcestrategy-interface-and-transactionflusssourcestrategy-implementation" class="hash-link" aria-label="Direct link to 2. SourceStrategy interface and TransactionFlussSourceStrategy implementation" title="Direct link to 2. SourceStrategy interface and TransactionFlussSourceStrategy implementation" translate="no">​</a></h4>
<p>This interface represents the reading from a source and returning records with a DataStream Flink API of type <code>DataStream&lt;T&gt;</code>, exposing the <code>createSource</code> signature.</p>
<p><img decoding="async" loading="lazy" alt="soustr" src="https://fluss.apache.org/assets/images/soustr-4f3749a544ecc4415f40dc84db0f56ea.png" width="513" height="693" class="img_ev3q"></p>
<p>The <code>TransactionFlussSourceStrategy</code> implementation overrides the <code>createSource</code> method to read from the Fluss Transaction table using the <a href="https://fluss.apache.org/docs/engine-flink/datastream/#datastream-source" target="_blank" rel="noopener noreferrer" class="">FlussSource</a> connector and return a <code>DataStream&lt;Transaction&gt;</code>.</p>
<p>Snippet from TransactionFlussSourceStrategy.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createSource</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">FlussSource</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transactionSource </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">FlussSource</span><span class="token punctuation" style="color:#475569">.</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setBootstrapServers</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getBootstrapServers</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">// Fluss coordinator bootstrap server e.g coordinator-server:9122</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setDatabase</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDatabase</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">// fluss database name e.g fluss</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTransactionTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">// transaction table name e.g transaction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setStartingOffsets</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">OffsetsInitializer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">earliest</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">// initializes offsets to the earliest available offsets of each bucket</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setDeserializationSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransactionDeserializationSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getStreamEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromSource</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transactionSource</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">WatermarkStrategy</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">noWatermarks</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"fluss-transaction-source"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"transactions-datastream"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>As you can see in the snippet below , the deserialization of the Fluss <code>LogRecord</code> from the Transaction table into the <code>Transaction</code> POJO required by the DataStream type is handled by the <code>TransactionDeserializationSchema</code> class, which extends <code>FlussDeserializationSchema</code> to override the <code>deserialize</code> method. The <code>record</code> is used to instantiate a <code>Transaction</code> POJO. The corresponding changeLog type is mapped into the <code>kind</code> field and since the Fluss source is a Log Table it always equals to an insert.</p>
<p>Snippet from TransactionDeserializationSchema.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">deserialize</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">LogRecord</span><span class="token plain"> record</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">InternalRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> record</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getLong</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> accountId </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getLong</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> createdAt </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getLong</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">BigDecimal</span><span class="token plain"> amount </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDecimal</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toBigDecimal</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token plain"> kind </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">mapChangeType</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">record</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getChangeType</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> accountId</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> createdAt</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> amount</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> kind</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-transformstrategy-interface-and-fraudtransformstrategy-implementation">3. TransformStrategy interface and FraudTransformStrategy implementation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#3-transformstrategy-interface-and-fraudtransformstrategy-implementation" class="hash-link" aria-label="Direct link to 3. TransformStrategy interface and FraudTransformStrategy implementation" title="Direct link to 3. TransformStrategy interface and FraudTransformStrategy implementation" translate="no">​</a></h4>
<p>This interface models the transformation applied to a Datastreams. It takes a <code>Datastream&lt;I&gt;</code>, returns <code>Datastream&lt;O&gt;</code> exposing the <code>transform</code> method.</p>
<p><img decoding="async" loading="lazy" alt="fraudstr" src="https://fluss.apache.org/assets/images/fraudstr-31176a68980343cabd5b550767bd5da8.png" width="528" height="733" class="img_ev3q"></p>
<p>The <code>FraudTransformStrategy</code> implementation overrides the <code>transform</code> method, which takes a <code>DataStream&lt;Transaction&gt;</code> as a parameter and returns a <code>DataStream&lt;Fraud&gt;</code> after applying the following transformations:</p>
<ol>
<li class=""><code>keyBy(Transaction::getAccountId)</code>: groups the Transaction stream by <code>accountId</code> so that all transactions with the same <code>accountId</code> go to the same parallel subtask. After keyBy, you get a KeyedStream, which enables per-key state and timers.</li>
<li class=""><code>process(new FraudDetector())</code>: applies the <code>FraudDetector</code> <code>KeyedProcessFunction</code> to the keyed stream.
A KeyedProcessFunction is the lowest-level, per-key operator in Flink that provides:<!-- -->
<ul>
<li class="">Per-key state (e.g., ValueState) scoped to the current key.</li>
<li class="">Timers (processing-time and/or event-time) that invoke onTimer(...), also per key.</li>
<li class="">Ordered processing per key—events for the same key arrive in order to the same subtask.</li>
</ul>
</li>
</ol>
<p>FraudTransformStrategy.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudTransformStrategy</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">implements</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TransformStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">,</span><span class="token generics"> </span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FlinkEnv</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudTransformStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">FlinkEnv</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">env </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">transform</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Transaction</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> transactionsDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> transactionsDs</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">keyBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token operator" style="color:#475569">::</span><span class="token function" style="color:#7C3AED">getAccountId</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">process</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudDetector</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"fraud-detector"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p><code>FraudDetector</code> extends <code>KeyedProcessFunction&lt;Long, Transaction, Fraud&gt;</code>, where:</p>
<ul>
<li class=""><code>Long</code> is the type of the key (the <code>accountId</code>)</li>
<li class=""><code>Transaction</code> is the input type</li>
<li class=""><code>Fraud</code> is the output type</li>
</ul>
<p>The fraudulent record is identified by the <code>processElement</code> override method which implements a simple rule:</p>
<ul>
<li class="">If a small transaction is followed by a large transaction within one minute (processing time), emit a <code>Fraud</code>.</li>
</ul>
<p>Snippet from FraudDetector.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">processElement</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Transaction</span><span class="token plain"> transaction</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Context</span><span class="token plain"> context</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Collector</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> collector</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Exception</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">Boolean</span><span class="token plain"> lastTransactionWasSmall </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> flagState</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">value</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">lastTransactionWasSmall </span><span class="token operator" style="color:#475569">!=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">null</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAmount</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">compareTo</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">LARGE_AMOUNT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token class-name" style="color:#7C3AED">Fraud</span><span class="token plain"> fraud </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Fraud</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      fraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setTransactionId</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      fraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setAccountId</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAccountId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      collector</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">collect</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">fraud</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token function" style="color:#7C3AED">cleanUp</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">transaction</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAmount</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">compareTo</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">SMALL_AMOUNT</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    flagState</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">update</span><span class="token punctuation" style="color:#475569">(</span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">long</span><span class="token plain"> timer </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> context</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">timerService</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">currentProcessingTime</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token constant" style="color:#12325C">ONE_MINUTE</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    context</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">timerService</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">registerProcessingTimeTimer</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">timer</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    timerState</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">update</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">timer</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-enrichedfraudtransformstrategy-implementation">4. EnrichedFraudTransformStrategy implementation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#4-enrichedfraudtransformstrategy-implementation" class="hash-link" aria-label="Direct link to 4. EnrichedFraudTransformStrategy implementation" title="Direct link to 4. EnrichedFraudTransformStrategy implementation" translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" alt="enfraudstr" src="https://fluss.apache.org/assets/images/enfraudstr-53373fcc802f3fc87fd70cbd86be7a70.png" width="529" height="714" class="img_ev3q"></p>
<p>The <code>EnrichedFraudTransformStrategy</code> implementation reads the <code>Datastream&lt;Fraud&gt;</code> and perform an enrichment adding the account name for each record.
This is done switching from Flink Datastream API to Flink Table API and performing a temporal look up join against the the Fluss Account table which acts as a dimension.</p>
<p>Snipped from EnrichedFraudTransformStrategy.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">transform</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">Fraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> fraudsDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">Table</span><span class="token plain"> fraudsTb </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromDataStream</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              fraudsDs</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">TRANSACTION_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">BIGINT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">columnByExpression</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"procTime"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"PROCTIME()"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createTemporaryView</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"fraudsView"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> fraudsTb</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token class-name" style="color:#7C3AED">Table</span><span class="token plain"> enrichedFraudTb </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sqlQuery</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token string" style="color:#0E7C66">"SELECT\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"  f.transactionId AS transactionId,\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"  f.accountId AS accountId,\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"  a.name AS name\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"FROM fraudsView AS f\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"LEFT JOIN account FOR SYSTEM_TIME AS OF f.procTime AS a\n"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token operator" style="color:#475569">+</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"ON f.accountId = a.id"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> env</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableEnv</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toDataStream</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">enrichedFraudTb</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">      </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">map</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">          row </span><span class="token operator" style="color:#475569">-&gt;</span><span class="token plain">  </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">long</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getField</span><span class="token punctuation" style="color:#475569">(</span><span class="token constant" style="color:#12325C">TRANSACTION_ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">long</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getField</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">ACCOUNT_ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">String</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getField</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">NAME</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  </span><span class="token class-name" style="color:#7C3AED">RowKind</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">INSERT</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>The <code>fraudsView</code> is a dynamic table created on top of the fraud stream.
It exposes a processing-time attribute.
For each record in <code>f</code> (<code>fraudsView</code>), the corresponding account row is looked up as it existed at the processing time of <code>f</code> (<code>f.procTime</code>).
Enriched records are converted back and returned as a <code>DataStream&lt;EnrichedFraud&gt;</code> in order to be written to the Fluss sink by the next strategy.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-sinkstrategy-interface-and-fraudflusssinkstrategy-implementation">5. SinkStrategy interface and FraudFlussSinkStrategy implementation<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#5-sinkstrategy-interface-and-fraudflusssinkstrategy-implementation" class="hash-link" aria-label="Direct link to 5. SinkStrategy interface and FraudFlussSinkStrategy implementation" title="Direct link to 5. SinkStrategy interface and FraudFlussSinkStrategy implementation" translate="no">​</a></h3>
<p>This interface models the writing of a <code>Datastream&lt;T&gt;</code> to the FlinkSink, exposing the <code>createSink</code> method.</p>
<p><img decoding="async" loading="lazy" alt="sinkstr" src="https://fluss.apache.org/assets/images/sinkstr-1c199122a4b4b9280f11b0090f20846b.png" width="580" height="778" class="img_ev3q"></p>
<p><code>FraudFlussSinkStrategy</code> reads the <code>Datastream&lt;Fraud&gt;</code> and sinks record to Fluss Fraud table leveraging <a href="https://fluss.apache.org/docs/engine-flink/datastream/#datastream-sink" target="_blank" rel="noopener noreferrer" class="">FlussSink</a> sink connector.</p>
<p>FraudFlussSinkStrategy.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">class</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudFlussSinkStrategy</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">implements</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SinkStrategy</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">private</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">JobConfig</span><span class="token plain"> config</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">FraudFlussSinkStrategy</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">JobConfig</span><span class="token plain"> config</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">this</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">config </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> config</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token annotation punctuation" style="color:#475569">@Override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createSink</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">DataStream</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> enrichedFraudDs</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">FlussSink</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> enrichedFraudSink </span><span class="token operator" style="color:#475569">=</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token class-name" style="color:#7C3AED">FlussSink</span><span class="token punctuation" style="color:#475569">.</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setBootstrapServers</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getBootstrapServers</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setDatabase</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDatabase</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getEnrichedFraudTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setSerializationSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">EnrichedFraudSerializationSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    enrichedFraudDs</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sinkTo</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">enrichedFraudSink</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"enriched-fraud-fluss-sink"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    enrichedFraudDs</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">print</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lakehouse-tiering">Lakehouse tiering<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#lakehouse-tiering" class="hash-link" aria-label="Direct link to Lakehouse tiering" title="Direct link to Lakehouse tiering" translate="no">​</a></h2>
<p>The Fluss Fraud table has been tiered to the lakehouse.
The lakehouse is based on Iceberg as the open table format and MinIO as the S3‑compatible object storage.
The Iceberg Fraud table is automatically initialized when the corresponding Fluss table is created by the Fluss client.
This is possible because Fluss syncs the metadata of the Fluss Fraud table with the corresponding Iceberg table.</p>
<p>Snippet from FlussManager.java</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">createFraudDescriptor</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">schema</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">EnrichedFraud</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">TRANSACTION_ID</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.enabled"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"true"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.freshness"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"30s"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">property</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"table.datalake.auto-compaction"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"true"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Fluss knows the lakehouse details based on the configuration of the following <code>CoordinatorServer</code> properties:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.format: iceberg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.iceberg.type: rest</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.iceberg.warehouse: s3://fluss/data/</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.iceberg.uri: http://rest:8181</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.iceberg.s3.endpoint: http://minio:9000</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">datalake.iceberg.s3.path-style-access: true</span><br></div></code></pre></div></div>
<p>Data is synced by the Flink tiering job provided by the Fluss ecosystem, which merges new records from the Fluss Fraud table into the Iceberg Fraud table every 30 seconds (customizable).
The Iceberg table can support historical analysis performed by other compute engines able to read the format.
To learn more details about the tiering service works you can read the official <a href="https://fluss.apache.org/docs/streaming-lakehouse/overview/" target="_blank" rel="noopener noreferrer" class="">documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="benefits-of-the-streamhouse-with-fluss-for-the-fraud-detection-use-case">Benefits of the Streamhouse with Fluss for the fraud detection use case<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#benefits-of-the-streamhouse-with-fluss-for-the-fraud-detection-use-case" class="hash-link" aria-label="Direct link to Benefits of the Streamhouse with Fluss for the fraud detection use case" title="Direct link to Benefits of the Streamhouse with Fluss for the fraud detection use case" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="queryable-tables">Queryable Tables<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#queryable-tables" class="hash-link" aria-label="Direct link to Queryable Tables" title="Direct link to Queryable Tables" translate="no">​</a></h3>
<p>Unlike Apache Kafka, where topics are not queryable, Fluss tables allow direct querying for real-time insights. You can query both the Fluss Transaction and Account tables.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-more-external-caches">No More External Caches<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#no-more-external-caches" class="hash-link" aria-label="Direct link to No More External Caches" title="Direct link to No More External Caches" translate="no">​</a></h3>
<p>There is no need to deploy or scale external caches, databases, or state stores for lookups—simply use Fluss Primary Tables. The Fluss Account table fulfills this purpose.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automatic-tiering-to-the-lakehouse">Automatic Tiering to the Lakehouse<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#automatic-tiering-to-the-lakehouse" class="hash-link" aria-label="Direct link to Automatic Tiering to the Lakehouse" title="Direct link to Automatic Tiering to the Lakehouse" translate="no">​</a></h3>
<p>Real-time data is automatically compacted into Iceberg, via the built-in Flink service, seamlessly bridging streaming and batch.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="union-reads">Union Reads<a href="https://fluss.apache.org/blog/fluss_fraud_detection/#union-reads" class="hash-link" aria-label="Direct link to Union Reads" title="Direct link to Union Reads" translate="no">​</a></h3>
<p>Fluss enables combined reads of real-time and historical data (Fluss tables + Iceberg), delivering true real-time analytics without duplication.
With Flink you can query both the Fraud table in Fluss and the corresponding Iceberg Fraud table at the same time, obtaining a unified view of the records, including the latest ones that are only present in Fluss because they have not been synced yet. Moving forward, the community plans to extend this capability to support additional query engines, such as Apache Spark and StarRocks, further broadening its ecosystem compatibility and adoption.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fluss × Iceberg (Part 1): Why Your Lakehouse Isn’t a Streamhouse Yet]]></title>
            <link>https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/</link>
            <guid>https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/</guid>
            <pubDate>Thu, 11 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[As software and data engineers, we've witnessed Apache Iceberg revolutionize analytical data lakes with ACID transactions, time travel, and schema evolution. Yet when we try to push Iceberg into real-time workloads such as sub-second streaming queries, high-frequency CDC updates, and primary key semantics, we hit fundamental architectural walls. This blog explores how Fluss × Iceberg integration works and delivers a true real-time lakehouse.]]></description>
            <content:encoded><![CDATA[<p>As software and data engineers, we've witnessed Apache Iceberg revolutionize analytical data lakes with ACID transactions, time travel, and schema evolution. Yet when we try to push Iceberg into real-time workloads such as sub-second streaming queries, high-frequency CDC updates, and primary key semantics, we hit fundamental architectural walls. This blog explores how Fluss × Iceberg integration works and delivers a true real-time lakehouse.</p>
<p>Apache Fluss represents a new architectural approach: the <strong>Streamhouse</strong> for real-time lakehouses. Instead of stitching together separate streaming and batch systems, the Streamhouse unifies them under a single architecture. In this model, Apache Iceberg continues to serve exactly the role it was designed for: a highly efficient, scalable cold storage layer for analytics, while Fluss fills the missing piece: a hot streaming storage layer with sub-second latency, columnar storage, and built-in primary-key semantics.</p>
<p>After working on Fluss–Iceberg lakehouse integration and deploying this architecture at a massive scale, including Alibaba's 3 PB production deployment processing 40 GB/s, we're ready to share the architectural lessons learned. Specifically, why existing systems fall short, how Fluss and Iceberg naturally complement each other, and what this means for finally building true real-time lakehouses.</p>
<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/fluss-lakehouse-streaming_comp-62d4129ae29a5e1b5453892ae27cc212.png" width="768" height="336" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-real-time-lakehouse-imperative">The Real-Time Lakehouse Imperative<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#the-real-time-lakehouse-imperative" class="hash-link" aria-label="Direct link to The Real-Time Lakehouse Imperative" title="Direct link to The Real-Time Lakehouse Imperative" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-real-time-lakehouses-matter-now">Why Real-Time Lakehouses Matter Now<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#why-real-time-lakehouses-matter-now" class="hash-link" aria-label="Direct link to Why Real-Time Lakehouses Matter Now" title="Direct link to Why Real-Time Lakehouses Matter Now" translate="no">​</a></h3>
<p>Four converging forces are driving the need for sub-second data infrastructure:</p>
<p><strong>1. Business Demand for Speed:</strong> Modern businesses operate in real-time. Pricing decisions, inventory management, and fraud detection all require immediate response. Batch-oriented systems with T+1 day or even T+1 hour latency can't keep up with operational tempo.</p>
<p><strong>2. Immediate Decision Making:</strong> Operational analytics demands split-second insights. Manufacturing lines, delivery logistics, financial trading, and customer service all need to react to events as they happen, not hours or days later.</p>
<p><strong>3. AI/ML Needs Fresh Data:</strong> Here's the critical insight: <strong>You can't build the next TikTok recommender system on traditional lakehouses, which lack real-time streaming data for AI.</strong> Modern AI applications—personalized recommendations, real-time content ranking, and dynamic ad placement—require continuous model inference on fresh data.</p>
<p><strong>4. Agentic AI Requires Real-Time Context:</strong> AI agents need immediate access to the current system state to make decisions. Whether it's autonomous trading systems, intelligent routing agents, or customer service bots, agents can't operate effectively on stale data.</p>
<p><img decoding="async" loading="lazy" alt="Use Cases" src="https://fluss.apache.org/assets/images/lakehouse-usecases-e468957e3f77d3008209cf0232b3ba1a.png" width="1464" height="802" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-evolution-of-data-freshness">The Evolution of Data Freshness<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#the-evolution-of-data-freshness" class="hash-link" aria-label="Direct link to The Evolution of Data Freshness" title="Direct link to The Evolution of Data Freshness" translate="no">​</a></h3>
<p><strong>Traditional Batch Era (T+1 day):</strong> Hive-based data warehouses, daily ETL jobs run overnight, next-day readiness acceptable for reporting.</p>
<p><strong>Lakehouse Era (T+1 hour):</strong> Modern lakehouse formats (Iceberg, Delta Lake, Hudi), hourly micro-batch processing, better for near-real-time dashboards.</p>
<p><strong>Streaming Lakehouse Era (T+1 minute):</strong> Streaming integration with lakehouses (Paimon), minute-level freshness through continuous ingestion, suitable for operational analytics.</p>
<p><strong>The Critical Gap - Second-Level Latency:</strong> File-system-based lakehouses inherently face minute-level latency as their practical upper limit. This isn't a limitation of specific implementations; it's fundamental. File commits, metadata operations, and object storage consistency guarantees create unavoidable overhead.</p>
<p>Yet critical use cases demand sub-second to second-level latency: search and recommendation systems with real-time personalization, advertisement attribution tracking, anomaly detection for fraud and security monitoring, operational intelligence for manufacturing/logistics/ride-sharing, and Gen AI model inference requiring up-to-the-second features. The industry needs a <strong>hot real-time layer</strong> sitting in front of the lakehouse.</p>
<p><img decoding="async" loading="lazy" alt="Evolution Timeline" src="https://fluss.apache.org/assets/images/evolution_comp-f56db21895395b79316999b755510daa.png" width="1792" height="592" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-fluss--iceberg">What is Fluss × Iceberg?<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#what-is-fluss--iceberg" class="hash-link" aria-label="Direct link to What is Fluss × Iceberg?" title="Direct link to What is Fluss × Iceberg?" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-core-concept-hotcold-unified-storage">The Core Concept: Hot/Cold Unified Storage<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#the-core-concept-hotcold-unified-storage" class="hash-link" aria-label="Direct link to The Core Concept: Hot/Cold Unified Storage" title="Direct link to The Core Concept: Hot/Cold Unified Storage" translate="no">​</a></h3>
<p>The Fluss architecture delivers millisecond-level end-to-end latency for real-time data writing and reading. Its <strong>Tiering Service</strong> continuously offloads data into standard lakehouse formats like Apache Iceberg, enabling external query engines to analyze data directly. This streaming/lakehouse unification simplifies the ecosystem, ensures data freshness for critical use cases, and combines real-time and historical data seamlessly for comprehensive analytics.</p>
<p><strong>Unified Data Locality:</strong> Fluss aligns partitions and buckets across both streaming and lakehouse layers, ensuring consistent data layout. This alignment enables direct Arrow-to-Parquet conversion without network shuffling or repartitioning, dramatically reducing I/O overhead and improving pipeline performance.
Think of your data as having two thermal zones:</p>
<p><strong>Hot Tier (Fluss):</strong> Last 1 hour of data, NVMe/SSD storage, sub-second latency, primary key indexed (RocksDB), streaming APIs, Apache Arrow columnar format. High-velocity writes, frequent updates, sub-second query latency requirements.</p>
<p><strong>Cold Tier (Iceberg):</strong> Historical data (hours to years), S3/HDFS object storage, minute-level latency, Parquet columnar format, ACID transactions, analytical query engines. Infrequent updates, optimized for analytical scans, stored cost-efficiently.</p>
<p>Traditional architectures force you to maintain <strong>separate systems</strong> for these zones: Kafka/Kinesis for streaming (hot), Iceberg for analytics (cold), complex ETL pipelines to move data between them, and applications writing to both systems (dual-write problem).</p>
<p><img decoding="async" loading="lazy" alt="Kappa vs Lambda Architecture" src="https://fluss.apache.org/assets/images/kappa-vs-lambda_comp-dbf5c3b6b3bafa92cca9dfb14069d04a.png" width="1088" height="627" class="img_ev3q"></p>
<p><strong>Fluss × Iceberg unifies these as tiered storage with Kappa architecture:</strong> Applications write once to Fluss. A stateless Tiering Service (Flink job) automatically moves data from hot to cold storage based on configured freshness (e.g., 30 seconds, 5 minutes). Query engines see a single table that seamlessly spans both tiers—eliminating the dual-write complexity of Lambda architecture.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-architecture-matters">Why This Architecture Matters<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#why-this-architecture-matters" class="hash-link" aria-label="Direct link to Why This Architecture Matters" title="Direct link to Why This Architecture Matters" translate="no">​</a></h3>
<p><strong>Single write path:</strong> Your application writes to Fluss. Period. No dual-write coordination, no consistency headaches across disconnected systems.</p>
<p><strong>Automatic lifecycle management:</strong> Data naturally flows from hot → cold based on access patterns and configured retention. Freshness is configurable in minutes via table properties.</p>
<p><strong>Auto Table creation:</strong> Support both append &amp; primary key table, with mapping schema and unified data locality via partitioning.</p>
<p><strong>Auto Table Maintenance:</strong> Enable with a single flag (table.datalake.auto-maintenance=true). The tiering service automatically detects small files during writes, applies bin-packing compaction to merge them into optimal sizes.</p>
<p><strong>Query flexibility:</strong> Run streaming queries on hot data (Fluss), analytical queries on cold data (Iceberg), or union queries that transparently span both tiers.</p>
<p><img decoding="async" loading="lazy" alt="Tiering Service" src="https://fluss.apache.org/assets/images/fluss-tiering-lake-acbf286eaeec95d46d09b4610cbeb86e.png" width="1625" height="802" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-iceberg-misses-today">What Iceberg Misses Today<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#what-iceberg-misses-today" class="hash-link" aria-label="Direct link to What Iceberg Misses Today" title="Direct link to What Iceberg Misses Today" translate="no">​</a></h2>
<p>Apache Iceberg was architected for batch-optimized analytics. While it supports streaming ingestion, fundamental design decisions create unavoidable limitations for real-time workloads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gap-1-metadata-overhead-limits-write-frequency">Gap 1: Metadata Overhead Limits Write Frequency<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#gap-1-metadata-overhead-limits-write-frequency" class="hash-link" aria-label="Direct link to Gap 1: Metadata Overhead Limits Write Frequency" title="Direct link to Gap 1: Metadata Overhead Limits Write Frequency" translate="no">​</a></h3>
<p>Every Iceberg commit rewrites <code>metadata.json</code> and manifest list files. For analytics with commits every 5-15 minutes, this overhead is negligible. For streaming with high-frequency commits, it becomes a bottleneck.</p>
<p><strong>The Math:</strong> Consider a streaming table with 100 events/second, committing every second:</p>
<ul>
<li class="">Each commit adds a manifest list entry</li>
<li class="">After 1 hour: <strong>3,600 manifest lists</strong></li>
<li class="">After 1 day: <strong>86,400 manifest lists</strong></li>
<li class=""><code>metadata.json</code> grows to megabytes</li>
<li class="">Individual commit latency stretches to multiple seconds</li>
</ul>
<p><strong>Compounding Factor:</strong> The problem compounds with partitioning. A 128-partition table with per-partition commits can generate thousands of metadata operations per second. <strong>Metadata becomes the bottleneck, not data throughput.</strong></p>
<p><strong>Real-World Evidence - Snowflake's acknowledgment:</strong> Their Iceberg streaming documentation explicitly warns about this, setting <code>MAX_CLIENT_LAG</code> defaults to 30 seconds (versus 1 second for native tables). The metadata overhead makes sub-second latency impractical.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gap-2-polling-based-reads-create-latency-multiplication">Gap 2: Polling-Based Reads Create Latency Multiplication<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#gap-2-polling-based-reads-create-latency-multiplication" class="hash-link" aria-label="Direct link to Gap 2: Polling-Based Reads Create Latency Multiplication" title="Direct link to Gap 2: Polling-Based Reads Create Latency Multiplication" translate="no">​</a></h3>
<p>Iceberg doesn't have a push-based notification system. Streaming readers poll for new snapshots.</p>
<p><strong>Latency Breakdown:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">1. Writer commits snapshot              → 0ms</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">2. Metadata hits S3 (eventual consistency) → 0-5,000ms</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">3. Reader polls (5-10s interval)        → 5,000-10,000ms</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">4. Reader discovers snapshot            → 5,000-15,000ms</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">5. Reader fetches data files from S3    → 5,100-15,500ms</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   ────────────────────────────────────────────────────</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   Total end-to-end latency: 5+ to 15+ seconds</span><br></div></code></pre></div></div>
<p>Compare this to a push model where producers write and consumers immediately receive notifications. End-to-end latency drops to <strong>single-digit milliseconds</strong>.</p>
<p>For real-time dashboards, fraud detection, or operational analytics requiring sub-second freshness, this polling latency is a non-starter.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gap-3-primary-key-support-is-declarative-not-enforced">Gap 3: Primary Key Support Is Declarative, Not Enforced<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#gap-3-primary-key-support-is-declarative-not-enforced" class="hash-link" aria-label="Direct link to Gap 3: Primary Key Support Is Declarative, Not Enforced" title="Direct link to Gap 3: Primary Key Support Is Declarative, Not Enforced" translate="no">​</a></h3>
<p>Iceberg V2 tables accept PRIMARY KEY declarations in DDL:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> users </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  user_id </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  email STRING</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  created_at </span><span class="token keyword" style="color:#194670">TIMESTAMP</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">-- This is a hint, not a constraint</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>However, Iceberg <strong>does not enforce</strong> primary keys:</p>
<ul>
<li class="">❌ No uniqueness validation on write</li>
<li class="">❌ No built-in deduplication</li>
<li class="">❌ No indexed lookups (point queries scan entire table)</li>
</ul>
<p><strong>The Consequence:</strong> For CDC workloads, you must implement deduplication logic in your streaming application (typically using Flink state). For tables with billions of rows, this state becomes enormous—<strong>50-100+ TB in production scenarios</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gap-4-high-frequency-updates-create-write-amplification">Gap 4: High-Frequency Updates Create Write Amplification<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#gap-4-high-frequency-updates-create-write-amplification" class="hash-link" aria-label="Direct link to Gap 4: High-Frequency Updates Create Write Amplification" title="Direct link to Gap 4: High-Frequency Updates Create Write Amplification" translate="no">​</a></h3>
<p>Iceberg supports updates via Merge-On-Read (MOR) with delete files:</p>
<p><strong>Equality deletes:</strong> Store all column values for deleted rows. For CDC updates (<code>-U</code> records), this means writing full row content to delete files before writing the updated version. For wide tables (50+ columns), this <strong>doubles the write volume</strong>.</p>
<p><strong>Position deletes:</strong> More efficient but require maintaining file-level position mappings. For streaming updates scattered across many files, position deletes proliferate rapidly.</p>
<p><strong>The Small File Problem:</strong> Streaming workloads naturally create many small files. Production teams report cases where <strong>500 MB of CDC data exploded into 2 million small files</strong> before compaction, slowing queries by <strong>10-100x</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-fluss-fills-these-gaps">How Fluss Fills These Gaps<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#how-fluss-fills-these-gaps" class="hash-link" aria-label="Direct link to How Fluss Fills These Gaps" title="Direct link to How Fluss Fills These Gaps" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-1-log-indexed-streaming-storage-with-push-based-reads">Solution 1: Log-Indexed Streaming Storage with Push-Based Reads<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#solution-1-log-indexed-streaming-storage-with-push-based-reads" class="hash-link" aria-label="Direct link to Solution 1: Log-Indexed Streaming Storage with Push-Based Reads" title="Direct link to Solution 1: Log-Indexed Streaming Storage with Push-Based Reads" translate="no">​</a></h3>
<p><strong>Addresses:</strong> Gap 1 (Metadata Overhead) &amp; Gap 2 (Polling-Based Reads)</p>
<p>Fluss reimagines streaming storage using <strong>Apache Arrow IPC columnar format</strong> with <strong>Apache Kafka's battle-tested replication protocol</strong>.</p>
<p><strong>How it solves metadata overhead:</strong></p>
<p>Iceberg's metadata bottleneck occurs when you commit frequently. Fluss sidesteps this entirely:</p>
<ol>
<li class=""><strong>High-frequency writes go to Fluss</strong>—append-only log segments with no global metadata coordination</li>
<li class=""><strong>Iceberg receives batched commits</strong>—the tiering service aggregates minutes of data into single, well-formed Parquet files</li>
<li class=""><strong>Configurable freshness</strong>—<code>table.datalake.freshness = '1min'</code> means Iceberg sees ~1 commit per minute, not thousands</li>
</ol>
<p><strong>Result:</strong> Iceberg operates exactly as designed: periodic batch commits with clean manifest evolution. The streaming complexity stays in Fluss.</p>
<p><strong>How it solves polling latency:</strong></p>
<ul>
<li class=""><strong>Push-based real-time:</strong> Consumers receive millisecond-latency push notifications when new data arrives. No polling intervals.</li>
<li class=""><strong>End-to-end latency:</strong> Sub-second, typically single-digit milliseconds</li>
<li class="">Real-time queries hit Fluss directly; they don't wait for Iceberg snapshots</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-2-primary-key-tables-with-native-upsert-support">Solution 2: Primary Key Tables with Native Upsert Support<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#solution-2-primary-key-tables-with-native-upsert-support" class="hash-link" aria-label="Direct link to Solution 2: Primary Key Tables with Native Upsert Support" title="Direct link to Solution 2: Primary Key Tables with Native Upsert Support" translate="no">​</a></h3>
<p><strong>Addresses:</strong> Gap 3 (Primary Key Not Enforced) &amp; Gap 4 (Update Write Amplification)</p>
<p>Fluss provides first-class primary key semantics using an LSM tree architecture:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">┌─────────────────────────────────────────────────┐</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ Primary Key Table (e.g., inventory)             │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">├─────────────────────────────────────────────────┤</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ KV Tablet (RocksDB - current state):            │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ ┌─────────────────────────────────────┐         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ │ sku_id=101 → {quantity: 50, ...}    │         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ │ sku_id=102 → {quantity: 23, ...}    │         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ └─────────────────────────────────────┘         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│                                                 │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ Log Tablet (changelog - replicated 3x):         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ ┌─────────────────────────────────────┐         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ │ +I[101, 100, ...]  // Insert        │         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ │ -U[101, 100, ...] +U[101, 50, ...]  │ Update  │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ │ -D[102, 23, ...]   // Delete        │         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ └─────────────────────────────────────┘         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│                                                 │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ Features:                                       │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - Enforced uniqueness (PK constraint)           │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - 500K+ QPS point queries (RocksDB index)       │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - Pre-deduplicated changelog (CDC)              │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - Read-your-writes consistency                  │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - Changelog Read for streaming consumers        │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">│ - Lookup Join for dimensional enrichment        │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">└─────────────────────────────────────────────────┘</span><br></div></code></pre></div></div>
<p><strong>How it works:</strong></p>
<p>When you write to a primary key table, Fluss:</p>
<ol>
<li class="">Reads current value from RocksDB (if exists)</li>
<li class="">Determines change type: <code>+I</code> (insert), <code>-U/+U</code> (update), <code>-D</code> (delete)</li>
<li class="">Appends changelog to replicated log tablet (WAL)</li>
<li class="">Waits for log commit (high watermark)</li>
<li class="">Flushes to RocksDB</li>
<li class="">Acknowledges client</li>
</ol>
<p><strong>Guarantees:</strong> Read-your-writes consistency—if you see a change in the log, you can query it. If you see a record in the table, its change exists in the changelog.</p>
<p><strong>Critical Capabilities:</strong></p>
<ul>
<li class=""><strong>Point queries by primary key</strong> use RocksDB index, achieving <strong>500,000+ QPS on single tables</strong> in production</li>
<li class="">This replaces external KV stores (Redis, DynamoDB) for dimension table serving</li>
<li class=""><strong>CDC without deduplication:</strong> The changelog is already deduplicated at write time</li>
<li class="">Downstream Flink consumers read pre-processed CDC events with <strong>no additional state required</strong></li>
</ul>
<p><strong>Tiering Primary Key Tables to Iceberg:</strong></p>
<p>When tiering PK tables, Fluss:</p>
<ol>
<li class="">First writes the snapshot (no delete files)</li>
<li class="">Then writes changelog records</li>
<li class="">For changelog entries (<code>-D</code> or <code>-U</code> records), Fluss writes equality-delete files containing all column values</li>
</ol>
<p>This design enables Iceberg data to serve as Fluss changelog—complete records can be reconstructed from equality-delete entries for downstream consumption.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-3-unified-ingestion-with-automatic-tiering">Solution 3: Unified Ingestion with Automatic Tiering<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#solution-3-unified-ingestion-with-automatic-tiering" class="hash-link" aria-label="Direct link to Solution 3: Unified Ingestion with Automatic Tiering" title="Direct link to Solution 3: Unified Ingestion with Automatic Tiering" translate="no">​</a></h3>
<p><strong>Addresses:</strong> All gaps by offloading real-time complexity from Iceberg</p>
<p><strong>Architecture Flow:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Application</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    ├─→ Fluss Table (single write)</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         ├─→ Real-time consumers (Flink, StarRocks, etc.)</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         │   - Sub-second latency</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         │   - Column-projected streaming reads</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         │   - Primary key lookups</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         │</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">         └─→ Tiering Service (Flink job, automatic)</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">              └─→ Apache Iceberg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  - Parquet files</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  - Atomic commits</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                  - Historical analytics</span><br></div></code></pre></div></div>
<p><strong>Tiering Service Architecture:</strong></p>
<p>Stateless Flink jobs: Multiple jobs register with the Fluss Coordinator Server. The coordinator assigns tables to jobs via an in-memory queue. Each job processes one table at a time, commits results, and requests the next table.</p>
<p><strong>Key Capabilities:</strong></p>
<ul>
<li class=""><strong>Auto-create lake table:</strong> Automatically provisions the corresponding Iceberg table</li>
<li class=""><strong>Handles both table types seamlessly:</strong> append-only Log Tables for event streams and Primary Key Tables with full upsert/delete support</li>
<li class=""><strong>Auto mapping schema:</strong> Translates Fluss schema to Iceberg schema with system columns</li>
<li class=""><strong>Arrow → Parquet conversion:</strong> Transforms columnar Arrow batches to Parquet format</li>
<li class=""><strong>Freshness in minutes:</strong> Configurable via <code>table.datalake.freshness</code> property</li>
</ul>
<p><strong>Benefits:</strong></p>
<ul>
<li class=""><strong>Elastic scaling:</strong> Deploy 3 jobs for 3x throughput, stop idle jobs to reclaim resources</li>
<li class=""><strong>No single point of failure:</strong> Job failure doesn't block all tables</li>
<li class=""><strong>Load balancing:</strong> Automatic distribution based on sync lag</li>
</ul>
<p><strong>Integrated Compaction:</strong></p>
<p>While tiering data, the service optionally performs bin-packing compaction:</p>
<ol>
<li class="">Scans manifests to identify small files in the current bucket</li>
<li class="">Schedules background rewrite (small files → large files)</li>
<li class="">Waits for compaction before committing</li>
<li class="">Produces atomic snapshot: new data files + compacted files</li>
</ol>
<p><strong>Configuration:</strong> <code>table.datalake.auto-maintenance=true</code></p>
<p><strong>Result:</strong> Streaming workloads avoid small file proliferation without separate maintenance jobs.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-4-union-read-for-seamless-query-across-tiers">Solution 4: Union Read for Seamless Query Across Tiers<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#solution-4-union-read-for-seamless-query-across-tiers" class="hash-link" aria-label="Direct link to Solution 4: Union Read for Seamless Query Across Tiers" title="Direct link to Solution 4: Union Read for Seamless Query Across Tiers" translate="no">​</a></h3>
<p><strong>Enables:</strong> Querying hot + cold data as a single logical table</p>
<p>The architectural breakthrough enabling a real-time lakehouse is <strong>client-side stitching with metadata coordination</strong>. This is what makes Fluss truly a <strong>Streaming Lakehouse</strong>—unlocking real-time data to the Lakehouse with union delta log (minutes) on Fluss.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-union-read-works">How Union Read Works<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#how-union-read-works" class="hash-link" aria-label="Direct link to How Union Read Works" title="Direct link to How Union Read Works" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Union Read Architecture" src="https://fluss.apache.org/assets/images/fluss-union-read-4ff8d8754ac002861e57cbb82186b24b.png" width="1705" height="689" class="img_ev3q"></p>
<p>Union Read seamlessly combines hot and cold data through intelligent offset coordination, as illustrated above:</p>
<p><strong>The Example:</strong> Consider a query that needs records for users Jark, Mehul, and Yuxia:</p>
<ol>
<li class="">
<p><strong>Offset Coordination:</strong> Fluss CoordinatorServer provides Snapshot 06 as the Iceberg boundary. At this snapshot, Iceberg contains <code>{Jark: 30, Yuxia: 20}</code>.</p>
</li>
<li class="">
<p><strong>Hot Data Supplement:</strong> Fluss's real-time layer holds the latest updates beyond the snapshot: <code>{Jark: 30, Mehul: 20, Yuxia: 20}</code> (including Mehul's new record).</p>
</li>
<li class="">
<p><strong>Union Read in Action:</strong> The query engine performs a union read:</p>
<ul>
<li class="">Reads <code>{Jark: 30, Yuxia: 20}</code> from Iceberg (Snapshot 06)</li>
<li class="">Supplements with <code>{Mehul: 20}</code> from Fluss (new data after the snapshot)</li>
</ul>
</li>
<li class="">
<p><strong>Sort Merge:</strong> Results are merged and deduplicated, producing the final unified view: <code>{Jark: 30, Mehul: 20}</code> (Yuxia's update already in Iceberg).</p>
</li>
</ol>
<p><strong>Key Benefit:</strong> The application queries a single logical table while the system intelligently routes between Iceberg (historical) and Fluss (real-time) with zero gaps or overlaps.</p>
<p><strong>Union Read Capabilities:</strong></p>
<ul>
<li class=""><strong>Query both Historical &amp; Real-time Data:</strong> Seamlessly access cold and hot tiers</li>
<li class=""><strong>Exchange using Arrow-native format:</strong> Efficient data transfer between tiers</li>
<li class=""><strong>Efficient process/integration for query engines:</strong> Optimized for Flink, StarRocks, Spark</li>
</ul>
<p><strong>SQL Syntax (Apache Flink):</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Automatic union read (default behavior)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> orders </span><span class="token keyword" style="color:#194670">WHERE</span><span class="token plain"> event_time </span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">NOW</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">-</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'1'</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">HOUR</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Seamlessly reads: Iceberg (historical) + Fluss (real-time)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Explicit cold-only read (Iceberg only)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> orders$lake </span><span class="token keyword" style="color:#194670">WHERE</span><span class="token plain"> order_date </span><span class="token operator" style="color:#475569">&lt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">CURRENT_DATE</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Consistency Guarantees:</strong></p>
<p>The tiering service embeds Fluss offset metadata in Iceberg snapshot summaries:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token property" style="color:#12325C">"commit-user"</span><span class="token operator" style="color:#475569">:</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"__fluss_lake_tiering"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token property" style="color:#12325C">"fluss-bucket-offset"</span><span class="token operator" style="color:#475569">:</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"[{0: 5000}, {1: 5123}, {2: 5087}, ...]"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Fluss coordinator persists this mapping. When clients query, they receive the exact offset boundary. Union read logic ensures:</p>
<ul>
<li class=""><strong>No overlaps:</strong> Iceberg handles offsets ≤ boundary</li>
<li class=""><strong>No gaps:</strong> Fluss handles offsets &gt; boundary</li>
<li class=""><strong>Total ordering preserved</strong> per bucket</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-benefits">Architecture Benefits<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#architecture-benefits" class="hash-link" aria-label="Direct link to Architecture Benefits" title="Direct link to Architecture Benefits" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost-efficient-historical-storage">Cost-Efficient Historical Storage<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#cost-efficient-historical-storage" class="hash-link" aria-label="Direct link to Cost-Efficient Historical Storage" title="Direct link to Cost-Efficient Historical Storage" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Historical Analysis" src="https://fluss.apache.org/assets/images/fluss-lakehouse-history-e552aa7d21c4870cf58dbaf9b8f5d5ff.png" width="1707" height="802" class="img_ev3q"></p>
<p>Automatic tiering optimizes storage and analytics: efficient backfill, projection/filter pushdown, high Parquet compression, and S3 throughput.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-time-analytics">Real-Time Analytics<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#real-time-analytics" class="hash-link" aria-label="Direct link to Real-Time Analytics" title="Direct link to Real-Time Analytics" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Real-time Analytics" src="https://fluss.apache.org/assets/images/fluss-lakehouse-realtime-ee607fa9620435d919136446cf2553c5.png" width="1734" height="802" class="img_ev3q"></p>
<p>Union Read delivers sub-second lakehouse freshness: union delta log on Fluss, Arrow-native exchange, and seamless integration with Flink, Spark *, Trino, and StarRocks.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-takeaways">Key Takeaways<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#key-takeaways" class="hash-link" aria-label="Direct link to Key Takeaways" title="Direct link to Key Takeaways" translate="no">​</a></h3>
<p><strong>Single write, two read modes:</strong> Write once to Fluss. Query two ways: $lake suffix for cost-efficient historical batch analysis, or default (no suffix) for unified view with second-level freshness.</p>
<p><strong>Automatic data movement:</strong> Data automatically tiers from Fluss → Iceberg after configured time (e.g., 1 minute). No manual ETL jobs or data pipelines to maintain. Configurable freshness per table.</p>
<p><strong>Unified table abstraction:</strong> Applications see a single logical table. Query engine transparently routes to appropriate tier(s). Offset-based coordination ensures no gaps or duplicates.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting Started<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started" translate="no">​</a></h2>
<p>This gives you a working streaming lakehouse environment in minutes. Visit: <a href="https://fluss.apache.org/docs/quickstart/lakehouse/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/docs/quickstart/lakehouse/</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion-the-path-forward">Conclusion: The Path Forward<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#conclusion-the-path-forward" class="hash-link" aria-label="Direct link to Conclusion: The Path Forward" title="Direct link to Conclusion: The Path Forward" translate="no">​</a></h2>
<p>Apache Fluss and Apache Iceberg represent a fundamental rethinking of real-time lakehouse architecture. Instead of forcing Iceberg to become a streaming platform (which it was never designed to be), Fluss embraces Iceberg for its strengths—cost-efficient analytical storage with ACID guarantees—while adding the missing hot streaming layer.</p>
<p>The result is a Streamhouse that delivers:</p>
<ul>
<li class=""><strong>Sub-second query latency</strong> for real-time workloads</li>
<li class=""><strong>Second-level freshness</strong> for analytical queries (versus T+1 hour)</li>
<li class=""><strong>80% cost reduction</strong> by eliminating data duplication and system complexity</li>
<li class=""><strong>Single write path</strong> ending dual-write consistency problems</li>
<li class=""><strong>Automatic lifecycle management</strong> from hot to cold tiers</li>
</ul>
<p>For software/data engineers building real-time analytics platforms, the question isn't whether to use Fluss or Iceberg—it's recognizing they solve complementary problems. Fluss handles what happens in the last hour (streaming, updates, real-time queries). Iceberg handles everything before that (historical analytics, ML training, compliance).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-to-adopt">When to Adopt<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#when-to-adopt" class="hash-link" aria-label="Direct link to When to Adopt" title="Direct link to When to Adopt" translate="no">​</a></h3>
<p><strong>Strong signals you need Fluss:</strong></p>
<ul>
<li class="">Requirement for sub-second query latency on streaming data</li>
<li class="">High-frequency CDC workloads (100+ updates/second per key)</li>
<li class="">Need for primary key semantics with indexed lookups</li>
<li class="">Large Flink stateful jobs (10TB+ state) that could be externalized</li>
<li class="">Desire to unify real-time and historical queries</li>
<li class="">Tired of maintaining dual infrastructure one for batch, another for real-time</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps">Next Steps<a href="https://fluss.apache.org/blog/2025/12/02/fluss-x-iceberg-why-your-lakehouse-is-not-streamhouse-yet/#next-steps" class="hash-link" aria-label="Direct link to Next Steps" title="Direct link to Next Steps" translate="no">​</a></h3>
<ol>
<li class=""><strong>Explore the documentation:</strong> <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">fluss.apache.org</a></li>
<li class=""><strong>Review FIP-3:</strong> Detailed Iceberg integration specification</li>
<li class=""><strong>Try the quickstart:</strong> Deploy locally with Docker Compose</li>
<li class=""><strong>Join the community:</strong> Apache Fluss mailing lists, Slack, and GitHub</li>
<li class=""><strong>Evaluate Iceberg integration:</strong> Production-ready today, same architectural patterns</li>
</ol>
<hr>
<p>We've covered <strong>what</strong> Fluss × Iceberg is and <strong>how</strong> it works the architecture eliminates dual-write complexity, delivers sub-second freshness, and unifies streaming and batch under a single table abstraction.</p>
<p>But here's the elephant in the room: <strong>Apache Kafka dominates event streaming. Tableflow handles Kafka-to-Iceberg materialization. Why introduce another system?</strong></p>
<p><strong>Stay tuned for Part 2 as it tackles this question head-on</strong> by comparing Fluss with existing technologies.</p>]]></content:encoded>
            <category>Streaming Lakehouse</category>
            <category>Apache Iceberg</category>
            <category>Real-time Analytics</category>
            <category>Apache Fluss</category>
        </item>
        <item>
            <title><![CDATA[Announcing Apache Fluss (Incubating) 0.8: Streaming Lakehouse for Data + AI]]></title>
            <link>https://fluss.apache.org/blog/releases/0.8/</link>
            <guid>https://fluss.apache.org/blog/releases/0.8/</guid>
            <pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-b59a681a2fb3b0412e1719abcaca7522.jpg" width="2562" height="1118" class="img_ev3q"></p>
<p>🌊 We are excited to announce the official release of <strong>Apache Fluss (Incubating) 0.8</strong>!</p>
<p>This is our first release under the incubator of the Apache Software Foundation, marking a significant milestone in our journey to provide a robust streaming storage platform for real-time analytics.</p>
<p>Over the past four months, the community has made tremendous progress, delivering nearly 400 commits that push the boundaries of the Streaming Lakehouse ecosystem. This release includes multiple stability optimizations and introduces deeper integrations, performance breakthroughs, and next-generation stream processing capabilities. Highlights:</p>
<ul>
<li class="">🧊 Enhanced Streaming Lakehouse capabilities with full support for <a href="https://iceberg.apache.org/" target="_blank" rel="noopener noreferrer" class="">Apache Iceberg</a> and <a href="https://lancedb.github.io/lance/" target="_blank" rel="noopener noreferrer" class="">Lance</a></li>
<li class="">⚡ Introduction of <a href="https://cwiki.apache.org/confluence/display/FLINK/FLIP-486%3A+Introduce+A+New+DeltaJoin" target="_blank" rel="noopener noreferrer" class="">Delta Joins</a> with Flink, a game-changing innovation that redefines efficiency in stream processing by minimizing state and maximizing speed.</li>
<li class="">🔧 Supports hot updates for both cluster configurations and table configurations</li>
</ul>
<p>Apache Fluss 0.8 marks the beginning of a new era in streaming:
<strong>real-time</strong>, <strong>unified</strong>, and <strong>zero-state</strong>, purpose-built to power the next generation of data platforms with <strong>low-latency performance</strong>, <strong>scalability</strong>, and <strong>architectural simplicity</strong>.</p>
<p><img decoding="async" loading="lazy" alt="Improvements Diagram" src="https://fluss.apache.org/assets/images/overview-2a2cdcb5518e10ee2e25fe7fe2206cef.png" width="2658" height="1622" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="streaming-lakehouse-for-iceberg">Streaming Lakehouse for Iceberg<a href="https://fluss.apache.org/blog/releases/0.8/#streaming-lakehouse-for-iceberg" class="hash-link" aria-label="Direct link to Streaming Lakehouse for Iceberg" title="Direct link to Streaming Lakehouse for Iceberg" translate="no">​</a></h2>
<p>A key highlight of Fluss 0.8 is the introduction of <strong>Streaming Lakehouse for Apache Iceberg</strong> (<a href="https://cwiki.apache.org/confluence/display/FLUSS/FIP-3%3A+Support+tiering+Fluss+data+to+Iceberg" target="_blank" rel="noopener noreferrer" class="">FIP-3</a>),
which transforms Iceberg from a batch-oriented table format into a continuously updating Lakehouse. Apache Fluss acts as the <strong>real-time ingestion and storage layer</strong>, writing fresh data and updates into Iceberg with guaranteed ordering and exactly-once semantics.</p>
<p>This enables real-time data on Fluss to be tiered as Apache Iceberg tables, while providing table semantics like partitioning and bucketing on a single copy of data.
Moreover, it solves Iceberg’s long-standing update limitations through Fluss’s <strong>native support for upserts and deletes</strong> and its <strong>built-in compaction service</strong>,
which automatically merges small files and maintains optimized Iceberg snapshots.</p>
<p>Key benefits include:</p>
<ul>
<li class=""><strong>Unified Architecture</strong>: Fluss handles sub-second streaming reads and writes, while Iceberg stores compacted historical data.</li>
<li class=""><strong>Native Updates and Deletes</strong>: Fluss efficiently applies changes and tiers them into Iceberg without rewrite jobs.</li>
<li class=""><strong>Built-in Compaction Service</strong>: The built-in service maintains snapshot efficiency with no external tooling.</li>
<li class=""><strong>Efficient Backfilling</strong>: Enables lightning-fast backfill of historical data from Iceberg for streaming processing.</li>
<li class=""><strong>Lower Cost</strong>: Reduce storage cost by tiering cold data to Iceberg while keeping hot data in Fluss, eliminating the need for duplicate storage.</li>
<li class=""><strong>Lower Latency</strong>: Sub-second data freshness for Iceberg tables by Union Read from Fluss and Iceberg.</li>
</ul>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">server.yaml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic"># Iceberg configuration</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.format</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> iceberg</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic"># the catalog config about Iceberg, assuming using Hadoop catalog,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.iceberg.type</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> hadoop</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.iceberg.warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> /path/to/iceberg</span><br></div></code></pre></div></div>
<p>You can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/streaming-lakehouse/integrate-data-lakes/iceberg/">Iceberg Lakehouse documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-time-multimodal-ai-analytics-with-lance">Real-Time Multimodal AI Analytics with Lance<a href="https://fluss.apache.org/blog/releases/0.8/#real-time-multimodal-ai-analytics-with-lance" class="hash-link" aria-label="Direct link to Real-Time Multimodal AI Analytics with Lance" title="Direct link to Real-Time Multimodal AI Analytics with Lance" translate="no">​</a></h2>
<p>Another major enhancement in Fluss 0.8 is the addition of <strong>Streaming Lakehouse support for <a href="https://github.com/lancedb/lance" target="_blank" rel="noopener noreferrer" class="">Lance</a></strong> (<a href="https://cwiki.apache.org/confluence/display/FLUSS/FIP-5%3A+Support+tiering+Fluss+data+to+Lance" target="_blank" rel="noopener noreferrer" class="">FIP-5</a>),
a modern columnar and vector-native data format designed for AI and machine learning workloads.
This integration extends Apache Fluss towards being a real-time ingestion platform for multi-modal data &amp; AI,
not just traditional tabular streams, but also embeddings, vectors, and unstructured features used in AI systems.
With this release, Fluss can continuously ingest, update, and tier data into Lance tables with guaranteed ordering and freshness,
enabling fast synchronization between streaming pipelines and downstream ML or retrieval applications.</p>
<p>Key benefits include:</p>
<ul>
<li class=""><strong>Unified multi-modal data ingestion</strong>: Stream tabular, vector, and embedding data into Lance in real time.</li>
<li class=""><strong>AI/ML-ready storage</strong>: Keep feature vectors and embeddings continuously up-to-date for model training or inference.</li>
<li class=""><strong>Low-latency analytics and retrieval</strong>: Fast, continuous updates enable Lance data to be immediately usable for real-time search and recommendation.</li>
<li class=""><strong>Simplified architecture</strong>: Eliminates complex ETL pipelines between streaming systems and vector databases.</li>
</ul>
<p>Seamless integration: combines Fluss’s high-throughput streaming engine with Lance’s efficient columnar persistence for consistent, multi-modal data management.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">server.yaml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token key atrule" style="color:#194670">datalake.format</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> lance</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.lance.warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> s3</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">//&lt;bucket</span><span class="token punctuation" style="color:#475569">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.lance.endpoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> &lt;endpoint</span><span class="token punctuation" style="color:#475569">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.lance.allow_http</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#BE123C">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.lance.access_key_id</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> &lt;access_key_id</span><span class="token punctuation" style="color:#475569">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.lance.secret_access_key</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> &lt;secret_access_key</span><span class="token punctuation" style="color:#475569">&gt;</span><br></div></code></pre></div></div>
<p>See the <a href="https://lancedb.com/blog/fluss-integration/" target="_blank" rel="noopener noreferrer" class="">LanceDB blog post</a> for the full integration. You also can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/streaming-lakehouse/integrate-data-lakes/lance/">Lance Lakehouse documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-21">Flink 2.1<a href="https://fluss.apache.org/blog/releases/0.8/#flink-21" class="hash-link" aria-label="Direct link to Flink 2.1" title="Direct link to Flink 2.1" translate="no">​</a></h2>
<p>Apache Fluss is now fully compatible with <strong>Apache Flink 2.1</strong>, ensuring seamless integration with the latest Flink runtime and APIs.
This update strengthens Fluss’s role as a unified streaming storage layer, providing reliable performance and consistency for modern data pipelines built on Flink.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="delta-join">Delta Join<a href="https://fluss.apache.org/blog/releases/0.8/#delta-join" class="hash-link" aria-label="Direct link to Delta Join" title="Direct link to Delta Join" translate="no">​</a></h3>
<p>The Delta Join is a major step towards the era of zero-state streaming joins. This release introduces support for Delta Joins with Apache Flink.
By externalizing state into Fluss tables, Flink performs joins incrementally on data deltas, without maintaining large states.
This architecture reduces CPU and memory usage by <strong>up to 80%</strong>, eliminates over <strong>100 TB of state</strong> as witnessed in the first production use cases from <a class="" href="https://fluss.apache.org/blog/taobao-practice/">early adopters</a>,
and cuts checkpoint durations from <strong>90 seconds to just 1 second</strong>. Because all data lives natively in Fluss tables,
there’s <strong>no state bootstrapping</strong>; pipelines start instantly, stay lightweight, and achieve efficiency for real-time analytics at scale.</p>
<p>Below is a performance comparison (CPU, memory, state size, checkpoint interval) between Delta Join and Stream-Stream Join, as evaluated by Taobao’s Search &amp; Recommendation Systems team.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_delta1-76e0c23013cbefdb8bf9f76980497d38.png" width="904" height="246" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_delta2-db0bb3f31808d421f8e516221d44c29d.png" width="904" height="246" class="img_ev3q"></p>
<p>You can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/engine-flink/delta-joins/">Delta Join documentation</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="materialized-table">Materialized Table<a href="https://fluss.apache.org/blog/releases/0.8/#materialized-table" class="hash-link" aria-label="Direct link to Materialized Table" title="Direct link to Materialized Table" translate="no">​</a></h3>
<p>Apache Fluss 0.8 introduces support for Flink Materialized Tables, enabling seamless, low-latency materializations directly over Fluss streams.
Flink’s Materialized Table turns a SQL query into a continuously or periodically refreshed result table with a defined freshness target (e.g., seconds or minutes).
With Fluss as the underlying streaming source, users can declaratively build real-time tables that stay up to date without custom orchestration.
This integration unifies batch and streaming ETL: Fluss delivers high-throughput, low-latency data, while Flink continuously maintains derived tables for analytics,
APIs, and downstream workloads, providing real-time, consistent data pipelines with minimal operational overhead.
This integration further strengthens the batch &amp; stream unification.</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- 1. create a materialized table with 10 seconds freshness</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> MATERIALIZED </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> fluss</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">dw</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">sales_summary</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">FRESHNESS </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTERVAL</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">SECOND</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">SELECT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  product</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token function" style="color:#7C3AED">SUM</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">quantity</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> total_sales</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">AS</span><span class="token plain"> last_updated</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">FROM</span><span class="token plain"> fluss</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">dw</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">sales_detail</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">GROUP</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> product</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- 2. suspend data refresh for the materialized table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ALTER</span><span class="token plain"> MATERIALIZED </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> dwd_orders SUSPEND</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- 3. resume data refresh for the materialized table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ALTER</span><span class="token plain"> MATERIALIZED </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> dwd_orders RESUME</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Set table option via WITH clause</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sink.parallelism'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'10'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>You can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/engine-flink/ddl/#materialized-table">Materialized Table documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stability">Stability<a href="https://fluss.apache.org/blog/releases/0.8/#stability" class="hash-link" aria-label="Direct link to Stability" title="Direct link to Stability" translate="no">​</a></h2>
<p>In this release, we have made significant improvements in the stability and reliability of Apache Fluss under large-scale production workloads.
Through continuous validation across multiple business units within Alibaba Group, and <strong>especially through large-scale workloads during the Alibaba's Double 11 peak traffic</strong>, we have resolved over 35 stability-related issues.
These improvements substantially enhance Fluss’s robustness in mission-critical streaming use cases.</p>
<p>Key improvements include:</p>
<ul>
<li class=""><strong><a class="" href="https://fluss.apache.org/docs/maintenance/operations/graceful-shutdown/">Graceful Shutdown</a></strong>: Fluss supports cluster rolling upgrade, and we introduced a graceful shutdown mechanism for TabletServers in this version. During shutdown, leadership is proactively migrated before termination, ensuring that read/write latency remains unaffected during rolling upgrades.</li>
<li class=""><strong>Accelerated Coordinator Event Processing</strong>: Optimized the Coordinator’s event handling mechanism through asynchronous processing and batched ZooKeeper operations. As a result, all events are now processed in milliseconds.</li>
<li class=""><strong>Faster Coordinator Recovery</strong>: Parallelized initialization cuts Coordinator startup time from 10 minutes to just 20 seconds in production-scale benchmarks, this dramatically improves service availability and recovery speed.</li>
<li class=""><strong>Optimized Server Metrics</strong>: Refined metric granularity and reporting logic to reduce telemetry volume by 90% while preserving full observability.</li>
<li class=""><strong>Enhanced Metadata Performance</strong>: Addressed metadata bottlenecks during mass client restarts by strengthening the server local cache and introducing asynchronous ZooKeeper operations. This reduces metadata request latency from &gt;10 seconds to milliseconds, ensuring stable client reconnection under load.</li>
</ul>
<p>With these foundational stability improvements, Fluss 0.8 is now production-ready for the most demanding real-time workloads, including Alibaba’s annual Double 11 global shopping festival.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dynamic-configuration">Dynamic Configuration<a href="https://fluss.apache.org/blog/releases/0.8/#dynamic-configuration" class="hash-link" aria-label="Direct link to Dynamic Configuration" title="Direct link to Dynamic Configuration" translate="no">​</a></h2>
<p>Starting with Fluss version 0.8, certain <strong>cluster-level configurations</strong> and <strong>table-level configurations</strong> can be updated dynamically, without requiring a cluster restart or table recreation. This enables operators and developers to adjust system behavior in real time, improving operational agility and minimizing downtime.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dynamic-cluster-configs">Dynamic Cluster Configs<a href="https://fluss.apache.org/blog/releases/0.8/#dynamic-cluster-configs" class="hash-link" aria-label="Direct link to Dynamic Cluster Configs" title="Direct link to Dynamic Cluster Configs" translate="no">​</a></h3>
<p>Fluss now supports runtime updates for cluster configuration parameters. These changes take effect immediately across the cluster after being applied through the API.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Java Client</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token class-name" style="color:#7C3AED">Admin</span><span class="token plain"> admin </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAdmin</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Collection</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">AlterConfig</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> configsToUpdate </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Arrays</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">asList</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">AlterConfig</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"datalake.format"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"paimon"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">AlterConfigOpType</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">SET</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">admin</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">alterClusterConfigs</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">configsToUpdate</span><span class="token punctuation" style="color:#475569">)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dynamic-table-configs">Dynamic Table Configs<a href="https://fluss.apache.org/blog/releases/0.8/#dynamic-table-configs" class="hash-link" aria-label="Direct link to Dynamic Table Configs" title="Direct link to Dynamic Table Configs" translate="no">​</a></h3>
<p>Fluss now supports update options dynamically on a table using the <code>ALTER TABLE ... SET</code> statement. This supports all the client-wise options (like <code>scan.startup.mode</code>) and some storage-wise options (like <code>table.datalake.enabled</code>).</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockTitle_OeMC">Flink SQL</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Enable lakehouse storage for the given table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">ALTER</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> my_table </span><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>When you issue a <code>ALTER TABLE ... SET</code> command to update storage options on a table, the Fluss cluster validates and applies the new configuration immediately. The updated settings are propagated to all TabletServers and CoordinatorServer components, ensuring consistent behavior going forward.</p>
<p>This capability is especially useful for tuning performance, adapting to changing data patterns, or complying with evolving data governance requirements—all without service interruption.</p>
<p>You can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/maintenance/operations/updating-configs/">Updating Configs documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="helm-charts">Helm Charts<a href="https://fluss.apache.org/blog/releases/0.8/#helm-charts" class="hash-link" aria-label="Direct link to Helm Charts" title="Direct link to Helm Charts" translate="no">​</a></h2>
<p>This release also introduced Helm Charts. With this addition, users can now deploy and manage a full Fluss cluster using <a href="https://helm.sh/" target="_blank" rel="noopener noreferrer" class="">Helm</a>.
The Helm chart simplifies provisioning, upgrades, and scaling by packaging configuration, manifests, and dependencies into a single, versioned release.
This should help users running Fluss on Kubernetes faster, more reliably, and with easier integration into existing CI/CD and observability setups, significantly lowering the barrier for teams adopting Fluss in production.</p>
<p>You can find more detailed instructions in the <a class="" href="https://fluss.apache.org/docs/install-deploy/deploying-with-helm/">Deploying with Helm documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="java-version-upgrade">Java Version Upgrade<a href="https://fluss.apache.org/blog/releases/0.8/#java-version-upgrade" class="hash-link" aria-label="Direct link to Java Version Upgrade" title="Direct link to Java Version Upgrade" translate="no">​</a></h2>
<p>Starting with Fluss 0.8, the project has upgraded its default Java language version from <strong>Java 8 to Java 11</strong>. Accordingly, the official binary distribution is now compiled with Java 11.
As a result: The minimum required Java version for running Fluss clusters or using Fluss connectors/clients is now Java 11. Besides, we recommend using Java 17 for production deployments of Fluss server components, as it offers better performance and long-term support.</p>
<p>While the source code still maintains source compatibility with Java 8, official support for Java 8 is deprecated and will be removed in a future release. If you must run Fluss on Java 8, you can manually build the project from source.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ecosystem">Ecosystem<a href="https://fluss.apache.org/blog/releases/0.8/#ecosystem" class="hash-link" aria-label="Direct link to Ecosystem" title="Direct link to Ecosystem" translate="no">​</a></h2>
<p>The Apache Fluss community is actively expanding Fluss beyond the JVM ecosystem with new <strong>native clients</strong> for Rust and Python, enabling seamless integration across modern data and AI workflows.
We’ve established an <a href="https://github.com/apache/fluss-rust" target="_blank" rel="noopener noreferrer" class="">official repository</a> to host both the Rust and Python clients, developed with performance, safety, and developer experience in mind:</p>
<ul>
<li class=""><strong>🦀 Rust Client</strong>: Built on async I/O, zero-copy columnar streaming (via Apache Arrow), and Rust’s memory safety guarantees, this client unlocks high-performance query integration with native OLAP engines like DuckDB and StarRocks.</li>
<li class=""><strong>🐍 Python Client</strong>: Built as a native binding on top of the Rust client, it allows Python developers to interact with Fluss tables and streams directly from data science, ML, and analytics workflows.</li>
</ul>
<p>The Rust and Python clients are maintained in a <a href="https://github.com/apache/fluss-rust" target="_blank" rel="noopener noreferrer" class="">separate repository</a> to allow for faster iteration and releases, and therefore are not part of the Fluss 0.8 release.
However, the community is actively stabilizing the clients and plans to release them soon.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="upgrade-notes">Upgrade Notes<a href="https://fluss.apache.org/blog/releases/0.8/#upgrade-notes" class="hash-link" aria-label="Direct link to Upgrade Notes" title="Direct link to Upgrade Notes" translate="no">​</a></h2>
<p>The Fluss community is committed to delivering a smooth upgrade experience. This release maintains compatibility at the levels of network protocols and storage formats, with full bidirectional compatibility between clients and servers:</p>
<ul>
<li class="">Clients from version 0.7 can seamlessly connect to version 0.8 servers,</li>
<li class="">Clients from version 0.8 are also compatible with version 0.7 servers.</li>
</ul>
<p>However, Fluss 0.8 is the first official release since the project entered the Apache Incubator, and it includes changes such as package path updates (e.g., groupId and Java package names). As a result, applications that depend on the Fluss SDK will need to make corresponding code adjustments when upgrading to version 0.8. Please refer to the <a class="" href="https://fluss.apache.org/docs/maintenance/operations/upgrade-notes-0.8/">upgrade notes</a> for a comprehensive list of adjustments to make and issues to check during the upgrading process.</p>
<p>For a detailed list of all changes in this release, please refer to the <a href="https://github.com/apache/fluss/releases/tag/v0.8.0-incubating" target="_blank" rel="noopener noreferrer" class="">release notes</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="list-of-contributors">List of contributors<a href="https://fluss.apache.org/blog/releases/0.8/#list-of-contributors" class="hash-link" aria-label="Direct link to List of contributors" title="Direct link to List of contributors" translate="no">​</a></h2>
<p>The Apache Fluss community would like to express gratitude to all the contributors who made this release possible:</p>
<blockquote>
<p>Alibaba-HZY, CaoZhen, CenterCode, CodeDrinks, David, Giannis Polyzos, Hemanth Savasere, Hongshun Wang, Jark Wu, Jensen, Junbo Wang, Kerwin, Leonard Xu, Liebing, Maggie Cao, Mahesh Sambaram, MehulBatra, Michael Koepf, Rafael Sousa, Rion Williams, Ron, Sergey Nuyanzin, SeungMin, Wang Cheng, XianmingZhou00, Xuyang, Yang Guo, Yang Wang, Yunchi Pang, ZijunZhao, Zmm, andybj0228, buvb, cxxwang, dependabot[bot], jackylee, leosanqing, naivedogger, ocean.wy, pisceslj, totalo, xiaochen, xiaozhou, xx789, yunhong, yuxia Luo</p>
</blockquote>
<p>Apache Fluss is under active development. Be sure to stay updated on the project, give it a try and if you like it,
don’t forget to give it some ❤️ via ⭐ on <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">GitHub</a>.</p>]]></content:encoded>
            <category>releases</category>
        </item>
        <item>
            <title><![CDATA[Primary Key Tables: Unifying Log and Cache for 🚀 Streaming]]></title>
            <link>https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/</link>
            <guid>https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/</guid>
            <pubDate>Mon, 01 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Modern data platforms have traditionally relied on two foundational components: a log for durable, ordered event storage and a cache for low-latency access.]]></description>
            <content:encoded><![CDATA[<p>Modern data platforms have traditionally relied on two foundational components: a <strong>log</strong> for durable, ordered event storage and a <strong>cache</strong> for low-latency access.
Common architectures include combinations such as Kafka with Redis, or Debezium feeding changes into a key-value store.
While these patterns underpin a significant portion of production infrastructure, they also introduce <strong>complexity</strong>, <strong>fragility</strong>, and <strong>operational overhead</strong>.</p>
<p>Apache Fluss (Incubating) addresses this challenge with an elegant solution: <strong>Primary Key Tables (PK Tables)</strong>.
These persistent state tables provide the same semantics as running both a log and a cache, without needing two separate systems.
Every write produces a durable log entry and an immediately consistent key-value update.
Snapshots and log replay guarantee deterministic recovery, while clients benefit from the simplicity of interacting with one system for reads, writes, and queries.</p>
<p>In this post, we will explore how Fluss PK Tables work, why unifying log and cache into a persistent design is a critical advancement,
and how this model resolves long-standing challenges of maintaining consistency across multiple systems.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-log-cache-separation">🚧 The Log-Cache Separation<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-the-log-cache-separation" class="hash-link" aria-label="Direct link to 🚧 The Log-Cache Separation" title="Direct link to 🚧 The Log-Cache Separation" translate="no">​</a></h2>
<p>Before diving into how Fluss works, it’s worth pausing on the traditional architecture: a log (like Kafka) paired with a cache (like Redis or Memcached).
This pattern has been incredibly successful, but anyone who has operated it in production knows the headaches.</p>
<p>The biggest challenge is <strong>cache invalidation</strong>. Writes usually flow to the database or log first, and then the cache has to be updated or invalidated. In practice, this often creates timing windows: the log might show an update that the cache hasn’t yet applied, or the cache may return stale data long after it should have expired. Teams fight this with TTLs, background refresh daemons, or CDC-based updaters, but no solution is perfect. Staleness is a fact of life.
<img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/diagram1-1bdb485a3e6a61e0d0e590a9c0d67581.png" width="980" height="1038" class="img_ev3q"></p>
<p>Another pain point is <strong>dual writes and atomicity</strong>. Applications frequently need to update both the log and the cache (or log and DB, then cache). Without careful orchestration, often using an outbox pattern or distributed transactions, it’s easy to end up with mismatches. For example, the cache may be updated with a value that never made it into the log, or vice versa. This not only creates correctness issues but also makes recovery from failure very hard.</p>
<p>Operationally, running two systems is simply heavier. You need to deploy, monitor, scale, and secure both the log and the cache. Each comes with its own tuning knobs, resource usage patterns, and failure modes. When things go wrong, debugging is often about figuring out which system is “telling the truth.”</p>
<p>Finally, <strong>failover</strong> and <strong>recovery</strong> are fragile in a dual-system world. If the cache cluster restarts, you might start from empty and experience surges as clients repopulate hot keys. If the log has advanced while the cache is empty, reconciling the two can be messy. The promise of “fast reads and durable history” often comes with the hidden cost of reconciliation and re-warming.</p>
<p>This is the context Fluss was designed for. The goal is not to reinvent the wheel but to <strong>unify the log and the cache into one coherent system</strong> where writes, reads, and recovery all flow through the same consistent pipeline.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tldr">TL/DR<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#tldr" class="hash-link" aria-label="Direct link to TL/DR" title="Direct link to TL/DR" translate="no">​</a></h2>
<p>The <strong>Key-Value (KV) store</strong> forms the foundation of the <strong>Primary Key (PK) tables</strong> in Apache Fluss. Each <strong>KVTablet</strong> (representing a table bucket or partition), combines a <strong>RocksDB</strong> instance with an in-memory pre-write buffer.
Leaders merge incoming upserts and deletes into the latest value, then construct <strong>CDC/WAL batches</strong> (typically in Apache Arrow format) and append them to the log tablet. Only after this step is the buffered KV state flushed into RocksDB, ensuring strict <strong>read-after-log correctness</strong>.</p>
<p>Snapshots are created incrementally from RocksDB and uploaded to remote storage, enabling efficient state recovery and durability.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/diagram2-a18d8306fa2a61b50c0b64646b171cc5.png" width="1138" height="733" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="inside-fluss-pk-tables">Inside Fluss PK Tables<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#inside-fluss-pk-tables" class="hash-link" aria-label="Direct link to Inside Fluss PK Tables" title="Direct link to Inside Fluss PK Tables" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-fluss-pk-tables-the-unified-model">🔑 Fluss PK Tables: The Unified Model<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-fluss-pk-tables-the-unified-model" class="hash-link" aria-label="Direct link to 🔑 Fluss PK Tables: The Unified Model" title="Direct link to 🔑 Fluss PK Tables: The Unified Model" translate="no">​</a></h3>
<p>In Fluss, a Primary Key Table consists of several tightly integrated components:</p>
<ul>
<li class=""><strong>KvTablet:</strong> in the Tablet Server stages and merges writes, appends to the log, and flushes to RocksDB.</li>
<li class=""><strong>PreWriteBuffer:</strong> is an in-memory staging area that ensures writes line up with their log offsets.</li>
<li class=""><strong>LogTablet:</strong> is the append-only changelog, feeding downstream consumers and acting as the durable history.</li>
<li class=""><strong>RocksDB:</strong> is the embedded key-value store that acts as the cache, always kept consistent with the log.</li>
<li class=""><strong>Snapshot Manager and Uploader:</strong> periodically capture RocksDB state and upload it to remote object storage like S3 or HDFS.</li>
<li class=""><strong>Coordinator:</strong> tracks metadata such as which snapshot belongs to which offset.</li>
</ul>
<p>Together, these components give Fluss the power of a log and a cache without the pain of reconciling them.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="️-the-write-path">✍️ The Write Path<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#%EF%B8%8F-the-write-path" class="hash-link" aria-label="Direct link to ✍️ The Write Path" title="Direct link to ✍️ The Write Path" translate="no">​</a></h3>
<p>The write path is where Fluss’s guarantees come from. The ordering is strict: append to the log first, flush to RocksDB second, and acknowledge the client last. This removes the classic inconsistency where the log shows a change but the cache doesn’t.</p>
<p>Here’s how the flow looks across the key components:
<img decoding="async" loading="lazy" alt="Write Path" src="https://fluss.apache.org/assets/images/diagram3-acef82352ee9f714c1955ad1ae06a7d0.png" width="1091" height="852" class="img_ev3q"></p>
<p>When a client writes to a KV table, the request first lands in the <strong>KvTablet</strong>.
Each record is merged with any existing value (using a <strong>RowMerger</strong> so Fluss can support last-write-wins or partial updates). At the same time, a CDC event is created and added to the <strong>log tablet</strong>, ensuring downstream consumers always see ordered updates.</p>
<p>But before these changes are visible, they’re staged in a <strong>pre-write buffer</strong>. This buffer keeps operations aligned with their intended log offsets. Once the WAL append succeeds (including replication), the buffer is flushed into RocksDB. This order – WAL first, KV flush after –  guarantees that if you see a change in the log, you can also read it back from the table. That’s what makes lookup joins, caches, and CDC so reliable.</p>
<blockquote>
<p><strong>Note:</strong> One important detail is that log visibility is controlled by a log high-watermark offset. The KV flush operation and the updates to this high-watermark are performed under a lock. Since the log and KV data of same bucket, reside in the same process, we can use a local lock to synchronize these operations, avoiding the complexity of distributed transactions.</p>
<p>So it's not only about "if you see a change in the log, you can also read it back from the table", but also "if you see a record in the table, you can also see the change from the log".</p>
</blockquote>
<p><strong>Idempotent writes:</strong> ensure a message is written exactly once to a Fluss table, without any out of orderness. Even in scenarios that the producer retries sending the same message due to network problem or server failures.</p>
<p><strong>In a nutshell:</strong> Every write flows through a single path, producing both a log event and a cache update. Because acknowledgment comes only after RocksDB is durable, clients are guaranteed read-your-write consistency.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-snapshot-process">📸 The Snapshot Process<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-the-snapshot-process" class="hash-link" aria-label="Direct link to 📸 The Snapshot Process" title="Direct link to 📸 The Snapshot Process" translate="no">​</a></h3>
<p>A running leader can’t rely only on logs. If logs grow forever, recovery would be extremely slow.
That’s where <strong>snapshots</strong> come in. Periodically, the <strong>snapshot manager</strong> inside the tablet server captures a consistent checkpoint of RocksDB. It tags this snapshot with the <strong>next unread log offset</strong>, which is the exact point from which replay should resume later.</p>
<p>The snapshot is written locally, then handed off to the <strong>uploader</strong>, which sends the files to <strong>remote storage</strong> (S3, HDFS, etc.) and registers metadata in the <strong>Coordinator</strong>. Remote storage applies retention rules so only a handful of snapshots are kept.</p>
<p>This means there’s always a durable, recoverable copy of the state sitting in object storage, complete with the exact log position to continue from.</p>
<p><img decoding="async" loading="lazy" alt="Snapshot Process" src="https://fluss.apache.org/assets/images/diagram4-a3dd5f4992c3b796d2ec9cb0037d90a5.png" width="1201" height="674" class="img_ev3q"></p>
<p>Snapshots make the system resilient. Instead of reprocessing an entire log, a recovering node can start from the latest snapshot and replay only the logs after the recorded offset. This reduces recovery time dramatically while ensuring correctness.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-failover-and-recovery">⚡ Failover and Recovery<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-failover-and-recovery" class="hash-link" aria-label="Direct link to ⚡ Failover and Recovery" title="Direct link to ⚡ Failover and Recovery" translate="no">​</a></h3>
<p>When a leader crashes, Fluss promotes a follower to leader. Followers are log-only today (no hot RocksDB), so the new leader must:</p>
<ol>
<li class="">Fetch snapshot metadata from the coordinator.</li>
<li class="">Download the snapshot files from remote storage.</li>
<li class="">Restore RocksDB from that snapshot.</li>
<li class="">Replay log entries since the snapshot offset.</li>
<li class="">Resume serving reads and writes once KV is in sync.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Failover and Recovery" src="https://fluss.apache.org/assets/images/diagram5-229cd55eb1856e9f4f845ff74d5e85d2.png" width="1422" height="1092" class="img_ev3q">
This process ensures determinism: the snapshot defines the starting state, and the log offset defines exactly where replay begins. Recovery may not be instantaneous, but it is safe, automated, and predictable. Work is underway to add hot-standby RocksDB replicas to make this even faster.</p>
<p><strong>Note:</strong> For a large table, the recovery process might take even up to minutes, which might not be acceptable in many scenarios. To solve this issue, the Apache Fluss community will introduce a standby replica mechanism (see more <a href="https://cwiki.apache.org/confluence/display/FLUSS/FlP-13%3A+Support+rebalance+for+PrimaryKey+Table" target="_blank" rel="noopener noreferrer" class="">here</a>), so recovery can happen instantaneously.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-built-in-cache-advantage">✅ The Built-In Cache Advantage<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-the-built-in-cache-advantage" class="hash-link" aria-label="Direct link to ✅ The Built-In Cache Advantage" title="Direct link to ✅ The Built-In Cache Advantage" translate="no">​</a></h3>
<p>What makes Fluss PK Tables really stand out is that the <strong>cache is not an external system</strong>. Because RocksDB sits right inside the TabletServer and is updated in lockstep with the WAL, you never have to worry about invalidation.</p>
<p>This means:</p>
<ul>
<li class="">No race conditions where the log is ahead of the cache.</li>
<li class="">No cache stampedes on restart, because RocksDB is restored from snapshots deterministically.</li>
<li class="">No operational overhead of scaling, securing, and reconciling an external cache cluster.</li>
</ul>
<p>Instead of a patchwork of log &amp; DB &amp; cache, you just have Fluss. The log is your history, the KV is your current state, and the system guarantees they never drift apart.</p>
<p>By unifying the log and the cache into one design, Fluss solves problems that have plagued distributed systems for years. Developers no longer have to choose between correctness and performance, or spend weeks debugging mismatches between systems. Operators no longer need to run and tune two separate clusters.</p>
<p>For real-time analytics, AI/ML feature stores, or transactional streaming apps, the result is powerful: every update is both an event in a durable log and a fresh entry in a low-latency cache. Recovery is automated, consistency is guaranteed, and the architecture is simpler.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-queryable-state-done-right">🔍 Queryable State, Done Right<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-queryable-state-done-right" class="hash-link" aria-label="Direct link to 🔍 Queryable State, Done Right" title="Direct link to 🔍 Queryable State, Done Right" translate="no">​</a></h3>
<p>Users of Apache Flink and Kafka Streams have long wanted to “just query the state.” There has been lot's of demand for this patterns.</p>
<p><strong>Flink’s Queryable State</strong> feature tried to offer this, but it was always marked unstable, with no client-side stability guarantees and it has since been deprecated as of Flink 1.18 (and marked for removal), with project members citing a lack of maintainers as the reason.</p>
<p><strong>Kafka Streams’ Interactive Queries</strong> are still available, but they require you to build and operate your own RPC layer (e.g., a REST service) to expose state across instances, and availability can dip during task migrations/rebalances unless you provision standby replicas or explicitly allow stale reads from standbys during rebalances.
In practice, this adds operational work and consistency/availability trade-offs; under heavy concurrent reads/writes some teams have even hit RocksDB-level contention issues.</p>
<p>Fluss PK Tables deliver the same end-goal; direct, <strong>low-latency lookups of live state,</strong> but without those caveats: each write is <strong>durable in the log</strong> and <strong>applied consistently to RocksDB</strong>, and <strong>deterministic snapshots, along with log replay</strong> provide reliable recovery, so you can <strong>safely query</strong> state even after failures.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-real-time-dashboards---without-extra-serving-layers">📊 Real-Time Dashboards - Without Extra Serving Layers<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-real-time-dashboards---without-extra-serving-layers" class="hash-link" aria-label="Direct link to 📊 Real-Time Dashboards - Without Extra Serving Layers" title="Direct link to 📊 Real-Time Dashboards - Without Extra Serving Layers" translate="no">​</a></h3>
<p>A recurring pattern in streaming architectures is to use Flink for the heavy lifting, like streaming joins, aggregations, and deduplication
and then ship the results into a <strong>separate serving</strong> system purely for dashboards or APIs.</p>
<p>These external layers (often search like <code>ElasticSearch</code> or analytics engines) in lot's of cases they don't add analytical value; they exist mainly to provide a serving layer for applications and dashboards.</p>
<p><strong>This introduces an important trade-off:</strong> every additional system means duplicate storage, increased latency, and extra operational overhead. Data has to be landed, indexed, and kept in sync, even though the stream processor has already computed the end state.</p>
<p>With Fluss Primary Key Tables, you don’t need that extra serving layer. Your stateful computations in Flink can materialize directly into PK Tables, which are durable, consistent, and queryable in real time. This lets you power dashboards or APIs straight from the tables, cutting out redundant clusters and reducing end-to-end latency.</p>
<p><strong>In short:</strong> compute once in Flink, persist in Fluss, and serve directly, without duplicating storage or managing another system.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-closing-thoughts">🚀 Closing Thoughts<a href="https://fluss.apache.org/blog/pk-key-tables-log-cache-streaming/#-closing-thoughts" class="hash-link" aria-label="Direct link to 🚀 Closing Thoughts" title="Direct link to 🚀 Closing Thoughts" translate="no">​</a></h3>
<p>The above are some examples use cases, we have seen recently and I'm only eager to see what users will build with Fluss - realtime features stores is another PK table use case currently under investigation.</p>
<p>Fluss Primary Key Tables are one of the most compelling features of the platform. They embody the <strong>stream-table duality</strong> in practice: every write is a log entry and a KV update; every recovery is a snapshot plus replay; every cache read is guaranteed to be consistent with the log.</p>
<p>The complexity of coordinating a log and a cache disappears. With Fluss, you don’t have to choose between speed and safety; you get both.</p>
<p>In short, <strong>Fluss turns the log into your cache, and the cache into your log</strong>, which can be a major simplification for anyone building real-time systems.</p>
<p>And before you go 😊 don’t forget to give Fluss 🌊 some ❤️ via ⭐ on <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Taobao uses Apache Fluss (Incubating) for Real-Time Processing in Search and RecSys]]></title>
            <link>https://fluss.apache.org/blog/taobao-practice/</link>
            <guid>https://fluss.apache.org/blog/taobao-practice/</guid>
            <pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Streaming Storage More Suitable for Real-Time OLAP]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="streaming-storage-more-suitable-for-real-time-olap">Streaming Storage More Suitable for Real-Time OLAP<a href="https://fluss.apache.org/blog/taobao-practice/#streaming-storage-more-suitable-for-real-time-olap" class="hash-link" aria-label="Direct link to Streaming Storage More Suitable for Real-Time OLAP" title="Direct link to Streaming Storage More Suitable for Real-Time OLAP" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://fluss.apache.org/blog/taobao-practice/#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h3>
<p>The Data Development Team of Taobao has built a new <strong>generation of real-time data warehouse</strong> based on Apache Fluss.
Fluss solves the problems of redundant data transfer, difficulties in data profiling, and challenges in large scale stateful workload operations and maintenance.
By combining columnar storage with real-time update capabilities, Fluss supports column pruning, key-value point lookups, Delta Join, and seamless lake–stream integration, thereby <strong>cutting I/O and compute overhead</strong> while enhancing job stability and profiling efficiency.</p>
<p>Already deployed on Taobao’s A/B-testing platform for critical services such as search and recommendation, the system proved its resilience during the 618 Grand Promotion:
<strong>it handled tens of millions of requests with sub-second latency</strong>, lowered resource usage by <strong>30%</strong>, and removed more than 100 TB from state storage.
Looking ahead, the team will continue to extend Fluss within a <strong>Lakehouse architecture</strong> and broaden its use across <strong>AI-driven</strong> workloads.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="business-background">Business Background<a href="https://fluss.apache.org/blog/taobao-practice/#business-background" class="hash-link" aria-label="Direct link to Business Background" title="Direct link to Business Background" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/ab_experiment_platform_arch-0b365a2892747d8d709efae2a20eb810.png" width="904" height="346" class="img_ev3q"></p>
<p>Taobao A/B testing Analysis Platform, mainly focuses on A/B data of Taobao's C-end algorithms, aiming to promote scientific decision-making activities through the construction of generalized A/B data capabilities.
Since its inception in <strong>2015</strong> , it has continuously and effectively supported the analysis of Taobao's algorithm A/B data for <strong>10 years</strong> .
Currently, it is applied to <strong>over 100</strong> A/B testing scenarios across various business areas, including <strong>search, recommendation, content,</strong> <strong>user growth</strong>, and <strong>marketing</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/realtime_data_warehouse_arch-10cb25ecf32a9a576b09f081953aeb6d.png" width="904" height="364" class="img_ev3q"></p>
<p>Taobao provides the following capabilities:</p>
<ul>
<li class="">
<p><strong>A/B Data Public Data Warehouse:</strong> Serves various data applications of downstream algorithms, including: <code>online traffic splitting</code>, <code>distribution alignment</code>, <code>general features</code>, <code>scenario labels</code>, and other application scenarios.</p>
</li>
<li class="">
<p><strong>Scientific Experiment Evaluation:</strong> Implement mature scientific evaluation solutions in the industry, conduct long-term tracking of the effects of A/B testing, and help businesses obtain real and reliable experimental evaluation results.</p>
</li>
<li class="">
<p><strong>Multidimensional Ad Hoc OLAP Self-Service Analysis:</strong> Through mature data solutions, it supports multidimensional and ad hoc OLAP queries, serving data effectiveness analysis across all clients, businesses, and scenarios.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="business-pain-points">Business Pain Points<a href="https://fluss.apache.org/blog/taobao-practice/#business-pain-points" class="hash-link" aria-label="Direct link to Business Pain Points" title="Direct link to Business Pain Points" translate="no">​</a></h2>
<p>Currently, the real-time data warehouse of Taobao is based on technology stacks such as Flink, message queue, OLAP engine, etc., where the message queue is TT (Kafka-like architecture MQ) within Taobao, and the OLAP engine is Alibaba Cloud Hologres.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/origin_data_pipeline-eebb499bcdba0e096585e5febad7ad99.png" width="904" height="328" class="img_ev3q"></p>
<p>After ingesting access log data from the message queue, we execute business logic within Apache Flink.
However, as SQL complexity increases - particularly in the presence of <code>ORDER BY</code> and <code>JOIN</code> operations - the resulting retraction streams significantly expand.</p>
<p>This leads to a substantial increase in Flink state size, which in turn drives up the consumption of compute resources.
Such scenarios introduce notable challenges in both job development and ongoing maintenance. Additionally, the development lifecycle for these real-time jobs tends to be considerably longer compared to equivalent offline solutions.</p>
<p>Currently, the message queue still has some limitations, and the main problems encountered are as follows:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redundant-data-transfer">Redundant Data Transfer<a href="https://fluss.apache.org/blog/taobao-practice/#redundant-data-transfer" class="hash-link" aria-label="Direct link to Redundant Data Transfer" title="Direct link to Redundant Data Transfer" translate="no">​</a></h3>
<p>In traditional data warehouse environments, <strong>write-once</strong>, <strong>read-many</strong> is the prevailing access pattern, where each downstream consumer typically reads only a subset of the available data.
For example, in the exposure job for Taobao, the message queue provides 44 fields per record, yet the job only requires 13 of them.
However, due to the row-based nature of the message queue storage format, all 44 fields must still be read and transmitted during consumption.</p>
<p>This results in significant <strong>I/O inefficiency; approximately 70% of the network throughput</strong> is wasted on reading unused columns.
Consumers bear the full cost of data transfer, even though only a fraction of the data is relevant to their processing logic.
This contributes to <strong>excessive resource utilization</strong>, especially at scale.</p>
<p>Attempts at Optimization with Flink
To mitigate this, we explored column pruning within <strong>Apache Flink</strong> by explicitly defining the schema in the Source and introducing a UDF to drop unused columns early in the pipeline.
While this approach aimed to reduce data ingestion costs, its <strong>practical impact was minimal</strong>.
The performance gains were negligible, and the added complexity in pipeline design and maintenance introduced new operational challenges.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/column_pruning_operator-9f8fb7ad0280354e1561b63a6615b357.png" width="754" height="478" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenges-in-data-profiling--debugging">Challenges In Data Profiling &amp; Debugging<a href="https://fluss.apache.org/blog/taobao-practice/#challenges-in-data-profiling--debugging" class="hash-link" aria-label="Direct link to Challenges In Data Profiling &amp; Debugging" title="Direct link to Challenges In Data Profiling &amp; Debugging" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="message-queues-are-not-designed-for-random-access-or-key-value-lookups">Message queues are not designed for random access or key-value lookups<a href="https://fluss.apache.org/blog/taobao-practice/#message-queues-are-not-designed-for-random-access-or-key-value-lookups" class="hash-link" aria-label="Direct link to Message queues are not designed for random access or key-value lookups" title="Direct link to Message queues are not designed for random access or key-value lookups" translate="no">​</a></h4>
<p>In modern data warehouse architecture, <strong>data profiling is a foundational capability</strong>, essential for tasks such as issue diagnosis, anomaly detection, and case-specific troubleshooting. To meet these needs, two distinct data profiling approaches for message queues have been evaluated in production environments.</p>
<p>While each method offers unique benefits, <strong>both exhibit trade-offs and limitations</strong> that prevent them from fully addressing the breadth of business requirements. Neither approach provides a comprehensive solution for profiling within the constraints and characteristics of message queue systems, such as their lack of random access and stateless consumption model.</p>
<table><thead><tr><th>Query Type</th><th>Query Method</th><th>Advantages</th><th>Disadvantages</th></tr></thead><tbody><tr><td>Sampling query</td><td>Randomly query according to time slices.</td><td>High timeliness</td><td>Unable to query the specified data.</td></tr><tr><td>Synchronize additional storage query</td><td>Synchronize the data in the message queue to additional storage and then perform queries.</td><td>Can query specified data</td><td>1. Additional synchronization, storage, and query resources. 2. Has a synchronization delay</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/mq_profiling-80b5d670d20dccdfa5bd96aad32f47f5.png" width="904" height="374" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenges-with-state-visibility-in-flink">Challenges with State Visibility in Flink<a href="https://fluss.apache.org/blog/taobao-practice/#challenges-with-state-visibility-in-flink" class="hash-link" aria-label="Direct link to Challenges with State Visibility in Flink" title="Direct link to Challenges with State Visibility in Flink" translate="no">​</a></h4>
<p>In e-commerce analytics, identifying the <strong>first and last user interaction channels within the same day</strong> is a critical metric for evaluating both <strong>user acquisition performance</strong> and <strong>channel effectiveness</strong>.
To ensure accurate computation of this metric, the processing engine must perform <strong>sorting and deduplication</strong>, which inherently requires <strong>materializing all relevant upstream data into Flink State</strong>.</p>
<p>However, Flink’s managed state is inherently opaque;it functions as a high-performance internal component designed for scalability and fault tolerance, but it does not provide native visibility or introspection tools. This "black-box" nature of state introduces substantial challenges when attempting to <strong>debug</strong>, <strong>verify</strong>, or <strong>modify</strong> streaming jobs, especially in production environments. The lack of transparency significantly complicates tasks such as validating correctness, understanding intermediate state contents, or troubleshooting unexpected outcomes.</p>
<p>As a result, teams may encounter <strong>increased operational overhead</strong> and <strong>longer development cycles</strong>, particularly when dealing with stateful streaming jobs that require precise control over historical data.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/flink_sorting_job-b9edc790b2248e8d51e27ee15777781f.png" width="904" height="398" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="operational-challenges-of-large-flink-state-in-stateful-jobs">Operational Challenges of Large Flink State in Stateful Jobs<a href="https://fluss.apache.org/blog/taobao-practice/#operational-challenges-of-large-flink-state-in-stateful-jobs" class="hash-link" aria-label="Direct link to Operational Challenges of Large Flink State in Stateful Jobs" title="Direct link to Operational Challenges of Large Flink State in Stateful Jobs" translate="no">​</a></h3>
<p>In many Flink jobs, maintaining intermediate result sets in state is essential to support operations such as sorting and joining. When modifying a job, Flink attempts to reuse the previous state by validating the updated execution plan against the existing state schema. If the plan verification succeeds, the job can resume from the latest checkpoint. However, if the verification fails, the system is forced to <strong>reinitialize the entire state from scratch (epoch 0)</strong>, a process that is both <strong>time-consuming</strong> and <strong>operationally intensive</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/transaction_attribution_state-d6c2a559f0ec205036782549c3e1434c.png" width="904" height="210" class="img_ev3q"></p>
<p>In our current scenario, each incoming record triggers updates to both Sort State and Join State. These states have grown significantly in size, with the sort operator's state reaching up to <strong>90 TB</strong> and the join operator's state peaking at <strong>10 TB</strong>. Managing such large-scale state introduces a number of serious challenges:</p>
<ul>
<li class=""><strong>High infrastructure costs</strong> due to the volume of state data.</li>
<li class=""><strong>Increased risk of checkpoint timeouts</strong>, especially under load.</li>
<li class=""><strong>Degraded job stability</strong>, with failures more likely during recovery or scaling events.</li>
<li class=""><strong>Slow job restart and recovery times</strong>, making operational troubleshooting difficult.</li>
<li class=""><strong>Development inefficiency</strong>, as state schema changes often require full state resets.</li>
</ul>
<p>This highlights the need for <strong>more efficient state management strategies</strong>, such as state compaction, state TTLs, or alternative storage architectures, to support large-scale, high-throughput streaming workloads with minimal operational burden.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/transaction_attribution_flink_job-f9515c01b5423520775b54a66b57deeb.png" width="904" height="392" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="apache-fluss-key-advantages">Apache Fluss Key Advantages<a href="https://fluss.apache.org/blog/taobao-practice/#apache-fluss-key-advantages" class="hash-link" aria-label="Direct link to Apache Fluss Key Advantages" title="Direct link to Apache Fluss Key Advantages" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-apache-fluss">What is Apache Fluss?<a href="https://fluss.apache.org/blog/taobao-practice/#what-is-apache-fluss" class="hash-link" aria-label="Direct link to What is Apache Fluss?" title="Direct link to What is Apache Fluss?" translate="no">​</a></h3>
<blockquote>
<p>Fluss Official Documentation: <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/</a></p>
</blockquote>
<blockquote>
<p>GitHub: <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">https://github.com/apache/fluss</a></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fulss_arch-9347738f0d221a64c79f1a2c735b1210.png" width="904" height="242" class="img_ev3q"></p>
<p>Fluss, developed by the Flink team, is the next-generation stream storage for stream analysis, a stream storage built for real-time analysis. Fluss innovatively integrates columnar storage format and real-time update capabilities into stream storage, and is deeply integrated with Flink to help users build a streaming data warehouse with high throughput, low latency, and low cost. It has the following core features:</p>
<ul>
<li class=""><strong>Real-time reads and writes:</strong> Supports millisecond-level streaming read and write capabilities.</li>
<li class=""><strong>Columnar pruning:</strong> Store real-time stream data in columnar format. Column pruning can improve read performance by 10 times and reduce network costs.</li>
<li class=""><strong>Streaming Update:</strong> Supports real-time streaming updates for large-scale data. Supports partial column updates to achieve low-cost wide table stitching.</li>
<li class=""><strong>CDC Subscription:</strong> Updates will generate a complete Change Log (CDC), and by consuming CDC through Flink streaming, real-time data flow across the whole-pipeline of the data warehouse can be achieved.</li>
<li class=""><strong>Real-time Lookup Queries:</strong> Supports high-performance primary key point query and can be used as a dimension table association for real-time processing links.</li>
<li class=""><strong>Unified Lake and Stream:</strong> Seamlessly integrates Lakehouse and provides a real-time data layer for Lakehouse. This not only brings low-latency data to Lakehouse analytics but also endows stream storage with powerful analytical capabilities.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="table-types">Table Types<a href="https://fluss.apache.org/blog/taobao-practice/#table-types" class="hash-link" aria-label="Direct link to Table Types" title="Direct link to Table Types" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fulss_underlying_arch-a7af63a1ea8c1b9d495ba3ae2b4d5100.png" width="904" height="594" class="img_ev3q"></p>
<p><strong>Type:</strong> Divided into log tables and primary key tables. Log tables are columnar MQs that only support insert operations, while primary key tables can be updated according to the primary key and specified merge engine.</p>
<p><strong>Partition:</strong> Divides data into smaller, more manageable subsets according to specified columns. Fluss supports more diverse partitioning strategies, such as Dynamic create partitions. For the latest documentation on partitioning, please refer to: <a href="https://fluss.apache.org/docs/table-design/data-distribution/partitioning/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/docs/table-design/data-distribution/partitioning/</a> . Note that the partition type must be of String type and can be defined via the following SQL:</p>
<div class="language-SQL language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">temp</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  dt STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Create 2 partitions in advance</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Keep the first 2 partitions</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Automatic partitioning</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p><strong>Bucket:</strong> The smallest unit of read and write operations. For a primary key table, the bucket to which each piece of data belongs is determined based on the hash value of the primary key of each piece of data. For a log table, the configuration of column hashing can be specified in the with parameter when creating the table; otherwise, it will be randomly scattered.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="log-table">Log Table<a href="https://fluss.apache.org/blog/taobao-practice/#log-table" class="hash-link" aria-label="Direct link to Log Table" title="Direct link to Log Table" translate="no">​</a></h4>
<p>The log table is a commonly used table in Fluss, which writes data in the order of writing, only supports insert operations, and does not support update/delete operations, similar to MQ systems such as Kafka and TT. For the log tables, currently most of the data will be uploaded to a remote location, and only a portion of the data will be stored locally. For example, a log table has 128 buckets, only 128 * 2 (number of segments retained locally) * 1 (size of one segment) * 3 (number of replicas) GB = 768 GB will be stored locally. The table creation statement is as follows:</p>
<div class="language-SQL language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">temp</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  second_timestamp  STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">pk               STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">assist           STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">user_name        STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">with</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'256'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Number of buckets</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.ttl'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2d'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- TTL setting, default 7 days</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ZSTD'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Compression mode, currently added by default</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'client.writer.bucket.no-key-assigner'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sticky'</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Bucket mode</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>In Fluss, the log table is stored in Apache Arrow columnar format by default. This format stores data column by column rather than row by row, thereby enabling <strong>column pruning</strong>. This ensures that only the required columns are consumed during real-time consumption, thereby reducing IO overhead, improving performance, and reducing resource usage.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="primarykey-table">PrimaryKey Table<a href="https://fluss.apache.org/blog/taobao-practice/#primarykey-table" class="hash-link" aria-label="Direct link to PrimaryKey Table" title="Direct link to PrimaryKey Table" translate="no">​</a></h4>
<p>Compared to the log table, the primary key table supports insert, update, and delete operations, and different merge methods are implemented by specifying the Merge Engine. It should be noted that <code>bucket.key</code> and <code>partitioned.key</code> need to be subsets of <code>primary.key</code>, and if this KV table is used for DeltaJoin, <code>bucket.key</code> needs to be a prefix of <code>primary.key</code>. The table creation statement is as follows:</p>
<div class="language-SQL language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">temp</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">pk               STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">user_name        STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">item_id          STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">event_time       </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">dt               STRING</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token punctuation" style="color:#475569">,</span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_name</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">item_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">pk</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'512'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_name,item_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.ttl'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'1d'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Binlog retain 1 day.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token operator" style="color:#475569">=</span><span class="token string" style="color:#0E7C66">'versioned'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Take the last piece of data</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.merge-engine.versioned.ver-column'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'event_time'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Sort field</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Create 2 partitions in advance</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Keep the first 2 partitions</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ZSTD'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Among them, merge-engine supports versioned and first_row; after using versioned, it is necessary to limit the ver-column for sorting, currently only supporting types such as INT, BIGINT, and TIMESTAMP, and not supporting STRING; after using first_row, it only supports retaining the first record of each primary key and does not support sorting by column.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-functions">Core Functions<a href="https://fluss.apache.org/blog/taobao-practice/#core-functions" class="hash-link" aria-label="Direct link to Core Functions" title="Direct link to Core Functions" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="tailorable-columnar-storage">Tailorable Columnar Storage<a href="https://fluss.apache.org/blog/taobao-practice/#tailorable-columnar-storage" class="hash-link" aria-label="Direct link to Tailorable Columnar Storage" title="Direct link to Tailorable Columnar Storage" translate="no">​</a></h4>
<p>Fluss is a column-based streaming storage, with its underlying file storage adopting the Apache Arrow IPC streaming format. This enables Fluss to achieve efficient column pruning while maintaining millisecond-level streaming read and write capabilities.</p>
<p>A key advantage of Fluss is that column pruning is performed at the server level, and <strong>only the necessary columns are transferred to the Client</strong>. This architecture not only improves performance but also reduces network costs and resource consumption.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/comparison_of_row_and_column_storage_consumption-f4d5fcfb658e670c290ab2be2e504718.png" width="904" height="238" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="fluss-key-value-storage-architecture-and-benefits">Fluss Key-Value Storage Architecture and Benefits<a href="https://fluss.apache.org/blog/taobao-practice/#fluss-key-value-storage-architecture-and-benefits" class="hash-link" aria-label="Direct link to Fluss Key-Value Storage Architecture and Benefits" title="Direct link to Fluss Key-Value Storage Architecture and Benefits" translate="no">​</a></h4>
<p>Fluss’s <strong>Key-Value (KV) storage engine</strong> is built atop a high-performance <strong>log-structured table</strong>, where a KV index is constructed directly over the log stream. This index leverages an <strong>LSM-tree (Log-Structured Merge Tree)</strong> design, enabling high-throughput real-time updates and partial record modifications—making it particularly well-suited for building and maintaining wide tables at scale.</p>
<p>One of the standout advantages of this architecture is that the <strong>changelog</strong> produced by the KV store is <strong>natively consumable by Apache Flink</strong>. Unlike traditional message queues or log-based systems that require expensive post-processing and deduplication, Fluss’s integration avoids these overheads entirely. This results in <strong>significant reductions in compute cost</strong>, <strong>latency</strong>, and <strong>complexity</strong> across the data pipeline.</p>
<p>Fluss’s built-in KV index supports <strong>low-latency primary key lookups</strong>, making it ideal for operational queries, streaming joins, and dimension table enrichment. Beyond real-time ingestion and processing, Fluss also supports <strong>ad-hoc data exploration</strong>, with efficient execution of queries involving operations like <code>LIMIT</code> and <code>COUNT</code>. This allows users to <strong>debug</strong> and <strong>inspect live data</strong> within Fluss quickly and interactively;without requiring a separate query engine or offline process.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_kv_store_point_query-c1e70c05f23eeded2188aaa233d74efa.png" width="904" height="376" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="dual-stream-join--delta-join">Dual-Stream Join → Delta Join<a href="https://fluss.apache.org/blog/taobao-practice/#dual-stream-join--delta-join" class="hash-link" aria-label="Direct link to Dual-Stream Join → Delta Join" title="Direct link to Dual-Stream Join → Delta Join" translate="no">​</a></h4>
<p>In Flink, Dual-Stream Join is a very fundamental function, often used to build wide tables. However, it is also a function that often gives developers headaches. This is because Dual-Stream Join needs to maintain the full amount of upstream data in State, which results in its state usually being very large. This brings about many problems, including high costs, unstable jobs, checkpoint timeouts, slow restart recovery, and so on.</p>
<p>Fluss has developed a brand-new Flink join operator implementation called Delta Join, which fully leverages Fluss's streaming read and Prefix Lookup capabilities. Delta Join can be simply understood as "<strong>dual-sided driven dimension table join</strong>". When data arrives on the left side, it performs a point query on the right table based on the Join Key; when data arrives on the right side, it performs a point query on the left table based on the Join Key. Throughout the process, it does not require state like a dimension table join, but implements semantics similar to a Dual-Stream Join, meaning that any data update on either side will trigger an update to the associated results.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/dual_stream_join2delta_join-5082dd8b7403c88c6e19e6cbeefca6ee.png" width="904" height="152" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="lake-stream-integration">Lake-Stream Integration<a href="https://fluss.apache.org/blog/taobao-practice/#lake-stream-integration" class="hash-link" aria-label="Direct link to Lake-Stream Integration" title="Direct link to Lake-Stream Integration" translate="no">​</a></h4>
<p>In the Kappa architecture, due to differences in production pipelines, data is stored separately in streams and lakes, resulting in cost waste. At the same time, additional data services need to be defined to unify data consumption. The goal of lake-stream integration is to enable <strong>Lakehouse data</strong>" and <strong>streaming data</strong> to be stored, managed, and consumed as a unified whole, thereby avoiding data redundancy and metadata inconsistency issues.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/lake_stream_integration-8ba3bec3161e76abfcdf7c82f2392bc1.png" width="904" height="386" class="img_ev3q"></p>
<p>Fluss introduces <strong>Union Reads</strong> to fully support the unified data access layer required by <strong>Kappa architecture</strong> principles. This functionality enables seamless access to both real-time streaming data and historical batch data through a consistent abstraction, ensuring a <strong>fully managed, end-to-end unified data service</strong>.</p>
<p>To maintain alignment between streaming and lakehouse storage, Fluss operates a <strong>built-in Tiering Service</strong>. This service automatically transforms raw Fluss data into a lake-friendly format (e.g., columnar files), while <strong>preserving metadata consistency between stream and lake</strong>. This tight integration eliminates the need for external tooling to reconcile stream processing with downstream analytics systems.</p>
<p>Fluss also introduces <strong>partition and bucket alignment mechanisms</strong>, which ensure that data layout remains consistent across streaming and lakehouse layers. These alignment strategies allow for <strong>direct conversion of Arrow files to Parquet</strong> without <strong>triggering network shuffling or repartitioning</strong>, significantly reducing I/O overhead and improving performance across the data pipeline.</p>
<p>Together, these capabilities enable Fluss to function as a <strong>next-generation streaming storage layer</strong>, bridging the gap between low-latency stream processing and scalable lakehouse analytics—while optimizing cost, consistency, and operational simplicity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-evolution-and-capability-implementation">Architecture Evolution and Capability Implementation<a href="https://fluss.apache.org/blog/taobao-practice/#architecture-evolution-and-capability-implementation" class="hash-link" aria-label="Direct link to Architecture Evolution and Capability Implementation" title="Direct link to Architecture Evolution and Capability Implementation" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-evolution">Architecture Evolution<a href="https://fluss.apache.org/blog/taobao-practice/#architecture-evolution" class="hash-link" aria-label="Direct link to Architecture Evolution" title="Direct link to Architecture Evolution" translate="no">​</a></h3>
<p>Fluss innovatively integrates columnar storage format and real-time update capabilities into stream storage and deeply integrates with Flink. Based on Fluss's core capabilities, we further enhance the real-time architecture, building a high-throughput, low-latency, and low-cost lakehouse through Fluss.</p>
<p>The following takes the typical upgrade scenario of the Taobao as an example to introduce the implementation practice of Fluss.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="evolution-of-regular-jobs">Evolution of Regular Jobs<a href="https://fluss.apache.org/blog/taobao-practice/#evolution-of-regular-jobs" class="hash-link" aria-label="Direct link to Evolution of Regular Jobs" title="Direct link to Evolution of Regular Jobs" translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/regular_jobs_evolution-03d7f2ae3f29d7190bf343b71e1f995d.png" width="904" height="136" class="img_ev3q"></p>
<p>The above is the architecture before and after the job upgrade. For routine jobs such as <code>Source -&gt; ETL cleaning -&gt; Sink</code>, since the message queue is row-based storage, when consuming, Flink first loads the entire row of data into memory and then filters the required columns, resulting in a significant waste of Source IO.</p>
<p>After upgrading to Fluss, due to the columnar storage at the bottom of Fluss, column pruning in Fluss is performed at the server level, which means that the <strong>data sent to the client has already been pruned</strong>, thus saving a large amount of network costs.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="evolution-of-sorting-jobs">Evolution of Sorting Jobs<a href="https://fluss.apache.org/blog/taobao-practice/#evolution-of-sorting-jobs" class="hash-link" aria-label="Direct link to Evolution of Sorting Jobs" title="Direct link to Evolution of Sorting Jobs" translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/sorting_jobs_evolution-840cd946cecc1efed58ce5a0f7ab8dfa.png" width="904" height="246" class="img_ev3q"></p>
<p>In Flink, the implementation of sorting relies on Flink explicitly computing and using State to store the intermediate state of data. This model incurs significant business overhead and extremely low business reusability. The introduction of Fluss pushes this computation and storage down to the Sink side, and in conjunction with the Fluss Merge Engine, implements different deduplication methods for KV tables. Currently, it supports <strong>FirstRow Merge Engine</strong> (the first row) and <strong>Versioned Merge Engine</strong> (the latest row). The Changelog generated during deduplication can be directly read by Flink streams, saving a large amount of computing resources and enabling the rapid reuse and implementation of data services.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="evolution-of-dual-stream-join-jobs">Evolution of Dual-Stream Join Jobs<a href="https://fluss.apache.org/blog/taobao-practice/#evolution-of-dual-stream-join-jobs" class="hash-link" aria-label="Direct link to Evolution of Dual-Stream Join Jobs" title="Direct link to Evolution of Dual-Stream Join Jobs" translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/dual_stream_jobs_evolution-1cbe65429b071d1f1bb6b2497f491599.png" width="904" height="558" class="img_ev3q"></p>
<p>After the Fluss remodeling, the Dual-Stream Join of the Taobao transaction attributed job has been enhanced with the following upgrade points:</p>
<ul>
<li class=""><strong>Column pruning</strong> is truly pre-positioned to Source consumption, avoiding IO consumption of useless columns.</li>
<li class=""><strong>Introduce Fluss KV table &amp; Merge Engines</strong> to implement data sorting and eliminate the dependency on Flink sorting State.</li>
<li class=""><strong>Refactor the Dual-Stream Join into FlussDeltaJoin</strong>, using stream reading and index point query, and externalize the Flink Dual-Stream JoinState.</li>
</ul>
<p>A comprehensive comparison between traditional Dual-Stream Join and the new Fluss-based architecture reveals the following advantages and disadvantages of the two:</p>
<table><thead><tr><th>Job Type</th><th>Advantages</th><th>Disadvantages</th></tr></thead><tbody><tr><td>Traditional MQ &amp; Dual-Stream Join</td><td>One job can implement the Join business logic.</td><td>1. Data is consumed row by row, resulting in wasted IO resources for useless columns.2. The Join State of the dual-stream is too large, making job maintenance difficult.3. State Black box, difficult to probe internal state. 4. If the resource plan does not match after modifying the job, then State needs to be rerun.</td></tr><tr><td>Fluss &amp; Delta Join</td><td>1. Consume the required columns and reduce the IO traffic of useless columns.  2. Implemented through Delta Join, state is decoupled from jobs.</td><td>1. Status is traceable, and backfilling efficiency is high.2. Delta Join relies on the point query capability of the Fluss KV table. 3. The number of Flink jobs has increased.</td></tr></tbody></table>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="evolution-of-lake-jobs">Evolution of Lake Jobs<a href="https://fluss.apache.org/blog/taobao-practice/#evolution-of-lake-jobs" class="hash-link" aria-label="Direct link to Evolution of Lake Jobs" title="Direct link to Evolution of Lake Jobs" translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/lake_jobs_evolution-4c8a2cfd7386d4b16ba1b474fa554b3b.png" width="904" height="470" class="img_ev3q"></p>
<p>Under the Fluss lake-stream integrated architecture, Fluss provides a <strong>fully managed</strong> unified data service. Fluss and Paimon store stream and lake data respectively, output a Catalog to the computing engine (such as Flink), and the data is output externally in the form of a unified table. Consumers can directly access the data in Fluss and lake storage in the form of Union Read.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-of-core-competencies">Implementation of Core Competencies<a href="https://fluss.apache.org/blog/taobao-practice/#implementation-of-core-competencies" class="hash-link" aria-label="Direct link to Implementation of Core Competencies" title="Direct link to Implementation of Core Competencies" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="column-pruning-capability">Column Pruning Capability<a href="https://fluss.apache.org/blog/taobao-practice/#column-pruning-capability" class="hash-link" aria-label="Direct link to Column Pruning Capability" title="Direct link to Column Pruning Capability" translate="no">​</a></h4>
<p>In the job of consuming message queues, consumers typically only consume a portion of the data, but Flink jobs still need to read data from all columns, resulting in significant waste in Flink Source IO. Fundamentally, existing message queues are all row-based storage, and for scenarios that need to process large-scale data, the efficiency of row-based storage format appears insufficient. The underlying storage needs to have powerful Data Skipping capabilities and support features such as column pruning. In this case, Fluss with columnar storage is clearly more suitable.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_column_pruning_evolution-d062e069bcd12155c2dd8bdf8c7c226d.png" width="904" height="302" class="img_ev3q"></p>
<p>In our real-time data warehouse, 70% of jobs only consume partial columns of Source. Taking the Taobao recommendation clicks job as an example, out of the 43 fields in Source, we only need 13. Additional operator resources are required to trim the entire row of data, wasting more than 20% of IO resources. After using Fluss, it directly consumes the required columns, avoiding additional IO waste and reducing the additional resources brought by Flink column pruning operators. To date, multiple core jobs in the Taobao Search and Recommendation domain have already launched Fluss and have been verified during the Taobao 618 promotion.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/comparison_of_fluss_and_mq_column_pruning-9b1ea19ec04baad6b5790dfba8fd5ae6.png" width="904" height="334" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-time-data-profiling">Real-time Data Profiling<a href="https://fluss.apache.org/blog/taobao-practice/#real-time-data-profiling" class="hash-link" aria-label="Direct link to Real-time Data Profiling" title="Direct link to Real-time Data Profiling" translate="no">​</a></h4>
<p><strong>Lookup Queries</strong></p>
<p>Whether troubleshooting issues or conducting data exploration, data queries are necessary. However, the message queue only supports sampling queries in the interface and queries of synchronized data in additional storage. In sampling queries, it is not possible to query specified data; only a batch of output can be retrieved for display and inspection. Using the method of synchronizing additional storage, on the other hand, incurs minute-level latency as well as additional storage and computational costs.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/comparison_of_fulss_and_mq_data_profiling-6edbee5cf60680f5a15c8dac1ccb1db5.png" width="904" height="536" class="img_ev3q"></p>
<p>In Fluss KV Table, a KV index is built, so it can support high-performance primary key point queries, directly probe Fluss data through point query statements, and also support query functions such as LIMIT and COUNT to meet daily Data Profiling requirements. An example is as follows:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_query_example1-7f2a861db777f647bfe3ad322307452d.png" width="904" height="204" class="img_ev3q"></p>
<p><strong>Flink State Query</strong></p>
<p>Flink's State mechanism (such as KeyedState or OperatorState) provides efficient state management capabilities, but its internal implementation is a "Black box" to developers. Developers cannot directly query, analyze, or debug the data in State, resulting in a strong coupling between business logic and state management, making it difficult to dynamically adjust or expand. When data anomalies occur, developers can only infer the content in State from the result data, unable to directly access the specific data in State, leading to high costs for troubleshooting.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/flink_state_evolution-2fc62883cca1013c9dfe5d774a085a5a.png" width="904" height="270" class="img_ev3q"></p>
<p>We externalize Flink State into Fluss, such as states for duak-stream join, data deduplication, etc. Based on Fluss's KV index, we provide State exploration capabilities, white-box the internal data of State, and efficiently locate issues.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_query_example2-2e75b06cea33441f0183914d6a049103.png" width="904" height="270" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="merge-engine--delta-join">Merge Engine &amp; Delta Join<a href="https://fluss.apache.org/blog/taobao-practice/#merge-engine--delta-join" class="hash-link" aria-label="Direct link to Merge Engine &amp; Delta Join" title="Direct link to Merge Engine &amp; Delta Join" translate="no">​</a></h4>
<p>In the current real-time data warehouse, the transaction attribution task is a job that heavily relies on State, with State <strong>reaching up to 100TB</strong> , which includes operations such as sorting and Dual-Stream Join. After consuming TT data, we first perform data sorting and then conduct a Dual-Stream Join to attribute order data.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/transaction_attribution_state_size-c2ef4c0431192100599996d24ff10627.png" width="904" height="178" class="img_ev3q"></p>
<p>As shown in the figure, in the first attribution logic implementation of the attribution job, the State of the sorting operator is as high as <strong>90TB</strong> , and that of the Dual-Stream Join operator is <strong>10TB</strong> . Large State jobs bring many problems, including high costs, job instability, long CP time, etc.</p>
<p><strong>Sorting optimization, Merge Engine</strong></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_sort_tunning-da4fccacf7bce9e94414c36eebd66e86.png" width="904" height="362" class="img_ev3q"></p>
<p>In the implementation of the Merge Engine, it mainly relies on Fluss's KV table. The Changelog generated by KV can be read by Flink streams without additional deduplication operations, saving Flink's computing resources and achieving business reuse of data. Through Fluss's KV table, the sorting logic in our jobs is implemented, and there is no longer a need to maintain the state of the sorting operator in the jobs.</p>
<p><strong>Join Optimization, Dual Sides Driving</strong></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/comparison_of_dual_stream_join_and_delta_join-08049759413e940eb71f99d45449f52f.png" width="904" height="390" class="img_ev3q"></p>
<p>The jobs of Delta Join are as described above. The original job, after consuming data, sorts it according to business requirements, performs Dual-Stream Join after sorting, and saves the state for 24 hours. This results in issues such as long CP time, poor job maintainability, and the need to rerun when modifying the job state. In Fluss, data sorting is completed through the Merge Engine of the KV table, and through Delta Join, the job and state are decoupled, eliminating the need to rerun the state when modifying the job, making the state data queryable, and improving flexibility.</p>
<p>We use the transaction attribution job as an example. It should be noted that both sides of DeltaJoin need to be KV tables.</p>
<div class="language-SQL language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">-- Create left table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">sr_ds</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">dpv_versioned_merge</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   pk              </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   user_name       </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   item_id         </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   step_no         </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   event_time      </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   dt              </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_name</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">item_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">step_no</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'512'</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_name,item_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token operator" style="color:#475569">=</span><span class="token string" style="color:#0E7C66">'versioned'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Take the last piece of data</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine.versioned.ver-column'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'event_time'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token comment" style="color:#64748B;font-style:italic">-- Sort field</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ZSTD'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Create right table</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">fluss</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">sr_ds</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">.</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">deal_kv</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  pk               </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  user_name        </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  item_id          </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  event_time       </span><span class="token keyword" style="color:#194670">bigint</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  dt               </span><span class="token keyword" style="color:#194670">VARCHAR</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">user_name</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">item_id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">pk</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> PARTITIONED </span><span class="token keyword" style="color:#194670">BY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">dt</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token string" style="color:#0E7C66">'bucket.num'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'48'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bucket.key'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'user_name,item_id'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.time-unit'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'day'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.merge-engine'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'first_row'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-precreate'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.auto-partition.num-retention'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.log.arrow.compression.type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'ZSTD'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">-- Join logic</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> dpv_versioned_merge t1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">join</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> deal_kv t2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token keyword" style="color:#194670">on</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">dt </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">dt </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token operator" style="color:#475569">and</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_name </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">user_name </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">  </span><span class="token operator" style="color:#475569">and</span><span class="token plain"> t1</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">item_id </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> t2</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">item_id</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Some operations are shown in the above code. After migrating from the Dual-Stream Join to Merge Engine &amp; Delta Join, large states were successfully reduced, making the job run more stably and eliminating CP timeouts. Meanwhile, the actual usage of CPU and Memory also decreased.</p>
<p>In addition to resource reduction and performance improvement, there is also an enhancement in flexibility for our benefits. The state of traditional stream-to-stream connections is tightly coupled with Flink jobs, like an opaque "black box". When the job is modified, it is found that the historical resource plan is incompatible with the current job, and the State can only be rerun from scratch, which is time-consuming and labor-intensive. After using Delta Join, it is equivalent to decoupling the state from the job, so modifying the job does not require rerunning the State. Moreover, all data is stored in Fluss, making it queryable and analyzable, thus improving business flexibility and development efficiency.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss_historical_data_consumption-c36e04f8f69fcfd14eb3370abebf3e96.png" width="904" height="298" class="img_ev3q"></p>
<p>Meanwhile, Fluss maintains a Tiering Service to synchronize Fluss data to Paimon, with the latest data stored in Fluss and historical data in Paimon. Flink can support Union Read, which combines the data in Fluss and Paimon to achieve second-level freshness analysis.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/join_jobs_evolution-db92bbdb4894edfcdb28009b081b687a.png" width="904" height="394" class="img_ev3q"></p>
<p>In addition, Data Join also addresses the scenario of backtracking data. In the case of Dual-Stream Join, if a job modification causes the Operating Plan verification to fail, only data backfilling can be performed. During the Fluss backtracking process, the Paimon table archived by the unified lake-stream architecture combined with Flink Batch Join can be used to accelerate data backfilling.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="lake-stream-evolution">Lake-Stream Evolution<a href="https://fluss.apache.org/blog/taobao-practice/#lake-stream-evolution" class="hash-link" aria-label="Direct link to Lake-Stream Evolution" title="Direct link to Lake-Stream Evolution" translate="no">​</a></h4>
<p>Fluss provides high compatibility with Data lake warehouses. Through the underlying service, it automatically converts Fluss data into Paimon format data, enabling real-time data to be ingested into the lake with a single click, while ensuring consistent data partitioning and bucketing on both the lake and stream sides.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/lake_stream_evolution-1828502586ada6555bb15cd4719ccf5f.png" width="904" height="294" class="img_ev3q"></p>
<p>After having <strong>lake</strong> and <strong>stream</strong> two levels of data, Fluss has the key characteristic of sharing data. Paimon stores long-period, minute-level latency data; Fluss stores short-period, millisecond-level latency data, and the data of both can be shared with each other.</p>
<p>When performing Fluss stream reads, Paimon can provide efficient backtracking capabilities as historical data. After backtracking to the current checkpoint, the system will automatically switch to stream storage to continue reading and ensure that no duplicate data is read. In batch query analysis, Fluss stream storage can supplement Paimon with real-time data, thereby enabling analysis with second-level freshness. This feature is called Union Read.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="benefits-and-summary">Benefits and Summary<a href="https://fluss.apache.org/blog/taobao-practice/#benefits-and-summary" class="hash-link" aria-label="Direct link to Benefits and Summary" title="Direct link to Benefits and Summary" translate="no">​</a></h2>
<p>After a period of in-depth research, comprehensive testing, and smooth launch of Fluss, we successfully completed the evolution of the technical architecture based on Fluss as a solution. Among them, we conducted comprehensive tests on core capabilities such as Fluss column pruning and Delta Join to ensure the stability and performance of the solution met the standards.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="performance-test">Performance Test<a href="https://fluss.apache.org/blog/taobao-practice/#performance-test" class="hash-link" aria-label="Direct link to Performance Test" title="Direct link to Performance Test" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="read-and-write-performance">Read and Write Performance<a href="https://fluss.apache.org/blog/taobao-practice/#read-and-write-performance" class="hash-link" aria-label="Direct link to Read and Write Performance" title="Direct link to Read and Write Performance" translate="no">​</a></h4>
<p>We tested the read-write and column pruning capabilities of Fluss. Among them, in terms of read-write performance, we conducted tests with the same traffic. While maintaining the input <strong>RPS at 800 w/s</strong> and the output <strong>RPS at 44 w/s</strong> , we compared the actual CPU and Memory usage of Flink jobs. The details are as follows:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_write-27292f1029f6a85e421735a04a2b4a75.png" width="698" height="402" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_read-f565578d7f2a84163de5e0663147c105.png" width="698" height="402" class="img_ev3q"></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="column-pruning-performance">Column Pruning Performance<a href="https://fluss.apache.org/blog/taobao-practice/#column-pruning-performance" class="hash-link" aria-label="Direct link to Column Pruning Performance" title="Direct link to Column Pruning Performance" translate="no">​</a></h4>
<p>Regarding column pruning capabilities, we also tested TT, Fluss, and different numbers of columns in Fluss to explore the consumption of CPU, Memory, and IO. With the input <strong>RPS maintained at 25 w/s</strong>, the specific test results are as follows:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_column_pruning2-9187374565daf3d3a14757c8444a7371.png" width="698" height="402" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_column_pruning1-e4c22dae4b6104713ce26f5a370b36a8.png" width="698" height="402" class="img_ev3q"></p>
<p>At this point, we will find a problem: as the number of columns decreases (a total of 13 columns out of 43 columns are consumed), IO does not show a linear decrease. After verification, the storage of each column in the source is not evenly distributed, and the storage of some required columns <strong>accounts for 72%</strong> . It can be seen that the input IO traffic <strong>decreases by 20%</strong> , which is consistent with the expected proportion of the storage of the read columns.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="delta-join-performance">Delta Join Performance<a href="https://fluss.apache.org/blog/taobao-practice/#delta-join-performance" class="hash-link" aria-label="Direct link to Delta Join Performance" title="Direct link to Delta Join Performance" translate="no">​</a></h4>
<p>We also tested and compared the resources and performance of the above <strong>Dual-Stream Join and Fluss Delta Join</strong> , and under the condition of maintaining the <strong>input RPS of the left table at 68,000/s</strong> and the <strong>input RPS of the right table at 1,700/s</strong> , all jobs ran for 24 hours.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_delta1-76e0c23013cbefdb8bf9f76980497d38.png" width="904" height="246" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_delta2-db0bb3f31808d421f8e516221d44c29d.png" width="904" height="246" class="img_ev3q"></p>
<p>After actual testing, after migrating from the Dual-Stream Join to Merge Engine + Delta Join, it successfully <strong>reduced 95TB</strong> of large state, making the job run more stably and significantly shortening the CP time. Meanwhile, the actual usage of CPU and Memory also <strong>decreased by more than 80%</strong> .</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-backfilling-performance">Data Backfilling Performance<a href="https://fluss.apache.org/blog/taobao-practice/#data-backfilling-performance" class="hash-link" aria-label="Direct link to Data Backfilling Performance" title="Direct link to Data Backfilling Performance" translate="no">​</a></h4>
<p>Since Delta Join <strong>does not need to maintain state</strong> , it can use batch mode <strong>Batch Join</strong> to catch up. After catching up, it switches back to stream mode Delta Join. During this process, a small amount of data will be reprocessed, and the end-to-end consistency is ensured through the idempotence update mechanism of the result table. We <strong>performed a test comparison on the data catch-up of the above</strong> Dual-Stream Join and Batch Join <strong>for one day (24H).</strong></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_data_backtracking1-25192a04f93b0cc734d0dae30744a7fc.png" width="698" height="402" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/performance_data_backtracking2-74113a436a7f89f34f7405490b5bffc9.png" width="698" height="402" class="img_ev3q"></p>
<p>After actual testing, the batch mode Batch Join backtracking for one day (24H) takes <strong>70%+ less time</strong> compared to the Dual-Stream Join backtracking.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://fluss.apache.org/blog/taobao-practice/#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h3>
<p>Fluss has core features such as columnar pruning, streaming updates, real-time point queries, and lake-stream integration. At the same time, it innovatively integrates columnar storage format and real-time update capabilities into stream storage, and deeply integrates with Flink and Paimon to build a lake warehouse with high throughput, low latency, and low cost. Just as the original intention of Fluss: <strong>Real-time stream storage for analytics.</strong></p>
<p>After nearly three months of exploration, Fluss has been implemented in <strong>Taobao's core search and recommendation scenarios</strong> , built a unified lake-stream A/B data warehouse, and continuously served internal business through Taobao's A/B Experiment Platform. In practical applications, the following results have been achieved:</p>
<ul>
<li class="">
<p><strong>Scenarios are widely implemented:</strong> covering core Taobao business such as search and recommendation, and successfully passing the test of Taobao's 618 Grand Promotion <strong>,</strong> with peak traffic reaching tens of millions and average latency <strong>within 1 second</strong> .</p>
</li>
<li class="">
<p><strong>Column Pruning Resource Optimization</strong> : The column pruning feature has been implemented in all Fluss real-time jobs. When the consumption of columnar storage accounts for 75% of the source data, the average IO <strong>is reduced by 25%</strong> , and the average job resource consumption <strong>is reduced by 30%</strong> .</p>
</li>
<li class="">
<p><strong>Big State job optimization:</strong> Taking transaction attribution job as an example, the Delta Join &amp; Merge Engine is applied to reconstruct the first and last attribution. State is externally implemented, and the actual usage of CPU and Memory is <strong>reduced by 80% +</strong> . The state of Flink is <strong>decoupled</strong> from the job, and the <strong>100TB +</strong> big state is successfully reduced, making the job more stable.</p>
</li>
<li class="">
<p><strong>Data Profiling:</strong> Implemented capabilities including message queue profiling and State profiling, supports query operators such as LIMIT and COUNT, achieved white-boxing of State for sorting and Dual-Stream Join, with higher flexibility and more efficient problem location.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="planning">Planning<a href="https://fluss.apache.org/blog/taobao-practice/#planning" class="hash-link" aria-label="Direct link to Planning" title="Direct link to Planning" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/future_plans-1828502586ada6555bb15cd4719ccf5f.png" width="904" height="294" class="img_ev3q"></p>
<p>In the upcoming work, we will develop a new generation of lakehouse data architecture and continue to explore in Data &amp; AI scenarios.</p>
<ul>
<li class="">
<p><strong>Scenario Inclusiveness:</strong> In the second-level data warehouse, switch the whole-pipeline and all scenarios of the consumption message queue to the Fluss pipeline. Based on the Fluss lake-stream capabilities, provide corresponding Paimon minute-level data.</p>
</li>
<li class="">
<p><strong>AI Empowerment:</strong> Attempt to implement scenarios of MultiModal Machine Learning data pipelines, Agents, and model pre-training, and add AI-enhanced analytics.</p>
</li>
<li class="">
<p><strong>Capability Exploration:</strong> Explore more Fluss capabilities and empower business operations, optimizing more jobs through Fluss Aggregate Merge Engine and Partial Update capabilities.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://fluss.apache.org/blog/taobao-practice/#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<p>[1] <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">Fluss Official Documentation</a></p>
<p>[2] <a href="https://mp.weixin.qq.com/s?__biz=MzU3Mzg4OTMyNQ==&amp;mid=2247512207&amp;idx=1&amp;sn=e25320a020d7b20e25be8fd614e9f46b&amp;chksm=fd383ecdca4fb7dbf9a37e9c3840699ba8b1b2c57868db028f995c3bc7bcf6a0340e396d87c4&amp;scene=178&amp;cur_album_id=3764887437743669257&amp;search_click_id=#rd" target="_blank" rel="noopener noreferrer" class="">Fluss: Next-Generation Stream Storage Designed for Real-Time Analysis</a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Stream to Lake: Hands-On with Fluss Tiering into Paimon on Minio]]></title>
            <link>https://fluss.apache.org/blog/hands-on-fluss-lakehouse/</link>
            <guid>https://fluss.apache.org/blog/hands-on-fluss-lakehouse/</guid>
            <pubDate>Wed, 23 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Fluss stores historical data in a lakehouse storage layer while keeping real-time data in the Fluss server. Its built-in tiering service continuously moves fresh events into the lakehouse, allowing various query engines to analyze both hot and cold data. The real magic happens with Fluss's union-read capability, which lets Flink jobs seamlessly query both the Fluss cluster and the lakehouse for truly integrated real-time processing.]]></description>
            <content:encoded><![CDATA[<p>Fluss stores historical data in a lakehouse storage layer while keeping real-time data in the Fluss server. Its built-in tiering service continuously moves fresh events into the lakehouse, allowing various query engines to analyze both hot and cold data. The real magic happens with Fluss's union-read capability, which lets Flink jobs seamlessly query both the Fluss cluster and the lakehouse for truly integrated real-time processing.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/streamhouse-7b7f1260fc996ca89b50dbb9454584ab.png" width="3057" height="1237" class="img_ev3q"></p>
<p>In this hands-on tutorial, we'll walk you through setting up a local Fluss lakehouse environment, running some practical data operations, and getting first-hand experience with the complete Fluss lakehouse architecture. By the end, you'll have a working environment for experimenting with Fluss's powerful data processing capabilities.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="integrate-with-paimon-minio-lakehouse">Integrate with Paimon Minio Lakehouse<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#integrate-with-paimon-minio-lakehouse" class="hash-link" aria-label="Direct link to Integrate with Paimon Minio Lakehouse" title="Direct link to Integrate with Paimon Minio Lakehouse" translate="no">​</a></h2>
<p>For this tutorial, we'll use <strong>Fluss 0.7</strong> and <strong>Flink 1.20</strong> to run the tiering service on a local cluster. We'll configure <strong>Paimon</strong> as our lake format on <strong>Minio</strong> as the storage backend. Let's get started:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="minio-setup">Minio Setup<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#minio-setup" class="hash-link" aria-label="Direct link to Minio Setup" title="Direct link to Minio Setup" translate="no">​</a></h3>
<ol>
<li class="">
<p>Install Minio object storage locally.</p>
<p>Check out the official <a href="https://min.io/docs/minio/macos/index.html" target="_blank" rel="noopener noreferrer" class="">guide</a> for detailed instructions.</p>
</li>
<li class="">
<p>Start the Minio server</p>
<p>Run this command, specifying a local path to store your Minio data:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">minio server /tmp/minio-data</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Verify the Minio WebUI.</p>
<p>When your Minio server is up and running, you'll see endpoint information and login credentials:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">API: http://192.168.2.236:9000  http://127.0.0.1:9000</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   RootUser: minioadmin</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   RootPass: minioadmin</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">WebUI: http://192.168.2.236:61832 http://127.0.0.1:61832</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   RootUser: minioadmin</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">   RootPass: minioadmin</span><br></div></code></pre></div></div>
<p>Open the WebUI link and log in with these credentials.</p>
</li>
<li class="">
<p>Create a <code>fluss</code> bucket through the WebUI.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss-bucket-f466f0c827a5a51c7293f78eba966afc.png" width="3760" height="978" class="img_ev3q"></p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fluss-cluster-setup">Fluss Cluster Setup<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#fluss-cluster-setup" class="hash-link" aria-label="Direct link to Fluss Cluster Setup" title="Direct link to Fluss Cluster Setup" translate="no">​</a></h3>
<ol>
<li class="">
<p>Download Fluss</p>
<p>Grab the Fluss 0.7 binary release from the <a href="https://fluss.apache.org/downloads/" target="_blank" rel="noopener noreferrer" class="">Fluss official site</a>.</p>
</li>
<li class="">
<p>Add Dependencies</p>
<p>Download the <code>fluss-fs-s3-0.7.0.jar</code> from the <a href="https://fluss.apache.org/downloads/" target="_blank" rel="noopener noreferrer" class="">Fluss official site</a> and place it in your <code>&lt;FLUSS_HOME&gt;/lib</code> directory.</p>
<p>Next, download the <code>paimon-s3-1.0.1.jar</code> from the <a href="https://paimon.apache.org/docs/1.0/project/download/" target="_blank" rel="noopener noreferrer" class="">Paimon official site</a> and add it to <code>&lt;FLUSS_HOME&gt;/plugins/paimon</code>.</p>
</li>
<li class="">
<p>Configure the Data Lake</p>
<p>Edit your <code>&lt;FLUSS_HOME&gt;/conf/server.yaml</code> file and add these settings:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token key atrule" style="color:#194670">data.dir</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> /tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">remote.data.dir</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> /tmp/fluss</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">remote</span><span class="token punctuation" style="color:#475569">-</span><span class="token plain">data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.format</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> paimon</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.metastore</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> filesystem</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.warehouse</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> s3</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">//fluss/data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.s3.endpoint</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> http</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain">//localhost</span><span class="token punctuation" style="color:#475569">:</span><span class="token number" style="color:#B45309">9000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.s3.access-key</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> minioadmin</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.s3.secret-key</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> minioadmin</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token key atrule" style="color:#194670">datalake.paimon.s3.path.style.access</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#BE123C">true</span><br></div></code></pre></div></div>
<p>This configures Paimon as the datalake format on Minio as the warehouse.</p>
</li>
<li class="">
<p>Start Fluss</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token operator" style="color:#475569">&lt;</span><span class="token plain">FLUSS_HOME</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain">/bin/local-cluster.sh start</span><br></div></code></pre></div></div>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flink-cluster-setup">Flink Cluster Setup<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#flink-cluster-setup" class="hash-link" aria-label="Direct link to Flink Cluster Setup" title="Direct link to Flink Cluster Setup" translate="no">​</a></h3>
<ol>
<li class="">
<p>Download Flink</p>
<p>Download the Flink 1.20 binary package from the <a href="https://flink.apache.org/downloads/" target="_blank" rel="noopener noreferrer" class="">Flink downloads page</a>.</p>
</li>
<li class="">
<p>Add the Fluss Connector</p>
<p>Download <code>fluss-flink-1.20-0.7.0.jar</code> from the <a href="https://fluss.apache.org/downloads/" target="_blank" rel="noopener noreferrer" class="">Fluss official site</a> and copy it to:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">&lt;FLINK_HOME&gt;/lib</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Add Paimon Dependencies</p>
<ul>
<li class="">Download <code>paimon-flink-1.20-1.0.1.jar</code> and <code>paimon-s3-1.0.1.jar</code> from the <a href="https://paimon.apache.org/docs/1.0/project/download/" target="_blank" rel="noopener noreferrer" class="">Paimon official site</a> and place them in <code>&lt;FLINK_HOME&gt;/lib</code>.</li>
<li class="">Copy these Paimon plugin jars from Fluss into <code>&lt;FLINK_HOME&gt;/lib</code>:</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">&lt;FLINK_HOME&gt;/lib/fluss-lake-paimon-0.7.0.jar</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">&lt;FLINK_HOME&gt;/lib/flink-shaded-hadoop-2-uber-2.8.3-10.0.jar</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Increase Task Slots</p>
<p>Edit <code>&lt;FLINK_HOME&gt;/conf/config.yaml</code> to increase available task slots:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token key atrule" style="color:#194670">numberOfTaskSlots</span><span class="token punctuation" style="color:#475569">:</span><span class="token plain"> </span><span class="token number" style="color:#B45309">5</span><span class="token plain"> </span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Start Flink</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token operator" style="color:#475569">&lt;</span><span class="token plain">FLINK_HOME</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain">/bin/start-cluster.sh</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Verify</p>
<p>Open your browser to <code>http://localhost:8081/</code> and make sure the cluster is running.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="launching-the-tiering-service">Launching the Tiering Service<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#launching-the-tiering-service" class="hash-link" aria-label="Direct link to Launching the Tiering Service" title="Direct link to Launching the Tiering Service" translate="no">​</a></h3>
<ol>
<li class="">
<p>Get the Tiering Job Jar</p>
<p>Download the <code>fluss-flink-tiering-0.7.0.jar</code>.</p>
</li>
<li class="">
<p>Submit the Job</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token operator" style="color:#475569">&lt;</span><span class="token plain">FLINK_HOME</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain">/bin/flink run </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token operator" style="color:#475569">&lt;</span><span class="token plain">path_to_jar</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain">/fluss-flink-tiering-0.7.0.jar </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--fluss.bootstrap.servers</span><span class="token plain"> localhost:9123 </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.format</span><span class="token plain"> paimon </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.metastore</span><span class="token plain"> filesystem </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.warehouse</span><span class="token plain"> s3://fluss/data </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.s3.endpoint</span><span class="token plain"> http://localhost:9000 </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    --datalake.paimon.s3.access-key minioadmin </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    --datalake.paimon.s3.secret-key minioadmin </span><span class="token punctuation" style="color:#475569">\</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token parameter variable" style="color:#12325C">--datalake.paimon.s3.path.style.access</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">true</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Confirm Deployment</p>
<p>Check the Flink UI for the <strong>Fluss Lake Tiering Service</strong> job. Once it's running, your local tiering pipeline is good to go.</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/tiering-serivce-job-26534185b93fc7424151b7464b458bd4.png" width="3210" height="378" class="img_ev3q"></p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="data-processing">Data Processing<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#data-processing" class="hash-link" aria-label="Direct link to Data Processing" title="Direct link to Data Processing" translate="no">​</a></h2>
<p>Now let's dive into some actual data processing. We'll use the Flink SQL Client to interact with our Fluss lakehouse and run both batch and streaming queries.</p>
<ol>
<li class="">
<p>Launch the SQL Client</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token operator" style="color:#475569">&lt;</span><span class="token plain">FLINK_HOME</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain">/bin/sql-client.sh</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Create the Catalog and Table</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> CATALOG fluss_catalog </span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'type'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'fluss'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">   </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'bootstrap.servers'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'localhost:9123'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">USE</span><span class="token plain"> CATALOG fluss_catalog</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">TABLE</span><span class="token plain"> t_user </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">id</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">BIGINT</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">name</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> string </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">NULL</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">age</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">int</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">birth</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">DATE</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token identifier">id</span><span class="token identifier punctuation" style="color:#475569">`</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token operator" style="color:#475569">NOT</span><span class="token plain"> ENFORCED</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token keyword" style="color:#194670">WITH</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.enabled'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'true'</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token string" style="color:#0E7C66">'table.datalake.freshness'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'30s'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Write Some Data</p>
<p>Let's insert a couple of records:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'execution.runtime-mode'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'batch'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'sql-client.execution.result-mode'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'tableau'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> t_user</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">name</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">age</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">birth</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token string" style="color:#0E7C66">'Alice'</span><span class="token punctuation" style="color:#475569">,</span><span class="token number" style="color:#B45309">18</span><span class="token punctuation" style="color:#475569">,</span><span class="token keyword" style="color:#194670">DATE</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2000-06-10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token string" style="color:#0E7C66">'Bob'</span><span class="token punctuation" style="color:#475569">,</span><span class="token number" style="color:#B45309">20</span><span class="token punctuation" style="color:#475569">,</span><span class="token keyword" style="color:#194670">DATE</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2001-06-20'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Union Read</p>
<p>Now run a simple query to retrieve data from the table. By default, Flink will automatically combine data from both the Fluss cluster and the lakehouse:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> t_user</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> id </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  name </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> age </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      birth </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> Alice </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">18</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2000</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">   Bob </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2001</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+</span><br></div></code></pre></div></div>
<p>If you want to read data only from the lake table, simply append <code>$lake</code> after the table name:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> t_user$lake</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> id </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  name </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> age </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      birth </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __bucket </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __offset </span><span class="token operator" style="color:#475569">|</span><span class="token plain">                __timestamp </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> Alice </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">18</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2000</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">   Bob </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2001</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+-------+-----+------------+----------+----------+----------------------------+</span><br></div></code></pre></div></div>
<p>Great! Our records have been successfully synced to the data lake by the tiering service.</p>
<p>Notice the three system columns in the Paimon lake table: <code>__bucket</code>, <code>__offset</code>, and <code>__timestamp</code>. The <code>__bucket</code> column shows which bucket contains this row. The <code>__offset</code> and <code>__timestamp</code> columns are used for streaming data processing.</p>
</li>
<li class="">
<p>Streaming Inserts</p>
<p>Let's switch to streaming mode and add two more records:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">SET</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'execution.runtime-mode'</span><span class="token plain"> </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'streaming'</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">INTO</span><span class="token plain"> t_user</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">id</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">name</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">age</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain">birth</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">VALUES</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token string" style="color:#0E7C66">'Catlin'</span><span class="token punctuation" style="color:#475569">,</span><span class="token number" style="color:#B45309">25</span><span class="token punctuation" style="color:#475569">,</span><span class="token keyword" style="color:#194670">DATE</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2002-06-10'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token string" style="color:#0E7C66">'Dylan'</span><span class="token punctuation" style="color:#475569">,</span><span class="token number" style="color:#B45309">28</span><span class="token punctuation" style="color:#475569">,</span><span class="token keyword" style="color:#194670">DATE</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'2003-06-20'</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Now query the lake again:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> t_user$lake</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+----+--------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> op </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> id </span><span class="token operator" style="color:#475569">|</span><span class="token plain">   name </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> age </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      birth </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __bucket </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __offset </span><span class="token operator" style="color:#475569">|</span><span class="token plain">                __timestamp </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+----+--------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  Alice </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">18</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2000</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">    Bob </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2001</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> t_user$lake</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+----+--------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> op </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> id </span><span class="token operator" style="color:#475569">|</span><span class="token plain">   name </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> age </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      birth </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __bucket </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __offset </span><span class="token operator" style="color:#475569">|</span><span class="token plain">                __timestamp </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">----+----+--------+-----+------------+----------+----------+----------------------------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  Alice </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">18</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2000</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">    Bob </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2001</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">       </span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1970</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">01</span><span class="token plain"> </span><span class="token number" style="color:#B45309">07</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59</span><span class="token plain">:</span><span class="token number" style="color:#B45309">59.999000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">3</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> Catlin </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">25</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2002</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">10</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2025</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">07</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">19</span><span class="token plain"> </span><span class="token number" style="color:#B45309">19</span><span class="token plain">:</span><span class="token number" style="color:#B45309">03</span><span class="token plain">:</span><span class="token number" style="color:#B45309">54.150000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token operator" style="color:#475569">+</span><span class="token plain">I </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">4</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  Dylan </span><span class="token operator" style="color:#475569">|</span><span class="token plain">  </span><span class="token number" style="color:#B45309">28</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2003</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">06</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">20</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">        </span><span class="token number" style="color:#B45309">3</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2025</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">07</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">19</span><span class="token plain"> </span><span class="token number" style="color:#B45309">19</span><span class="token plain">:</span><span class="token number" style="color:#B45309">03</span><span class="token plain">:</span><span class="token number" style="color:#B45309">54.150000</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p>The first time we queried, our new records hadn't been synced to the lake table yet. After waiting a moment, they appeared.</p>
<p>Notice that the <code>__offset</code> and <code>__timestamp</code> values for these new records are no longer the default values. They now show the actual offset and timestamp when the records were added to the table.</p>
</li>
<li class="">
<p>Inspect the Paimon Files</p>
<p>Open the Minio WebUI, and you'll see the Paimon files in your bucket:</p>
<p><img decoding="async" loading="lazy" src="https://fluss.apache.org/assets/images/fluss-bucket-data-47a0df43a938f44f5671f184588fd5ef.png" width="1508" height="530" class="img_ev3q"></p>
<p>You can also check the Parquet files and manifest in your local filesystem under <code>/tmp/minio-data</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">/tmp/minio-data ❯ tree .</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">└── fluss</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    └── data</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        ├── default.db__XLDIR__</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        └── fluss.db</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            └── t_user</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                ├── bucket-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── changelog-1bafcc32-f88a-42a6-bc92-d3ccf4f62d4c-0.parquet</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── changelog-f1853f1c-2588-4035-8233-e4804b1d8344-0.parquet</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── data-1bafcc32-f88a-42a6-bc92-d3ccf4f62d4c-1.parquet</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         └── data-f1853f1c-2588-4035-8233-e4804b1d8344-1.parquet</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │             └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                ├── manifest</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-d554f475-ad8f-47e0-a83b-22bce4b233d6-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-d554f475-ad8f-47e0-a83b-22bce4b233d6-1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-e7fbe5b1-a9e4-4647-a07a-5cc71950a5be-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-e7fbe5b1-a9e4-4647-a07a-5cc71950a5be-1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-list-8975f7d7-9fec-4ac9-bb31-12be03d297d0-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-list-8975f7d7-9fec-4ac9-bb31-12be03d297d0-1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-list-8975f7d7-9fec-4ac9-bb31-12be03d297d0-2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-list-bba1f130-e7ab-4f5e-8ce3-928a53524136-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         ├── manifest-list-bba1f130-e7ab-4f5e-8ce3-928a53524136-1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         └── manifest-list-bba1f130-e7ab-4f5e-8ce3-928a53524136-2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │             └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                ├── schema</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │         └── schema-0</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                │             └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                └── snapshot</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    ├── LATEST</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    ├── snapshot-1</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    │         └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    └── snapshot-2</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                        └── xl.meta</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">28 directories, 19 files</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>View Snapshots</p>
<p>You can also check the snapshots from the system table by appending <code>$lake$snapshots</code> after the Fluss table name:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">Flink </span><span class="token keyword" style="color:#194670">SQL</span><span class="token operator" style="color:#475569">&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">select</span><span class="token plain"> </span><span class="token operator" style="color:#475569">*</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">from</span><span class="token plain"> t_user$lake$snapshots</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">-------------+-----------+----------------------+-------------------------+-------------+----------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain"> snapshot_id </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> schema_id </span><span class="token operator" style="color:#475569">|</span><span class="token plain">          commit_user </span><span class="token operator" style="color:#475569">|</span><span class="token plain">             commit_time </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> commit_kind </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">      </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">-------------+-----------+----------------------+-------------------------+-------------+----------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">           </span><span class="token number" style="color:#B45309">1</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">         </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __fluss_lake_tiering </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2025</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">07</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">19</span><span class="token plain"> </span><span class="token number" style="color:#B45309">19</span><span class="token plain">:</span><span class="token number" style="color:#B45309">00</span><span class="token plain">:</span><span class="token number" style="color:#B45309">41.286</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      APPEND </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">      </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">|</span><span class="token plain">           </span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">         </span><span class="token number" style="color:#B45309">0</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> __fluss_lake_tiering </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2025</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">07</span><span class="token operator" style="color:#475569">-</span><span class="token number" style="color:#B45309">19</span><span class="token plain"> </span><span class="token number" style="color:#B45309">19</span><span class="token plain">:</span><span class="token number" style="color:#B45309">04</span><span class="token plain">:</span><span class="token number" style="color:#B45309">38.964</span><span class="token plain"> </span><span class="token operator" style="color:#475569">|</span><span class="token plain">      APPEND </span><span class="token operator" style="color:#475569">|</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">      </span><span class="token operator" style="color:#475569">|</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token operator" style="color:#475569">+</span><span class="token comment" style="color:#64748B;font-style:italic">-------------+-----------+----------------------+-------------------------+-------------+----------+</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">2</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">rows</span><span class="token plain"> </span><span class="token operator" style="color:#475569">in</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">set</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0.33</span><span class="token plain"> seconds</span><span class="token punctuation" style="color:#475569">)</span><br></div></code></pre></div></div>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://fluss.apache.org/blog/hands-on-fluss-lakehouse/#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>In this guide, we've explored the Fluss lakehouse architecture and set up a complete local environment with Fluss, Flink, Paimon, and Minio. We've walked through practical examples of data processing that showcase how Fluss seamlessly integrates real-time and historical data. With this setup, you now have a solid foundation for experimenting with Fluss's powerful lakehouse capabilities on your own machine.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fluss Joins the Apache Incubator]]></title>
            <link>https://fluss.apache.org/blog/fluss-joins-asf/</link>
            <guid>https://fluss.apache.org/blog/fluss-joins-asf/</guid>
            <pubDate>Thu, 10 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[On June 5th, Fluss, the next-generation streaming storage project open-sourced and donated by Alibaba, successfully passed the vote and officially became an incubator project of the Apache Software Foundation (ASF). This marks a significant milestone in the development of the Fluss community, symbolizing that the project has entered a new phase that is more open,]]></description>
            <content:encoded><![CDATA[<p>On June 5th, Fluss, the next-generation streaming storage project open-sourced and donated by Alibaba, successfully passed the <a href="https://lists.apache.org/thread/mnol4wxovpz6klt196d3x239t4mp6z5o" target="_blank" rel="noopener noreferrer" class="">vote</a> and officially became an incubator project of the Apache Software Foundation (ASF). This marks a significant milestone in the development of the Fluss community, symbolizing that the project has entered a new phase that is more open,
neutral, and standardized. Moving forward, Fluss will leverage the ASF ecosystem to accelerate the building of a global developer community, continuously driving innovation and adoption of next-generation real-time data infrastructure.</p>
<p><img decoding="async" loading="lazy" alt="ASF" src="https://fluss.apache.org/assets/images/asf-0621eab6f6aadbdfebfc5e24a36667c1.png" width="1231" height="631" class="img_ev3q"></p>
<p>The Fluss community has recently completed all donation procedures and successfully transferred the project to the Apache Software Foundation.
During the keynote speech at Flink Forward Asia 2025, held on July 3rd in Singapore, project creator Jark Wu officially announced the exciting news,
sharing the new <a href="https://github.com/apache/fluss/" target="_blank" rel="noopener noreferrer" class="">repository address</a> and the <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">official website domain</a>.</p>
<p><img decoding="async" loading="lazy" alt="FF Announcement" src="https://fluss.apache.org/assets/images/announcement-73511437ca09935c2c70c5339a4e6bc7.png" width="1203" height="800" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-fluss">What is Fluss?<a href="https://fluss.apache.org/blog/fluss-joins-asf/#what-is-fluss" class="hash-link" aria-label="Direct link to What is Fluss?" title="Direct link to What is Fluss?" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" alt="Architecture" src="https://fluss.apache.org/assets/images/architecture-0d8148aebd6d7b8c666e2484f2e4ce8c.png" width="1218" height="552" class="img_ev3q"></p>
<p>Apache Fluss (incubating) is a next-generation streaming storage designed for real-time analytics scenarios.
It aims to address the high costs and inefficiencies of traditional streaming storage technologies in stream processing and Lakehouse architectures.
It offers the following core features:</p>
<ul>
<li class=""><strong>Columnar Streaming Storage:</strong> Supports real-time streaming read and write with millisecond-level latency. Real-time streaming data is stored in the Apache Arrow columnar format to leverage query pushdown technologies such as column pruning and partition pruning during streaming read. It improves read performance by up to 10 times and reduces network costs.</li>
<li class=""><strong>Real-Time Updates and Lookup Queries:</strong> Innovatively introduces real-time update capabilities into stream storage. Through high-performance streaming updates, partial updates, changelog feed, key-value lookup, and DeltaJoin features, it collaborates efficiently with Flink to build a cost-effective, real-time streaming data warehouse.</li>
<li class=""><strong>Streaming Lakehouse:</strong> Achieves unified storage of data lakehouse and data streams, enabling data sharing between them. The Lakehouse provides low-cost historical data support for streams, while streams inject real-time data capabilities into the Lakehouse, delivering real-time data analysis experiences to the Lakehouse.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-two-year-journey">The Two-Year Journey<a href="https://fluss.apache.org/blog/fluss-joins-asf/#the-two-year-journey" class="hash-link" aria-label="Direct link to The Two-Year Journey" title="Direct link to The Two-Year Journey" translate="no">​</a></h3>
<p>In July 2023, the Flink team at Alibaba Cloud launched the Fluss project.
The name <strong>"Fluss"</strong> is derived from the abbreviation of "<strong>Fl</strong>ink <strong>U</strong>nified <strong>S</strong>treaming <strong>S</strong>torage", signifying its mission to build a unified streaming storage foundation for Apache Flink.
Coincidentally, "Fluss" means <strong>"river"</strong> in German, symbolizing the continuous flow of data.</p>
<p>After more than a year of internal incubation and refinement, Alibaba officially announced the open-sourcing of the Fluss project on November 29, 2024, during the keynote speech at Flink Forward Asia 2024 in Shanghai.
Since then, Fluss has embarked on a path of diverse and international development, attracting contributions from more than 60 developers worldwide.
The community’s activity has been steadily growing, with a major version released approximately every three months.</p>
<p>At the same time, Fluss has achieved large-scale adoption within Alibaba Group.
Currently, it supports <strong>data scales of over 3 PB</strong>, with a cluster <strong>throughput peak of 40 GB/s</strong>, and a maximum single-table <strong>lookup query QPS of up to 500,000 per second</strong>, and single-table data volume <strong>reaching up to 500 billion rows</strong>.
In key business scenarios such as log collection and analysis, search recommendation, and real-time data warehouses, Fluss has demonstrated outstanding performance and capabilities.</p>
<p><img decoding="async" loading="lazy" alt="Alibaba Production" src="https://fluss.apache.org/assets/images/alibaba-84407d53e3ba58ed346f59ed45f7d834.png" width="1195" height="338" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-apache">Why Apache?<a href="https://fluss.apache.org/blog/fluss-joins-asf/#why-apache" class="hash-link" aria-label="Direct link to Why Apache?" title="Direct link to Why Apache?" translate="no">​</a></h3>
<p>The Apache Software Foundation (ASF) is the cradle of global open-source big data technologies, nurturing numerous world-changing projects such as Hadoop, Spark, Iceberg, Kafka, and Flink. Fluss looks forward to joining the ASF and becoming a part of the movement that shapes the future of real-time infrastructure. At the same time, Fluss has a strong need for deep integration with these Apache projects, and joining the ASF will accelerate the integration process within the ecosystem. More importantly, the ASF's core values of openness, collaboration, and neutrality align closely with Fluss's vision. By joining the Apache Incubator, we align with this spirit and gain access to a larger community, better governance, and long-term sustainability.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="special-thanks">Special Thanks<a href="https://fluss.apache.org/blog/fluss-joins-asf/#special-thanks" class="hash-link" aria-label="Direct link to Special Thanks" title="Direct link to Special Thanks" translate="no">​</a></h3>
<p>Special thanks to the Fluss incubation mentors for their valuable support and guidance during the project's journey into the ASF Incubator.</p>
<ul>
<li class=""><strong>@Yu Li (Champion):</strong> PMC member of Flink and HBase projects, experienced mentor of multiple open-source projects, and successfully guided top-level projects such as Apache Paimon and Apache Celeborn.</li>
<li class=""><strong>@Jingsong Lee:</strong> Chair of the Apache Paimon PMC and member of the Apache Flink PMC.</li>
<li class=""><strong>@Zili Chen:</strong> A seasoned mentor of multiple open-source projects, PMC member of Pulsar, Zookeeper and Curator. He also serves as a member of the Apache Board in 2025.</li>
<li class=""><strong>@Becket Qin:</strong> An active mentor of multiple open-source projects and PMC member of projects including Apache Flink and Apache Kafka.</li>
<li class=""><strong>@Jean-Baptiste Onofré:</strong> Karaf PMC Chair, PMC on ACE, ActiveMQ, Archiva, Aries, Beam, Brooklyn, Camel, Felix, Incubator. He is also the incubation champion for Apache Polaris.</li>
</ul>
<p>We would also like to express our gratitude to all contributors of the Fluss community!</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="join-the-surfing">Join the Surfing<a href="https://fluss.apache.org/blog/fluss-joins-asf/#join-the-surfing" class="hash-link" aria-label="Direct link to Join the Surfing" title="Direct link to Join the Surfing" translate="no">​</a></h3>
<p>We sincerely invite developers and users who are interested in Fluss to join our open-source community and help drive the project forward. We look forward to your participation!</p>
<ul>
<li class="">GitHub Repository: <a href="https://github.com/apache/fluss/" target="_blank" rel="noopener noreferrer" class="">https://github.com/apache/fluss/</a>  (give it some ❤️ via ⭐)</li>
<li class="">Official Website: <a href="https://fluss.apache.org/" target="_blank" rel="noopener noreferrer" class="">https://fluss.apache.org/</a></li>
<li class="">Slack: <a href="https://join.slack.com/t/apache-fluss/shared_invite/zt-33wlna581-QAooAiCmnYboJS8D_JUcYw" target="_blank" rel="noopener noreferrer" class="">Apache Fluss </a></li>
<li class="">Mailing List: <code>dev@fluss.apache.org</code> (by sending any mail to <code>dev-subscribe@fluss.apache.org</code> to subscribe)</li>
</ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Apache Fluss Java Client: A Deep Dive]]></title>
            <link>https://fluss.apache.org/blog/fluss-java-client/</link>
            <guid>https://fluss.apache.org/blog/fluss-java-client/</guid>
            <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Banner]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Banner" src="https://fluss.apache.org/assets/images/banner-9b4e9683efcde75c4961249525c8f269.png" width="1477" height="975" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://fluss.apache.org/blog/fluss-java-client/#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>Apache Fluss is a streaming data storage system built for real-time analytics, serving as a low-latency data layer in modern data Lakehouses.
It supports sub-second streaming reads and writes, storing data in a columnar format for efficiency, and offers two flexible table types: <strong>append-only Log Tables</strong> and <strong>updatable Primary Key Tables</strong>.
In practice, this means Fluss can ingest high-throughput event streams <em>(using log tables)</em> while also maintaining <em>up-to-date</em> reference data or state <em>(using primary key tables)</em>, a combination ideal for
scenarios like IoT, where you might stream sensor readings and look up information for those sensors in real-time, without
the need for external K/V stores.</p>
<p>In this tutorial, we'll introduce the <strong>Fluss Java Client</strong> by walking through a simple home IoT system example.
We will use <code>Fluss's Admin client</code> to create a primary key table for sensor information and a log table for sensor readings, then use the client
to write data to these tables and read/enrich the streaming sensor data.</p>
<p>By the end, you'll see how a sensor reading can be ingested into a log table and immediately enriched with information from a primary key table (essentially performing a real-time lookup join for streaming data enrichment).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="preflight-check">Preflight Check<a href="https://fluss.apache.org/blog/fluss-java-client/#preflight-check" class="hash-link" aria-label="Direct link to Preflight Check" title="Direct link to Preflight Check" translate="no">​</a></h2>
<p>The full source code can be found <a href="https://github.com/ververica/ververica-fluss-examples/tree/main/fluss-java-client" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token function" style="color:#7C3AED">docker</span><span class="token plain"> compose up</span><br></div></code></pre></div></div>
<p>The first thing we need to do is establish a connection to the Fluss cluster.
The <code>Connection</code> is the main entry point for the Fluss client, from which we obtain an <code>Admin</code> (for metadata operations) and Table instances (for data operations)</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token comment" style="color:#64748B;font-style:italic">// Configure connection to Fluss cluster</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Configuration</span><span class="token plain"> conf </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Configuration</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">conf</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setString</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"bootstrap.servers"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"localhost:9123"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain">  </span><span class="token comment" style="color:#64748B;font-style:italic">// Fluss server endpoint</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Connection</span><span class="token plain"> connection </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">ConnectionFactory</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createConnection</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">conf</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token comment" style="color:#64748B;font-style:italic">// Get Admin client for managing databases and tables</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Admin</span><span class="token plain"> admin </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getAdmin</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>The above code snippet shows the bare minimum requirements for connecting and interacting with a Fluss Cluster.
For our example we will use the following mock data - to keep things simple - which you can find below:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">SensorReading</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> readings </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">22.5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45.0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">87.5</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23.1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">44.5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">88.0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">21.8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">46.2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1012.9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">86.9</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">24.0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">43.8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">89.2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">22.9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45.3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">87.8</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23.4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">44.9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">88.3</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">7</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">21.7</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">46.5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1012.8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">86.5</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">24.2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">43.5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">89.5</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23.0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45.1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">87.9</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">22.6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45.7</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1013.0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">87.4</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">final</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token generics punctuation" style="color:#475569">&lt;</span><span class="token generics class-name" style="color:#7C3AED">SensorInfo</span><span class="token generics punctuation" style="color:#475569">&gt;</span><span class="token plain"> sensorInfos </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">List</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Outdoor Temp Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Roof"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Main Lobby Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Humidity"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lobby"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">20</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"ERROR"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Server Room Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Server Room"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"MAINTENANCE"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Warehouse Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Pressure"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Warehouse"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Conference Room Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Humidity"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Conference Room"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">25</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Office 1 Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Office 1"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">18</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"LOW_BATTERY"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">7</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Office 2 Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Humidity"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Office 2"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">7</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">12</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">45</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lab Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lab"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"ERROR"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Parking Lot Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Pressure"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Parking Lot"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">14</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">15</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Backyard Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Backyard"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">10</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"OK"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token comment" style="color:#64748B;font-style:italic">// SEND SOME UPDATES</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Main Lobby Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Humidity"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lobby"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">20</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"ERROR"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">9</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">48</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lab Sensor"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"Lab"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2024</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">8</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">30</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"ERROR"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2025</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">23</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">11</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">16</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="operating-the-cluster">Operating The Cluster<a href="https://fluss.apache.org/blog/fluss-java-client/#operating-the-cluster" class="hash-link" aria-label="Direct link to Operating The Cluster" title="Direct link to Operating The Cluster" translate="no">​</a></h2>
<p>Let's create a database for our IoT data, and within it define two tables:</p>
<ul>
<li class=""><strong>Sensor Readings Table:</strong> A log table that will collect time-series readings from sensors (like temperature and humidity readings). This table is append-only (new records are added continuously, with no updates/deletes) which is ideal for immutable event streams</li>
<li class=""><strong>Sensor Information Table:</strong> A primary key table that stores metadata for each sensor (like sensor ID, location, type). Each <code>sensorId</code> will be unique and acts as the primary key. This table can be updated as sensor info changes (e.g., sensor relocated or reconfigured).</li>
</ul>
<p>Using the Admin client, we can programmatically create these tables.</p>
<p>First, we'll ensure the database exists (creating it if not), then define schemas for each table and create them:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="schema-definitions">Schema Definitions<a href="https://fluss.apache.org/blog/fluss-java-client/#schema-definitions" class="hash-link" aria-label="Direct link to Schema Definitions" title="Direct link to Schema Definitions" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="log-table-sensor-readings">Log table (sensor readings)<a href="https://fluss.apache.org/blog/fluss-java-client/#log-table-sensor-readings" class="hash-link" aria-label="Direct link to Log table (sensor readings)" title="Direct link to Log table (sensor readings)" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">getSensorReadingsSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">INT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"timestamp"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">TIMESTAMP</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"temperature"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DOUBLE</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"humidity"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DOUBLE</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"pressure"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DOUBLE</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"batteryLevel"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DOUBLE</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="primary-key-table-sensor-information">Primary Key table (sensor information)<a href="https://fluss.apache.org/blog/fluss-java-client/#primary-key-table-sensor-information" class="hash-link" aria-label="Direct link to Primary Key table (sensor information)" title="Direct link to Primary Key table (sensor information)" translate="no">​</a></h4>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">getSensorInfoSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Schema</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newBuilder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">INT</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"name"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"type"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"location"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"installationDate"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">DATE</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"state"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">STRING</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">column</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"lastUpdated"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">DataTypes</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">TIMESTAMP</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">primaryKey</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">             </span><span class="token operator" style="color:#475569">&lt;</span><span class="token operator" style="color:#475569">--</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Define</span><span class="token plain"> a </span><span class="token class-name" style="color:#7C3AED">Primary</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">Key</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="table-creation">Table Creation<a href="https://fluss.apache.org/blog/fluss-java-client/#table-creation" class="hash-link" aria-label="Direct link to Table Creation" title="Direct link to Table Creation" translate="no">​</a></h3>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">void</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">setupTables</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Admin</span><span class="token plain"> admin</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">throws</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">ExecutionException</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">InterruptedException</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> readingsDescriptor </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token function" style="color:#7C3AED">getSensorReadingsSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">comment</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"This is the sensor readings table"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token comment" style="color:#64748B;font-style:italic">// drop the tables or ignore if they exist</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    admin</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">dropTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">readingsTablePath</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    admin</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">dropTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfoTablePath</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">     </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    admin</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">readingsTablePath</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> readingsDescriptor</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token plain"> sensorInfoDescriptor </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TableDescriptor</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">builder</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">schema</span><span class="token punctuation" style="color:#475569">(</span><span class="token function" style="color:#7C3AED">getSensorInfoSchema</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">distributedBy</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">comment</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"This is the sensor information table"</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">build</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">     </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    admin</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfoTablePath</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> sensorInfoDescriptor</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>We specify a distribution with <code>.distributedBy(3, "sensorId")</code>.
Fluss tables are partitioned into buckets (similar to partitions in Kafka topics) for scalability.
Here we use 3 buckets, meaning data gets distributed across 3 buckets. Multiple buckets allow for higher throughput or to parallelize reads/writes.
If using multiple buckets, Fluss would hash on the bucket key (<code>sensorId</code> in our case) to assign records to buckets.</p>
<p>For the <code>sensor_readings</code> table, we define a schema without any primary key. In Fluss, a table created without a primary key clause is a Log Table.
A log table only supports appending new records (no updates or deletes), making it perfect for immutable time-series data or logs.</p>
<p>In the log table, specifying a bucket key like <code>sensorId</code> ensures all readings from the same sensor end up to the same bucket providing strict ordering guarantees.</p>
<p>With our tables created let's go and write some data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="table-writes">Table Writes<a href="https://fluss.apache.org/blog/fluss-java-client/#table-writes" class="hash-link" aria-label="Direct link to Table Writes" title="Direct link to Table Writes" translate="no">​</a></h2>
<p>With our tables in place, let's insert some data using the Fluss Java API.
The client allows us to write or read data from it.
We'll demonstrate two patterns:</p>
<ul>
<li class=""><strong>Upserting</strong> into the primary key table (sensor information).</li>
<li class=""><strong>Appending</strong> to the log table (sensor readings).</li>
</ul>
<p>Fluss provides specialized writer interfaces for each table type: an <strong>UpsertWriter</strong> for primary key tables and an <strong>AppendWriter</strong> for log tables.
Under the hood, the Fluss client currently expects data as <strong>GenericRow</strong> objects (a generic row data format).</p>
<blockquote>
<p><strong>Note:</strong> Internally Fluss uses <strong>InternalRow</strong> as an optimized, binary representation of data for better performance and memory efficiency.
<strong>GenericRow</strong> is a generic implementation of InternalRow. This allows developers to interact with data easily while Fluss processes it efficiently using the underlying binary format.</p>
</blockquote>
<p>Since we are creating <strong>Pojos</strong> though this means that we need to convert these into a GenericRow in order to write them into Fluss.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">energyReadingToRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">.</span><span class="token keyword" style="color:#194670">class</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDeclaredFields</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sensorId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TimestampNtz</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromLocalDateTime</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">timestamp</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">temperature</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">humidity</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">pressure</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">batteryLevel</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">public</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">static</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">sensorInfoToRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token plain"> sensorInfo</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token keyword" style="color:#194670">class</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDeclaredFields</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">length</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sensorId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BinaryString</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromString</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BinaryString</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromString</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">type</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BinaryString</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromString</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">location</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">int</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">installationDate</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toEpochDay</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">BinaryString</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromString</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">state</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">setField</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">TimestampNtz</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">fromLocalDateTime</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">lastUpdated</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain">     </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">return</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p><strong>Note:</strong> For certain data types like <code>String</code> or <code>LocalDateTime</code> we need to use certain functions like
<code>BinaryString.fromString("string_value")</code> or <code>TimestampNtz.fromLocalDateTime(datetime)</code> otherwise you might
come across some conversion exceptions.</p>
<p>Let's start by writing data to the <code>Log Table</code>. This requires getting an <code>AppendWriter</code> as follows:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Creating table writer for table {} ..."</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">SENSOR_READINGS_TBL</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Table</span><span class="token plain"> table </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getSensorReadingsTablePath</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">AppendWriter</span><span class="token plain"> writer </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> table</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newAppend</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createWriter</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">readings</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">forEach</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">reading </span><span class="token operator" style="color:#475569">-&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">energyReadingToRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">reading</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">append</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">writer</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">flush</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Sensor Readings Written Successfully."</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>At this point we have successfully written 10 sensor readings to our table.</p>
<p>Next, let's write data to the <code>Primary Key Table</code>. This requires getting an <code>UpsertWriter</code> as follows:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain">logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Creating table writer for table {} ..."</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token constant" style="color:#12325C">SENSOR_INFORMATION_TBL</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">Table</span><span class="token plain"> sensorInfoTable </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> connection</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTable</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getSensorInfoTablePath</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">UpsertWriter</span><span class="token plain"> upsertWriter </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sensorInfoTable</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newUpsert</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createWriter</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token class-name" style="color:#7C3AED">AppUtils</span><span class="token punctuation" style="color:#475569">.</span><span class="token plain">sensorInfos</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">forEach</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo </span><span class="token operator" style="color:#475569">-&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">GenericRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token function" style="color:#7C3AED">sensorInfoToRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">sensorInfo</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    upsertWriter</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">upsert</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">upsertWriter</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">flush</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>At this point we have successfully written 10 sensor information records to our table, because
updates will be handled on the primary key and merged.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scans--lookups">Scans &amp; Lookups<a href="https://fluss.apache.org/blog/fluss-java-client/#scans--lookups" class="hash-link" aria-label="Direct link to Scans &amp; Lookups" title="Direct link to Scans &amp; Lookups" translate="no">​</a></h2>
<p>Now comes the real-time data enrichment part of our example.
We want to simulate a process where each incoming sensor reading is immediately looked up against the sensor information table to add context (like location and type) to the raw reading.
This is a common pattern in streaming systems, often achieved with lookup joins.</p>
<p>With the Fluss Java client, we can do this by combining a <strong>log scanner on the readings table</strong> with <strong>point lookups on the sensor information table</strong>.</p>
<p>To consume data from a Fluss table, we use a *<em>Scanner</em>.
For a log table, Fluss provides a <strong>LogScanner</strong> that allows us to <strong>subscribe to one or more buckets</strong> and poll for new records.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token class-name" style="color:#7C3AED">LogScanner</span><span class="token plain"> logScanner </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> readingsTable</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newScan</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain">         </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createLogScanner</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token class-name" style="color:#7C3AED">Lookuper</span><span class="token plain"> sensorInfoLookuper </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sensorInfoTable</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newLookup</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createLookuper</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>We set up a scanner on the <code>sensor_readings</code> table, and next we need to subscribe to all its buckets, and then poll for any available records:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token keyword" style="color:#194670">int</span><span class="token plain"> numBuckets </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> readingsTable</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTableInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getNumBuckets</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token keyword" style="color:#194670">int</span><span class="token plain"> i </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"> i </span><span class="token operator" style="color:#475569">&lt;</span><span class="token plain"> numBuckets</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"> i</span><span class="token operator" style="color:#475569">++</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">     </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Subscribing to Bucket {}."</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> i</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    logScanner</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">subscribeFromBeginning</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">i</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Start polling for records. For each incoming record we will use the <strong>Lookuper</strong> to <code>lookup</code> sensor information from the primary key table,
and creating a <strong>SensorReadingEnriched</strong> record.</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token plain"> </span><span class="token keyword" style="color:#194670">while</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token boolean" style="color:#B45309">true</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Polling for records..."</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token class-name" style="color:#7C3AED">ScanRecords</span><span class="token plain"> scanRecords </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> logScanner</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">poll</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">Duration</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">ofSeconds</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">TableBucket</span><span class="token plain"> bucket </span><span class="token operator" style="color:#475569">:</span><span class="token plain"> scanRecords</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">buckets</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">ScanRecord</span><span class="token plain"> record </span><span class="token operator" style="color:#475569">:</span><span class="token plain"> scanRecords</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">records</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">bucket</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#475569">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">InternalRow</span><span class="token plain"> row </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> record</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getRow</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Received reading from sensor '{}' at '{}'."</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTimestampNtz</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Performing lookup to get the information for sensor '{}'. "</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">LookupResult</span><span class="token plain"> lookupResult </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> sensorInfoLookuper</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">lookup</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token plain"> sensorInfo </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> lookupResult</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getRowList</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">stream</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">map</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">r </span><span class="token operator" style="color:#475569">-&gt;</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorInfo</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getString</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getString</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getString</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    </span><span class="token class-name" style="color:#7C3AED">LocalDate</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">ofEpochDay</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getString</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">parse</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">r</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTimestampNtz</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> formatter</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">findFirst</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">get</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Retrieved information for '{}' with id: {}"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sensorId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token plain"> reading </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReading</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getInt</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">0</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    </span><span class="token class-name" style="color:#7C3AED">LocalDateTime</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">parse</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain">row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getTimestampNtz</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token number" style="color:#B45309">6</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">toString</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> formatter</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDouble</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">2</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDouble</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDouble</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    row</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">getDouble</span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">5</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token class-name" style="color:#7C3AED">SensorReadingEnriched</span><span class="token plain"> readingEnriched </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> </span><span class="token keyword" style="color:#194670">new</span><span class="token plain"> </span><span class="token class-name" style="color:#7C3AED">SensorReadingEnriched</span><span class="token punctuation" style="color:#475569">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">sensorId</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">timestamp</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">temperature</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">humidity</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">pressure</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    reading</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">batteryLevel</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">name</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">type</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">location</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">                    sensorInfo</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">state</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            </span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"Bucket: {} - {}"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> bucket</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> readingEnriched</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">            logger</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">info</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"---------------------------------------"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">        </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token punctuation" style="color:#475569">}</span><br></div></code></pre></div></div>
<p>Let's summarize what's happening here:</p>
<ul>
<li class="">We create a LogScanner for the <code>sensor_readings</code> table using <em>table.newScan().createLogScanner()</em>.</li>
<li class="">We subscribe to each bucket of the table from the beginning (offset 0). Subscribing <code>from beginning</code> means we'll read all existing data from the start; alternatively, one could subscribe from the latest position to only get new incoming data or based on other attributes like time. In our case, since we just inserted data, from-beginning will capture those inserts.</li>
<li class="">We then call <code>poll(Duration)</code> on the scanner to retrieve available records, waiting up to the given timeout (1 second here). This returns a <code>ScanRecords</code> batch containing any records that were present. We iterate over each <code>TableBucket</code> and then over each <code>ScanRecord</code> within that bucket.</li>
<li class="">For each record, we extract the fields via the InternalRow interface (which provides typed access to each column in the row) and <strong>convert them into a Pojo</strong>.</li>
<li class="">Next, for each reading, we perform a <strong>lookup</strong> on the <strong>sensor_information</strong> table to get the sensor's info. We construct a key (GenericRow with just the sensor_id) and use <strong>sensorTable.newLookup().createLookuper().lookup(key)</strong>. This performs a point lookup by primary key and returns a <code>LookupResult future</code>; we call <code>.get()</code> to get the result synchronously. If present, we retrieve the InternalRow of the sensor information and <strong>convert it into a Pojo</strong>.</li>
<li class="">We then combine the data: logging an enriched message that includes the sensor's information alongside the reading values.</li>
</ul>
<p>Fluss's lookup API gives us quick primary-key retrieval from a table, which is exactly what we need to enrich the streaming data.
In a real application, this enrichment could be done on the fly in a streaming job (and indeed <strong>Fluss is designed to support high-QPS lookup joins in real-time pipelines</strong>), but here we're simulating it with client calls for clarity.</p>
<p>If you run the above code found <a href="https://github.com/ververica/ververica-fluss-examples" target="_blank" rel="noopener noreferrer" class="">here</a>, you should see an output like the following:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.594 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">DownloadRemoteLog-</span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">sensors_db.sensor_readings_tbl</span><span class="token punctuation" style="color:#475569">]</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> c.a.f.c.t.s.l.RemoteLogDownloader</span><span class="token variable" style="color:#12325C">$DownloadRemoteLogThread</span><span class="token plain"> - Starting</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.599 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">0</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.599 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">1</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.600 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">2</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.600 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Polling </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> records</span><span class="token punctuation" style="color:#475569">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.965 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Received reading from sensor </span><span class="token string" style="color:#0E7C66">'3'</span><span class="token plain"> at </span><span class="token string" style="color:#0E7C66">'2025-06-23T09:45'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:13.966 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Performing lookup to get the information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> sensor </span><span class="token string" style="color:#0E7C66">'3'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.032 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Retrieved information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Server Room Sensor'</span><span class="token plain"> with id: </span><span class="token number" style="color:#B45309">3</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.033 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - SensorReadingEnriched</span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">sensorId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">3</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">timestamp</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2025</span><span class="token plain">-06-23T09:45, </span><span class="token assign-left variable" style="color:#12325C">temperature</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">21.8</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">humidity</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">46.2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">pressure</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1012.9</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">batteryLevel</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">86.9</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">name</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Server Room Sensor, </span><span class="token assign-left variable" style="color:#12325C">type</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Temperature, </span><span class="token assign-left variable" style="color:#12325C">location</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Server Room, </span><span class="token assign-left variable" style="color:#12325C">state</span><span class="token operator" style="color:#475569">=</span><span class="token plain">MAINTENANCE</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.045 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.046 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Received reading from sensor </span><span class="token string" style="color:#0E7C66">'4'</span><span class="token plain"> at </span><span class="token string" style="color:#0E7C66">'2025-06-23T10:00'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.046 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Performing lookup to get the information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> sensor </span><span class="token string" style="color:#0E7C66">'4'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.128 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Retrieved information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Warehouse Sensor'</span><span class="token plain"> with id: </span><span class="token number" style="color:#B45309">4</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.128 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - SensorReadingEnriched</span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">sensorId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">4</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">timestamp</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2025</span><span class="token plain">-06-23T10:00, </span><span class="token assign-left variable" style="color:#12325C">temperature</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">24.0</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">humidity</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">43.8</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">pressure</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1013.5</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">batteryLevel</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">89.2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">name</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Warehouse Sensor, </span><span class="token assign-left variable" style="color:#12325C">type</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Pressure, </span><span class="token assign-left variable" style="color:#12325C">location</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Warehouse, </span><span class="token assign-left variable" style="color:#12325C">state</span><span class="token operator" style="color:#475569">=</span><span class="token plain">OK</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.129 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.129 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Received reading from sensor </span><span class="token string" style="color:#0E7C66">'8'</span><span class="token plain"> at </span><span class="token string" style="color:#0E7C66">'2025-06-23T11:00'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.129 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Performing lookup to get the information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> sensor </span><span class="token string" style="color:#0E7C66">'8'</span><span class="token builtin class-name" style="color:#7C3AED">.</span><span class="token plain"> </span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.229 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Retrieved information </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">'Lab Sensor'</span><span class="token plain"> with id: </span><span class="token number" style="color:#B45309">8</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.229 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - SensorReadingEnriched</span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">sensorId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">8</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">timestamp</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2025</span><span class="token plain">-06-23T11:00, </span><span class="token assign-left variable" style="color:#12325C">temperature</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">24.2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">humidity</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">43.5</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">pressure</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1013.6</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">batteryLevel</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">89.5</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">name</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Lab Sensor, </span><span class="token assign-left variable" style="color:#12325C">type</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Temperature, </span><span class="token assign-left variable" style="color:#12325C">location</span><span class="token operator" style="color:#475569">=</span><span class="token plain">Lab, </span><span class="token assign-left variable" style="color:#12325C">state</span><span class="token operator" style="color:#475569">=</span><span class="token plain">ERROR</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:07:14.229 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="column-pruning-scans">Column Pruning Scans<a href="https://fluss.apache.org/blog/fluss-java-client/#column-pruning-scans" class="hash-link" aria-label="Direct link to Column Pruning Scans" title="Direct link to Column Pruning Scans" translate="no">​</a></h2>
<p>Column pruning lets you fetch only the columns you need, <strong>reducing network overhead and improving read performance</strong>. With Fluss’s Java client, you can specify a subset of columns in your scan:</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token class-name" style="color:#7C3AED">LogScanner</span><span class="token plain"> logScanner </span><span class="token operator" style="color:#475569">=</span><span class="token plain"> readingsTable</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">newScan</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">project</span><span class="token punctuation" style="color:#475569">(</span><span class="token class-name" style="color:#7C3AED">List</span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">of</span><span class="token punctuation" style="color:#475569">(</span><span class="token string" style="color:#0E7C66">"sensorId"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"timestamp"</span><span class="token punctuation" style="color:#475569">,</span><span class="token plain"> </span><span class="token string" style="color:#0E7C66">"temperature"</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain">    </span><span class="token punctuation" style="color:#475569">.</span><span class="token function" style="color:#7C3AED">createLogScanner</span><span class="token punctuation" style="color:#475569">(</span><span class="token punctuation" style="color:#475569">)</span><span class="token punctuation" style="color:#475569">;</span><br></div></code></pre></div></div>
<p>Let's break this down:</p>
<ul>
<li class=""><code>.project(...)</code> instructs the client to request only the specified columns (sensorId,timestamp and temperature) from the server.</li>
<li class="">Fluss’s columnar storage means non-requested columns (e.g., humidity, etc.) <strong>aren’t transmitted, saving bandwidth and reducing client-side parsing overhead</strong>.</li>
<li class="">You can combine projection with filters or lookups to further optimize your data access patterns.</li>
</ul>
<p>Example output:</p>
<div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#0A0F1C;--prism-background-color:#F8FAFC"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar" style="color:#0A0F1C;background-color:#F8FAFC"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#0A0F1C"><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.114 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">0</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.114 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">1</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.114 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Subscribing to Bucket </span><span class="token number" style="color:#B45309">2</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.114 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Polling </span><span class="token keyword" style="color:#194670">for</span><span class="token plain"> records</span><span class="token punctuation" style="color:#475569">..</span><span class="token plain">.</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.171 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - </span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">3,2025</span><span class="token plain">-06-23T09:45,21.8</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - </span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">4,2025</span><span class="token plain">-06-23T10:00,24.0</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - </span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">8,2025</span><span class="token plain">-06-23T11:00,24.2</span><span class="token punctuation" style="color:#475569">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - ---------------------------------------</span><br></div><div class="token-line" style="color:#0A0F1C"><span class="token plain"></span><span class="token number" style="color:#B45309">16</span><span class="token plain">:12:35.172 INFO  </span><span class="token punctuation" style="color:#475569">[</span><span class="token plain">main</span><span class="token punctuation" style="color:#475569">]</span><span class="token plain"> com.ververica.scanner.FlussScanner - Bucket: TableBucket</span><span class="token punctuation" style="color:#475569">{</span><span class="token plain">tableId</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">2</span><span class="token plain">, </span><span class="token assign-left variable" style="color:#12325C">bucket</span><span class="token operator" style="color:#475569">=</span><span class="token number" style="color:#B45309">1</span><span class="token punctuation" style="color:#475569">}</span><span class="token plain"> - </span><span class="token punctuation" style="color:#475569">(</span><span class="token number" style="color:#B45309">10,2025</span><span class="token plain">-06-23T11:30,22.6</span><span class="token punctuation" style="color:#475569">)</span><br></div></code></pre></div></div>
<p>Notice, how only the requested columns are returned from the server.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://fluss.apache.org/blog/fluss-java-client/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>In this blog post, we've introduced the Fluss Java Client by guiding you through a full example of creating tables, writing data, and reading/enriching data in real-time.
We covered how to use the <code>Admin</code> client to define a <strong>Primary Key table</strong> (for reference data that can be updated) and a <strong>Log table</strong> (for immutable event streams), and how to use the Fluss client to upsert and append data accordingly.
We also demonstrated reading from a log table using a scanner and performing a lookup on a primary key table to enrich the streaming data on the fly.</p>
<p>This IoT sensor scenario is just one example of Fluss in action and also highlights the <strong>Stream/Table duality</strong> within the same system.
Fluss's ability to handle high-throughput append streams and fast key-based lookups makes it well-suited for real-time analytics use cases like this and many others.
With this foundation, you can explore more advanced features of Fluss to build robust real-time data applications. Happy streaming! 🌊</p>
<p>And before you go 😊 don’t forget to give Fluss 🌊 some ❤️ via ⭐ on <a href="https://github.com/apache/fluss" target="_blank" rel="noopener noreferrer" class="">GitHub</a></p>]]></content:encoded>
        </item>
    </channel>
</rss>