<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[Tiger Data Blog]]></title>
        <description><![CDATA[Insights, product updates, and tips from TigerData (Creators of TimescaleDB) engineers on Postgres, time series & AI. IoT, crypto, and analytics tutorials & use cases.]]></description>
        <link>https://www.tigerdata.com/blog</link>
        <image>
            <url>https://www.tigerdata.com/icon.ico</url>
            <title>Tiger Data Blog</title>
            <link>https://www.tigerdata.com/blog</link>
        </image>
        <generator>RSS for Node</generator>
        <lastBuildDate>Tue, 07 Apr 2026 09:54:48 GMT</lastBuildDate>
        <atom:link href="https://www.tigerdata.com/blog" rel="self" type="application/rss+xml"/>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Start on Postgres, Scale on Postgres: How TimescaleDB 2.25 Continues to Improve the Way Postgres Scales]]></title>
            <description><![CDATA[Start on Postgres, scale on Postgres: TimescaleDB 2.25 delivers 289× faster queries, better chunk pruning, and lower-cost continuous aggregates at scale.]]></description>
            <link>https://www.tigerdata.com/blog/start-on-postgres-scale-on-postgres</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/start-on-postgres-scale-on-postgres</guid>
            <category><![CDATA[Announcements & Releases]]></category>
            <category><![CDATA[TimescaleDB]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Mike Freedman]]></dc:creator>
            <pubDate>Tue, 17 Feb 2026 17:33:46 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2026/02/timescaledb-2-25.png">
            </media:content>
            <content:encoded><![CDATA[<p>Most developers start building with Postgres because it’s simple, reliable, and flexible. You get a clear relational model, transactional semantics you can trust, and an ecosystem that lets teams move quickly without committing to a complex architecture early. The challenge is keeping that simplicity as systems grow. Higher ingest, larger datasets, and increasingly real-time analytical workloads can push teams toward a second system long before they want one.</p><p>This pressure is most visible in time-series workloads that demand real-time performance. High write rates, append-heavy tables, and repeated queries over recent windows stress both storage and execution paths. Without reducing the amount of work required per query, scale quickly becomes an architectural problem rather than a <a href="https://timescale.ghost.io/blog/postgres-optimization-treadmill/" rel="noreferrer">performance optimization</a> one, shifting effort from incremental tuning to changes in system design.</p><p>TimescaleDB is designed to change that trajectory. “Start on Postgres, scale on Postgres” is a promise, but it is grounded in a specific architectural approach: performance at scale comes from reducing the work the database must do as data grows, then parallelizing what remains. TimescaleDB 2.25 continues this evolution by tightening the execution and maintenance paths that dominate cost at scale, so common workloads become cheaper and operationally steadier under sustained growth.</p><p>This release focuses on three outcomes: faster queries without constant tuning, efficient scaling to larger datasets and higher ingest, and real-time analytics that stays current and trustworthy without introducing a second system.</p><h2 id="faster-postgres-queries-at-scale-with-less-tuning">Faster Postgres queries at scale, with less tuning</h2><p>Compression, chunk pruning, and columnar execution already reduce query cost by limiting how much data needs to be read and processed. In 2.25, more queries can avoid work entirely, and the planner is more consistent about selecting those cheaper plans.</p><p>A clear example is aggregation on compressed data. In earlier releases, queries using functions like <code>MIN</code>, <code>MAX</code>, <code>FIRST</code>, or <code>LAST</code> benefited from compression and metadata, but they still required scanning compressed batches and performing aggregation during execution. The scan was cheaper than a row-oriented approach, but it was still work proportional to the data touched.</p><p>In 2.25, these aggregates can often be answered directly from sparse metadata maintained for compressed chunks. The planner can choose a custom execution path that reads summaries rather than scanning or decompressing data. This is implemented via the new <code>ColumnarIndexScan</code> plan node (see <a href="https://github.com/timescale/timescaledb/pull/9088"><u>PR #9088</u></a>,<a href="https://github.com/timescale/timescaledb/pull/9103"> <u>PR #9103</u></a>, and <a href="https://github.com/timescale/timescaledb/pull/9108"><u>PR #9108</u></a>). On workloads where this applies, the 2.25 release notes report this class of queries speeding up by up to 289x. For teams running dashboards or monitoring queries over large compressed datasets, this can translate into dramatically faster response times with no query changes required.</p><p>The important shift here is in cost structure. Once an answer can be derived from metadata, performance is no longer tied to the number of rows stored inside a chunk. It is tied to the minimum work required to identify relevant chunks and read their summaries, which becomes more valuable as datasets grow.</p><p>A complementary improvement applies the same idea to another common pattern: time-filtered queries that do not need to materialize column values. For queries like <code>SELECT COUNT(*) FROM events WHERE time &gt; ...</code>, previously, the execution path could still require decompressing the time column to evaluate the predicate, even though the query does not need to read time values for every row. In 2.25, the time column can often be skipped entirely for these cases, reducing CPU and memory pressure while preserving the same result (see <a href="https://github.com/timescale/timescaledb/pull/9094"><u>PR #9094</u></a>). The release notes describe this pattern as up to 50x faster for the example query.</p><p>As these fast paths expand, plan stability becomes just as important as peak speed. Even when an efficient path exists, teams feel it when the planner chooses it inconsistently or when small changes in query shape lead to surprising regressions. In 2.25, planner improvements around columnar scan paths and ordering help make compression-aware execution more predictable (see <a href="https://github.com/timescale/timescaledb/pull/8986"><u>PR #8986</u></a> and <a href="https://github.com/timescale/timescaledb/pull/9133"><u>PR #9133</u></a>). Fewer surprises mean less time spent tuning and diagnosing why a query slowed down as data evolved.</p><h2 id="efficient-scaling-for-high-ingest-postgres-workloads">Efficient scaling for high-ingest Postgres workloads</h2><p>A hard part of scaling is not only achieving good performance at a given size, but preserving efficiency as data volume, ingest rate, and concurrency grow together over time. In practice, scaling pressure shows up in two ways. Some costs grow gradually, such as planning and execution work increasing with the number of partitions. Others appear more abruptly, when accumulated complexity makes execution brittle and small changes in data or query shape trigger different plans and sudden slowdowns.</p><p>TimescaleDB’s scaling model is designed to address both. It relies on clear boundaries: partitioning data into chunks, using metadata to prune irrelevant chunks, and compressing data to reduce the work required within each chunk. In 2.25, several refinements make these boundaries behave more efficiently and consistently under sustained growth.</p><p>One pressure point is that chunk counts rise over long retention windows, making pruning and constraint handling increasingly important. Earlier versions already used constraints and metadata to skip irrelevant chunks, but there were cases where constraint handling became more permissive than necessary, causing queries to consider more chunks than required as datasets aged. In 2.25, constraint handling improves for fully covered chunks, helping keep both planning and execution costs more tightly bounded as data volume increases (see <a href="https://github.com/timescale/timescaledb/pull/9127"><u>PR #9127</u></a>).</p><p>Planning behavior under high partition counts is another area where inefficiency and brittleness can emerge together. As hypertables accumulate thousands of chunks, planning time and plan quality can matter as much as execution speed, especially for joins and more complex query shapes. TimescaleDB 2.25 includes fixes for a planning performance regression on Postgres 16 and later affecting some join queries (see <a href="https://github.com/timescale/timescaledb/pull/8706"><u>PR #8706</u></a>). These changes reduce both how quickly planning cost grows and how likely it is to tip into unstable behavior as workloads evolve.</p><p>The result is more efficient scaling in practice. Costs still grow with data, but they grow more slowly and with fewer surprises, allowing Postgres to continue scaling in place rather than forcing architectural changes to manage accumulated overhead.</p><h2 id="real-time-analytics-in-postgres-without-a-split-architecture">Real-time analytics in Postgres, without a split architecture</h2><p>As refresh frequency increases and datasets grow, keeping analytics fresh inside the primary database can create background pressure. That pressure grows unless refresh and maintenance paths stay efficient. TimescaleDB has long supported real-time analytics inside Postgres through continuous aggregates, compression, and retention policies. In 2.25, the focus is on lowering the operational footprint of staying current as systems run continuously.</p><p>One improvement is compressed continuous aggregate refresh. Earlier versions supported refreshing into compressed hypertables, but the refresh path could include intermediate steps that added extra I/O and CPU work. In 2.25, direct compression on continuous aggregate refresh is enabled via a configuration option, reducing unnecessary data movement when keeping aggregates up to date (see <a href="https://github.com/timescale/timescaledb/pull/8777"><u>PR #8777</u></a> and <a href="https://github.com/timescale/timescaledb/pull/9038"><u>PR #9038</u></a>). The semantics are unchanged, but the cost of maintaining freshness is lower, especially for frequent refresh schedules.</p><p>This is complemented by refinements to batching. Large refresh transactions can temporarily increase WAL volume and create uneven load. In 2.25, the default <code>buckets_per_batch</code> for continuous aggregate refresh policies is adjusted to keep transactions smaller (from 1 to 10 buckets), reducing WAL holding and making refresh behavior steadier under sustained ingest (see <a href="https://github.com/timescale/timescaledb/pull/9031"><u>PR #9031</u></a>).</p><p>The release also includes incremental improvements that reduce background churn from lifecycle operations like retention and deletes on long-running datasets, along with correctness and robustness fixes for compressed and partitioned workloads. For example, support for retention policies on UUIDv7-partitioned hypertables expands the set of configurations where lifecycle management remains reliable over time (see <a href="https://github.com/timescale/timescaledb/pull/9102"><u>PR #9102</u></a>). These changes are small individually, but they matter for trust. Real-time analytics only works if results stay aligned with transactional truth as schemas and workloads evolve.</p><h2 id="closing">Closing</h2><p>TimescaleDB 2.25 continues to make Postgres a better place to run real-time analytics at scale: faster queries through less work, smoother behavior as data and ingest grow, and lower operational overhead for keeping analytics current and correct.&nbsp;</p><p>All in service of a simple yet powerful idea: <strong>start on Postgres, scale on Postgres. </strong><a href="https://timescale.ghost.io/blog/postgres-optimization-treadmill/" rel="noreferrer"><strong>Learn why vanilla Postgres hits performance ceilings at scale</strong></a><strong>.</strong></p><p><strong><em>To learn more, check out the </em></strong><a href="https://github.com/timescale/timescaledb/releases"><strong><em><u>full release notes</u></em></strong></a><strong><em> or </em></strong><a href="https://console.cloud.timescale.com/signup"><strong><em><u>try Tiger Cloud for free</u></em></strong></a><strong><em> and experience TimescaleDB 2.25 on your largest hypertables. </em></strong><a href="https://www.tigerdata.com/blog/from-4-databases-to-1-how-plexigrid-replaced-influxdb-got-350x-faster-queries-tiger-data" rel="noreferrer"><strong><em>Learn how Plexigrid consolidated 4 databases into Postgres and got 350x faster queries.</em></strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Postgres for Agents]]></title>
            <description><![CDATA[Agentic Postgres: the first database built for agents. Native search, instant forks, MCP integration, new CLI, and free tier. Built for agents. Designed for developers.]]></description>
            <link>https://www.tigerdata.com/blog/postgres-for-agents</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/postgres-for-agents</guid>
            <category><![CDATA[Announcements & Releases]]></category>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[AI agents]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Ajay Kulkarni]]></dc:creator>
            <pubDate>Tue, 21 Oct 2025 13:46:50 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2025/10/2025-ABL-Launch-Blog-Thumbnail.png">
            </media:content>
            <content:encoded><![CDATA[<h3 id="announcing-agentic-postgres-the-first-database-built-for-agents"><em>Announcing Agentic Postgres: The first database built for agents.</em></h3><h2 id="agents-are-the-new-developer">Agents are the New Developer</h2><p>80% of Claude Code <a href="https://www.reddit.com/r/singularity/comments/1khxwjh/claude_code_wrote_80_of_its_own_code_anthropic_dev/"><u>was written by AI</u></a>. More than a quarter of all new code at Google <a href="https://arstechnica.com/ai/2024/10/google-ceo-says-over-25-of-new-google-code-is-generated-by-ai/"><u>was generated by AI</u></a> <em>one year ago</em>. It’s safe to say that in the next 12 months, the majority of all new code will be written by AI.</p><p>Agents don’t behave like humans. They behave in new ways. Software development tools need to evolve. Agents need a new kind of database made for how they work.</p><p>But what would a database for agents look like?</p><p>At Tiger, we’ve obsessed over databases for the past 10 years. We’ve built high-performance systems for time series data, scaled Postgres across millions of workloads, and served thousands of customers and hundreds of thousands of developers around the world.&nbsp;</p><p>​​So when agents arrived, we felt it immediately. In our bones. This new era of computing would need its own kind of data infrastructure. One that still delivered power without complexity, but built for a new type of user.&nbsp;</p><p>How do agents behave?</p><ul><li>Agents don’t click, they call.</li><li>Agents don’t remember, they retrieve.</li><li>Agents can download expertise to become experts.</li><li>Agents can parallelize effortlessly, acting like a multi-threaded team.</li><li>Agents need a safe sandbox where they can play (or wreak havoc).</li><li>Agents can also hammer your infrastructure (and your budget) if you’re not careful.</li></ul><p>We started on this problem over a year ago. Multiple teams working in parallel, months of engineering and internal user feedback, rethinking everything from the storage layer to how agents actually reason.&nbsp;</p><p>Here’s what we built.&nbsp;</p><h2 id="introducing-agentic-postgres">Introducing Agentic Postgres</h2><p>Today we’re launching Agentic Postgres, the first database designed from the ground up for agents. It includes:</p><p><strong>The best database MCP server ever built</strong></p><p>Agentic Postgres includes our new <a href="https://www.tigerdata.com/blog/free-postgres-mcp-prompt-templates" rel="noreferrer">MCP server</a> that enables agents not just to interact with the database but also understand how to use it well. We’ve taken our 10+ years of Postgres experience and distilled it into a set of built-in master prompts. This gives agents safe, structured access to the database through high-level tools for schema design, query tuning, migrations, and more. The MCP server also performs native full-text and semantic search over the Postgres docs, so agents can instantly retrieve the right context as they think.&nbsp;</p><pre><code class="language-markdown">&gt; I want to create a personal assistant app. Please create a free service on Tiger. Then using Postgres best practices, describe the schema you would create.</code></pre><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/DE84BB33-B4FE-4F6E-8398-9267033F6870-2_thumb.jpg" data-kg-custom-thumbnail="">
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/DE84BB33-B4FE-4F6E-8398-9267033F6870-2.mp4" poster="https://img.spacergif.org/v1/1920x1080/0a/spacer.png" width="1920" height="1080" loop="" autoplay="" muted="" playsinline="" preload="metadata" style="background: transparent url('https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/DE84BB33-B4FE-4F6E-8398-9267033F6870-2_thumb.jpg') 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:30</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1×</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"></path>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p><strong>Native search and retrieval</strong></p><p>Agentic Postgres comes with native full-text and semantic search built directly into the database. For semantic search, we’ve improved our existing extension pgvectorscale, for higher-throughput indexing, better recall, and lower latency at scale than pgvector.&nbsp;</p><p>For full-text search, <a href="https://www.tigerdata.com/blog/introducing-pg_textsearch-true-bm25-ranking-hybrid-retrieval-postgres" rel="noreferrer">pg_textsearch</a>, our newest Postgres extension, implements BM25 for modern ranked keyword search optimized for hybrid AI applications alongside pgvectorscale. The current preview release uses an in-memory structure for fast writes and queries. Future releases will add disk-based segments with compression and BlockMax WAND optimization, applying the same battle-tested techniques from production search engines.</p><p>Together, these extensions let agents retrieve structured data instantly without leaving Postgres.&nbsp;&nbsp;</p><pre><code class="language-markdown">&gt; Using service qilk2gqjuz, analyze user feedback with hybrid search (combining text search and semantic search). Group similar feedback by theme and show counts for each theme, using an ascii bar chart. First, look at the pg_textsearch (BM25) and pgvectorscale documentation in the Tiger docs to get the proper syntax, and then use those extensions.</code></pre><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-2--1-_thumb.jpg" data-kg-custom-thumbnail="">
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-2--1-.mp4" poster="https://img.spacergif.org/v1/1920x1080/0a/spacer.png" width="1920" height="1080" loop="" autoplay="" muted="" playsinline="" preload="metadata" style="background: transparent url('https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-2--1-_thumb.jpg') 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">0:50</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1×</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"></path>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p><strong>Fast, zero-copy forks</strong></p><p>At the core of Agentic Postgres is a new copy-on-write block storage layer that makes <a href="https://www.tigerdata.com/blog/fast-zero-copy-database-forks" rel="noreferrer">databases instantly forkable</a>. Every agent can spin up its own isolated environment, a full copy of production data in seconds, without duplicating data (or costs). Every fork is lightweight and efficient, so you only pay for the blocks that change. It’s perfect for experiments, benchmarks, and migrations that can run safely in parallel.&nbsp;</p><pre><code class="language-markdown">&gt; Please create a fork of gf868h9j1y using the last snapshot, and then test 3 different indexes that we should create to speed up performance, then delete the fork, and report back on your findings. Before you start run “tiger service fork --help” and “tiger service delete --help” to get the right syntax. Use MCP over psql, using the password from the local keychain.</code></pre><figure class="kg-card kg-video-card kg-width-regular" data-kg-thumbnail="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-3--1-_thumb.jpg" data-kg-custom-thumbnail="">
            <div class="kg-video-container">
                <video src="https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-3--1-.mp4" poster="https://img.spacergif.org/v1/1920x1080/0a/spacer.png" width="1920" height="1080" loop="" autoplay="" muted="" playsinline="" preload="metadata" style="background: transparent url('https://storage.ghost.io/c/6b/cb/6bcb39cf-9421-4bd1-9c9d-fa7b6755ba0e/content/media/2025/10/demo-3--1-_thumb.jpg') 50% 50% / cover no-repeat;"></video>
                <div class="kg-video-overlay">
                    <button class="kg-video-large-play-icon" aria-label="Play video">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                            <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                        </svg>
                    </button>
                </div>
                <div class="kg-video-player-container kg-video-hide">
                    <div class="kg-video-player">
                        <button class="kg-video-play-icon" aria-label="Play video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M23.14 10.608 2.253.164A1.559 1.559 0 0 0 0 1.557v20.887a1.558 1.558 0 0 0 2.253 1.392L23.14 13.393a1.557 1.557 0 0 0 0-2.785Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-pause-icon kg-video-hide" aria-label="Pause video">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <rect x="3" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                                <rect x="14" y="1" width="7" height="22" rx="1.5" ry="1.5"></rect>
                            </svg>
                        </button>
                        <span class="kg-video-current-time">0:00</span>
                        <div class="kg-video-time">
                            /<span class="kg-video-duration">1:06</span>
                        </div>
                        <input type="range" class="kg-video-seek-slider" max="100" value="0">
                        <button class="kg-video-playback-rate" aria-label="Adjust playback speed">1×</button>
                        <button class="kg-video-unmute-icon" aria-label="Unmute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M15.189 2.021a9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h1.794a.249.249 0 0 1 .221.133 9.73 9.73 0 0 0 7.924 4.85h.06a1 1 0 0 0 1-1V3.02a1 1 0 0 0-1.06-.998Z"></path>
                            </svg>
                        </button>
                        <button class="kg-video-mute-icon kg-video-hide" aria-label="Mute">
                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
                                <path d="M16.177 4.3a.248.248 0 0 0 .073-.176v-1.1a1 1 0 0 0-1.061-1 9.728 9.728 0 0 0-7.924 4.85.249.249 0 0 1-.221.133H5.25a3 3 0 0 0-3 3v2a3 3 0 0 0 3 3h.114a.251.251 0 0 0 .177-.073ZM23.707 1.706A1 1 0 0 0 22.293.292l-22 22a1 1 0 0 0 0 1.414l.009.009a1 1 0 0 0 1.405-.009l6.63-6.631A.251.251 0 0 1 8.515 17a.245.245 0 0 1 .177.075 10.081 10.081 0 0 0 6.5 2.92 1 1 0 0 0 1.061-1V9.266a.247.247 0 0 1 .073-.176Z"></path>
                            </svg>
                        </button>
                        <input type="range" class="kg-video-volume-slider" max="100" value="100">
                    </div>
                </div>
            </div>
            
        </figure><p><strong>New CLI and free tier</strong></p><p>We’ve also built a new CLI that makes it easy to explore, fork, and build with Agentic Postgres, and we’re launching a <a href="https://www.tigerdata.com/blog/introducing-agentic-postgres-free-plan-experiment-ai-on-postgres"><u>free tier</u></a> so every developer and every agent can get hands-on right away.</p><p>This is all launching today. You can try this today with 3 basic commands in your terminal:</p><pre><code class="language-Shell"># 3 commands to install the Tiger CLI and MCP. That's it!
$ curl -fsSL https://cli.tigerdata.com | sh
$ tiger auth login
$ tiger mcp install
</code></pre><p>Then just tell your agent to spin up a new free service using MCP, or simply call <code>tiger create service</code> from the command line to get going.</p><h2 id="powered-by-fluid-storage">Powered by Fluid Storage</h2><p>Agentic Postgres is powered by <a href="https://www.tigerdata.com/blog/fluid-storage-forkable-ephemeral-durable-infrastructure-age-of-agents" rel="noreferrer">Fluid Storage</a>, our new distributed storage layer. Fluid Storage is built on a disaggregated architecture of a horizontally scalable distributed block store using local NVMe storage, a storage proxy layer that exposes copy-on-write volumes, and a user-space storage device driver.</p><p>It’s storage that looks like a local disk to Postgres yet scales like a cloud service.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2025/10/2025-Oct-28-fluid-storage-architecture-diagram-1.png" class="kg-image" alt="" loading="lazy" width="2000" height="1043" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/10/2025-Oct-28-fluid-storage-architecture-diagram-1.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/10/2025-Oct-28-fluid-storage-architecture-diagram-1.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/10/2025-Oct-28-fluid-storage-architecture-diagram-1.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2025/10/2025-Oct-28-fluid-storage-architecture-diagram-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>As a result, Fluid Storage delivers instant forks, snapshots, and automatic scaling (up or down) without downtime or over-provisioning.  In benchmark testing, a single volume sustains throughput over 110,000 IOPS, while retaining all of Fluid’s elasticity and copy-on-write capabilities.&nbsp;</p><p>All free services on Tiger Cloud run on Fluid Storage today, so every developer can experience its performance and flexibility firsthand.&nbsp;</p><p>And this is just the start. We’ll dive deeper into each of these (<a href="https://www.tigerdata.com/blog/free-postgres-mcp-prompt-templates" rel="noreferrer">MCP</a>, <a href="https://www.tigerdata.com/blog/introducing-pg_textsearch-true-bm25-ranking-hybrid-retrieval-postgres" rel="noreferrer">pg_textsearch</a>, <a href="https://github.com/timescale/pgvectorscale" rel="noreferrer">pgvectorscale</a>, <a href="https://www.tigerdata.com/blog/fast-zero-copy-database-forks" rel="noreferrer">forkable databases</a>, <a href="https://www.tigerdata.com/blog/fluid-storage-forkable-ephemeral-durable-infrastructure-age-of-agents" rel="noreferrer">Fluid Storage</a>, <a href="https://github.com/timescale/tiger-cli" rel="noreferrer">CLI</a>, <a href="https://www.tigerdata.com/blog/introducing-agentic-postgres-free-plan-experiment-ai-on-postgres" rel="noreferrer">free tier</a>) later this week and next.&nbsp;</p><h2 id="built-for-agents-and-developers">Built for Agents and Developers</h2><p>Agentic Postgres is built for agents, so developers can work on higher level problems.&nbsp;</p><p>Building <em>with </em>and <em>for </em>agents, we’ve learned something simple: agents are not here to replace us. They’re here to elevate us.</p><p>Agents take on the mechanical, repetitive work, freeing us to focus on what matters most: architecture, design, creativity, impact. They make us faster, smarter, and enable us to do more ambitious work than we could do alone.&nbsp;&nbsp;</p><p>The myth is that AI will replace developers. The truth is that developers who build with agents will replace those who don’t.&nbsp;</p><p>Agentic Postgres is for developers who want to build <em>with</em> AI. For developers who care more about working applications than disposable demos. For developers who want AI to feel like engineering, not just experimentation. </p><p>Today’s launch is just the beginning. There are still some rough edges. We’d love your help sanding them down. But expect more to come: more launches, big and small, in the weeks, months, and years ahead.</p><p>Agents are the new developers. Agentic Postgres is their new playground.</p><p><strong>Built for Agents. Designed to Elevate Developers.</strong></p><p>So let’s build. Together.</p><p>Get started today:&nbsp;</p><pre><code class="language-markdown">$ curl -fsSL https://cli.tigerdata.com | sh</code></pre>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tiger Lake: A New Architecture for Real-Time Analytical Systems and Agents]]></title>
            <description><![CDATA[Mike Freedman, CTO of Tiger Data, introduces Tiger Lake: a native Postgres–lakehouse bridge for real-time, analytical, and agentic systems.]]></description>
            <link>https://www.tigerdata.com/blog/tiger-lake-a-new-architecture-for-real-time-analytical-systems-and-agents</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/tiger-lake-a-new-architecture-for-real-time-analytical-systems-and-agents</guid>
            <category><![CDATA[Announcements & Releases]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[Tiger Lake]]></category>
            <dc:creator><![CDATA[Mike Freedman]]></dc:creator>
            <pubDate>Thu, 17 Jul 2025 12:59:06 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2025/07/2025-july-15-tigerlake-thumbnail.png">
            </media:content>
            <content:encoded><![CDATA[<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">🔈</div><div class="kg-callout-text">Tiger Lake is currently in public beta for scale and enterprise users.&nbsp;<a href="https://console.cloud.timescale.com/signup" target="_blank" rel="noopener noreferrer">Sign up</a>&nbsp;for Tiger Cloud to try out your use case.</div></div><p>Modern applications are becoming more dynamic, more intelligent, and more real time. Dashboards refresh with incoming telemetry. Monitoring systems respond to shifting baselines. Agents make decisions in context, not in isolation. Each depends on the same foundational requirement: the ability to unify live events with deep historical state.</p><p>Yet the data remains fragmented.</p><p>Operational systems, built on Postgres, handle ingestion and serving. Analytical systems, built on the lakehouse, handle enrichment and modeling. Connecting them means stitching together streams, pipelines, and custom jobs—each introducing latency, fragility, and cost. The result is a patchwork of systems that struggle to deliver the full picture, let alone do so in real time.</p><p>This fragmentation doesn’t just slow teams down—it limits what developers can build. You can’t deliver real-time dashboards with historical depth or ground agents in fresh operational context when the data is split by design.</p><p>This architectural divide is no longer sustainable.</p><p><a href="https://docs.tigerdata.com/use-timescale/latest/tigerlake/"><u>Tiger Lake</u></a> bridges that divide. Now in public beta, it introduces a new data loop—continuous, bidirectional, and deeply integrated—between Postgres and the lakehouse. It simplifies the stack, preserves open formats, and brings operational and analytical context into the same system.</p><h2 id="introducing-tiger-lake-real-time-data-full-context-systems">Introducing Tiger Lake: Real-Time Data, Full-Context Systems</h2><p>Tiger Lake eliminates the need for external pipelines, complex orchestration frameworks, and proprietary middleware. It is built directly into Tiger Cloud and integrated with Tiger Postgres, our production-grade Postgres engine for transactional, analytical, and agentic workloads.</p><p>The architecture uses open standards from end to end:</p><ul><li>Apache Iceberg tables stored in Amazon S3 Tables for lakehouse integration</li><li>Continuous replication from Postgres tables or hypertables into Iceberg</li><li>Streaming ingestion back into Postgres for low-latency serving and operations</li><li>Pushing down queries from Postgres to Iceberg for efficient rollups</li></ul><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2025/07/2025-july-14-tigerlake-post-diagram-1.png" class="kg-image" alt="Tiger Lake architecture diagram" loading="lazy" width="2000" height="1866" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/07/2025-july-14-tigerlake-post-diagram-1.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/07/2025-july-14-tigerlake-post-diagram-1.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/07/2025-july-14-tigerlake-post-diagram-1.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2025/07/2025-july-14-tigerlake-post-diagram-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>These capabilities come built in. What previously required Flink jobs, DAG schedulers, and custom glue now works natively. Streaming behavior and schema compatibility are designed into the system from the start.</p><p>To understand how Tiger Lake reshapes data architecture, it helps to <a href="https://www.tigerdata.com/blog/the-database-meets-the-lakehouse-toward-a-unified-architecture-for-modern-applications"><u>revisit the medallion model</u></a> and consider how it evolves when real-time context becomes a core design principle.</p><p>You can think of it as an <strong>operational medallion architecture</strong>:</p><ul><li><strong>Bronze:</strong> Raw data lands in Iceberg-backed S3.</li><li><strong>Silver: </strong>Cleaned and validated data is replicated to Postgres.</li><li><strong>Gold:</strong> Aggregates are computed in Postgres for real-time serving, then streamed back to Iceberg for feature analysis.</li></ul><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2025/07/medallion-architecture.png" class="kg-image" alt="Operational medallion architecture" loading="lazy" width="2000" height="735" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/07/medallion-architecture.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/07/medallion-architecture.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/07/medallion-architecture.png 1600w, https://timescale.ghost.io/blog/content/images/2025/07/medallion-architecture.png 2234w" sizes="(min-width: 720px) 720px"></figure><p>Traditional Bronze–Silver–Gold workflows were built for batch systems. Tiger Lake enables a continuous flow where enrichment and serving happen in real time.</p><p>This shift transforms an overly complex pipeline into a dynamic and simpler real-time data loop. Context and data moves freely between systems. Operational and analytical layers stay connected without redundant jobs or duplicated infrastructure.</p><p>All data remains native, up to date, and queryable with standard SQL. Tiger Lake supports a single write path that powers real-time applications, dashboards, and the lakehouse, using the architecture that best fits the developer. Users can write data to Postgres, then have appropriate data and rollups automatically synced to their lakehouse; conversely, users already feeding raw data into the lakehouse can automatically bring it to Postgres for operational serving. Now, applications can reason across the now and the then—without orchestration code or synchronization overhead.</p><blockquote><em>"We stitched together Kafka, Flink, and custom code to stream data from Postgres to Iceberg. It worked, but it was fragile and high-maintenance," <strong>said Kevin Otten, Director of Technical Architecture at Speedcast.</strong> "Tiger Lake replaces all of that with native infrastructure. It’s the architecture we wish we had from day one."</em></blockquote><h2 id="from-architecture-to-outcomes">From Architecture to Outcomes</h2><p>Tiger Lake enables real-time systems that were previously too complex to operate or too expensive to build.</p><h3 id="customer-facing-dashboards">Customer-facing dashboards</h3><p>Dashboards can now combine live metrics with historical aggregates in a single query. There is no need for dual stacks or stale insights. Tiger Lake supports high-throughput ingestion at production scale, powering pipelines that visualize billions of rows in real time. Everything lives in one system, continuously updated and instantly queryable.</p><blockquote><em>"With Tiger Lake, we finally unified our real-time and historical data," <strong>said Maxwell Carritt, Lead IoT Engineer at Pfeifer &amp; Langen.</strong> "Now we seamlessly stream from Tiger Postgres into Iceberg, giving our analysts the power to explore, model, and act on data across S3, Athena, and Tiger Data."</em></blockquote><h3 id="monitoring-systems">Monitoring systems</h3><p>With a single source of truth and a continuous data loop, alerting becomes faster and more reliable. Engineers can run one SQL query to inspect fresh telemetry and historical incidents together—improving triage speed, reducing false positives, and staying focused on what matters.</p><p>Simplifying the data plane also improves system resilience. Tiger Lake lets monitoring systems operate on the same live operational backbone, where Iceberg provides historical depth and Tiger Postgres delivers low-latency access.</p><h3 id="agents">Agents</h3><p>Tiger Lake makes grounding possible without additional infrastructure. Developers can embed recent user activity and long-term interaction history directly inside Postgres. There is no need for orchestration, vector drift management or custom AI pipelines.</p><p>Imagine a support agent receives a new inquiry. The large body of historical support cases remain in Iceberg, while Tiger Lake created automated chunk and <a href="https://www.tigerdata.com/blog/a-beginners-guide-to-vector-embeddings" rel="noreferrer">vector embeddings</a> in Postgres. Now vector search against the operational database can answer AI chat questions quickly, while ensuring that embeddings stay fresh and up-to-date without complex orchestration pipelines.&nbsp;&nbsp;</p><p>In doing so, Tiger Lake is also a key building block in what we call Agentic Postgres, a Postgres foundation for intelligent systems that learn, decide, and act.</p><blockquote><em>"With Tiger Lake, we believe Tiger Data is setting a strong foundation for turning Postgres into the operational engine of the open lakehouse for applications,"<strong> said Ken Yoshioka, CTO, Lumia Health.</strong> "It allows us the flexibility to grow our biotech startup quickly with infrastructure designed for both analytics and agentic AI."</em></blockquote><p>Companies like Speedcast, Lumia Health, and Pfeifer &amp; Langen are already building full-context and real-time analytical systems with Tiger Lake. These architectures power industrial telemetry, agentic workflows, and real-time operations, all from a unified, continuously streaming platform.</p><h3 id="coming-soon-round-trip-intelligence">Coming soon: Round-trip intelligence</h3><ul><li><strong>Later this summer:</strong> Query Iceberg catalogs directly from within Postgres. Explore, join, and reason across lakehouse and operational data using SQL.</li><li><strong>Fall 2025: </strong>Full round-trip workflows: ingest into Postgres, enrich in Iceberg and stream results back automatically. This lets developers move from event to analysis to action in one architecture.</li></ul><h3 id="how-to-set-up-tiger-lake">How to set up Tiger Lake</h3><p>Getting started is simple. No complex orchestration or manual integrations:</p><ul><li>Create a bucket for Iceberg-compatible S3 tables.</li><li>Provide ARN permissions to Tiger Cloud.</li><li>Enable table sync in Tiger Postgres:</li></ul><pre><code class="language-SQL">ALTER TABLE my_hypertable SET (
  tigerlake.iceberg_sync = true
);</code></pre><h2 id="the-future-of-data-architecture-is-real-time-contextual-and-open">The Future of Data Architecture Is Real-Time, Contextual, and Open</h2><p>Tiger Lake introduces a new kind of architecture. It is continuous by design, scalable by default, and optimized for applications that need full context and complete data in real time.</p><p>Operational data flows into the lakehouse for enrichment and modeling. Enriched insights flow back into Postgres for low-latency serving. Applications and agents complete the loop, responding with precision and speed.</p><p>We believe this is the foundation for what comes next:</p><ul><li>Systems that unify operational use cases and internal analytics</li><li>Architectures that reduce complexity instead of compounding it</li><li>Workloads that are not just reactive but grounded in understanding</li></ul><p>You should not have to choose between context and simplicity. You should not have to patch together systems that were never designed to work together. And you should not have to replatform to evolve.</p><p>Together with next-generation storage architecture and our Postgres-native AI tooling, Tiger Lake forms the backbone of Agentic Postgres. This is a foundation built for intelligent workloads that learn, simulate, and act. We’ll share more soon.</p><p>Try it today on <a href="https://console.cloud.timescale.com/signup"><u>Tiger Cloud</u></a>, and check out the <a href="https://docs.tigerdata.com/use-timescale/latest/tigerlake/"><u>Tiger Lake docs</u></a> to get started.</p><p>— Mike</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Speed Without Sacrifice: Building the Modern PostgreSQL for the Analytical and Agentic Era]]></title>
            <description><![CDATA[Cofounders of Tiger Data (creators of TimescaleDB) Ajay Kulkarni and Mike Freedman discuss the company’s new name, showing how it reflects the company’s evolution.]]></description>
            <link>https://www.tigerdata.com/blog/timescale-becomes-tigerdata</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/timescale-becomes-tigerdata</guid>
            <category><![CDATA[Announcements & Releases]]></category>
            <category><![CDATA[General]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[Tiger Data]]></category>
            <dc:creator><![CDATA[Ajay Kulkarni]]></dc:creator>
            <pubDate>Tue, 17 Jun 2025 14:14:45 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2025/06/tiger-data-hero-2.gif">
            </media:content>
            <content:encoded><![CDATA[<p><em>Timescale is now Tiger Data.</em></p><p><strong>TL;DR: Eight years ago, we launched Timescale to bring time-series to PostgreSQL. Our mission was simple: help developers building time-series applications.</strong></p><p><strong>Since then, we have built a thriving business: 2,000 customers, mid 8-digit ARR (&gt;100% growth year over year), $180 million raised from top investors.&nbsp;</strong></p><p><strong>We serve companies who are building real-time analytical products and large-scale AI workloads like: Mistral, HuggingFace, Nvidia, Toyota, Tesla, NASA, JP Morgan Chase, Schneider Electric, Palo Alto Networks, and Caterpillar. These are companies building developer tools, industrial dashboards, crypto exchanges, AI-native games, financial RAG applications, and more.&nbsp;</strong></p><p><strong>We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future. So we’re changing our name: from Timescale to Tiger Data. Not to change who we are, but to reflect who we’ve become. Tiger Data is bold, fast, and built to power the next era of software.</strong></p><h2 id="developers-thought-we-were-crazy">Developers Thought We Were Crazy</h2><p>When we started 8 years ago, SQL databases were “old fashioned.” NoSQL was the future. Hadoop, MongoDB, Cassandra, InfluxDB – these were the new, exciting NoSQL databases. PostgreSQL was old and boring.</p><p>That’s when we launched Timescale: a time-series database on PostgreSQL. Developers thought we were crazy. PostgreSQL didn’t scale. PostgreSQL wasn’t fast. Time-series needed a NoSQL database. Or so they said.</p><p><em>“While I appreciate PostgreSQL every day, am I the only one who thinks this is a rather bad idea?” – top HackerNews comment on our launch (</em><a href="https://news.ycombinator.com/item?id=14035416"><em><u>link</u></em></a><em>)</em></p><p>But we believed in PostgreSQL. We knew that boring could be awesome, especially with databases. And frankly, we were selfish: PostgreSQL was the only database that we wanted to use.</p><p><strong>Today, PostgreSQL has won.</strong>&nbsp;</p><p>There are no more “SQL vs. NoSQL” debates. MongoDB, Cassandra, InfluxDB, and other NoSQL databases are seen as technical dead ends. Snowflake and Databricks are acquiring PostgreSQL companies. No one talks about Hadoop. The Lakehouse has won.&nbsp;</p><p><strong>Today, agentic workloads are here.&nbsp;</strong></p><p>Agents need a fast database. We see this in our customer base: private equity firms and hedge funds using agents to help understand market movements (“How did the market respond to Apple WWDC 2025?”); industrial equipment manufacturers building chat interfaces on top of internal manuals to help field technicians; developer platforms storing agentic interactions into history tables for greater transparency and trust; and so on.</p><h2 id="what-started-as-a-heretical-idea-is-now-a-thriving-business">What Started as a Heretical Idea Is Now a Thriving Business&nbsp;</h2><p>We have also changed. We met in September 1997, during our first week at MIT. We soon became friends, roommates, even marathon training partners (Boston 1998).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2025/06/founder-image.png" class="kg-image" alt="Tiger Data (creators of TimescaleDB) cofounders" loading="lazy" width="1790" height="435" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/06/founder-image.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/06/founder-image.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/06/founder-image.png 1600w, https://timescale.ghost.io/blog/content/images/2025/06/founder-image.png 1790w" sizes="(min-width: 720px) 720px"><figcaption><i><em class="italic" style="white-space: pre-wrap;">While our hairlines and drinks (turmeric shots!) have changed, our enthusiasm has not</em></i></figcaption></figure><p>That friendship became the foundation for an entrepreneurial journey that has surpassed even our boldest imaginations.&nbsp;</p><p>What started as a heretical idea is now a thriving business:</p><ul><li>2,000 customers</li><li>Mid 8-digit ARR, growing &gt;100% y/y</li><li>200 people in 25 countries</li><li>$180 million raised from top investors</li><li>60%+ gross margins</li></ul><p>Cloud usage is up 5x in the last 18 months, based on paid customers alone.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2025/06/2025-cloud-growth-dark-mode.png" class="kg-image" alt="Cloud usage is up 5x in the last 18 months" loading="lazy" width="2000" height="1252" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/06/2025-cloud-growth-dark-mode.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/06/2025-cloud-growth-dark-mode.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/06/2025-cloud-growth-dark-mode.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2025/06/2025-cloud-growth-dark-mode.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>And that’s only the paid side of the story. Our open-source community is 10x-20x larger. (Based on telemetry, it’s 10x, but we estimate that at least half of all deployments have telemetry turned off.)</p><p>TimescaleDB is everywhere. It’s included in PostgreSQL offerings around the world: from Azure, Alibaba, and Huawei to Supabase, DigitalOcean, and Fly.io. You’ll also find it on Databricks Neon, Snowflake Crunchy Bridge, OVHCloud, Render, Vultr, Linode, Aiven, and more.</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2025/06/2025-community-cloud-dark-mode.png" class="kg-image" alt="Community 10-20x" loading="lazy" width="2000" height="1298" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2025/06/2025-community-cloud-dark-mode.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2025/06/2025-community-cloud-dark-mode.png 1000w, https://timescale.ghost.io/blog/content/images/size/w1600/2025/06/2025-community-cloud-dark-mode.png 1600w, https://timescale.ghost.io/blog/content/images/size/w2400/2025/06/2025-community-cloud-dark-mode.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="we-are-tiger-data">We Are Tiger Data</h2><p>Today, we are more than a time-series database. We are powering developer tools, SaaS applications, AI-native games, financial RAG applications, and more. The majority of workloads on our Cloud product aren’t time-series. Companies are running entire applications on us. CTOs would say to us, <em>“You keep talking about how you are the best time-series database, but I see you as the best PostgreSQL.”</em>&nbsp;</p><p><strong>So we are now “Tiger Data.”</strong> We offer the fastest PostgreSQL. Speed without sacrifice.</p><p>Our cloud offering is “Tiger Cloud.” Our logo stays the same: the tiger, looking forward, focused and fast. Some things do not change. Our open source time-series <a href="https://www.tigerdata.com/blog/top-8-postgresql-extensions" rel="noreferrer">PostgreSQL extension</a> remains TimescaleDB. Our vector extension is still pgvectorscale.&nbsp;</p><p><strong>Why “Tiger”? </strong>The tiger has been our mascot since 2017, symbolizing the speed, power, and precision we strive for in our database. Over time, it’s become a core part of our culture: from weekly “Tiger Time” All Hands and monthly “State of the Tiger” business reviews, to welcoming new teammates as “tiger cubs” to the “jungle.” As we reflected on our products, performance, and community, we realized: we aren’t just Timescale. We’re Tiger. Today, we’re making that official.</p><p><strong>This is not a reinvention: it’s a reflection of how we already serve our customers today.</strong></p><p><strong>Polymarket</strong> uses Tiger Data to track their price history. During the last election Polymarket ramped up 4x when trade volumes were extra high, to power over $3.7 billion dollars worth of trades.</p><p><strong>Linktree</strong> uses Tiger Data for their premium analytics product, saving $17K per month on 12.6 TB from compression savings. They also compressed their time to launch, going from 2 weeks to 2 days for shipping analytical features.</p><p><strong>Titan America</strong> uses Tiger Data’s compression and continuous aggregates to reduce costs and increase visibility into their facilities for manufacturing cement, ready-mixed concrete, and related materials.&nbsp;</p><p><strong>Lucid Motors</strong> uses Tiger Data for real-time telemetry and autonomous driving analytics.&nbsp;</p><p><strong>The Financial Times </strong>runs time-sensitive analytics and <a href="https://www.tigerdata.com/learn/vector-search-vs-semantic-search" rel="noreferrer">semantic search</a>.&nbsp;</p><h2 id="tiger-is-the-fastest-postgres-for-modern-workloads">Tiger Is the Fastest Postgres for Modern Workloads</h2><p>We are building the fastest Postgres: purpose-built for the modern operational workloads where traditional <a href="https://www.tigerdata.com/learn/understanding-oltp" rel="noreferrer">OLTP</a> databases break down.&nbsp;</p><p>Operational workloads that go far beyond simple transactions are now the norm. They require real-time, user-facing analytics over massive <a href="https://www.tigerdata.com/learn/how-to-handle-high-cardinality-data-in-postgresql" rel="noreferrer">high-cardinality datasets</a>, from event streams to time-series to user-level behavioral data.&nbsp;</p><p>As the frontier moves further with agentic applications, the demands grow even more. These systems don’t just read and write: they observe, decide, and act. These AI applications require fast vector search across embeddings, and fast branching of data environments for experimentation and context-sensitive responses.</p><p><strong>Tiger is not a fork. It’s not a wrapper. It is PostgreSQL, extended with innovations in the database engine and cloud infrastructure to deliver speed without sacrifice.</strong></p><p><strong>How are we so fast?</strong> Because of consistent, disciplined engineering efforts to serve customer needs over several years. Here is a non-exhaustive list:&nbsp;</p><ul><li>Hypertables (2017)</li><li>Native <a href="https://www.tigerdata.com/blog/building-columnar-compression-in-a-row-oriented-database" rel="noreferrer">columnar</a> compression (2019)</li><li>Real-time materialized views for faster queries (2020)</li><li>Decoupled compute and storage (2021)</li><li>Tiered Storage to S3 Parquet (2022)</li><li>Vectorized query execution for fast analytics (2023)</li><li>Hybrid row-columnar store for faster queries on recent and historical data (2024)</li><li>Faster vector workloads on PostgreSQL via pgvectorscale (2024)</li><li>300x faster mutations (updates, upserts, deletes) to compressed columnar data (2024)</li><li>2500x faster distinct queries, 6x faster point queries on high-cardinality columns (2025)</li><li>Rapid horizontal scaling with load-balanced read replica sets (2025)</li><li>Enhanced high-performance storage up to 64 TB and 32,000 IOPS (2025)</li></ul><p><strong>Tiger brings together the familiarity and reliability of Postgres with the performance of purpose-built engines.</strong></p><p>We built the fastest PostgreSQL. Not because we wanted to, but because our customers wanted us to.</p><h2 id="building-the-modern-postgresql-for-the-analytical-and-agentic-era">Building the Modern PostgreSQL for the Analytical and Agentic Era</h2><p>PostgreSQL has won. The Lakehouse has won. Every application is becoming an analytical application.&nbsp;Agents are here, in production, and need to be fast. The future is hybrid, developers and agents, with better latency and throughput needs.</p><p>In this era, modern applications must:</p><ul><li>Handle terabytes and petabytes of data</li><li>Support real-time analytics</li><li>Integrate Gen AI features</li><li>Serve both humans and software agents, across dev, test, and production lifecycles</li><li>Meet sub-second latency and high concurrency expectations</li><li>Scale across operational databases and cost-efficient lakehouses</li><li>Maintain transactional integrity</li><li>Deliver all of this reliably and cost-effectively, because data volumes grow much faster than budgets</li></ul><p>Our history to date, our time in this market, our lived experience watching all these changes unfold in real-time screams to us one thing: <strong>modern applications need a new kind of operational database.</strong>&nbsp;</p><p>One built for transactional, analytical, and agentic workloads. One that also acts as the operational serving layer for the Lakehouse. One built on Postgres.</p><p>That is what we are building.</p><p>And wow do we have some fun product announcements queued up for the upcoming weeks and months. A more agentic PostgreSQL. A deeper integration with the Lakehouse via Iceberg. A new compressed insert approach yielding 10 million rows per second. A new type of disaggregated storage architecture with zero-copy instant forks and replicas that we are deploying in our cloud for greater performance, as a replacement for EBS. And more.</p><p>We can’t wait to show it all to you. But first we had to clearly communicate who we are. <strong>We are Tiger Data.&nbsp;</strong></p><h2 id="come-join-us">Come Join Us</h2><p><strong>Tiger is the Fastest PostgreSQL. </strong>The operational database platform built for transactional, analytical, and agentic workloads. The only database platform that provides Speed without Sacrifice.</p><p>This is not a rebrand, but a recommitment to our customers, to our developers, and to our core mission.</p><p>If this mission resonates with you, come join us. Give us product feedback. Spread the word. Wear the swag. Join the team.&nbsp;</p><p>It’s Go Time. 🐯🚀</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[13 Tips to Improve PostgreSQL Insert Performance]]></title>
            <description><![CDATA[Some of these may surprise you, but all 13 ways will improve ingest (INSERT) performance using PostgreSQL and TimescaleDB.]]></description>
            <link>https://www.tigerdata.com/blog/13-tips-to-improve-postgresql-insert-performance</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/13-tips-to-improve-postgresql-insert-performance</guid>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[PostgreSQL Performance]]></category>
            <category><![CDATA[PostgreSQL Tips]]></category>
            <dc:creator><![CDATA[Mike Freedman]]></dc:creator>
            <pubDate>Wed, 17 Apr 2024 12:00:00 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2023/10/Screenshot-2023-10-12-at-4.06.02-PM.png">
            </media:content>
            <content:encoded><![CDATA[<p>Ingest performance is critical for many common PostgreSQL use cases, including application monitoring, application analytics, IoT monitoring, and more. <a href="https://timescale.ghost.io/blog/time-series-data/" rel="noreferrer">These use cases have something in common</a>: unlike standard relational "business" data, changes are treated as <em>inserts</em>, not overwrites. In other words, every new value becomes a <strong>new </strong>row in the database instead of replacing the row's prior value with the latest one.</p><p>If you're operating in a scenario where you need to retain all data vs. overwriting past values, optimizing the speed at which your database can ingest new data becomes essential.</p><p>At <a href="https://www.timescale.com" rel="noreferrer">Tiger Data</a> (the creators of TimescaleDB), we have a lot of experience <a href="https://www.timescale.com/learn/postgresql-performance-tuning-how-to-size-your-database" rel="noreferrer">optimizing performance</a>, so in this article, we will look at PostgreSQL inserts and how to improve their performance. We'll include the following:<br><br><strong>1. &nbsp;Useful tips for improving PostgreSQL insert performance, in general,</strong> such as moderating your use of indexes, reconsidering foreign key constraints, avoiding unnecessary UNIQUE keys, using separate disks for WAL (Write-Ahead Logging) and data, and deploying on performant disks. Each of these strategies can help optimize the speed at which your database ingests new data.</p><p><strong>2.&nbsp; TimescaleDB-specific insert performance tips</strong> (TimescaleDB works like PostgreSQL under the hood).</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">💫</div><div class="kg-callout-text">Don't know what TimescaleDB is? <a href="https://www.timescale.com/learn/is-postgres-partitioning-really-that-hard-introducing-hypertables" rel="noreferrer">Read this article.</a></div></div><h2 id="postgresql-insert-overview">PostgreSQL Insert Overview</h2><p><a href="https://www.postgresql.org/docs/current/sql-insert.html" rel="noreferrer">One of PostgreSQL's fundamental commands, the <code>INSERT</code> operation</a> plays a crucial role in adding new data to a database. It adds one or more rows to a table, filling each column with specified data. When certain columns are not specified in the insert query, PostgreSQL automatically fills these columns with their default values, if any are defined. This feature ensures that the database maintains integrity and consistency, even when all column values are not provided.</p><p>The <code>INSERT</code> operation is fundamental for data ingest processes, where new data is continually added to the database. It allows for the efficient and organized storage of new information, making it accessible for querying and analysis.<br><br>Here’s a simple example of an <code>INSERT</code> query:</p><pre><code class="language-sql">INSERT INTO employees (name, position, department)
VALUES ('John Doe', 'Software Engineer', 'Development');</code></pre><p>In this example, the <code>INSERT INTO</code> statement specifies the table <code>employees</code> to which the row will be added. The columns <code>name</code>, <code>position</code>, and <code>department</code> are explicitly mentioned, indicating where the provided data should be inserted. <br><br>Following the <code>VALUES</code> keyword, the actual data to be inserted into these columns is provided in parentheses. If the <code>employees</code> table contains other columns for which default values are defined and are not included in the <code>INSERT</code> statement, PostgreSQL will automatically fill those columns with the default values.</p><h2 id="when-insert-performance-matters">When Insert Performance Matters</h2><p>The speed at which data can be ingested into a database directly impacts its utility and responsiveness, especially when <strong>reaction speed</strong> in real-time or near-real-time data processing is essential.</p><p>One prominent example of such a use case is <a href="https://timescale.ghost.io/blog/guide-to-postgres-data-management/" rel="noreferrer"><strong>time-series data management</strong></a>. Time-series data, characterized by its sequential nature, accumulates moment by moment, often originating from sensors, financial transactions, or user activity logs. <br><br>The value of time-series data lies in its timeliness and the insights that can be gleaned from analyzing patterns over time. To maintain the integrity and relevance of these insights, insert performance must be optimized to ensure data is updated consistently and without delay. High insert performance allows for the seamless integration of new data, preserving the chronological order and enabling accurate <a href="https://www.timescale.com/learn/real-time-analytics-in-postgres" rel="noreferrer">real-time analysis</a>.</p><p><strong>Application monitoring</strong> represents another critical area where insert performance is paramount. Effective monitoring systems rely on continuously ingesting application metrics and logs to provide an up-to-date view of the application's health and performance. Any lag in data ingest can lead to delays in detecting and responding to issues, potentially affecting user experience and system stability. Strong insert performance ensures that monitoring systems remain current, allowing for immediate action in response to any anomalies detected.</p><p><strong>Event detection applications</strong>, such as fraud detection systems, also underscore the importance of fast insert speeds. In these scenarios, the ability to rapidly ingest and process data can mean the difference between catching a fraudulent transaction as it happens or missing it entirely. </p><p>Fast data ingest enables these systems to analyze events in real time, applying algorithms to detect suspicious patterns and react promptly. The reaction speed is crucial in minimizing risk and protecting assets, highlighting the critical role of insert performance in maintaining system efficacy.</p><h2 id="improving-insert-performance">Improving Insert Performance</h2><p>The previous use cases stress the critical role of ingest speed in real-time or high-volume databases, such as those handling time series. These use cases make up for most of our customer base here at Tiger Data, so we're pretty confident to recommend these five best practices for improving ingest performance in vanilla PostgreSQL:</p><h3 id="1-use-indexes-in-moderation">1. Use indexes in moderation</h3><p><a href="https://www.timescale.com/learn/postgresql-performance-tuning-optimizing-database-indexes" rel="noreferrer">Having the right indexes</a> can speed up your queries, but they’re not a silver bullet. Incrementally maintaining indexes with each new row requires additional work. Check the number of indexes you’ve defined on your table (use the <code>psql</code> command <code>\d table_name</code>), and determine whether their potential query benefits outweigh the storage and insert overhead. Since every system is different, there aren’t any hard and fast rules or “magic number” of indexes—just be reasonable.</p><h3 id="2-reconsider-foreign-key-constraints">2. Reconsider foreign key constraints</h3><p>Sometimes, it's necessary to build <a href="https://www.postgresql.org/docs/current/tutorial-fk.html" rel="noreferrer">foreign keys (FK)</a> from one table to other relational tables. When you have an FK constraint, every <code>INSERT</code> will typically need to read from your referenced table, which can degrade performance. Consider denormalizing your data—we sometimes see pretty extreme use of FK constraints from a sense of “elegance” rather than engineering trade-offs.</p><h3 id="3-avoid-unnecessary-unique-keys">3. Avoid unnecessary UNIQUE keys</h3><p>Developers are often trained to specify primary keys in database tables, and many ORMs love them. Yet, many use cases—including common monitoring or time-series applications—don’t require them, as each event or sensor reading can simply be logged as a separate event by inserting it at the tail of a hypertable's current chunk during write time. </p><p>If a <code>UNIQUE</code> constraint is otherwise defined, that insert can necessitate an index lookup to determine if the row already exists, which will adversely impact the speed of your <code>INSERT</code>.</p><h3 id="4-use-separate-disks-for-wal-and-data">4. Use separate disks for WAL and data</h3><p>While this is a more advanced optimization that isn't always needed, if your disk becomes a bottleneck, you can further increase throughput by using a separate disk (tablespace) for the database's WAL and data.</p><h3 id="5-use-performant-disks">5. Use performant disks </h3><p>Sometimes developers deploy their database in environments with slower disks, whether due to poorly-performing HDD, remote storage area networks (SANs), or other types of configurations. And because when you insert rows, the data is durably stored in the WAL before the transaction completes, slow disks can impact insert performance. One thing to do is check your disk IOPS using the <code>ioping</code> command.<br><br>Read test:</p><pre><code>$ ioping -q -c 10 -s 8k .
--- . (hfs /dev/disk1 930.7 GiB) ioping statistics ---
9 requests completed in 208 us, 72 KiB read, 43.3 k iops, 338.0 MiB/s
generated 10 requests in 9.00 s, 80 KiB, 1 iops, 8.88 KiB/s
min/avg/max/mdev = 18 us / 23.1 us / 35 us / 6.17 us</code></pre><p>Write test:</p><pre><code>$ ioping -q -c 10 -s 8k -W .
--- . (hfs /dev/disk1 930.7 GiB) ioping statistics ---
9 requests completed in 10.8 ms, 72 KiB written, 830 iops, 6.49 MiB/s
generated 10 requests in 9.00 s, 80 KiB, 1 iops, 8.89 KiB/s
min/avg/max/mdev = 99 us / 1.20 ms / 2.23 ms / 919.3 us</code></pre><p>You should see at least thousands of read IOPS and many hundreds of write IOPS. If you are seeing far fewer, your disk hardware is likely affecting your INSERT performance. See if alternative storage configurations are feasible.</p><div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">✨</div><div class="kg-callout-text">Read our <a href="https://www.timescale.com/blog/benchmarking-postgresql-batch-ingest" rel="noreferrer">benchmark on batch ingest in PostgreSQL</a>.</div></div><h2 id="using-timescaledb-to-improve-ingest-performance">Using TimescaleDB to Improve Ingest Performance</h2><p><a href="https://www.timescale.com/performance" rel="noreferrer">TimescaleDB is built to improve query and ingest performance in PostgreSQL.</a> </p><p>The most common uses for TimescaleDB involve storing massive amounts of data for cloud infrastructure metrics, product analytics, web analytics, IoT devices, and many use cases involving <a href="https://www.timescale.com/learn/postgresql-partition-strategies-and-more" rel="noreferrer">large PostgreSQL tables</a>. The ideal TimescaleDB scenarios are time-centric, almost solely append-only (lots of INSERTs), and require fast ingestion of large amounts of data within small time windows. </p><p>Here you have eight more techniques for improving ingest performance with TimescaleDB:</p><h3 id="6-use-parallel-writes">6. Use parallel writes </h3><p>Each <code>INSERT</code> or <code>COPY</code> command to TimescaleDB (as in PostgreSQL) is executed as a single transaction and thus runs in a single-threaded fashion. To achieve higher ingest, you should execute multiple <code>INSERT</code> or <code>COPY</code> commands in parallel. </p><p>For help with bulk loading large CSV files in parallel, check out  TimescaleDB's <a href="https://github.com/timescale/timescaledb-parallel-copy">parallel copy command</a>.</p><p>⭐ <strong>Pro tip</strong>: make sure your client machine has enough cores to execute this parallelism (running 32 client workers on a 2 vCPU machine doesn’t help much— the workers won’t actually be executed in parallel).</p><h3 id="7-insert-rows-in-batches">7. Insert rows in batches </h3><p>To achieve higher ingest rates, you should insert your data with many rows in each <code>INSERT</code> call (or else use some bulk insert command, like COPY or our parallel copy tool). </p><p>Don't insert your data row-by-row—instead, try at least hundreds (or thousands) of rows per insert. This allows the database to spend less time on connection management, transaction overhead, SQL parsing, etc., and more time on data processing.</p><h3 id="8-properly-configure-sharedbuffers">8. Properly configure shared_buffers</h3><p>We typically recommend 25&nbsp;% of available RAM. If you install TimescaleDB via a method that runs <a href="https://github.com/timescale/timescaledb-tune"><code>timescaledb-tune</code></a>, it should automatically configure <code>shared_buffers</code> to something well-suited to your hardware specs. </p><p>Note: in some cases, typically with virtualization and constrained cgroups memory allocation, these automatically-configured settings may not be ideal. To check that your <code>shared_buffers</code> are set to within the 25&nbsp;% range,  run <code>SHOW shared_buffers</code> from your <code>psql</code> connection.</p><h3 id="9-run-our-docker-images-on-linux-hosts">9. Run our Docker images on Linux hosts</h3><p>If you are<em> </em><a href="https://docs.timescale.com/latest/getting-started/installation/docker/installation-docker/?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=install-docs-docker">running a TimescaleDB Docker container (which runs Linux)</a><em> </em>on top of another Linux operating system, you're in great shape. The container is basically providing process isolation, and the overhead is extremely minimal. </p><p>If you're running the container on a Mac or Windows machine, you'll see some performance hits for the OS virtualization, including for I/O.</p><p>Instead, if you need to run on Mac or Windows, we recommend <a href="https://docs.timescale.com/latest/getting-started/installation/?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=install-docs">installing directly</a> instead of using a Docker image.</p><h3 id="10-avoid-too-many-or-too-small-chunks">10.&nbsp;Avoid too many or too small chunks</h3><p>We don't currently recommend using space partitioning. And if you do, remember that this number of chunks is created for every time interval. </p><p>So, if you create 64 space partitions and daily chunks, you'll have 24,640 chunks per year. This may lead to a bigger performance hit during query time (due to planning overhead) than during insert time, but it's something to consider nonetheless.<br><br>Another thing to avoid is using an incorrect integer value when you specify the time interval range in <code>create_hypertable</code>. </p><p>⭐ <strong>Pro tip</strong>: </p><ul><li>If your time column uses a native timestamp type, then any integer value should be in terms of microseconds (so one day = 86400000000). We recommend using interval types ('1 day') to avoid the potential for any confusion. </li><li>If your time column is an integer or bigint itself,  use the appropriate range: if the integer timestamp is in seconds, use 86400; if the bigint timestamp is in nanoseconds, use 86400000000000.<br><br>In both cases, you can use <a href="https://docs.timescale.com/latest/api?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=chunk-pretty-api-docs#chunk_relation_size_pretty"><code>chunk_relation_size_pretty</code></a> to make sure your chunk sizes or partition ranges seem reasonable:</li></ul><pre><code class="language-SQL">=&gt; SELECT chunk_table, ranges, total_size
FROM chunk_relation_size_pretty('hypertable_name')
ORDER BY ranges DESC LIMIT 4;
chunk_table               |                         ranges                          | total_size
-----------------------------------------+---------------------------------------------------------+------------
_timescaledb_internal._hyper_1_96_chunk | {"['2020-02-13 23:00:00+00','2020-02-14 00:00:00+00')"} | 272 MB
_timescaledb_internal._hyper_1_95_chunk | {"['2020-02-13 22:00:00+00','2020-02-13 23:00:00+00')"} | 500 MB
_timescaledb_internal._hyper_1_94_chunk | {"['2020-02-13 21:30:00+00','2020-02-13 22:00:00+00')"} | 500 MB
_timescaledb_internal._hyper_1_93_chunk | {"['2020-02-13 20:00:00+00','2020-02-13 21:00:00+00')"} | 500 MB</code></pre><h3 id=""></h3><h3 id="11-avoid-%E2%80%9Ctoo-large%E2%80%9D-chunks">11. Avoid “too large” chunks</h3><p>To maintain higher ingest rates, you want your latest chunk and all its associated indexes to stay in memory so that writes to the chunk and index updates merely update memory. (The write is still durable, as inserts are written to the WAL on disk before the database pages are updated.) </p><p>If your chunks are too large, then writes to even the latest chunk will start swapping to disk.</p><p>As a rule of thumb, we recommend that the latest chunks and all their indexes fit comfortably within the database's <code>shared_buffers</code>. You can check your chunk sizes via the <a href="https://docs.timescale.com/latest/api?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=chunk-pretty-api-docs#chunk_relation_size_pretty"><code>chunk_relation_size_pretty</code></a> SQL command.</p><p></p><pre><code class="language-SQL">=&gt; SELECT chunk_table, table_size, index_size, toast_size, total_sizeFROM chunk_relation_size_pretty('hypertable_name')ORDER BY ranges DESC LIMIT 4;
chunk_table               | table_size | index_size | toast_size | total_size
-----------------------------------------+------------+------------+------------+------------
_timescaledb_internal._hyper_1_96_chunk | 200 MB     | 64 MB      | 8192 bytes | 272 MB
_timescaledb_internal._hyper_1_95_chunk | 388 MB     | 108 MB     | 8192 bytes | 500 MB
_timescaledb_internal._hyper_1_94_chunk | 388 MB     | 108 MB     | 8192 bytes | 500 MB
_timescaledb_internal._hyper_1_93_chunk | 388 MB     | 108 MB     | 8192 bytes | 500 MB</code></pre><p></p><p>If your chunks are too large, you can update the range for future chunks via the <a href="https://docs.timescale.com/latest/api?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=set-chunk-interval-api-docs#set_chunk_time_interval"><code>set_chunk_time_interval</code></a> command. However, this does not modify the range of existing chunks (e.g., by rewriting large chunks into multiple small chunks). </p><p>For configurations where individual chunks are much larger than your available memory, we recommend dumping and reloading your hypertable data to properly sized chunks.</p><p>Keeping the latest chunk applies to all active hypertables; if you are actively writing to two hypertables, the latest chunks from both should fit within <code>shared_buffers</code>.</p><h3 id="12-write-data-in-loose-time-order">12. Write data in loose time order</h3><p>When chunks are sized appropriately (see #10 and #11), the latest chunk(s) and their associated indexes are naturally maintained in memory. New rows inserted with recent timestamps will be written to these chunks and indexes already in memory. </p><p>If a row with a sufficiently older timestamp is inserted—i.e., it's an out-of-order or backfilled write—the disk pages corresponding to the older chunk (and its indexes) will need to be read in from disk. This will significantly increase write latency and lower insert throughput.</p><p>Particularly, when you are loading data for the first time, try to load data in sorted, increasing timestamp order. </p><p>Be careful if you're bulk-loading data about many different servers, devices, and so forth: </p><ul><li>Do not bulk insert data sequentially by server (i.e., all data for server A, then server B, then C, and so forth). This will cause disk thrashing as loading each server will walk through all chunks before starting anew. </li><li>Instead, arrange your bulk load so that data from all servers are inserted in loose timestamp order (e.g., day 1 across all servers in parallel, then day 2 across all servers in parallel, etc.)</li></ul><h3 id="13-watch-row-width">13. Watch row width</h3><p>The overhead from inserting a wide row (say, 50, 100, 250 columns) is going to be much higher than inserting a narrower row (more network I/O, more parsing and data processing, larger writes to WAL, etc.). Most of our published benchmarks are using <a href="https://github.com/timescale/tsbs">TSBS</a>, which uses 12 columns per row. So you'll correspondingly see lower insert rates if you have very wide rows.</p><p>If you are considering very wide rows because you have different types of records, and each type has a disjoint set of columns, you might want to try using multiple hypertables (one per record type)—particularly if you don't often query across these types.</p><p>Additionally, JSONB records are another good option if virtually all columns are sparse. That said, if you're using sparse wide rows, use NULLs for missing records whenever possible, not default values, for the most performance gains (NULLs are much cheaper to store and query).</p><p>Finally, the cost of wide rows is actually much less once you compress rows using <a href="https://timescale.ghost.io/blog/blog/building-columnar-compression-in-a-row-oriented-database/?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=1-5-release-blog">TimescaleDB’s native compression</a>.  Rows are converted into more columnar compressed form, sparse columns compress extremely well, and compressed columns aren’t read from disk for queries that don’t fetch individual columns.</p><h2 id="summary">Summary</h2><p><a href="https://www.timescale.com/learn/types-of-data-supported-by-postgresql-and-timescale" rel="noreferrer">If ingest performance is critical to your use case</a>, consider using TimescaleDB. You can <a href="https://console.cloud.timescale.com/signup">get started with hosted TimescaleDB (Tiger Cloud)</a> for free today or <a href="https://docs.timescale.com/latest/getting-started/installation/?utm_source=timescale-13-insert-tips&amp;utm_medium=blog&amp;utm_campaign=july-2020-advocacy&amp;utm_content=install-docs">download TimescaleDB</a> to your own hardware. </p><p>Our approach to support is to address your whole solution, so we're here to help you achieve your desired performance results (see more details about our <a href="https://www.timescale.com/support" rel="noreferrer">Support team and ethos</a>). </p><p>Lastly, <a href="https://slack.timescale.com/">our Slack community</a> is a great place to connect with 8,000+ other developers with similar use cases, as well as myself, Tiger Data engineers, product team members, and developer advocates.</p><h3 id="keep-learning-about-improving-postgresql-performance">Keep learning about improving PostgreSQL performance</h3><p>If you're interested in improving your PostgreSQL performance, you'll find the following resources useful: </p><p><strong>👉 </strong><a href="https://www.tigerdata.com/learn/postgresql-partition-strategies-and-more" rel="noreferrer"><strong>Navigating growing PostgreSQL tables</strong></a><strong>. </strong>Are your PostgreSQL queries slowing down as your database tables grow? Learn about a few tactics that can get you back on track.</p><p><a href="https://www.timescale.com/learn/when-to-consider-postgres-partitioning" rel="noreferrer"><strong>👉 When to consider PostgreSQL partitioning.</strong></a><strong> </strong>Postgres partitioning can be a powerful tool to scale your database, although it’s not a one-size-fits-all solution. Learn if it's the solution you're looking for. </p><p><strong>👉 </strong>When your tables start growing, it might be time for some PostgreSQL fine-tuning. Get advice on how to optimize your database step by step: </p><ul><li><a href="https://www.timescale.com/learn/postgresql-performance-tuning-how-to-size-your-database" rel="noreferrer">Sizing your database properly (CPU, memory) </a></li><li><a href="https://www.timescale.com/learn/postgresql-performance-tuning-key-parameters" rel="noreferrer">Key PostgreSQL parameters to fine-tune (e.g., work_mem, shared_buffers)</a></li><li><a href="https://www.timescale.com/learn/postgresql-performance-tuning-optimizing-database-indexes" rel="noreferrer">Optimizing indexes</a></li><li><a href="https://www.timescale.com/learn/postgresql-performance-tuning-designing-and-implementing-database-schema" rel="noreferrer">Schema design best practices</a></li></ul><p><strong>👉</strong><a href="https://timescale.ghost.io/blog/timescale-cloud-tips-how-to-optimize-your-ingest-rate/" rel="noreferrer"><strong> Further tips on improving inserts</strong></a><strong>. </strong><br></p><h2 id="faqs-improving-postgresql-insert-performance">FAQs: Improving PostgreSQL Insert Performance</h2><p><strong>Q: How can I improve PostgreSQL insert performance when dealing with large amounts of data?</strong></p><p>A: To optimize PostgreSQL insert performance, focus on using indexes in moderation, inserting rows in batches rather than one by one, and ensuring your disk hardware is performant. If disk becomes a bottleneck, consider using separate disks for WAL and data. For bulk loading data, try tools like TimescaleDB's parallel copy command, which can significantly increase throughput.</p><p><strong>Q: What role do indexes and constraints play in PostgreSQL insert performance?</strong></p><p>A: While indexes speed up queries, they can slow down inserts since each new row requires index maintenance. Foreign key constraints force PostgreSQL to read from referenced tables during inserts, and <code>UNIQUE</code> constraints necessitate index lookups to check for duplicates. Consider whether these constraints are truly necessary for your use case, especially for append-only scenarios like time-series data.</p><p><strong>Q: How should I configure my hardware for optimal PostgreSQL insert performance?</strong></p><p>A: Use performant disks capable of thousands of read IOPS and hundreds of write IOPS, which you can check with the <code>ioping</code> command. Configure <code>shared_buffers</code> to approximately 25% of available RAM to ensure enough memory for caching active data. If running in containers, use Linux hosts for Docker images to minimize virtualization overhead.</p><p><strong>Q: What's the optimal approach for batch inserting data into PostgreSQL?</strong></p><p>A: Instead of row-by-row insertion, batch hundreds or thousands of rows per <code>INSERT</code> command to reduce overhead from connection management and SQL parsing. Execute multiple <code>INSERT</code> or <code>COPY</code> commands in parallel to leverage multiple cores. When bulk loading time-series data, insert in loose time order (e.g., day 1 across all servers, then day 2) rather than sequentially by server to prevent disk thrashing.</p><p><strong>Q: How can TimescaleDB improve insert performance compared to vanilla PostgreSQL?</strong></p><p>A: TimescaleDB enhances PostgreSQL insert performance through features like hypertables with automatic time-based chunking, which keeps recent data in memory for faster writes. It offers native compression to reduce the cost of wide rows and provides tools like parallel copy for efficient bulk loading. TimescaleDB also helps maintain appropriate chunk sizes (neither too large nor too small) to optimize memory usage and prevent unnecessary disk operations.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Achieving the Best of Both Worlds: Ensuring Up-To-Date Results With Real-Time Aggregation]]></title>
            <description><![CDATA[Real-time aggregates (released with TimescaleDB 1.7) build on continuous aggregates' ability to increase query speed and optimize storage. Learn what's new, details about how they work, and how to get started. ]]></description>
            <link>https://www.tigerdata.com/blog/achieving-the-best-of-both-worlds-ensuring-up-to-date-results-with-real-time-aggregation</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/achieving-the-best-of-both-worlds-ensuring-up-to-date-results-with-real-time-aggregation</guid>
            <category><![CDATA[Product & Engineering]]></category>
            <category><![CDATA[Engineering]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Sven Klemm]]></dc:creator>
            <pubDate>Thu, 07 May 2020 15:11:33 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2020/05/mana5280-dkeOcAkors4-unsplash.jpg">
            </media:content>
            <content:encoded><![CDATA[<p>Real-time aggregates (released with TimescaleDB 1.7) build on continuous aggregates' ability to increase query speed and optimize storage. Learn what's new, details about how they work, and how to get started. </p><p>One constant across all time-series use cases is data: metrics, logs, events, sensor readings; IT and application performance monitoring, SaaS applications, IoT, martech, fintech, and more.  Lots (and lots) of data. What’s more, it typically arrives <em>continuously.</em></p><p>This need to handle large volumes of constantly generated data motivated some of our earliest TimescaleDB architectural decisions, such as its use of automated time-based partitioning and local-only indexing to achieve high insert rates.  And last year, we added type-specific <a href="https://www.tigerdata.com/blog/building-columnar-compression-in-a-row-oriented-database" rel="noreferrer">columnar</a> compression to significantly shrink the overhead involved in storing all of this data (often by 90% or higher – <a href="https://timescale.ghost.io/blog/blog/building-columnar-compression-in-a-row-oriented-database/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=1-6-release-blog">see our technical description and benchmarking results</a>). </p><p>And another key capability in TimescaleDB, which is the focus of this post, has been <em>continuous aggregates</em>, which we first <a href="https://timescale.ghost.io/blog/blog/continuous-aggregates-faster-queries-with-automatically-maintained-materialized-views/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggs-1-3-blog">introduced</a> in TimescaleDB 1.3.  Continuous aggregates allow one to specify a SQL query that continually processes raw data into a so-called materialized table.  </p><p>Continuous aggregates are somewhat similar to materialized views in databases, but unlike a materialized view (as in <a href="https://www.postgresql.org/docs/current/rules-materializedviews.html">PostgreSQL</a>), continuous aggregates do not need to be refreshed manually; the view will be refreshed automatically in the background as new data is added, or old data is modified. Additionally, TimescaleDB does not need to re-calculate all of the data on every refresh. Only new and/or invalidated data will be calculated. And since this re-aggregation is automatic – it executes as a background job at regular intervals – this process doesn’t add any maintenance burden to your database.</p><p>This is where most database or streaming systems that offer continuous aggregates or continuous queries give up.  We knew we could do better.</p><p>Enter Real-Time Aggregation, introduced in TimescaleDB 1.7 (<a href="https://timescale.ghost.io/blog/blog/timescaledb-1-7-fast-continuous-aggregates-with-real-time-views-postgresql-12-support-and-more-community-features/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=1-7-release-announcement-blog">see our release blog</a>).</p><h2 id="quick-background-on-continuous-aggregates">Quick Background on Continuous Aggregates</h2><p>The benefit of continuous aggregations are two fold:</p><ul><li><strong>Query performance.</strong>  By executing queries against pre-calculated results, rather than the underlying raw data,  continuous aggregates can significantly improve query performance.</li><li><strong>Storage savings with </strong><a href="https://docs.timescale.com/latest/using-timescaledb/continuous-aggregates?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggs-drop-data-docs#dropping-data"><strong>downsampling</strong></a><strong><em>. </em></strong> Continuous aggregates are often combined with data retention policies for better storage management.  Raw data can be continually aggregated into a materialized table, and dropped after it reaches a certain age.  So the database may only store some fixed period of raw data (say, one week), yet store aggregate data for much longer.</li></ul><p>Consider the following example, collecting system metrics around CPU usage and storing it in a CPU metrics <a href="https://www.tigerdata.com/blog/database-indexes-in-postgresql-and-timescale-cloud-your-questions-answered" rel="noreferrer">hypertable</a>, where each row includes a timestamp, hostname, and 3 metrics around CPU usage (usage_user, usage_system, usage_iowait).  </p><p>We collect these statistics every second per server.</p><pre><code>            time              | hostname |     usage_user     |    usage_system     |    usage_iowait
-------------------------------+----------+--------------------+---------------------+---------------------
2020-05-06 02:32:34.627143+00 | host0    | 0.5378765249290502 |  0.2958572490961302 | 0.10685818344495246
2020-05-06 02:32:34.627143+00 | host1    | 0.3175958910709298 |  0.7874926624954846 | 0.16615243032654803
2020-05-06 02:32:34.627143+00 | host2    | 0.4788377981501064 | 0.18277343256546175 |  0.7183967491020162</code></pre><p>So a query that wants to compute the per-hourly histogram of usage consumption over the course of 7 days for 10 servers will process 10 servers * 60 seconds * 60 minutes * 24 hours * 7 days= 6,048,000 rows of data.</p><p>On the other hand, if we pre-compute a histogram per hour, then the same query on the continuous aggregate table will only need to process 10 servers * 24 hours * 7 days = 1680 rows of data.</p><p>But pre-computed results in the continuous aggregate view will lag behind the latest data, as the materialization only runs at scheduled intervals.  So, both to more cheaply handle out-of-order data and to avoid excessive load, there is typically some <em>refresh lag </em>between the raw data and when it’s materialized.  In fact, this refresh lag is configurable in TimescaleDB, such that the continuous aggregation engine will not materialize data that’s newer than the refresh lag.  </p><p>(Slightly more specifically, if we compute aggregations across some <a href="https://docs.timescale.com/latest/using-timescaledb/reading-data?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=time-bucket-docs#time-bucket">time bucket</a>, such as hourly, then each hourly interval has a start time and end time.  TimescaleDB will only materialize data when its corresponding aggregation interval’s <em>end time</em> is older than the refresh lag. So, if we are doing hourly rollups with 30 minute refresh lag, then we’d only perform the materialized aggregation from, say, 2:00am - 3:00am <em>after</em> 2:30pm.)</p><p>So, on one hand, using a continuous aggregate view has cut down the amount of data we process at query time by 3600x (i.e., from more than 6 million rows to fewer than 2000).  But, in this view, we’re often missing the last hour or so of data.</p><p>While you could just make the refresh lag smaller and smaller to workaround this problem, it comes at the cost of higher and higher load; unless these aggregates are recomputed on <em>every</em> new insert (expensive!), they’re fundamentally always stale.</p><h2 id="introducing-real-time-aggregation">Introducing Real-Time Aggregation</h2><p>With real-time aggregation, when you query a continuous aggregate view, rather than just getting the pre-computed aggregate from the materialized table, the query will transparently combine this pre-computed aggregate with raw data from the hypertable that’s yet to be materialized.  And, by combining raw and materialized data in this way, you get accurate and up-to-date results, while still enjoying the speedups that come from pre-computing a large portion of the result.</p><p>Let’s return to the example above.  Recall that when we created hourly rollups, we set the refresh lag to 30 minutes, so our continuous aggregate view will lag behind by 30-90 minutes.</p><p>But, when querying a view that supports real-time aggregation, the single query as before for hourly data across the past week will process and combine the results from two tables:</p><ul><li>Materialized table: 10 servers * (22 hours + 24 hours * 6 days) = 1660 rows</li><li>Raw data: 10 servers * 60 seconds * 90 minutes = 54,000 rows  </li></ul><p>So now, with these “back of the envelope” calculations, we’ve processed a total of 55,660 rows, still well below the 6 million from before. Moreover, the last 90 minutes of data are more likely to already be memory resident for even better performance, given the database page caching already happening for recent data.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2020/09/image.png" class="kg-image" alt="Diagram showing how data moves to a materialized table as it ages and continuous aggregate queries execute, and how real-time aggregates combine this data with newer, not yet materialized data" loading="lazy" width="1500" height="1154" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2020/09/image.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2020/09/image.png 1000w, https://timescale.ghost.io/blog/content/images/2020/09/image.png 1500w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Real-time aggregates allow you to query your pre-calculated data </span><b><strong style="white-space: pre-wrap;">and</strong></b><span style="white-space: pre-wrap;"> newer, not yet materialized "raw" data</span></figcaption></figure><p>The above illustration shows this in practice. The database internally maintains a <strong>c<em>ompletion threshold</em></strong> as metadata, which records the point-in-time to which all previous records from the raw table have been materialized.  This completion threshold lags behind the <em>refresh lag </em>we discussed earlier, and gets updated by the database engine whenever a background task updates the materialized view.</p><p><em>(In fact, it’s a bit more complicated given TimescaleDB’s ability to handle late data that gets written after some time region has already been materialized, i.e., behind the completion threshold.  But we’re going to ignore how TimescaleDB tracks invalidation regions in this post.)</em></p><p>So now when processing our query covering the interval , the database engine will conceptually take a UNION ALL between results from the materialized table starting at <code>now() - interval '7 days'</code> up to the completion threshold, with results from the raw table from the completion threshold up to <code>now()</code>.</p><p>But rather than just describe this behavior, let’s walk through a concrete example and compare our query times without continuous aggregates, with vanilla continuous aggregates, and with real-time aggregation enabled.</p><p>These capabilities were developed by Timescale engineers: <a href="https://github.com/svenklemm"><em>Sven Klemm</em></a><em>, </em><a href="https://github.com/cevian"><em>Matvey Arye</em></a><em>, </em><a href="https://github.com/gayyappan"><em>Gayathri Ayyapan</em></a><em>, </em><a href="https://github.com/davidkohn88"><em>David Kohn</em></a>, and <a href="https://github.com/JLockerman"><em>Josh Lockerman</em></a>.</p><h2 id="testing-real-time-aggregation">Testing Real-Time Aggregation</h2><p>In the following, I’ve created a TimescaleDB 1.7 instance via <a href="https://www.timescale.com/products">Managed Service for TimescaleDB</a> (specially, an “basic-100-compute-optimized” instance with PostgreSQL 12, 4 vCPU, and 100GB SSD storage), and then created the following hypertable:</p><pre><code class="language-SQL">$ psql postgres://tsdbadmin@tsdb-bb8e760-internal-90d0.a.timescaledb.io:26479/defaultdb?sslmode=require

=&gt; CREATE TABLE cpu (
      time TIMESTAMPTZ,
      hostname TEXT,
      usage_user FLOAT,
      usage_system FLOAT,
      usage_iowait FLOAT
   );

=&gt; SELECT create_hypertable ('cpu', 'time', 
      chunk_time_interval =&gt; interval '1d');</code></pre><p>I’m now going to load the hypertable with 14 days of synthetic data (which is created with the following INSERT statement):</p><pre><code class="language-SQL">=&gt; INSERT INTO cpu (
   SELECT time, hostname, random(), random(), random()
      FROM generate_series(NOW() - interval '14d', NOW(), '1s') AS time
      CROSS JOIN LATERAL (
         SELECT 'host' || host_id::text AS hostname 
            FROM generate_series(0,9) AS host_id
      ) h
   );</code></pre><p>Okay, so that inserted 12,096,010 rows of synthetic data into our hypertable of the following format, stretching from 2:32am UTC on April 22 to 2:32am UTC on May 6:</p><pre><code class="language-SQL">=&gt; SELECT * FROM cpu ORDER BY time DESC LIMIT 3;

             time              | hostname |     usage_user     |    usage_system     |    usage_iowait     
-------------------------------+----------+--------------------+---------------------+---------------------
 2020-05-06 02:32:34.627143+00 | host0    | 0.5378765249290502 |  0.2958572490961302 | 0.10685818344495246
 2020-05-06 02:32:34.627143+00 | host1    | 0.3175958910709298 |  0.7874926624954846 | 0.16615243032654803
 2020-05-06 02:32:34.627143+00 | host2    | 0.4788377981501064 | 0.18277343256546175 |  0.7183967491020162


=&gt; SELECT min(time) AS start, max(time) AS end FROM cpu;

-[ RECORD 1 ]------------------------
start | 2020-04-22 02:32:34.627143+00
end   | 2020-05-06 02:32:34.627143+00</code></pre><p>Let’s now create a continuous aggregate view on this table with hourly <a href="https://docs.timescale.com/latest/api?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=api-docs-histograms#histogram">histograms</a>: </p><pre><code class="language-SQL">=&gt; CREATE VIEW cpu_1h 
   WITH (timescaledb.continuous, 
         timescaledb.refresh_lag = '30m',
         timescaledb.refresh_interval = '30m')
   AS
      SELECT 
         time_bucket('1 hour', time) AS hour,
         hostname, 
         histogram(usage_user, 0.0, 1.0, 5) AS hist_usage_user,
         histogram(usage_system, 0.0, 1.0, 5) AS hist_usage_system,
         histogram(usage_iowait, 0.0, 1.0, 5) AS hist_usage_iowait
      FROM cpu
      GROUP BY hour, hostname;</code></pre><p>By default, queries to this view use these real-time aggregation features.  If you want to disable real-time aggregation, set <code>materialized_only = true</code> when creating the view or by later ALTERing the view.  (See <a href="https://docs.timescale.com/latest/api?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggs-create-view-docs#continuous_aggregate-create_view">API docs here</a>.)</p><p>Now, the job scheduling framework will start to asynchronously process this view, which we can see in our <a href="https://docs.timescale.com/latest/api?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggs-stats-docs#timescaledb_information-continuous_aggregate_stats">informational view</a>.  (You can also <a href="https://docs.timescale.com/latest/api?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggs-refresh-view-docs#continuous_aggregate-refresh_view">manually force</a> the materialization to occur if needed.)  <br></p><pre><code class="language-SQL">=&gt; SELECT * FROM timescaledb_information.continuous_aggregate_stats;

- [ RECORD 1 ]
view_name              | cpu_1h
completed_threshold    | 2020-05-06 02:00:00+00
invalidation_threshold | 2020-05-06 02:00:00+00
job_id                 | 1000
last_run_started_at    | 2020-05-06 02:34:08.300524+00
last_successful_finish | 2020-05-06 02:34:09.04923+00
last_run_status        | Success
job_status             | Scheduled
last_run_duration      | 00:00:00.748706
next_scheduled_run     | 2020-05-06 03:04:09.04923+00
total_runs             | 17
total_successes        | 17
total_failures         | 0
total_crashes          | 0
</code></pre><p>From this data, we see that the materialized view includes data up to 2:00am on May 6, while from above we’ve learned that the raw data goes up to 2:32am. </p><p>Let’s try our query directly on the raw table, and use an EXPLAIN ANALYZE to both show the database plan, as well as actually execute the query and collect timing information.  (Note that in many use cases, one would offset queries from <code>now() - &lt;some interval&gt;</code>. But to ensure that we use identical datasets in our subsequent analysis, we explicitly select the interval offset from the dataset’s last timestamp.)</p><pre><code class="language-SQL">=&gt; EXPLAIN (ANALYZE, COSTS OFF)
   SELECT 
      time_bucket('1 hour', time) AS hour,
      hostname, 
      histogram(usage_user, 0.0, 1.0, 5) AS hist_usage_user,
      histogram(usage_system, 0.0, 1.0, 5) AS hist_usage_system,
      histogram(usage_iowait, 0.0, 1.0, 5) AS hist_usage_iowait
   FROM cpu
   WHERE time &gt; '2020-05-06 02:32:34.627143+00'::timestamptz - interval '7 days'
   GROUP BY hour, hostname
   ORDER BY hour DESC;

QUERY PLAN             
----------------------------------------------------------------
 Finalize GroupAggregate (actual time=1859.306..1862.331 rows=1690 loops=1)
   Group Key: (time_bucket('01:00:00'::interval, cpu."time")), cpu.hostname
   -&gt;  Gather Merge (actual time=1841.735..1849.604 rows=1881 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         -&gt;  Sort (actual time=1194.162..1194.222 rows=627 loops=3)
               Sort Key: (time_bucket('01:00:00'::interval, cpu."time")) DESC, cpu.hostname
               Sort Method: quicksort  Memory: 25kB
               Worker 0:  Sort Method: quicksort  Memory: 274kB
               Worker 1:  Sort Method: quicksort  Memory: 274kB
               -&gt;  Partial HashAggregate (actual time=1193.198..1193.594 rows=627 loops=3)
                     Group Key: time_bucket('01:00:00'::interval, cpu."time"), cpu.hostname
                     -&gt;  Parallel Custom Scan (ChunkAppend) on cpu (actual time=9.840..716.952 rows=2016000 loops=3)
                           Chunks excluded during startup: 7
                           -&gt;  Parallel Seq Scan on _hyper_1_14_chunk (actual time=14.751..199.098 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_13_chunk (actual time=14.749..201.100 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_12_chunk (actual time=0.025..182.591 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_11_chunk (actual time=0.031..182.812 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_10_chunk (actual time=0.035..183.918 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_9_chunk (actual time=0.019..184.416 rows=864000 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                           -&gt;  Parallel Seq Scan on _hyper_1_8_chunk (actual time=0.823..91.605 rows=386225 loops=2)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                                 Rows Removed by Filter: 45775
                           -&gt;  Parallel Seq Scan on _hyper_1_15_chunk (actual time=0.022..20.277 rows=91550 loops=1)
                                 Filter: ("time" &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))

 Planning Time: 1.917 ms
 Execution Time: 1921.753 ms</code></pre><p>Note that TimescaleDB’s constraint exclusion excluded 7 of the chunks from being queried given the WHERE predicate (as the query was for the last 7 days of the 14 day dataset), then processed the query on the remaining 8 chunks (performing a scan over 6,048,000 rows) using two parallel workers.  The query in total took just over 1.9 seconds.</p><p>Now let’s try the query on our materialized table, first turning off real-time aggregation just for this experiment: </p><pre><code class="language-SQL">=&gt; ALTER VIEW cpu_1h set (timescaledb.materialized_only = true);</code></pre><p>First, let’s look at the table definition, which defines a SELECT on the materialized view with the specified GROUP BYs.  But we also see that each of the histograms calls “finalize_agg.”  TimescaleDB doesn’t precisely pre-compute and store the exact answer that’s specified in the query, but rather a <a href="https://www.postgresql.org/docs/current/xaggr.html#XAGGR-PARTIAL-AGGREGATES">partial aggregate</a> that is then “finalized” at query time, which will allow for greater parallelization and rebucketing at query time (in a future release).</p><pre><code class="language-SQL"> \d+ cpu_1h;

                                          View "public.cpu_1h"
      Column       |           Type           | Collation | Nullable | Default | Storage  | Description 
-------------------+--------------------------+-----------+----------+---------+----------+-------------
 hour              | timestamp with time zone |           |          |         | plain    | 
 hostname          | text                     |           |          |         | extended | 
 hist_usage_user   | integer[]                |           |          |         | extended | 
 hist_usage_system | integer[]                |           |          |         | extended | 
 hist_usage_iowait | integer[]                |           |          |         | extended | 

View definition:
 SELECT _materialized_hypertable_2.hour,
    _materialized_hypertable_2.hostname,
    _timescaledb_internal.finalize_agg('histogram(double precision,double precision,double precision,integer)'::text, NULL::name, NULL::name, '{{pg_catalog,float8},{pg_catalog,float8},{pg_catalog,float8},{pg_catalog,int4}}'::name[], _materialized_hypertable_2.agg_3_3, NULL::integer[]) AS hist_usage_user,
    _timescaledb_internal.finalize_agg(...) AS hist_usage_system,
    _timescaledb_internal.finalize_agg(...) AS hist_usage_iowait
   FROM _timescaledb_internal._materialized_hypertable_2
  GROUP BY _materialized_hypertable_2.hour, _materialized_hypertable_2.hostname;</code></pre><p>Now let’s run the query with vanilla continuous aggregates enabled:</p><pre><code class="language-SQL">=&gt; EXPLAIN (ANALYZE, COSTS OFF)
   SELECT * FROM cpu_1h
   WHERE hour &gt; '2020-05-06 02:32:34.627143+00'::timestamptz - interval '7 days'
   ORDER BY hour DESC;

QUERY PLAN
----------------------------------------------------------------
 Sort (actual time=3.218..3.312 rows=1670 loops=1)
   Sort Key: _materialized_hypertable_2.hour DESC
   Sort Method: quicksort  Memory: 492kB
   -&gt;  HashAggregate (actual time=1.943..2.891 rows=1670 loops=1)
         Group Key: _materialized_hypertable_2.hour, _materialized_hypertable_2.hostname
         -&gt;  Custom Scan (ChunkAppend) on _materialized_hypertable_2 (actual time=0.064..0.688 rows=1670 loops=1)
               Chunks excluded during startup: 1
               -&gt;  Seq Scan on _hyper_2_17_chunk (actual time=0.063..0.590 rows=1670 loops=1)
                     Filter: (hour &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))
                     Rows Removed by Filter: 270

 Planning Time: 0.645 ms
 Execution Time: 3.461 ms</code></pre><p>Just 4 milliseconds, after a scan of 1,670 rows in the materialized hypertable.  And let’s look at the most recent 3 rows returned for a specific host:</p><pre><code class="language-SQL">=&gt; SELECT hour, hostname, hist_usage_user
    FROM cpu_1h
    WHERE hour &gt; '2020-05-06 02:32:34.627143+00'::timestamptz - interval '7 days'         
       AND hostname = 'host0'
    ORDER BY hour DESC LIMIT 3;

          hour          | hostname |      hist_usage_user      
------------------------+----------+---------------------------
 2020-05-06 01:00:00+00 | host0    | {0,781,676,712,719,712,0}
 2020-05-06 00:00:00+00 | host0    | {0,736,714,776,689,685,0}
 2020-05-05 23:00:00+00 | host0    | {0,714,759,715,692,720,0}</code></pre><p>Note that the last record is from the 1:00am - 2:00am hour.</p><p>Now let’s re-enable real-time aggregation and try the same query, first showing how the real-time aggregation is defined as a UNION ALL between the materialized and raw data.</p><pre><code class="language-SQL">=&gt; ALTER VIEW cpu_1h set (timescaledb.materialized_only = false);

=&gt; \d+ cpu_1h;

                                          View "public.cpu_1h"
      Column       |           Type           | Collation | Nullable | Default | Storage  | Description 
-------------------+--------------------------+-----------+----------+---------+----------+-------------
 hour              | timestamp with time zone |           |          |         | plain    | 
 hostname          | text                     |           |          |         | extended | 
 hist_usage_user   | integer[]                |           |          |         | extended | 
 hist_usage_system | integer[]                |           |          |         | extended | 
 hist_usage_iowait | integer[]                |           |          |         | extended | 

View definition:
 SELECT _materialized_hypertable_2.hour,
    _materialized_hypertable_2.hostname,
    _timescaledb_internal.finalize_agg(...) AS hist_usage_user,
    _timescaledb_internal.finalize_agg(...) AS hist_usage_system,
    _timescaledb_internal.finalize_agg(...) AS hist_usage_iowait
   FROM _timescaledb_internal._materialized_hypertable_2
  WHERE _materialized_hypertable_2.hour &lt; COALESCE(_timescaledb_internal.to_timestamp(_timescaledb_internal.cagg_watermark(1)), '-infinity'::timestamp with time zone)
  GROUP BY _materialized_hypertable_2.hour, _materialized_hypertable_2.hostname
UNION ALL
 SELECT time_bucket('01:00:00'::interval, cpu."time") AS hour,
    cpu.hostname,
    histogram(cpu.usage_user, 0.0::double precision, 1.0::double precision, 5) AS hist_usage_user,
    histogram(cpu.usage_system, 0.0::double precision, 1.0::double precision, 5) AS hist_usage_system,
    histogram(cpu.usage_iowait, 0.0::double precision, 1.0::double precision, 5) AS hist_usage_iowait
   FROM cpu
  WHERE cpu."time" &gt;= COALESCE(_timescaledb_internal.to_timestamp(_timescaledb_internal.cagg_watermark(1)), '-infinity'::timestamp with time zone)
  GROUP BY (time_bucket('01:00:00'::interval, cpu."time")), cpu.hostname;


=&gt; EXPLAIN (ANALYZE, COSTS OFF)
   SELECT * FROM cpu_1h
   WHERE hour &gt; '2020-05-06 02:32:34.627143+00'::timestamptz - interval '7 days'
   ORDER BY hour DESC;

QUERY PLAN               
----------------------------------------------------------------
 Sort (actual time=20.871..21.055 rows=1680 loops=1)
   Sort Key: _materialized_hypertable_2.hour DESC
   Sort Method: quicksort  Memory: 495kB
   -&gt;  Append (actual time=1.842..20.536 rows=1680 loops=1)
         -&gt;  HashAggregate (actual time=1.841..2.789 rows=1670 loops=1)
               Group Key: _materialized_hypertable_2.hour, _materialized_hypertable_2.hostname
               -&gt;  Custom Scan (ChunkAppend) on _materialized_hypertable_2 (actual time=0.105..0.580 rows=1670 loops=1)
                     Chunks excluded during startup: 1
                     -&gt;  Index Scan using _hyper_2_17_chunk__materialized_hypertable_2_hour_idx on _hyper_2_17_chunk (actual time=0.104..0.475 rows=1670 loops=1)
                           Index Cond: ((hour &lt; COALESCE(_timescaledb_internal.to_timestamp(_timescaledb_internal.cagg_watermark(1)), '-infinity'::timestamp with time zone)) AND (hour &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval)))
         -&gt;  HashAggregate (actual time=17.641..17.655 rows=10 loops=1)
               Group Key: time_bucket('01:00:00'::interval, cpu."time"), cpu.hostname
               -&gt;  Custom Scan (ChunkAppend) on cpu (actual time=0.165..12.297 rows=19550 loops=1)
                     Chunks excluded during startup: 14
                     -&gt;  Index Scan using _hyper_1_15_chunk_cpu_time_idx on _hyper_1_15_chunk (actual time=0.163..9.723 rows=19550 loops=1)
                           Index Cond: ("time" &gt;= COALESCE(_timescaledb_internal.to_timestamp(_timescaledb_internal.cagg_watermark(1)), '-infinity'::timestamp with time zone))
                           Filter: (time_bucket('01:00:00'::interval, "time") &gt; ('2020-05-06 02:32:34.627143+00'::timestamp with time zone - '7 days'::interval))

 Planning Time: 3.532 ms
 Execution Time: 22.905 ms
</code></pre><p>Still very fast at just over 26 milliseconds (scanning 1,670 materialized rows and 19,550 raw rows), and now the results:</p><pre><code class="language-SQL">=&gt; SELECT hour, hostname, hist_usage_user
   FROM cpu_1h
WHERE hour &gt; '2020-05-06 02:32:34.627143+00'::timestamptz - interval '7 days'
      AND hostname = 'host0'
   ORDER BY hour DESC LIMIT 3;

          hour          | hostname |      hist_usage_user      
------------------------+----------+---------------------------
 2020-05-06 02:00:00+00 | host0    | {0,384,388,385,400,398,0}
 2020-05-06 01:00:00+00 | host0    | {0,781,676,712,719,712,0}
 2020-05-06 00:00:00+00 | host0    | {0,736,714,776,689,685,0}

</code></pre><p>Unlike when we were processing the materialized table without the real-time aggregation, we have up-to-date data with data from the 2:00 - 3:00am hour.  This is because the materialized table didn’t have data from the last hour, while the real-time aggregation was able to compute that result from the raw data at query time.  You can also notice that there is less data in the final row (namely, each histogram bucket has about half the counts as the prior rows), as this final row was the aggregation of 32 minutes of raw data, not a full hour. </p><p>You can also observe these two stages of real-time aggregation in the above query plan:  the materialized hypertable is processed in the first section via <code>Custom Scan (ChunkAppend) on _materialized_hypertable_2</code>, while the underlying raw hypertable is processed in the second section via <code>Custom Scan (ChunkAppend) on cpu</code>, and each processes only before or after the offset specified by the completion threshold (shown with  <code>_timescaledb_internal.cagg_watermark(1)</code> in the plan).</p><p>So, in summary:  a complete, up-to-date aggregate over the data, both at a fraction of the latency of querying the raw data, and avoiding the excessive overhead of schemes that update materalizations through per-row or per-statement triggers.</p><table>
<thead>
<tr>
<th>Query Type</th>
<th>Latency</th>
<th>Freshness</th>
</tr>
</thead>
<tbody>
<tr>
<td>Raw Data</td>
<td>1924 ms</td>
<td>Up-to-date</td>
</tr>
<tr>
<td>Continuous Aggregates</td>
<td>4 ms</td>
<td>Lags up to 90 minutes</td>
</tr>
<tr>
<td>Real-Time Aggregation</td>
<td>26 ms</td>
<td>Up-to-date</td>
</tr>
</tbody>
</table>
<p><strong>Continuous aggregates and real-time aggregation for the win!</strong></p><h2 id="conclusions">Conclusions</h2><p>What motivated us to build TimescaleDB is the firm belief that time-series use cases need a best-in-class, flexible time-series database, with advanced capabilities specifically designed for time-series workloads.  We developed real-time aggregation for time-series use cases such as devops monitoring, real-time analytics, and IoT, where fast queries over high-volume workloads and accurate, real-time results really matter. </p><p>Real-time aggregation joins a number of advanced capabilities in TimescaleDB around data lifecycle management and time-series analytics, including automated data retention, data reordering, native compression, downsampling, and traditional continuous aggregates.</p><p>And, <strong>there’s still much more to come</strong>. Keep an eye out for our much-anticipated TimescaleDB 2.0 release, which introduces horizontal scaling to TimescaleDB for terabyte to petabyte workloads.</p><h3 id="want-to-check-out-real-time-aggregation">Want to check out real-time aggregation?</h3><ul><li>Ready to dig in? Check out our <a href="https://docs.timescale.com/latest/using-timescaledb/continuous-aggregates/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=continuous-aggregates-docs">docs</a>.</li><li>Brand new to TimescaleDB?  Get started <a href="https://docs.timescale.com/latest/getting-started/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=getting-started-docs">here</a>.</li></ul><p>If you have any questions along the way, we’re always available via our <a href="https://slack.timescale.com">community Slack</a> (we’re <a href="https://timescaledb.slack.com/archives/D011A62GNR0">@mike</a> and <a href="https://timescaledb.slack.com/archives/D0137UNE550">@sven </a>, come say hi 👋).</p><p>And, if you are interested in keeping up-to-date with future TimescaleDB releases, <a href="https://www.timescale.com/signup/release-notes/?utm_source=timescale-real-time-aggregates-details&amp;utm_medium=blog&amp;utm_campaign=1-7-release&amp;utm_content=release-notes-subscribe">sign up for our Release Notes</a>.  It’s low-traffic, we promise.</p><p>Until next time, keep it real!</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Time-series data: Why (and how) to use a relational database instead of NoSQL]]></title>
            <description><![CDATA[These days, time-series data applications (e.g., data center / server / microservice / container monitoring, sensor / IoT analytics, financial data analysis, etc.) are proliferating.]]></description>
            <link>https://www.tigerdata.com/blog/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c</guid>
            <category><![CDATA[General]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Mike Freedman]]></dc:creator>
            <pubDate>Thu, 20 Apr 2017 14:00:00 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2018/12/warehouse.png">
            </media:content>
            <content:encoded><![CDATA[<p>These days, <a href="https://timescale.ghost.io/blog/time-series-data/">time-series data</a> applications (e.g., data center / server / microservice / container monitoring, sensor / IoT analytics, financial data analysis, etc.) are proliferating.</p><p>As a result, time-series databases are in fashion (<a href="https://misfra.me/2016/04/09/tsdb-list/" rel="noopener">here are 33 of them</a>). Most of these renounce the trappings of a traditional relational database and adopt what is generally known as a NoSQL model. Usage patterns are similar: <a href="https://www.percona.com/blog/2017/02/10/percona-blog-poll-database-engine-using-store-time-series-data/" rel="noopener">a recent survey</a> showed that developers preferred NoSQL to relational databases for time-series data by over 2:1.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-78.png" class="kg-image" alt="" loading="lazy" width="570" height="240"><figcaption><b><strong style="white-space: pre-wrap;">Relational databases include:</strong></b><span style="white-space: pre-wrap;"> MySQL, MariaDB Server, PostgreSQL. </span><b><strong style="white-space: pre-wrap;">NoSQL databases include:</strong></b><span style="white-space: pre-wrap;"> Elastic, InfluxDB, MongoDB, Cassandra, Couchbase, Graphite, Prometheus, ClickHouse, OpenTSDB, DalmatinerDB, KairosDB, RiakTS. </span><b><strong style="white-space: pre-wrap;">Source: </strong></b><a href="https://www.percona.com/blog/2017/02/10/percona-blog-poll-database-engine-using-store-time-series-data/"><b><strong style="white-space: pre-wrap;">Percona</strong></b></a><b><strong style="white-space: pre-wrap;">.&nbsp;</strong></b></figcaption></figure><p>Typically, the reason for adopting NoSQL time-series databases comes down to scale. While relational databases have many useful features that most NoSQL databases do not (robust secondary index support; complex predicates; a rich query language; JOINs, etc), they are difficult to scale.</p><p>And because time-series data piles up very quickly, many developers believe relational databases are ill-suited for it.</p><p>We take a different, somewhat heretical stance: relational databases can be quite powerful for time-series data. One just needs to solve the scaling problem. That is what we do in <a href="https://github.com/timescale/timescaledb" rel="noopener">TimescaleDB</a>.</p><p>When we <a href="https://timescale.ghost.io/blog/when-boring-is-awesome-building-a-scalable-time-series-database-on-postgresql-2900ea453ee2/">announced TimescaleDB two weeks ago</a>, we received a lot of positive feedback from the community. But we also heard from skeptics, who found it hard to believe that one should (or could) build a scalable time-series database on a relational database (in our case, PostgreSQL).</p><p>There are two separate ways to think about scaling: <strong>scaling up</strong> so that a single machine can store more data, and <strong>scaling out</strong> so that data can be stored across multiple machines.</p><p>Why are both important? The most common approach to scaling out across a cluster of <em>N</em> servers is to partition, or shard, a dataset into <em>N</em> partitions. If each server is limited in its throughput or performance (i.e., unable to scale up), then the overall cluster throughput is greatly reduced.</p><p>This post discusses <strong>scaling up</strong>. (A <strong>scaling-out</strong> post will be published on a later date.)</p><p>In particular, this post explains:</p><ul><li>Why relational databases do not normally scale up well</li><li>How LSM trees (typically used in NoSQL databases) do not adequately solve the needs of many time-series applications</li><li>How time-series data is unique, how one can leverage those differences to overcome the scaling problem, and some performance results</li></ul><p>Our motivations are twofold: <strong>for anyone facing similar problems</strong>, to share what we’ve learned; and <strong>for those considering using TimescaleDB for time-series data</strong> (including the skeptics!), to explain some of our design decisions.</p><hr><h2 id="why-databases-do-not-normally-scale-up-well-swapping-inout-of-memory-is-expensive">Why databases do not normally scale up well: Swapping in/out of memory is expensive</h2><p>A common problem with scaling database performance on a single machine is the significant cost/performance trade-off between memory and disk. While memory is faster than disk, it is much more expensive: about 20x costlier than solid-state storage like Flash, 100x more expensive than hard drives. Eventually, our entire dataset will not fit in memory, which is why we’ll need to write our data and indexes to disk.</p><p>This is an old, common problem for relational databases. Under most relational databases, a table is stored as a collection of fixed-size pages of data (e.g., 8KB pages in PostgreSQL), on top of which the system builds data structures (such as <a href="https://en.wikipedia.org/wiki/B-tree" rel="noopener">B-trees</a>) to index the data. With an index, a query can quickly find a row with a specified ID (e.g., bank account number) without scanning the entire table or “walking” the table in some sorted order.</p><p>Now, if the working set of data and indexes is small, we can keep it in memory.</p><p>But if the data is sufficiently large that we can’t fit all (similarly fixed-size) pages of our B-tree in memory, then updating a random part of the tree can involve significant disk I/O as we read pages from disk into memory, modify in memory, and then write back out to disk (when evicted to make room for other B-tree pages). And a relational database like PostgreSQL keeps a B-tree (or other data structure) for <em>each</em> table index, in order for values in that index to be found efficiently. So, the problem compounds as you index more columns.</p><p>In fact, because the database only accesses the disk in page-sized boundaries, even seemingly small updates can cause these swaps to occur: To change one cell, the database may need to swap out an existing 8KB page and write it back to disk, then read in the new page before modifying it.</p><p>But why not use smaller- or variable-sized pages? There are two good reasons: minimizing disk fragmentation, and (in case of a spinning hard disk) minimizing the overhead of the “seek time” (usually 5–10ms) required in physically moving the disk head to a new location.</p><p>What about solid-state drives (SSDs)? While solutions like NAND Flash drives eliminate any physical “seek” time, they can only be read from or written to at the page-level granularity (today, typically 8KB). So, even to update a single byte, the SSD firmware needs to read an 8KB page from disk to its buffer cache, modify the page, then write the updated 8KB page back to a new disk block.</p><p>The cost of swapping in and out of memory can be seen in this performance graph from PostgreSQL, where insert throughput plunges with table size and increases in variance (depending on whether requests hit in memory or require (potentially multiple) fetches from disk).</p><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2018/12/gif3-1.gif" class="kg-image" alt="" loading="lazy" width="1067" height="600" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/gif3-1.gif 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2018/12/gif3-1.gif 1000w, https://timescale.ghost.io/blog/content/images/2018/12/gif3-1.gif 1067w" sizes="(min-width: 720px) 720px"></figure><p><em>Insert throughput as a function of table size for PostgreSQL 9.6.2, running with 10 workers on an Azure standard DS4 v2 (8 core) machine with SSD-based (premium LRS) storage. Clients insert individual rows into the database (each of which has 12 columns: a timestamp, an indexed randomly-chosen primary id, and 10 additional numerical metrics). The PostgreSQL rate starts over 15K inserts/second, but then begins to drop significantly after 50M rows and begins to experience very high variance (including periods of only 100s of inserts/sec).</em></p><hr><h2 id="enter-nosql-databases-with-log-structured-merge-trees-and-new-problems">Enter NoSQL databases with Log-Structured Merge Trees (and new problems)</h2><p>About a decade ago, we started seeing a number of “NoSQL” storage systems address this problem via <a href="http://www.cs.umb.edu/~poneil/lsmtree.pdf" rel="noopener">Log-structured merge (LSM) trees</a>, which reduce the cost of making small writes by only performing larger append-only writes to disk.</p><p>Rather than performing “in-place” writes (where a small change to an existing page requires reading/writing that entire page from/to disk), LSM trees queue up several new updates (including deletes!) into pages and write them as a single batch to disk. In particular, all writes in an LSM tree are performed to a sorted table maintained <em>in memory</em>, which is then flushed to disk as an immutable batch when of sufficient size (as a “sorted string table”, or SSTable). This reduces the cost of making small writes.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-79.png" class="kg-image" alt="" loading="lazy" width="631" height="136" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-79.png 600w, https://timescale.ghost.io/blog/content/images/2018/12/image-79.png 631w"><figcaption><b><strong style="white-space: pre-wrap;">In an LSM tree, all updates are first written a sorted table in memory, and then flushed to disk as an immutable batch, stored as an SSTable, which is often indexed in memory. Source: </strong></b><a href="https://www.igvita.com/2012/02/06/sstable-and-log-structured-storage-leveldb/"><b><strong style="white-space: pre-wrap;">igvita.com</strong></b></a></figcaption></figure><p>This architecture — which has been adopted by many “NoSQL” databases like LevelDB, Google BigTable, Cassandra, MongoDB (WiredTiger), and <a href="https://www.outfluxdata.com">InfluxDB</a> — may seem great at first. Yet it introduces other tradeoffs: higher memory requirements and poor secondary index support.</p><p><strong>Higher-memory requirements:</strong> Unlike in a B-tree, in an LSM tree there is no single ordering: no global index to give us a sorted order over all keys. Consequently, looking up a value for a key gets more complex: first, check the memory table for the latest version of the key; otherwise, look to (potentially many) on-disk tables to find the latest value associated with that key. To avoid excessive disk I/O (and if the values themselves are large, such as the webpage content stored in Google’s BigTable), indexes for all SSTables may be kept entirely in memory, which in turn increases memory requirements.</p><p><strong>Poor secondary index support:</strong> Given that they lack any global sorted order, LSM trees do not naturally support secondary indexes. Various systems have added some additional support, such as by duplicating the data in a different order. Or, they emulate support for richer predicates by building their primary key as the concatenation of multiple values. Yet this approach comes with the cost of requiring a larger scan among these keys at query time, thus supporting only items with a limited cardinality (e.g., discrete values, not numeric ones).</p><p>There is a better approach to this problem. Let’s start by better understanding time-series data.</p><hr><h2 id="time-series-data-is-different">Time-series data is different</h2><p>Let’s take a step back, and look at the original problem that relational databases were designed to solve. Starting from <a href="https://en.wikipedia.org/wiki/IBM_System_R" rel="noopener">IBM’s seminal System R</a> in the mid-1970s, relational databases were employed for what became known as online transaction processing (<a href="https://www.tigerdata.com/learn/understanding-oltp" rel="noreferrer">OLTP</a>).</p><p>Under OLTP, operations are often transactional updates to various rows in a database. For example, think of a bank transfer: a user debits money from one account and credits another. This corresponds to updates to two rows (or even just two cells) of a database table. Because bank transfers can occur between any two accounts, the two rows that are modified are somewhat randomly distributed over the table.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-80.png" class="kg-image" alt="" loading="lazy" width="1404" height="899" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-80.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2018/12/image-80.png 1000w, https://timescale.ghost.io/blog/content/images/2018/12/image-80.png 1404w" sizes="(min-width: 720px) 720px"><figcaption><b><strong style="white-space: pre-wrap;">Time-series data arises from many different settings: industrial machines; transportation and logistics; DevOps, datacenter, and server monitoring; and financial applications.</strong></b></figcaption></figure><p>Now let’s consider a few examples of time-series workloads:</p><ul><li><strong>DevOps/server/container monitoring.</strong> The system typically collects metrics about different servers or containers: CPU usage, free/used memory, network tx/rx, disk IOPS, etc. Each set of metrics is associated with a timestamp, unique server name/ID, and a set of tags that describe an attribute of what is being collected.</li><li><strong>IoT sensor data.</strong> Each IoT device may report multiple sensor readings for each time period. As an example, for environmental and air quality monitoring this could include: temperature, humidity, barometric pressure, sound levels, measurements of nitrogen dioxide, carbon monoxide, particulate matter, etc. Each set of readings is associated with a timestamp and unique device ID, and may contain other metadata.</li><li><strong>Financial data. </strong>Financial tick data may include streams with a timestamp, the name of the security, and its current price and/or price change. Another type of financial data is payment transactions, which would include a unique account ID, timestamp, transaction amount, as well as any other metadata. (Note that this data is different than the OLTP example above: here we are recording every transaction, while the OLTP system was just reflecting the current state of the system.)</li><li><strong>Fleet/asset management.</strong> Data may include a vehicle/asset ID, timestamp, GPS coordinates at that timestamp, and any metadata.</li></ul><p>In all of these examples, the datasets are a stream of measurements that involve inserting “new data” into the database, typically to the latest time interval. While it’s possible for data to arrive much later than when it was generated/timestamped, either due to network/system delays or because of corrections to update existing data, this is typically the exception, not the norm.</p><p>In other words, these two workloads have very different characteristics:</p><h4 id="oltp-writes"><strong>OLTP Writes</strong></h4><ul><li>Primarily UPDATES</li><li>Randomly distributed (over the set of primary keys)</li><li>Often transactions across multiple primary keys</li></ul><h4 id="time-series-writes"><strong>Time-series Writes</strong></h4><ul><li>Primarily INSERTs</li><li>Primarily to a recent time interval</li><li>Primarily associated with both a timestamp and a separate primary key (e.g., server ID, device ID, security/account ID, vehicle/asset ID, etc.)</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-81.png" class="kg-image" alt="" loading="lazy" width="1600" height="774" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-81.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2018/12/image-81.png 1000w, https://timescale.ghost.io/blog/content/images/2018/12/image-81.png 1600w" sizes="(min-width: 720px) 720px"><figcaption><b><strong style="white-space: pre-wrap;">TimescaleDB stores each chunk in an internal database table, so indexes only grow with the size of each chunk, not the entire </strong></b><a href="https://www.tigerdata.com/blog/database-indexes-in-postgresql-and-timescale-cloud-your-questions-answered" rel="noreferrer"><b><strong style="white-space: pre-wrap;">hypertable</strong></b></a><b><strong style="white-space: pre-wrap;">. As inserts are largely to the more recent interval, that one remains in memory, avoiding expensive swaps to disk.</strong></b></figcaption></figure><p>Why does this matter? As we will see, one can take advantage of these characteristics to solve the scaling-up problem on a relational database.</p><hr><h2 id="a-new-way-adaptive-timespace-chunking">A new way: Adaptive time/space chunking</h2><p><em>NOTE September 2021: following publication of this post, as explained in </em><a href="https://github.com/timescale/timescaledb/issues/2574"><em>this GitHub issue</em></a><em> adaptive chunking was deprecated from latest releases of TimescaleDB. There is </em><a href="https://github.com/timescale/timescaledb/issues/3472"><em>a feature request</em></a><em> for the approach to be reinstated. You may wish to follow or upvote that request. </em></p><p>When previous approaches tried to avoid small writes to disk, they were trying to address the broader OLTP problem of UPDATEs to random locations. But as we just established, time-series workloads are different: writes are primarily INSERTS (not UPDATES), to a recent time interval (not a random location). In other words, time-series workloads are <em>append only.</em></p><p>This is interesting: it means that, if data is sorted by time, we would always be writing towards the “end” of our dataset. Organizing data by time would also allow us to keep the actual working set of database pages rather small, and maintain them in memory. And reads, which we have spent less time discussing, could also benefit: if many read queries are to recent intervals (e.g., for real-time dashboarding), then this data would be already cached in memory.</p><p>At first glance, it may seem like indexing on time would give us efficient writes and reads for free. But once we want any other indexes (e.g., another primary key like server/device ID, or any secondary indexes), then this naive approach would revert us back to making random inserts into our B-tree for that index.</p><p>There is another way, which we call, “adaptive time/space chunking”. This is what we use in TimescaleDB.</p><p>Instead of just indexing by time, TimescaleDB builds distinct <em>tables </em>by splitting data according to two dimensions: the time interval <em>and</em> a primary key (e.g., server/device/asset ID). We refer to these as <em>chunks</em> to differentiate them from <em>partitions</em>, which are typically defined by splitting the primary key space. Because each of these chunks are stored as a database table itself, and the query planner is aware of the chunk’s ranges (in time and keyspace), the query planner can immediately tell to which chunk(s) an operation’s data belongs. (This applies both for inserting rows, as well as for pruning the set of chunks that need to be touched when executing queries.)</p><p>The key benefit of this approach is that now all of our indexes are built only across these much smaller chunks (tables), rather than a single table representing the entire dataset. So if we size these chunks properly, we <em>can </em>fit the latest tables (and their B-trees) completely in memory, and avoid this swap-to-disk problem, while maintaining support for multiple indexes.</p><h2 id="approaches-to-implementing-chunking">Approaches to implementing chunking</h2><p>The two intuitive approaches to design this time/space chunking each have significant limitations:</p><h3 id="approach-1-fixed-duration-intervals">Approach #1: Fixed-duration intervals</h3><p>Under this approach, all chunks can have fixed, identical time intervals, e.g., 1 day. This works well if the volume of data collected per interval does not change. However, as services become popular, their infrastructure correspondingly expands, leading to more servers and more monitoring data. Similarly, successful IoT products will deploy ever more numbers of devices. And once we start writing too much data to each chunk, we’re regularly swapping to disk (and will find ourselves back at square one). On the flip side, choosing too-small intervals to start with leads to other performance downsides, e.g., having to touch many tables at query time.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-82.png" class="kg-image" alt="" loading="lazy" width="699" height="204" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-82.png 600w, https://timescale.ghost.io/blog/content/images/2018/12/image-82.png 699w"><figcaption><b><strong style="white-space: pre-wrap;">Each chunk has a fixed duration in time. Yet if the data volume per time increases, then eventually chunk size becomes too large to fit in memory.</strong></b></figcaption></figure><h3 id="approach-2-fixed-sized-chunks">Approach #2: Fixed-sized chunks</h3><p>With this approach, all chunks have fixed target sizes, e.g., 1GB. A chunk is written to until it reaches its maximum size, at which point it becomes “closed” and its time interval constraints become fixed. Later data falling within the chunk’s “closed” interval will still be written to the chunk, however, in order to preserve the correctness of the chunk’s time constraints.</p><p>A key challenge is that the time interval of the chunk depends on the order of data. Consider if data (even a single datapoint) arrives “early” by hours or even days, potentially due to a non-synchronized clock, or because of varying delays in systems with intermittent connectivity. This early datapoint will stretch out the time interval of the “open” chunk, while subsequent on-time data can drive the chunk over its target size. The insert logic for this approach is also more complex and expensive, driving down throughput for large batch writes (such as large COPY operations), as the database needs to make sure it inserts data in temporal order to determine when a new chunk should be created (even in the <em>middle</em> of an operation). Other problems exist for fixed- or max-size chunks as well, including time intervals that may not align well with data retention policies (“delete data after 30 days”).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-83.png" class="kg-image" alt="" loading="lazy" width="706" height="202" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-83.png 600w, https://timescale.ghost.io/blog/content/images/2018/12/image-83.png 706w"><figcaption><b><strong style="white-space: pre-wrap;">Each chunk’s time interval is fixed only once its maximum size has been reached. Yet if data arrives early, this creates a large interval for the chunk, and the chunk eventually becomes too large to fit in memory.</strong></b></figcaption></figure><p>TimescaleDB takes a third approach that couples the strengths of both approaches.</p><h3 id="approach-3-adaptive-intervals-our-current-design">Approach #3: Adaptive intervals (our current design)</h3><p><a href="https://timescale.ghost.io/blog/blog/time-series-data-why-and-how-to-use-a-relational-database-instead-of-nosql-d0cd6975e87c/#a-new-way-adaptive-timespace-chunking"><em>Please see note, above. </em></a></p><p>Chunks are created with a fixed interval, but the interval adapts from chunk-to-chunk based on changes in data volumes in order to hit maximum target sizes.</p><p>By avoiding open-ended intervals, this approach ensures that data arriving early doesn’t create too-long time intervals that will subsequently lead to over-large chunks. Further, like static intervals, it more naturally supports retention policies specified on time, e.g., “delete data after 30 days”. Given TimescaleDB’s time-based chunking, such policies are implemented by simply dropping chunks (tables) in the database. This means that individual <em>files </em>in the underlying file system can simply be deleted, rather than needing to delete individual <em>rows,</em> which requires erasing/invalidating portions of the underlying file. Such an approach therefore avoids fragmentation in the underlying database files, which in turn avoids the need for <a href="https://www.postgresql.org/docs/9.6/static/routine-vacuuming.html" rel="noopener">vacuuming</a>. And this vacuuming can be prohibitively expensive in very large tables.</p><p>Still, this approach ensures that chunks are sized appropriately so that the latest ones can be maintained in memory, even as data volumes may change.</p><p>Partitioning by primary key then takes each time interval and further splits it into a number of smaller chunks, which all share the same time interval but are disjoint in terms of their primary keyspace. This enables better parallelization both on servers with multiple disks — for both inserts and queries  — as well as multiple servers. More on these issues in a later post.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-84.png" class="kg-image" alt="" loading="lazy" width="709" height="421" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-84.png 600w, https://timescale.ghost.io/blog/content/images/2018/12/image-84.png 709w"><figcaption><b><strong style="white-space: pre-wrap;">If the data volume per time increases, then chunk interval decreases to maintain right-sized chunks.</strong></b> <b><strong style="white-space: pre-wrap;">If data arrives early, then data is stored into a “future” chunk to maintain right-sized chunks.</strong></b></figcaption></figure><hr><h2 id="result-15x-improvement-in-insert-rate">Result: 15x improvement in insert rate</h2><p>Keeping chunks at the right size is how we achieve our INSERT results that surpass vanilla PostgreSQL, that Ajay already showed in his <a href="https://timescale.ghost.io/blog/when-boring-is-awesome-building-a-scalable-time-series-database-on-postgresql-2900ea453ee2/">earlier post</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-85.png" class="kg-image" alt="" loading="lazy" width="1600" height="900" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-85.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2018/12/image-85.png 1000w, https://timescale.ghost.io/blog/content/images/2018/12/image-85.png 1600w" sizes="(min-width: 720px) 720px"><figcaption><b><strong style="white-space: pre-wrap;">Insert throughput of TimescaleDB vs. PostgreSQL, using the same workload as described earlier. Unlike vanilla PostgreSQL, TimescaleDB maintains a constant insert rate (of about 14.4K inserts/second, or 144K metrics/second, with very low variance), independent of dataset size.</strong></b></figcaption></figure><p>This consistent insert throughput also persists when writing large batches of rows in single operations to TimescaleDB (instead of row-by-row). Such batched inserts are common practice for databases employed in more high-scale production environments, e.g., when ingesting data from a distributed queue like Kafka. <strong>In such scenarios, a single Timescale server can ingest 130K rows (or 1.3M metrics) per second, approximately 15x that of vanilla PostgreSQL once the table has reached a couple 100M rows.</strong></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2018/12/image-86.png" class="kg-image" alt="" loading="lazy" width="1600" height="900" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2018/12/image-86.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2018/12/image-86.png 1000w, https://timescale.ghost.io/blog/content/images/2018/12/image-86.png 1600w" sizes="(min-width: 720px) 720px"><figcaption><b><strong style="white-space: pre-wrap;">Insert throughput of TimescaleDB vs. PostgreSQL when performing INSERTs of 10,000-row batches.</strong></b></figcaption></figure><hr><h2 id="summary">Summary</h2><p>A relational database can be quite powerful for time-series data. Yet, the costs of swapping in/out of memory significantly impacts their performance. But NoSQL approaches that implement Log Structured Merge Trees have only shifted the problem, introducing higher memory requirements and poor secondary index support.</p><p>By recognizing that time-series data is different, we are able to organize data in a new way: adaptive time/space chunking. This minimizes swapping to disk by keeping the working data set small enough to fit inside memory, while allowing us to maintain robust primary and secondary index support (and the full feature set of PostgreSQL). And as a result, we are able to <strong>scale up </strong>PostgreSQL significantly, resulting in a 15x improvement on insert rates.</p><p>But what about performance comparisons to NoSQL databases? That post is coming soon.</p><p>In the meantime, you can download the latest version of TimescaleDB, released under the permissive Apache 2 license, on <a href="https://github.com/timescale/timescaledb" rel="noopener">GitHub</a>.</p><hr><p><em>Like this post? Interested in learning more?</em></p><p><em>Check out our </em><a href="https://github.com/timescale/timescaledb" rel="noopener"><strong><em>GitHub</em></strong></a><em>, join our </em><a href="http://slack.timescale.com/" rel="noopener"><em><strong>Slack communit</strong>y</em></a><em>, and sign up for the community mailing list below. We’re also </em><a href="https://www.timescale.com/careers" rel="noopener"><em>hiring</em></a><em>!</em><br></p>]]></content:encoded>
        </item>
    </channel>
</rss>