<?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:58:23 GMT</lastBuildDate>
        <atom:link href="https://www.tigerdata.com/blog" rel="self" type="application/rss+xml"/>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Counter Analytics in PostgreSQL: Beyond Simple Data Denormalization]]></title>
            <description><![CDATA[Record counting on demand or denormalized counters? We break down the two and show you an alternative using PostgreSQL.]]></description>
            <link>https://www.tigerdata.com/blog/counter-analytics-in-postgresql-beyond-simple-data-denormalization</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/counter-analytics-in-postgresql-beyond-simple-data-denormalization</guid>
            <category><![CDATA[PostgreSQL Tips]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Jônatas Davi Paganini]]></dc:creator>
            <pubDate>Wed, 04 Dec 2024 21:42:37 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2024/12/Counter-Analytics-in-PostgreSQL-Beyond-Simple-Data-Denormalization.png">
            </media:content>
            <content:encoded><![CDATA[<p>If you've been working with PostgreSQL, you've probably seen memes advocating for denormalized counters instead of counting related records on demand. The debate usually looks like this:</p><pre><code class="language-SQL">-- The "don't do this" approach: counting related records on demand
SELECT COUNT(*) FROM post_likes WHERE post_id = $1;
-- The "do this instead" approach: maintaining a denormalized counter
SELECT likes_count FROM posts WHERE post_id = $1;</code></pre><p>Let's break down these approaches. In the first approach, we calculate the like count by scanning the <code>post_likes</code> table each time we need the number. In the second approach, we maintain a pre-calculated counter in the <code>posts</code> table which we update whenever someone likes or unlikes a post.</p><p>The denormalized counter approach is often recommended for OLTP (online transaction processing) workloads because it trades write overhead for read performance. Instead of executing a potentially expensive <code>COUNT</code> query that needs to scan the entire <code>post_likes</code> table, we can quickly fetch a pre-calculated number.&nbsp;</p><p>This is particularly valuable in social media applications, where like counts are frequently displayed but rarely updated—you're showing like counts on posts much more frequently than users are actually liking posts.</p><p>However, when we enter the world of time-series data and high-frequency updates, this conventional wisdom needs a second look. Let me share an example that made me reconsider this approach while working with a PostgreSQL database optimized for time series via the <a href="https://github.com/timescale/timescaledb"><u>TimescaleDB extension</u></a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://timescale.ghost.io/blog/content/images/2024/12/Counter-Analytics-in-PostgreSQL-Beyond-Simple-Data-Denormalization_Justin-Bieber.png" class="kg-image" alt="" loading="lazy" width="789" height="661" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2024/12/Counter-Analytics-in-PostgreSQL-Beyond-Simple-Data-Denormalization_Justin-Bieber.png 600w, https://timescale.ghost.io/blog/content/images/2024/12/Counter-Analytics-in-PostgreSQL-Beyond-Simple-Data-Denormalization_Justin-Bieber.png 789w" sizes="(min-width: 720px) 720px"><figcaption><a href="https://www.linkedin.com/posts/moez-zhioua_ever-heard-of-the-justin-bieber-problem-in-activity-7266724225112047617-CLLe/"><u><span class="underline" style="white-space: pre-wrap;">Source</span></u></a></figcaption></figure><p>While this advice might make sense for traditional OLTP workloads, when working with time-series data in TimescaleDB, we need to take a different approach to data modeling.</p><div class="kg-card kg-callout-card kg-callout-card-grey"><div class="kg-callout-emoji">🔖</div><div class="kg-callout-text">To learn more about <a href="https://www.timescale.com/learn/data-modeling-on-postgresql"><u>data modeling in PostgreSQL, check out our guide</u></a>.</div></div><h2 id="counter-analytics-vs-data-denormalization-and-its-limitations">Counter Analytics vs. Data Denormalization and Its Limitations</h2><p>Let's start with a common scenario: tracking post likes in a social media application. The traditional data denormalization approach might look like this:</p><pre><code class="language-SQL">-- Traditional table structure
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    content TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    likes_count INTEGER DEFAULT 0
);

CREATE TABLE post_likes (
    post_id INTEGER REFERENCES posts(id),
    user_id INTEGER,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (post_id, user_id)
);
</code></pre><p>With this structure, every like operation requires two updates:</p><pre><code class="language-SQL">-- When a user likes a post
BEGIN;
INSERT INTO post_likes (post_id, user_id) VALUES (1, 123);
UPDATE posts SET likes_count = likes_count + 1 WHERE id = 1;
COMMIT;
</code></pre><h3 id="the-hidden-costs-of-data-denormalization">The hidden costs of data denormalization</h3><p>While this might seem efficient at first glance, it introduces several problems:</p><p>1.&nbsp;&nbsp;<strong>VACUUM overhead</strong>: Every update to <code>likes_count</code> creates a new version of the row in the posts table. PostgreSQL's MVCC (<a href="https://timescale.ghost.io/blog/how-to-reduce-your-postgresql-database-size/#:~:text=Since%20PostgreSQL%20runs%20under%20the%20MVCC%20system"><u>multiversion concurrency control</u></a>) means old versions aren't immediately removed, leading to the following:</p><pre><code class="language-SQL">-- Check bloat in posts table
SELECT schemaname, relname, n_dead_tup, n_live_tup, last_vacuum
FROM pg_stat_user_tables
WHERE relname = 'posts';</code></pre><p>2.&nbsp;<strong>Transaction contention</strong>: Multiple concurrent likes on the same post create lock contention on the <code>posts</code> row.</p><h2 id="the-timescaledb-way-counter-analytics-for-time-series">The TimescaleDB Way: Counter Analytics for Time Series</h2><p>This is one of those cases where TimescaleDB can give PostgreSQL a helping hand. Instead of maintaining a running counter, let's leverage TimescaleDB's strengths. We’ll start by using a <a href="https://docs.timescale.com/use-timescale/latest/hypertables/"><u>hypertable</u></a> to partition the data automatically by the time column.</p><pre><code class="language-SQL">-- Create a hypertable for post_likes
CREATE TABLE post_likes (
    post_id INTEGER,
    user_id INTEGER,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    PRIMARY KEY (post_id, user_id, created_at)
);

SELECT create_hypertable('post_likes', by_range('created_at', INTERVAL '1 month'));</code></pre><p>The <a href="https://github.com/timescale/timescaledb"><u>TimescaleDB extension</u></a> will automatically create new child tables and split them into several partitions, in this case, one per month. Check the key performance advantage of adopting hypertables:</p><ul><li>Parallel computation for queries: all counts and statistics can be parallelized across the partitions.</li><li>Data lifecycle: tables partitioned by time allow you to easily compress data after X days or drop the entire partition after X months.</li><li><a href="https://timescale.ghost.io/blog/building-columnar-compression-in-a-row-oriented-database/"><u>Columnar compression</u></a> can be enabled and will work as an index to segment the data.</li></ul><p>It’s important to remember that the hypertable architecture is a <a href="https://www.timescale.com/learn/pg_partman-vs-hypertables-for-postgres-partitioning"><u>paradigm shift in database partitioning</u></a>. That’s because the partition stores its own table statistics and indices, making the policies faster for dropping entire partitions without any extra work for vacuum or updates.</p><h3 id="continuous-aggregates-for-efficient-counting">Continuous aggregates for efficient counting</h3><p>Parallelizing will not avoid rescanning the full dataset for any necessary statistics. To increase efficiency, we can consider grouping data hourly and processing it hour by hour. Vanilla PostgreSQL does not allow partial refreshes on materialized views, which is why Timescale developed the continuous aggregation feature.</p><p>The <a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/"><u>continuous aggregate</u></a> will maintain pre-computed counts. Instead of computing counts during query time or updating every new like, we can create a materialized view with superpowers.</p><pre><code class="language-SQL">-- Create a view for hourly like counts
CREATE MATERIALIZED VIEW post_likes_hourly
WITH (timescaledb.continuous) AS
SELECT 
    post_id,
    time_bucket('1 hour', created_at) AS bucket,
    count(*) as likes_count
FROM post_likes
GROUP BY post_id, time_bucket('1 hour', created_at);

-- Set refresh policy
SELECT add_continuous_aggregate_policy('post_likes_hourly',
    start_offset =&gt; INTERVAL '3 hours',
    end_offset =&gt; INTERVAL '1 hour',
    schedule_interval =&gt; INTERVAL '1 hour');</code></pre><p>The refresh policy makes it run on a schedule and only refreshes the part that has not been computed yet. Through a “watermark” mechanism, the refresh time is stored, and the data is updated from the latest watermark point. You can read more about it in our <a href="https://timescale.ghost.io/blog/real-time-analytics-for-time-series-continuous-aggregates/"><u>dev’s intro to continuous aggregates</u></a>.</p><p>You may be thinking, “What? But what if I change the raw data?” TimescaleDB can also track it and refresh only the updated parts.</p><p>If you like this idea, you'll probably also love the ability to use continuous aggregates <a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/hierarchical-continuous-aggregates/"><u>hierarchically</u></a>.</p><h2 id="benefits-of-the-timescaledb-approach">Benefits of the TimescaleDB Approach</h2><ol><li><strong>Efficient storage</strong>: TimescaleDB's chunking mechanism automatically partitions data by time, making fewer VACUUM operations necessary.</li><li><strong>Better concurrency</strong>: no need to update a single counter row, <a href="https://timescale.ghost.io/blog/how-timescaledb-solves-common-postgresql-problems-in-database-operations-with-data-retention-management/"><u>eliminating lock contention</u></a>.&nbsp;</li><li><strong>Rich analytics</strong>: we can easily answer complex questions.</li></ol><pre><code class="language-SQL">-- Get likes trend over time
SELECT 
    post_id,
    bucket,
    likes_count,
    sum(likes_count) OVER (PARTITION BY post_id ORDER BY bucket) as cumulative_likes
FROM post_likes_hourly
WHERE post_id = 1
ORDER BY bucket DESC;</code></pre><h3 id="performance-comparison-counter-analytics-vs-data-denormalization">Performance comparison: Counter analytics vs. data denormalization</h3><p>Let's benchmark both approaches:</p><pre><code class="language-SQL">-- Traditional approach
EXPLAIN ANALYZE
UPDATE posts SET likes_count = likes_count + 1 WHERE id = 1;

-- TimescaleDB approach
EXPLAIN ANALYZE
INSERT INTO post_likes (post_id, user_id, created_at) 
VALUES (1, 123, NOW());</code></pre><p>The TimescaleDB approach shows better performance characteristics under high concurrency and provides more analytical capabilities.</p><h2 id="best-practices-for-real-time-counts">Best Practices for Real-Time Counts</h2><p>For applications requiring <a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/real-time-aggregates/"><u>real-time counts</u></a>, we can set the materialized view parameter <code>timescaledb.materialized_only=false</code> to refresh the view on demand.</p><pre><code class="language-SQL">CREATE MATERIALIZED VIEW post_likes_hourly
WITH (timescaledb.continuous, timescaledb.materialized_only=false) AS
SELECT 
    post_id,
    time_bucket('1 hour', created_at) AS bucket,
    count(*) as likes_count
FROM post_likes
GROUP BY post_id, time_bucket('1 hour', created_at);</code></pre><p>Behind the scenes, TimescaleDB will create a hypertable for the materialized view and refresh the view according to the refresh policy. When the refresh starts, it saves a watermark to track the latest refreshed bucket.</p><p>When you query the <code>posts_liks_hourly</code>, it combines the materialized data with the latest bucket from the hypertable filtering only on the buckets greater than the watermark. It means that instead of scanning the raw dataset, it will just process the part that has not materialized yet.&nbsp;</p><h2 id="establishing-a-retention-policy">Establishing a Retention Policy</h2><p>Now that we have a continuous aggregate, we need to establish a <a href="https://docs.timescale.com/use-timescale/latest/data-retention/create-a-retention-policy/"><u>retention policy</u></a> to prevent the hypertable from growing indefinitely. As we're storing the data in chunks, we can set a retention policy to delete the chunks that are older than a certain period.</p><pre><code class="language-SQL">SELECT add_retention_policy('post_likes_hourly', INTERVAL '1 month');</code></pre><p>This command runs a background job that deletes chunks older than one month. The past data will be deleted in the background, and the continuous aggregate will remain up to date.</p><p>Also, the data will be removed only when the entire partition is going to be dropped. Every partition has its own metadata, without any need to update statistics or give any extra work for the VACUUM process.</p><h2 id="conclusion">Conclusion</h2><p>While denormalized counters might seem appealing for simple OLTP workloads, TimescaleDB's time-series capabilities offer a more scalable and maintainable solution. By leveraging continuous aggregates and proper time-series modeling, we can achieve better performance, richer analytics, and more reliable data management.</p><p>Remember:</p><ul><li>Use hypertables for time-series data</li><li>Leverage continuous aggregates for efficient computations</li><li>Consider the full lifecycle of your data, including retention policies</li><li>Think in terms of time-series patterns rather than traditional OLTP patterns</li></ul><figure class="kg-card kg-image-card"><img src="https://timescale.ghost.io/blog/content/images/2024/12/Screenshot-2024-12-04-at-18.51.52.png" class="kg-image" alt="" loading="lazy" width="1078" height="1452" srcset="https://timescale.ghost.io/blog/content/images/size/w600/2024/12/Screenshot-2024-12-04-at-18.51.52.png 600w, https://timescale.ghost.io/blog/content/images/size/w1000/2024/12/Screenshot-2024-12-04-at-18.51.52.png 1000w, https://timescale.ghost.io/blog/content/images/2024/12/Screenshot-2024-12-04-at-18.51.52.png 1078w" sizes="(min-width: 720px) 720px"></figure><p>This approach might require a mindset shift, but the benefits in terms of scalability and maintenance make it worthwhile for time-series workloads. To give TimescaleDB a try, <a href="https://docs.timescale.com/self-hosted/latest/install/"><u>install it on your machine</u></a>. If you prefer a mature, managed PostgreSQL platform that delivers even more scalability, you can <a href="https://console.cloud.timescale.com/signup"><u>try Timescale Cloud for free</u></a>.</p><h3 id="learn-more">Learn more</h3><ul><li><a href="https://timescale.ghost.io/blog/how-to-reduce-your-postgresql-database-size/"><u>How to Reduce Your PostgreSQL Database Size</u></a></li><li><a href="https://www.timescale.com/learn/pg_partman-vs-hypertables-for-postgres-partitioning/"><u>Pg_partman vs. Hypertables for Postgres Partitioning | Timescale</u></a></li><li><a href="https://timescale.ghost.io/blog/how-timescaledb-solves-common-postgresql-problems-in-database-operations-with-data-retention-management/"><u>How TimescaleDB Solves Common PostgreSQL Problems in Database Operations With Data Retention Management</u></a></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Better Ruby ORM for Time Series and Analytics]]></title>
            <description><![CDATA[Seamlessly create rollups from rolled-up data (hierarchical continuous aggregates) on your Ruby On Rails application for faster time-series & analytics queries.]]></description>
            <link>https://www.tigerdata.com/blog/building-a-better-ruby-orm-for-time-series-and-analytics</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/building-a-better-ruby-orm-for-time-series-and-analytics</guid>
            <category><![CDATA[Ruby]]></category>
            <category><![CDATA[PostgreSQL, Blog]]></category>
            <category><![CDATA[Time Series Data]]></category>
            <category><![CDATA[Analytics]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <dc:creator><![CDATA[Jônatas Davi Paganini]]></dc:creator>
            <pubDate>Wed, 27 Nov 2024 13:30:11 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2024/11/Building-a-Better-Ruby-ORM-for-Time-Series-and-Analytics_final.png">
            </media:content>
            <content:encoded><![CDATA[<p>Rails developers know the joy of working with ActiveRecord. <a href="https://dhh.dk"><u>DHH</u></a> didn’t just give us a framework; he gave us a philosophy, an intuitive way to manage data that feels delightful. But when it comes to time-series data, think metrics, logs, or events, ActiveRecord can start to feel a little stretched. Handling huge volumes of time-stamped data efficiently for analytics? That’s a challenge it wasn’t designed to solve (and neither was PostgreSQL).</p><p>This is where <a href="https://github.com/timescale/timescaledb" rel="noreferrer">TimescaleDB</a> comes in. Built on PostgreSQL (it’s an extension), TimescaleDB is purpose-built for time series and other demanding workloads, and thanks to the <a href="https://rubygems.org/gems/timescaledb" rel="noreferrer">timescaledb gem</a>, it integrates seamlessly into Rails. You don’t have to leave behind the conventions or patterns you love, it just works alongside them.</p><p>One of TimescaleDB’s standout features is<strong> </strong><a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/about-continuous-aggregates/" rel="noreferrer"><strong>continuous aggregates</strong></a>. Think of them as an upgrade to materialized views, automatically refreshing in the background so your data is always up-to-date and fast to query. With the new timescaledb gem<strong> continuous aggregates macro</strong>, you can define hierarchical time-based summaries in a single line of Ruby. It even reuses your existing ActiveRecord scopes, so you’re not duplicating logic you’ve already written.</p><p>Now, your Rails app can effortlessly handle real-time analytics dashboards or historical reports, scaling your time-series workloads while staying true to the Rails philosophy.</p><h2 id="better-time-series-data-aggregations-using-ruby-the-inspiration">Better Time-Series Data Aggregations Using Ruby: The Inspiration</h2><p>The following code snippet highlights the real-life use case that inspired me to build a continuous aggregates macro for better time-series data aggregations. It’s part of a <a href="https://github.com/rubygems/rubygems.org/pull/4979"><u>RubyGems contribution I made</u></a>, and it’s still a work in progress. However, it’s worth validating how this idea can reduce the Ruby code you’ll have to maintain.</p><h3 id="example-model">Example model</h3><pre><code class="language-Ruby">class Download &lt; ActiveRecord::Base
  extend Timescaledb::ActsAsHypertable
  include Timescaledb::ContinuousAggregatesHelper

  acts_as_hypertable time_column: 'ts'

  scope :total_downloads, -&gt; { select("count(*) as total") }
  scope :downloads_by_gem, -&gt; { select("gem_name, count(*) as total").group(:gem_name) }
  scope :downloads_by_version, -&gt; { select("gem_name, gem_version, count(*) as total").group(:gem_name, :gem_version) }

  continuous_aggregates(
    timeframes: [:minute, :hour, :day, :month],
    scopes: [:total_downloads, :downloads_by_gem, :downloads_by_version],
    refresh_policy: {
      minute: { start_offset: "10 minutes", end_offset: "1 minute", schedule_interval: "1 minute" },
      hour:   { start_offset: "4 hour",     end_offset: "1 hour",   schedule_interval: "1 hour" },
      day:    { start_offset: "3 day",      end_offset: "1 day",    schedule_interval: "1 day" },
      month:  { start_offset: "3 month",    end_offset: "1 day",  schedule_interval: "1 day" }
  })
end
</code></pre><p>The <a href="https://docs.timescale.com/use-timescale/latest/continuous-aggregates/refresh-policies/"><u><code>refresh_policy</code></u></a> will work for all basic frames, but it is not mandatory and can be skipped. Now, remember that declaring the macro in the model has almost no effect until you run a migration that uses such metadata. The creation of the continuous aggregates needs to happen on a database migration through the call of migration helpers that can use the information. Let’s take a look at the helpers we have.</p><h3 id="the-migration-helpers">The migration helpers</h3><p>The macro will create a continuous aggregate in the model, but for migration, it can generate the SQL code for all the views iterating on each timeframe and scope you specify.</p><p>The <code>create_continuous_aggregates</code> and <code>drop_continuous_aggregates</code>  methods are designed to be invoked during the database migration step.</p><p>So, after saving your model with the new <code>continuous_aggregate</code> definition, you can use the <code>create_continuous_aggregate</code> method to invoke the creation of all materialized views in the database. If you use <code>refresh_policy</code>, it will also add all the policies along with the aggregation. Here’s what a migration file would look like:</p><pre><code class="language-Ruby">class SetupMyAmazingCaggsMigration &lt; ActiveRecord::Migration[7.0]
  def up
    Download.create_continuous_aggregates
  end

  def down
    Download.drop_continuous_aggregates
  end
end
</code></pre><p>It will automatically create all the continuous aggregates for all timeframes and scopes in the right dependency order. When the <code>create_continuous_aggregates</code> is called, 12 continuous aggregates will be created, starting from minute to month.</p><h3 id="the-migration-output">The migration output</h3><p>Let’s take a deep look at what the SQL behind the scenes looks like when the method <code>create_continuous_aggregates</code> is called. From the first scope, it builds the continuous aggregates, fetching the data from the raw data.</p><pre><code class="language-SQL">CREATE MATERIALIZED VIEW IF NOT EXISTS total_downloads_per_minute
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 minute', ts) as ts, count(*) as total
FROM "downloads"
GROUP BY 1
WITH NO DATA;
</code></pre><p>Every materialization occurs independently, and to happen automatically, a refresh policy needs to be added. As it was specified generically by timeframe, it now incorporates the minute refresh for the policy.</p><pre><code class="language-SQL">SELECT add_continuous_aggregate_policy('total_downloads_per_minute',
  start_offset =&gt; INTERVAL '10 minutes',
  end_offset =&gt;  INTERVAL '1 minute',
  schedule_interval =&gt; INTERVAL '1 minute');
</code></pre><p>Now, continuing the creation, it goes for the hourly level, already reusing the data from the previous materialized view.</p><pre><code class="language-SQL">CREATE MATERIALIZED VIEW IF NOT EXISTS total_downloads_per_hour
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', ts) as ts, sum(total) as total FROM "total_downloads_per_minute" 
GROUP BY 1
WITH NO DATA;
</code></pre><p>An hourly policy is also established to guarantee that it will refresh automatically. The same iteration is repeated for daily and monthly timeframes. Later, the same process will repeat for the other timeframes.</p><pre><code class="language-SQL">SELECT add_continuous_aggregate_policy('total_downloads_per_hour',
  start_offset =&gt; INTERVAL '4 hour',
  end_offset =&gt;  INTERVAL '1 hour',
  schedule_interval =&gt; INTERVAL '1 hour');

CREATE MATERIALIZED VIEW IF NOT EXISTS total_downloads_per_day
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 day', ts) as ts, sum(total) as total FROM "total_downloads_per_hour" GROUP BY 1
WITH NO DATA;

SELECT add_continuous_aggregate_policy('total_downloads_per_day',
  start_offset =&gt; INTERVAL '3 day',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');

CREATE MATERIALIZED VIEW IF NOT EXISTS total_downloads_per_month
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 month', ts) as ts, sum(total) as total FROM "total_downloads_per_day" GROUP BY 1
WITH NO DATA;

SELECT add_continuous_aggregate_policy('total_downloads_per_month',
  start_offset =&gt; INTERVAL '3 month',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_gem_per_minute
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 minute', ts) as ts, gem_name, count(*) as total FROM "downloads" GROUP BY 1, gem_name
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_gem_per_minute',
  start_offset =&gt; INTERVAL '10 minutes',
  end_offset =&gt;  INTERVAL '1 minute',
  schedule_interval =&gt; INTERVAL '1 minute');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_gem_per_hour
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', ts) as ts, gem_name, sum(total) as total FROM "downloads_by_gem_per_minute" GROUP BY 1, gem_name
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_gem_per_hour',
  start_offset =&gt; INTERVAL '4 hour',
  end_offset =&gt;  INTERVAL '1 hour',
  schedule_interval =&gt; INTERVAL '1 hour');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_gem_per_day
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 day', ts) as ts, gem_name, sum(total) as total FROM "downloads_by_gem_per_hour" GROUP BY 1, gem_name
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_gem_per_day',
  start_offset =&gt; INTERVAL '3 day',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_gem_per_month
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 month', ts) as ts, gem_name, sum(total) as total FROM "downloads_by_gem_per_day" GROUP BY 1, gem_name
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_gem_per_month',
  start_offset =&gt; INTERVAL '3 month',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_version_per_minute
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 minute', ts) as ts, gem_name, gem_version, count(*) as total FROM "downloads" GROUP BY 1, gem_name, gem_version
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_version_per_minute',
  start_offset =&gt; INTERVAL '10 minutes',
  end_offset =&gt;  INTERVAL '1 minute',
  schedule_interval =&gt; INTERVAL '1 minute');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_version_per_hour
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', ts) as ts, gem_name, gem_version, sum(total) as total FROM "downloads_by_version_per_minute" GROUP BY 1, gem_name, gem_version
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_version_per_hour',
  start_offset =&gt; INTERVAL '4 hour',
  end_offset =&gt;  INTERVAL '1 hour',
  schedule_interval =&gt; INTERVAL '1 hour');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_version_per_day
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 day', ts) as ts, gem_name, gem_version, sum(total) as total FROM "downloads_by_version_per_hour" GROUP BY 1, gem_name, gem_version
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_version_per_day',
  start_offset =&gt; INTERVAL '3 day',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');

CREATE MATERIALIZED VIEW IF NOT EXISTS downloads_by_version_per_month
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 month', ts) as ts, gem_name, gem_version, sum(total) as total FROM "downloads_by_version_per_day" GROUP BY 1, gem_name, gem_version
WITH NO DATA;

SELECT add_continuous_aggregate_policy('downloads_by_version_per_month',
  start_offset =&gt; INTERVAL '3 month',
  end_offset =&gt;  INTERVAL '1 day',
  schedule_interval =&gt; INTERVAL '1 day');
</code></pre><p>That’s massive, right?! It’s probably too boring to read it all because it’s almost a repetitive structure, iterating over all the scopes. The <code>continuous_aggregates</code> leverages all logic by iterating over all the timeframes with all scopes. It reuses minute data in the hourly view and uses the same technique from hour to day, day to month, and so on. </p><p>In contrast, reusing the aggregations, if written all by hand, makes the process really error-prone. The <code>Model.drop_continuous_aggregates</code> method uses the reverse dependency path to call the <code>drop materialized view</code> from month to minute.</p><p>Continuously aggregating statistics can replace dozens of background jobs hosted by your application, avoiding serialization and deserialization efforts apart from bandwidth, I/O (input/output), and overuse of resources in general. </p><p>Reusing the previous timeframes makes it very fast and lightweight for the database to process. Adopting hierarchical processing also allows all processing to be done at a predictable speed because the number of rows will be static and only dependent on the cardinality of the data. </p><p>Processing aggregations in the database means there will only be calls between the database and the disk, releasing interactions between the application and the database and forcing network data trips to process it on application background jobs.</p><p>Now, let’s take a look at how the rollup works.</p><h2 id="hyperfunctions-integration-for-faster-time-series-analysis">Hyperfunctions Integration for Faster Time-Series Analysis</h2><p>Timescale also built a specialized extension for time-series data processing, the <a href="https://docs.timescale.com/self-hosted/latest/tooling/install-toolkit/"><u>timescaledb-toolkit</u></a>. It helps improve the developer experience and query performance, and most of its functions are called hyperfunctions.</p><p><a href="https://docs.timescale.com/api/latest/hyperfunctions/"><u>Hyperfunctions</u></a> are designed to reuse and make statistics fast for hypertables, allowing you to roll up granular aggregations into bigger timeframes. In the case of the Ruby library, it should work well with both regular statistics functions and also roll up the hyperfunctions already available.</p><p>The most important part of using multiple timeframes and scopes is to understand how the <code>rollup</code> scope works.&nbsp;</p><p>For example, if you have a scope called <code>total_downloads</code> and a timeframe of <code>day</code>, the rollup will rewrite the query to group by the day.</p><pre><code class="language-SQL"># Original query
SELECT count(*) FROM downloads;

# Rolled up query
SELECT time_bucket('1 day', created_at) AS day, count(*) FROM downloads GROUP BY day;
</code></pre><p>In Ruby, the rollup method will help to roll up such queries in a more efficient way. Let’s consider the <code>total_downloads</code> scope as an example:</p><pre><code class="language-Ruby">Download.total_downloads.map(&amp;:attributes) #  =&gt; [{"total"=&gt;6175}
# SELECT count(*) as total FROM "downloads"
</code></pre><p>The rollup scope will help to group data by a specific timeframe. Let’s start with one minute:</p><pre><code class="language-Ruby">Download.total_downloads.rollup("'1 min'").map(&amp;:attributes)
# SELECT time_bucket('1 min', ts) as ts, count(*) as total FROM "downloads" GROUP BY 1
=&gt; [{"ts"=&gt;2024-04-26 00:10:00 UTC, "total"=&gt;110},
 {"ts"=&gt;2024-04-26 00:11:00 UTC, "total"=&gt;1322},
 {"ts"=&gt;2024-04-26 00:12:00 UTC, "total"=&gt;1461},
 {"ts"=&gt;2024-04-26 00:13:00 UTC, "total"=&gt;1150},
 {"ts"=&gt;2024-04-26 00:14:00 UTC, "total"=&gt;1127},
 {"ts"=&gt;2024-04-26 00:15:00 UTC, "total"=&gt;1005}]
</code></pre><p>As you can see, the <code>time_bucket</code> function is introduced, and a group by clause is also added.</p><p>If the current query uses a component like <a href="https://docs.timescale.com/api/latest/hyperfunctions/financial-analysis/candlestick_agg/"><u>candlestick_agg</u></a>, it will be able to call the <a href="https://docs.timescale.com/api/latest/hyperfunctions/financial-analysis/candlestick_agg/#rollup"><u>rollup</u></a> SQL function, and that’s where the name of the function comes from.</p><p>What if I want to sum the counters from the materialized view behind the scenes and roll up to a bigger frame? That’s when the aggregated classes join the game.</p><p>Continuous aggregates are hypertables. They’re materialized views that are periodically being updated in the background according to the refresh policy. Every aggregation can be accessed and refreshed independently.</p><h3 id="aggregates-classes">Aggregates classes</h3><p>In the previous example, the rollup was done directly in the raw data. Now, let’s explore how the <code>continuous_aggregates</code> macro creates a class for each aggregated view that is in the database. The classes can be accessed as subclasses in the model and also inherit the model as they’re fully dependent on it.</p><p>So, to access the materialized data, instead of building the query from raw data, nested classes are created with the <code>Model::ScopeNamePerTimeframe</code> naming convention.</p><pre><code class="language-Ruby">Download::TotalDownloadsPerMinute.all.map(&amp;:attributes)
# SELECT "total_downloads_per_minute".* FROM "total_downloads_per_minute"
=&gt; [{"ts"=&gt;2024-04-26 00:10:00 UTC, "total"=&gt;110},
 {"ts"=&gt;2024-04-26 00:11:00 UTC, "total"=&gt;1322},
 {"ts"=&gt;2024-04-26 00:12:00 UTC, "total"=&gt;1461},
 {"ts"=&gt;2024-04-26 00:13:00 UTC, "total"=&gt;1150},
 {"ts"=&gt;2024-04-26 00:14:00 UTC, "total"=&gt;1127},
 {"ts"=&gt;2024-04-26 00:15:00 UTC, "total"=&gt;1005}]
</code></pre><p>To roll up from the materialized data, we need to consider how the data was built. So, to have the counter, we need to count rows from the hypertable raw data, but for bigger timeframes, we can just sum the counters. Here’s what it looks like if you need to roll up any scope to other timeframes:</p><pre><code class="language-Ruby">Download::TotalDownloadsPerMinute.select("sum(total) as total").rollup("'2 min'").map(&amp;:attributes)
# SELECT time_bucket('2 min', ts) as ts, sum(total) as total FROM "total_downloads_per_minute" GROUP BY 1
=&gt; [{"ts"=&gt;2024-04-26 00:12:00 UTC, "total"=&gt;2611}, {"ts"=&gt;2024-04-26 00:14:00 UTC, "total"=&gt;2132}, {"ts"=&gt;2024-04-26 00:10:00 UTC, "total"=&gt;1432}]
</code></pre><p>With the <code>rollup</code> scope, you can easily build custom scopes and regroup as you need. It supports a few statistic scenarios on rollup to automatically detect SQL statements that contain <code>count(*) as total</code> and transform them into <code>sum(total) as total</code>them. It can also get a min of min or max of max values when it’s rolling up into larger time frames.</p><h3 id="refresh-aggregates">Refresh aggregates</h3><p>If you need to refresh all aggregates manually in the right order, you can also use the <code>refresh_aggregates</code> method:</p><pre><code class="language-Ruby">Download.refresh_aggregates
</code></pre><h2 id="next-steps">Next steps</h2><p>That’s all, folks! I <a href="https://ideia.me/timescaledb-gem-continuous-aggregates-updates"><u>posted</u></a> a few more details in my blog during the development phase. If you have any questions or feedback, join the <a href="https://timescaledb.slack.com/archives/C04MQ3DKXEV/p1715632355486219"><u><code>#ruby</code></u></a> channel on the TimescaleDB Slack. Also, GitHub ⭐s for our <a href="https://github.com/timescale/timescaledb-ruby"><u>Ruby library</u></a> are very much welcome!</p><p>To give it a try and use the <code>continuous_aggregates</code> macro on your project, install the <a href="https://rubygems.org/gems/timescaledb" rel="noreferrer"><code>timescaledb</code></a> gem. Happy coding—but write fewer lines of code.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Self-Hosted or Cloud Database? A Countryside Reflection on Infrastructure Choices]]></title>
            <description><![CDATA[Read what country living can teach you about infrastructure choices and choosing a self-hosted vs. cloud database.]]></description>
            <link>https://www.tigerdata.com/blog/self-hosted-or-cloud-database-a-countryside-reflection-on-infrastructure-choices</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/self-hosted-or-cloud-database-a-countryside-reflection-on-infrastructure-choices</guid>
            <category><![CDATA[Cloud]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[Benchmarks & Comparisons]]></category>
            <dc:creator><![CDATA[Jônatas Davi Paganini]]></dc:creator>
            <pubDate>Wed, 03 Apr 2024 15:54:16 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2024/04/Self-hosted-vs-cloud-database_cover--1-.webp">
            </media:content>
            <content:encoded><![CDATA[<p>The choice between using a cloud database or opting for a self-managed setup is critical for every developer as it affects the entire framework through which an organization processes its data. Funny enough, nothing has taught me more invaluable lessons about infrastructure than living in the countryside for the past four years. These experiences have shaped my mindset about what I prefer to manage myself and what I’d rather have as a service. </p><p>Much like my countryside life, which has the benefits and drawbacks of managing one's infrastructure versus relying on external services, this article aims to draw parallels to guide you through your digital infrastructure decisions. </p><p>I’ll also compare deployment options, emphasizing that the implications of this choice extend far beyond the technical: they influence your organization's agility, efficiency, and long-term scalability. I hope these insights will act as a sort of compass, directing you toward a decision that aligns with your strategic objectives and operational capabilities.</p><h2 id="self-hosting-vs-cloud-a-water-management-lesson">Self-Hosting vs. Cloud: A Water Management Lesson</h2><p>Before explaining how my water system got me thinking of databases and infrastructure choices, let me clarify the decision on the table here. Self-hosting a database involves running it on your own physical or virtual servers, requiring maintenance, security, and scalability management. In contrast, a cloud database is hosted and managed by a third-party cloud provider, offering scalability, automated backups, and reduced maintenance overhead, allowing developers to focus more on application development and less on infrastructure management.</p><p>The thing is, <em>water is no different from data</em>. In businesses, data flows like water through systems, streaming seamlessly and requiring to be stored. Water or data are crucial for your system infrastructure—they are vital for the show to go on, irrespective of their challenges.</p><h3 id="system-infrastructure-as-a-water-system">System infrastructure as a water system</h3><p>In the countryside, choosing my water system was similar to selecting a well-integrated infrastructure. System infrastructure refers to the underlying framework that supports the operation of software applications. The same could be said about my water system if talking about general human operations.</p><p>In the driest seasons, I was compelled to build a resilient water recycling system. After heavy rains, my large repository would be brimming, providing essentials for showers, dishes, and laundry. However, managing this infrastructure wasn’t without its challenges. Annually, I’d face issues like broken pipes, the need to pump water from the lake, water pump failures, and clogged filters.</p><p>These experiences parallel the challenges in managing business infrastructure:</p><ul><li><strong>Unexpected breakdowns</strong>: just as pipes break, systems can fail.</li><li><strong>Resource scarcity</strong>: like running out of water, businesses can face resource shortages.</li><li><strong>Maintenance needs</strong>: like repairing a water pump, systems require regular upkeep.</li><li><strong>Regular updates</strong>: comparable to changing filters, systems need continual updates.</li></ul><h2 id="self-hosting-vs-cloud-services-making-a-choice">Self-Hosting vs. Cloud Services: Making a Choice</h2><p>Reflecting on my rural infrastructure, self-hosting was my only option. But what about businesses? Consider these questions:</p><ul><li>Do you have the infrastructure and skills to manage emergencies anytime?</li><li>Are you prepared to invest in and maintain your infrastructure?</li><li>If the answer to any of these is “no,” self-hosting might be a temporary solution.</li></ul><h3 id="the-right-mindset-for-system-infrastructure">The right mindset for system infrastructure</h3><p>This is more than opting for self-managing a database or a cloud provider; it’s about understanding your business limitations and choosing the option that sustains your business longer.</p><p>To make an informed choice, here are some key considerations you’ll need to make:</p><ul><li><strong>Learning costs during downtimes</strong>: Downtime is not just a technical setback—it's a period of intensive learning under pressure. Organizations must evaluate whether they have the resources and resilience to absorb the learning curve of diagnosing and resolving infrastructure failures in-house. The cost of this learning, both in terms of time and lost productivity, can be significant.</li><li><strong>Business risks during outages:</strong> Outages directly threaten your business continuity. The longer your systems are down, the greater the risk to your reputation, customer satisfaction, and revenue. Assessing the potential impact of outages is crucial in understanding whether the self-hosted approach aligns with your risk tolerance and business continuity plans.</li><li><strong>Team commitment to infrastructure responsibilities:</strong> Choosing to self-host means your team will bear the full weight of infrastructure responsibilities—from routine maintenance to emergency response. This commitment requires a dedicated, skilled team that's prepared to tackle challenges as they arise. Reflect on whether your team has the bandwidth and expertise to manage these tasks without detracting from their core functions.</li><li><strong>Training availability:</strong> Your team's effectiveness in managing a self-hosted infrastructure heavily relies on their ongoing education and training. Consider whether you have access to the necessary training resources to keep your team up-to-date with the latest technologies and best practices in infrastructure management.</li></ul><p>These considerations go beyond the surface-level appeal of having complete control over your infrastructure. They highlight the depth of <em>commitment</em> and <em>preparedness</em> needed to ensure that a self-hosted solution supports, rather than hinders, your organization's goals. </p><p>Below, I've outlined some of the primary areas of concern when opting for self-hosting, paired with the inevitable consequences businesses might face if they don’t prepare correctly:</p>
<!--kg-card-begin: html-->
<table style="border:none;border-collapse:collapse;"><colgroup><col width="147"><col width="463"></colgroup><tbody><tr style="height:35.5pt"><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:bottom;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Challenge Area</span></p></td><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:bottom;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Consequence</span></p></td></tr><tr style="height:106.75pt"><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:middle;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Without a ready emergency response</span></p></td><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:middle;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><ul style="margin-top:0;margin-bottom:0;padding-inline-start:48px;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Eventually, a critical error will halt systems, leading to operational paralysis.</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">The team will need to scramble to respond, undermining the stability of all operations.</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">There will be an expensive delay while figuring out the necessary steps to recovery.</span></p></li></ul></td></tr><tr style="height:124.75pt"><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:middle;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Without investment and maintenance</span></p></td><td style="border-left:solid #e3e3e3 0.75pt;border-right:solid #e3e3e3 0.75pt;border-bottom:solid #e3e3e3 0.75pt;border-top:solid #e3e3e3 0.75pt;vertical-align:middle;background-color:#ffffff;padding:5pt 5pt 5pt 5pt;overflow:hidden;overflow-wrap:break-word;"><ul style="margin-top:0;margin-bottom:0;padding-inline-start:48px;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Infrastructure will become overwhelmed as operations scale, leading to performance bottlenecks.</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Bugs and system issues will proliferate as the infrastructure expands, reducing system reliability.</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.2;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Fragile infrastructure components will fail, precipitating emergencies and further destabilizing operations.</span></p></li></ul></td></tr></tbody></table>
<!--kg-card-end: html-->
<p>Looking at these complexities, the contrast with cloud services becomes clearer, illustrating the value of scalability, reliability, and reduced operational burdens that cloud services can offer.</p><p>You can build a resilient team for self-hosting your database, but you’ll need sufficient resources and investment. Plus, you will have to be fully transparent with your customers to build customer confidence. If self-hosting isn’t feasible, let your customers know or find ways to enhance your infrastructure together.&nbsp;</p><p>This is where Timescale’s self-hosting support options can lend you a helping hand. 🤝</p><h2 id="self-hosting-with-timescale">Self-Hosting With Timescale</h2><p>Self-hosting does not mean you have to do everything alone. When considering the self-hosting route for managing time-series data, Timescale is about empowering control. It enables users who want to self-host and control their infrastructure to be backed by comprehensive support to ensure their operations run smoothly. </p><p>To do this, Timescale provides specialized support packages tailored to production and development environments designed to mitigate the challenges of self-hosting.</p><h4 id="timescale-production-and-development-support-packages">Timescale Production and Development Support Packages</h4><p>For organizations committed to self-hosting their time-series databases, Timescale provides a <a href="https://timescale.ghost.io/blog/empowering-control-production-and-developer-support-for-self-managed-timescaledb/"><u>tiered support system designed to address the needs of both production and development stages</u></a>. This support includes:</p><ul><li><strong>All email Support requests are fielded within one business day</strong>, ensuring that any queries or issues are promptly addressed, minimizing delays in troubleshooting and resolution.</li><li><strong>24x7 on-call support with a one-hour response time for severe or critical issues</strong> that threaten production environments. Timescale offers dedicated on-call support to provide real-time expertise, significantly reducing downtime.</li><li><strong>Dedicated Support portal</strong>: A centralized location for all your support needs, providing easy access to assistance and resources.</li><li><strong>Production Support as a Service:</strong> This feature offloads the burden of emergency responses and infrastructure troubleshooting from your team, allowing you to focus on core operations while relying on Timescale's expertise.</li></ul><p>For more detailed information on how TimescaleDB can support your self-hosting requirements, visit <a href="https://www.timescale.com/self-managed-support"><u>Timescale's Self-Managed Support Page</u></a>.</p><h3 id="self-managed-timescaledb-features">Self-managed TimescaleDB features</h3><p>Besides providing support for your self-hosted database, TimescaleDB enhances PostgreSQL—one of the best-known reliable databases—with features specifically designed for time-series data, making it an attractive option for self-hosting scenarios:</p><ul><li><strong>Hypertables</strong>: These are designed to handle massive datasets by automatically partitioning data across time and space while still allowing you to interact with them as though they were standard PostgreSQL tables.</li><li><strong>Continuous Aggregates</strong>: Time-series queries often require aggregating data over time intervals. Continuous aggregates simplify this by automatically updating incrementally, saving processing time and resources.</li><li><strong>Compression</strong>: Leveraging <a href="https://www.tigerdata.com/blog/building-columnar-compression-in-a-row-oriented-database" rel="noreferrer">columnar storage</a> and time-partitioned data structures, TimescaleDB offers efficient compression mechanisms to reduce storage costs and improve query performance.</li><li><strong>Full SQL</strong>: Unlike some NoSQL databases designed for time-series data, TimescaleDB does not compromise on the power of SQL, offering full compatibility with PostgreSQL for ease of use and flexibility.</li></ul><p>(To learn more about these features, <a href="https://docs.timescale.com/" rel="noreferrer">check out our documentation</a>.)</p><p>With Timescale, you can mitigate some of the traditional challenges associated with self-hosted databases, benefiting from a system that combines the scalability and flexibility of a conventional SQL database with the performance and efficiency required for modern <a href="https://www.tigerdata.com/learn/understanding-database-workloads-variable-bursty-and-uniform-patterns" rel="noreferrer">data workloads</a>. </p><p>But, even with the Timescale Support team by your side, managing your self-hosted setup remains a significant responsibility. It may be time to start considering an alternative: cloud services. The cloud technology era is not just upon us—it is shaping the future of data management, offering a distinct path from traditional self-hosting models. Shame I can't get a similar model for my water system.</p><p>Cloud services are specifically designed to expedite business operations and iterations. They represent an ideal solution for companies that prefer not to invest heavily in internal teams dedicated to ensuring resilience and managing infrastructure complexities. The suitability of cloud services for your organization hinges on several factors, including your business objectives, the current stage of your Service Level Agreements (SLAs), and your growth ambitions.</p><h2 id="the-role-of-cloud-services-in-scaling-and-infrastructure-management">The Role of Cloud Services in Scaling and Infrastructure Management</h2><p>Cloud services provide a reliable framework for scaling, enabling you to adapt quickly to changing demands without the upfront costs typically associated with physical infrastructure investments. Here are some key advantages:</p><ul><li><strong>Infrastructure investment at scale</strong>: Cloud services allow businesses to purchase infrastructure wholesale, translating to significant savings on time and personnel despite potentially higher direct spending.</li><li><strong>Security and reliability</strong>: By offloading security and reliability concerns to the cloud provider, companies can focus more on their core business functions.</li><li><strong>Transparency and control</strong>: While adopting cloud services may result in less operational transparency, the trade-off comes with access to a suite of services and support that can dramatically simplify infrastructure management.</li></ul><h2 id="timescale%E2%80%99s-cloud-services-empowering-your-data-management">Timescale’s Cloud Services: Empowering Your Data Management</h2><p>Timescale provides a comprehensive cloud solution designed to optimize time-series data management without the operational overhead of self-hosting:</p><ul><li><strong>Free Production Support</strong>: Ensures that your operations run smoothly with expert assistance readily available.</li><li><a href="https://docs.timescale.com/use-timescale/latest/data-tiering/tour-data-tiering/" rel="noreferrer"><strong>Data tiering</strong></a><strong> and </strong><a href="https://timescale.ghost.io/blog/savings-unlocked-why-we-switched-to-a-pay-for-what-you-store-database-storage-model/" rel="noreferrer"><strong>usage-based cost tiers</strong></a>: Optimizes your storage spending according to your actual needs, ensuring cost-efficiency.</li><li><strong>Scalability without the traditional constraints</strong>: With compute and storage decoupled, scalability becomes both cost-efficient and performance-optimized.</li><li><a href="https://timescale.ghost.io/blog/how-high-availability-works-in-our-cloud-database/" rel="noreferrer"><strong>High availability</strong></a><strong>, security, and compliance</strong>: Features like automated backups, upgrades, and end-to-end encryption ensure your data is secure, compliant, and available when needed.</li><li><strong>Insights and analytics</strong>: In-console metric visualization and <a href="https://timescale.ghost.io/blog/database-monitoring-and-query-optimization-introducing-insights-on-timescale/" rel="noreferrer">detailed query information</a> enhance your ability to monitor and improve performance.</li></ul><h2 id="the-verdict-self-hosting-vs-cloud-services">The Verdict: Self-Hosting vs. Cloud Services</h2><p>Choosing between self-hosting and cloud services boils down to a strategic decision based on your company's specific needs and goals:</p><p><strong>Self-hosting</strong> offers clarity, transparency, and guaranteed integration with your existing systems, giving you total control over your infrastructure.</p><p><strong>Cloud services</strong> streamline infrastructure investment and updates, removing the need for extensive personnel or tooling investments for security and emergencies, allowing you to concentrate on your core business.</p><p>Ultimately, these are not rigid rules but guiding principles to help you make informed decisions. Whether you opt for self-hosting or cloud services, <em>choose what aligns best with your business goals and needs</em>. In either scenario, Timescale provides tailored support and solutions to ensure your time-series data infrastructure is optimized, secure, and scalable.</p><p>Whether you're deciding between self-hosting and cloud services or looking for ways to optimize your current setup, the Timescale Slack Community <code>#tech-design</code> channel is an excellent resource for collective learning and support.</p><p>Join the <a href="https://slack.timescale.com"><u>Timescale Slack Community</u></a>, where you can talk to me and many other like-minded developers about their infrastructure choices. See you there! 👋</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Supercharge Your AI Agents With Postgres: An Experiment With OpenAI's GPT-4]]></title>
            <description><![CDATA[Read how you can work with AI agents as intermediaries between AI and databases.]]></description>
            <link>https://www.tigerdata.com/blog/supercharge-your-ai-agent-with-postgresql-an-experiment-with-openais-gpt-4</link>
            <guid isPermaLink="true">https://www.tigerdata.com/blog/supercharge-your-ai-agent-with-postgresql-an-experiment-with-openais-gpt-4</guid>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[PostgreSQL]]></category>
            <category><![CDATA[OpenAI]]></category>
            <dc:creator><![CDATA[Jônatas Davi Paganini]]></dc:creator>
            <pubDate>Wed, 26 Jul 2023 13:00:22 GMT</pubDate>
            <media:content medium="image" href="https://timescale.ghost.io/blog/content/images/2023/07/AI-agents.png">
            </media:content>
            <content:encoded><![CDATA[<p>Hello developers, AI enthusiasts, and everyone eager to push the boundaries of what's possible with technology! Today, we're exploring <strong>AI agents</strong> as intermediaries in a fascinating intersection of fields: Artificial Intelligence and databases.</p><h2 id="the-dawn-of-ai-agents">The Dawn of AI Agents</h2><p>AI agents are at the heart of the tech industry's ongoing revolution. As programs capable of autonomous actions in their environment, AI agents analyze, make decisions, and execute actions that drive a myriad of applications. From autonomous vehicles and voice assistants to recommendation systems and customer service bots, AI agents are changing the way we interact with technology.<br></p><p>But what if we could take it a step further? What if we could use AI to simplify how we interact with databases? Could AI agents act as intermediaries, interpreting human language and converting it into structured database queries?</p><h2 id="a-ruby-experiment-with-gpt-4">A Ruby Experiment With GPT-4</h2><p>That's exactly what we tried to achieve in a recent experiment. Leveraging OpenAI's GPT-4, a powerful language model, we conducted an experiment to see how we could use AI to interact with our databases using everyday language.<br></p><p>The experiment was built using Ruby, and you can find the <a href="https://jonatas.github.io/timescaledb/chat_gpt_tutorial">detailed explanation and code here</a>. The results were fascinating, revealing the potential power of using AI as a “middle-man” (Middle-tech? Middle-bot?) between humans and databases.<br></p><p><a href="https://asciinema.org/a/594564">Check out the videos throughout this blog post</a> to see it in action:</p>
<!--kg-card-begin: html-->
<a href="https://asciinema.org/a/594563" target="_blank"><img src="https://asciinema.org/a/594563.svg" /></a>
<!--kg-card-end: html-->
<h2 id="why-store-data-for-ai-agents">Why Store Data for AI Agents?</h2><p>Data storage is crucial for the successful application of AI, particularly for training and fine-tuning models. By storing interactions, results, and other relevant data, we can improve the performance and accuracy of our AI agents over time.<br></p><p>But data storage is not just about improving our AI; it's also about cost-effectiveness. With the OpenAI API, you pay per token, which can add up when dealing with large amounts of data. By using PostgreSQL as long-term memory for your AI agent, you can reduce the number of tokens you send to the OpenAI API, saving computational resources and money.</p>
<!--kg-card-begin: html-->
<a href="https://asciinema.org/a/594564" target="_blank"><img src="https://asciinema.org/a/594564.svg" /></a>
<!--kg-card-end: html-->
<h2 id="postgresql-flexible-and-robust">PostgreSQL: Flexible and Robust</h2><p>PostgreSQL is a powerful, open-source relational database system. With a reputation for reliability, robustness, and performance, it's a fantastic choice for your AI's long-term memory. PostgreSQL also offers flexibility and scalability, making it suitable for projects of all sizes.<br></p><p>Whether you're conducting experiments or deploying production-ready applications, PostgreSQL's flexibility and robust nature make it an excellent companion for your AI.<br></p><p>Needless to say, we’re huge PostgreSQL enthusiasts here at Timescale—so much so that we built Timescale on PostgreSQL. Timescale works just like PostgreSQL under the hood, offering the same 100 percent SQL support (not SQL-like) and a rich ecosystem of connectors and tools but supercharging PostgreSQL for analytics, events, and <a href="https://www.tigerdata.com/blog/time-series-introduction" rel="noreferrer">time series</a> (and time-series-like workloads). <br></p><p>With additional features like <a href="https://timescale.ghost.io/blog/compressing-immutable-data-changing-time-series-management/">compression</a> and <a href="https://timescale.ghost.io/blog/an-incremental-materialized-view-on-steroids-how-we-made-continuous-aggregates-even-better/">automatically updated incremental materialized views—we call them continuous aggregates</a>—Timescale allows you to scale PostgreSQL further for optimal performance while enjoying the best developer experience and cost-effectiveness. <br></p><p>But why all this talk about Timescale? As the conversation between human and machine is happening on point in time, I realize I’m dealing with time-series data. Cue in TimescaleDB for the rescue!<br></p><h2 id="join-the-timescale-community">Join the Timescale Community</h2><p>We're just scratching the surface of what's possible when combining AI with databases like PostgreSQL, and we'd love for you to join us on this journey.</p><p>Got a cool idea? A question? Or just want to share your thoughts on this topic? Join the Timescale Community on <a href="https://timescale.com/community/">Slack</a> and head over to the <code>#ai-llm-discussion</code> channel. Let's push the boundaries together and shape the future of AI!<br><br>Check this page to learn how to <a href="http://timescale.com/ai">power agents, chatbots, and other large language models AI applications with PostgreSQL</a>. To see what my fellow Timescalers Avthar, Mat, and Sam are already building, read their post on <a href="https://timescale.ghost.io/blog/postgresql-as-a-vector-database-create-store-and-query-openai-embeddings-with-pgvector/">PostgreSQL as a Vector Database: Create, Store, and Query OpenAI Embeddings With pgvector</a>.<br></p><p>Remember, technology grows exponentially when great minds come together. See you there!</p>]]></content:encoded>
        </item>
    </channel>
</rss>