Ruby on Rails has helped make it possible to scale out the database layer, meeting the demands of millions of Aura Frames customers enjoying their digital photo frames.
In late 2025, the team added additional primary databases to expand capacity for peak write and read load ahead of Christmas Day, the busiest day of the year for the company. Rails manages queries and schema changes for each primary database within the same codebase, and now with the additional capacity of many primary databases.
With 8 primary databases in total, each server instance can be vertically scaled ahead of peak load. When load returns to normal levels, instances are scaled down for cost savings.
The team leveraged native support for Multiple Databases and the disable_joins: true feature in Active Record, the ORM for Ruby on Rails. The disable_joins feature replaces SQL joins, issuing multiple SELECT statements to combine data in the application from different databases.
This post looks back at the technical details of that plan, as well as a variety of additional data layer scaling tactics, that culminated in a successful Christmas 2025 season, with peak U.S. and Canadian Apple App Store and Google Play Store rankings of #1.
The Aura Frames platform has been built with Ruby on Rails since the beginning (more than 10 years ago!). Christmas 2025 was the busiest day of the year for the company and technical platform, serving a peak of 41 million API requests per hour (~11.4K requests per second), and processing a peak of 11.8 million background jobs per hour (~3300 jobs/second). On the database side, the sum of DB peak transactions per second (TPS) was 226K.
For an introduction to the Aura Frames company and products, and a deeper dive on the Postgres side of things, please check out Part 1 of this series.
Brief Recap from Part 1: Besides Ruby on Rails, Aura Frames uses PostgreSQL and AWS as key technologies.
Due to not being ea
[...]On Christmas Day 2024, Postgres infrastructure powering the Aura Frames API had problems under peak load, being unavailable for three hours and disrupting the experience for new customers. The team knew it would need improvements to handle the surge for Christmas 2025 and beyond.
One year later, much of the resource intensive data access was reworked, the Postgres infrastructure was upsized, and this approach not only survived, but thrived, providing reliable service through the holiday season.
The sum of Transactions Per Second (TPS) across the DBs peaked at 226,000, with more than 100K TPS sustained for 10 hours and repeating on multiple days after Christmas, with an average query time of 25 microseconds.
The improved reliability meant customers could smoothly set up new frames and add photos, and they did it more than ever, with the Aura Frames app reaching #1 in U.S. and Canadian Apple and Android App Stores on Christmas Day.
In this post we’ll look back at the months of planning and execution that went into achieving that outcome!
A second post in this series will dig into the Ruby on Rails side, while this one will focus on Postgres.
Aura Frames (Aura Home, Inc.) is the company behind modern, high-quality, Wi-Fi connected digital photo frames that customers love.
The frames are easy to use via free iOS and Android apps, don’t require a subscription, and offer unlimited cloud storage for photos and videos. Once set up, family members can be invited to contribute photos and videos via the app from anywhere. Typically Aura frames have an average of 4 contributors adding content.
In 2025, more than 1 billion photos were shared to Aura frames globally.
While public engineering blog posts are limited, Aura was featured on the AWS Storage Blog in the past. Link: How Aura improves database performance using Amazon S3 Express One Zone for caching.
I began working with Aura in 2025. Au
[...]On March 8, 2026, British Columbia moved their clocks to a year-round Pacific Daylight Savings Time. In March, they did the spring forward one hour with their clocks to UTC-7, but they won't fall back to UTC-8 in November. Going forward, the UTC offset for America/Vancouver timezone is permanently UTC-7.
Let's use this as an opportunity to talk about date and time zone storage. In the most basic examples, the default is to store the UTC value, then calculate local time relative to UTC. However, people using calendar systems think in terms of local time (i.e. wall clock time), and never consider UTC. After modifying time zone data, these time calculations from UTC for a region will differ from the user's input value.
If you stored timestamps in a UTC-based column for British Columbia-based appointment in 2026 and beyond, your November through March appointments may be off by an hour!
See timestamptz columns don't store the local time. They store the UTC time, and the timezone is only used to convert to and from UTC when inserting and querying. If you stored a future appointment as a timestamptz in the America/Vancouver timezone, it was converted to UTC using the rules at the time of storage. When you query that appointment later, it converts back to local time using the current rules. If the rules changed from storage to query, the local time you get back is not what the user originally intended.
If you've not updated your tzdata package, then Postgres doesn't know about the change, and it will continue to convert using the old rules. How often are the tzdata packages in Ubuntu updated? Surprisingly, every few months.
If your columns are stored in timestamptz column types and work with customers in British Columbia, use the following SQL query to determine if the tzdata package has been updated:
SELECT
to_char(
'2026-12-01 10:00:00'::timestamp AT TIME ZONE 'America/Vancouver',
'HH24:MI:SS OF'
) AS november_2026_vancouver_offset;
If the value is 17:00:00 +00, then tzdata
I'm lately back from Chicago, where PG DATA 2026 is now in the books! This is the two-day successor to PGDay Chicago, where I've been involved for the past few years volunteering, speaking, and finally organizing (and also volunteering and speaking). Everything went blessedly smoothly with only a few close calls, and we're already starting to plan next year's event.
I've been on program committees for a few years now: besides prior PGDays in Chicago, I participated in the committees for PGConf EU Athens in 2024 and PGDay Armenia earlier this year. PG DATA is my first time chairing the committee, and I am immensely thankful for Karen Jex's tutelage. This is just long enough, just widely enough, at just the right historical moment, to have a front-row seat for a transition that's making talk selection much, much harder.
Fundamentally, assembling a conference program has worked like this: interested speakers submit abstracts that describe briefly their topic and goal. The committee deliberates, usually through some kind of voting mechanism. They rank a longlist, then draw a shortlist from it attempting to balance factors including topic variety, skill level coverage, diversity of speaker affiliation (half your speakers being from a single company doesn't look great at community-run conferences), and more. This makes up the bank of accepted talks and the reserve list in case accepted speakers cancel. Once speakers confirm acceptance, the committee plays schedule Tetris until things look like an event people want to go to.
It still works like that. What's changed, as far as talk selection goes, is that it is now possible to generate a superficially polished abstract with minimal effort and near-zero subject matter knowledge. You can demand an LLM "generate an abstract for an XYZ conference" with no more context than that, and receive a statistically plausible derivative of the conference abstracts and information about XYZ it was trained on. It will flow like a real abstract, it will build up something
[...]
In distributed database environments, balancing durability and performance is a constant tug-of-war. PostgreSQL’s synchronous_commit parameter sits at the heart of this, giving administrators a dial to choose exactly when a COMMIT returns success to the client.
The idea of remote_receive was born from a simple question: does skipping the standby's disk write yield a measurable, real-world performance benefit? By waiting only for WAL bytes to reach the standby's memory, could we get a meaningful boost over remote_write? I set out to implement and benchmark this feature to find out.
What followed was a journey through network latency, OS page caches, CPU scheduler thrashing, and benchmarking noise. Here is the breakdown of the implementation, the tests, the initial anomalies, and the final results.
remote_receive?
Before this branch, PostgreSQL offered four primary synchronous commit modes:
off: Fully asynchronous. (Fastest, least safe)
local: Waits for local disk flush on the primary.
remote_write: Waits for the standby to write the WAL to its OS buffer cache (pwrite).
remote_apply: Waits for the standby to fully replay the WAL. (Slowest, most safe)
remote_receive sits directly between local and remote_write. In this mode, the primary guarantees that the WAL bytes have physically arrived at the standby's walreceiver process buffer. It does not wait for the standby to call pwrite().
The Hypothesis: By completely bypassing the standby's disk I/O, remote_receive should deliver lower latency and higher throughput than remote_write, especially on replica hardware with slow disks.
To build this, I had to modify both the standby and the primary:
receivePtr (creating a 42-byte message, backward compatible).
walreceiver.c to send a reply meWhen I was at Turnitin, we were still kind of riding the tail end of the dot-com boom. People were rushing to ship things, and brief outages were not exactly good, but they were considered a normal part of running software on the internet. If the site was down for a few minutes, you’d shrug, dig in, and fix it.
That’s not really the world we live in anymore. Uptime is much more sensitive than it used to be. Five nines used to be the stretch goal – now four nines is something a lot of teams just treat as the expectation, and even a few minutes of outage in a month feels like a lot. We don’t really track averages in our metrics anymore, either; we track p99 latencies, because we actually care about that last 1% of users having a good experience.
The other thing that’s changed is how quickly outages get socialized. A noticeable hiccup in your service can end up on social media before your on-call has even finished acknowledging the page. In my experience, the worst situations are the ones where customers find out about an issue before the company does. That has both a financial cost and a reputational cost, and the reputational cost tends to linger long after the incident is resolved. Frequent outages chip away at users’ willingness to keep using your product.
Postgres is, of course, no exception. So that’s the world a Postgres DR plan has to operate in.
When people hear “disaster recovery,” I think the natural mental picture is a natural disaster – a flood, an earthquake, a wildfire, or maybe a long utility outage that takes a data center offline. And those are real concerns; we put generators and solar panels and multi-region replication in place partly to deal with exactly that.
But in my experience, most of the disasters that take a Postgres database down don’t look anything like that. They look like:
A NOT IN query can return the wrong answer without telling you. It is valid SQL, it runs without an error, and it hands back a perfectly well-formed result set that happens to be empty when it should not be. No warning, no hint, nothing in the logs: just zero rows where you expected hundreds, and a database that considers it correct.
Almost always the cause is a single NULL sitting somewhere you forgot to look, combined with two keywords you have typed a thousand times: NOT IN. None of it is a Postgres bug. This is exactly what the SQL standard mandates, implemented faithfully. That is precisely what makes it so easy to walk into, and why the planner could not safely optimize around it for about twenty-five years. It comes down to one if statement in the parser.
Nothing elaborate. A table of products, one of which has no category assigned yet, and a table of archived categories that happens to contain a NULL:
CREATE TABLE products (id int, category_id int);
INSERT INTO products VALUES (1, 10), (2, 20), (3, NULL), (4, 10);
CREATE TABLE archived (category_id int);
INSERT INTO archived VALUES (20), (NULL);
The NULL in archived is not contrived. The moment a column is nullable (and most are, by default), a NULL can find its way into any subquery you point a NOT IN at. That is the whole point: this is not an exotic data condition, it is the ordinary one.
Here is the request you have written a hundred times: give me the products whose category is not archived.
SELECT id, category_id FROM products
WHERE category_id NOT IN (SELECT category_id FROM archived);
You expect products 1 and 4 (category 10, which is not in the archived set). What comes back is:
id | category_id
----+-------------
(0 rows)
Every row gone. Not a subset, not an off-by-one: all of them. Drop the NULL from archived and the same query behaves:
SELECT id, category_id FROM products
WHERE category_id NOT IN (SELECT category_id FROM archived
WHERE [...]
Recently, a new type of question has entered the database arena: what did this data look like last Tuesday? Maybe it's the price of a product before the holiday sale kicked in, or which department an employee belonged to before that reorg nobody asked for. Short of adding an entire audit trigger system, how can we know what data looked like before and after a change at that exact date?The SQL:2011 standard formalized a proper solution over a decade ago with temporal tables. Other database engines adopted pieces of it relatively quickly. Characteristically, Postgres took its time. But Postgres 19 is finally bringing native temporal table support to the party — and it's been well worth the wait.Let’s see what we’re working with.
A colleague asked me recently if there was an API for querying the PostgreSQL Buildfarm database. I told him there was not. I'm aware that a number of people have been scraping the web pages for data, so it seemed like there was a good case for something better. And with a little help from claude code, I create one. It's live now. There's a full description at https://github.com/PGBuildFarm/server-code/blob/main/API.md
I'm particularly interested to hear from people how this might be usefully extended.
Here's an example of use, getting the latest status for the member crake on the master branch
TL;DR: The pgEdge AI DBA Workbench is four services on a shared Postgres datastore: a collector, a server, an alerter, and a React client that renders the dashboards and the chat panel where Ellie lives. Ellie is an agentic loop that drives any LLM you choose (Claude, ChatGPT, Ollama, or anything OpenAI-compatible) through a fixed set of database-aware tool calls. The model never queries Postgres directly, which is rather the point. Anomaly detection runs three tiers: z-score baselines, pgvector similarity against historical patterns, and LLM escalation for the residual cases. Source on GitHub under the PostgreSQL Licence.Most developers I talk to have a similar reaction when somebody tells them an LLM is going to manage their database: a polite nod, followed by a question about how the model is supposed to know the difference between a healthy snapshot on a quiet Tuesday and a runaway transaction on a Black Friday morning. It is a fair question, and it is the question we have been trying to answer whilst building the pgEdge AI DBA Workbench and its conversational interface, Ellie. You can run the Workbench as a pure monitoring platform with the AI switched off, and it is a perfectly capable one; but after months of working with Ellie enabled, none of us choose to.
PGDay Boston 2026 was a rewarding reminder of why I value the PostgreSQL community so much. It was delightful to reconnect with familiar faces, meet new people, and finally put some faces to names for the first time. One of the best parts of the day was the sense that this community is larger than any one employer or project. It is built on shared curiosity, shared responsibility, and a willingness to help one another learn. I’m honored to have been able to share my own thoughts in my Disaster Recovery talk as well.
The keynote, Michael Stonebraker’s “Where Did Postgres Come From?”, was a standout for me. I especially appreciated the history of Postgres and the years before Postgres, during the Ingres era. It was striking to hear how the project could have ended up as just another academic system, yet instead grew into something enduring because people outside of UC Berkeley took ownership of it and built a broader community around it. That story felt like a good reminder that open source succeeds not only through technical merit, but through stewardship and continuity.
I also enjoyed Brian Brennglass’s talk, “Managing and Observing Locks.” His demos made an intimidating topic much easier to follow, and I found the practical framing especially useful. Shree Vidhya Sampath’s session on leveraging Patroni’s synchronous replication while running PostgreSQL on Kubernetes was another highlight. I appreciated the clear discussion of election behavior, synchronous replication, and failover scenarios, including failure modes I had not experimented with myself.
Robert Haas’ “pg_plan_advice: Plan Stability and User Planner Control for PostgreSQL?” was impressive in his attention to detail, especially the way he tested edge cases that people might not think to check. Bruce Momjian’s “What’s Missing in Postgres?” was also thought-provoking because it framed missing features not as oversights, but often as deliberate choices shaped by the needs of the broader community. Ryan Booz’s “Mastering P
[...]The Prague PosgreSQL User Group met on June 1, 2026, organized by Gülçin Yıldırım Jelínek and Mayur B.
Speaker:
PGDay France 2026 took place from June 3-4
Organizers:
Talk selection Committee:
Speaker:
Lightning Talks:
PG DATA 2026 took place on June 4-5 2026.
Organizers:
Talk Selection Team:
CoC Committee:
Trainings:
Speaker:
Lightning Talk Speaker:
The old Postgres Conferences always sounded interesting, but the conference being in Ottawa in Canada and me being in Europe, I've always dismissed them as "too far away" and never bothered going.
Then the organizing team changed and the conference moved to Vancouver for 2024. That's even further away. 2025 in Montreal was a bit closer again, but…
At the October 2025 PGConf.EU conference in Riga, everyone was again excited about the upcoming PGConf.dev 2026 conference. Melanie Plageman, with her organizer hat on, told me "you should definitely come".
So, I finally signed up to join the legendary PostgreSQL developers conference. It was taking place in Vancouver again, and while I had ignored that another extra incentive in 2024, I have family living in Vancouver, so it would be nice to see my sister-in-law's family again.
Pavlo, Ants and I had coordinated travel so we would be on the same plane from Frankfurt to Vancouver. The conference started on Tuesday but we were already going on Sunday so we would have some time to accommodate before. The weird thing about flying west is that time isn't passing, we basically boarded at 10 am and nine hours "later" got off the plane at 10 am again, so we had a full Sunday to start exploring the town. But since we were tired, we were at the hotel at around 5 pm and I slept from 7 until 4 the next morning. Monday was spent going around Stanley Park on rented bikes, where we met the first other attendees.
Tuesday was the first conference day, filled with community discussion sessions. New to me, the conference had organized breakfast for all attendees, so everyone was coming in a bit earlier and we got the hallway track going. This is frankly the most interesting part of any conference to me, talking to old friends, meeting new faces and connecting then to names already known, and exchanging ideas about PostgreSQL and the world. After the opening, Devrim and I had a slot scheduled for making PostgreSQL ecosystem packaging (more) visible where
[...]PostgeSQL 19 beta 1 has been released on 2026-06-04.
The release is planned for September 2026, though it’s time to go through the new features and changes.
All three features below are cool and great developments from the authors of the patches that implemented them. But there are a lot of new cool features and extesnions to exisiting features done by a lot of people.
My thanks go out to all the people who made PostgreSQL 19 possible.
For all others there is still the opportunity to be part of that people, your time is now to test the current and comming beta and release candidates of PostgreSQL 19. And please report bugs you may find.
That way you help all users having a stable release version ready to use in production.
The INSERT became a new conflict action: It can now return a SELECT for conflicts.
Previously it already hat the options DO NOTHING and DO UPDATE, the first one did exactly what it was named, doing nothing, the second on offered to update the already existing columns.
The SELECT gives a complete new set of possibilities to handle existing data in an INSERT statement.
Previously one needed an extension, pg_repack, but repack is now a command directly availabe in PostgeSQL 19.
Not only is it now a command, it also supports the parameter CONCURRENTLY. That way repack does not affect current database sessions.
And you can pass an index name of an existing index to sort the repacked table after an index. When you have defined the sortorder with CLUSTER and you do not pass an index name, the sortorder is done after the index specified in CLUSTER.
That does obviously not solve the problem of having the table data always physically sorted, but at least with REPACK as regular maintenance job, the data is mostly sorted.
Graph Query Language (GQL) is a standardized query language for property graphs.
SQL/PGQ is the SQL standard extension to add the Graph Query Language to relatianonal datab
[...]
Every PostgreSQL developer eventually reaches the same architectural boundary, although the boundary usually appears as an ordinary product request rather than a database design problem. An application transaction needs to complete one business operation, but the surrounding platform also needs to write an audit record, launch a slow report, refresh a cache, fire a notification, or start some enrichment logic that should not delay the user. The first version of the application usually places that extra work inside the same transaction because that approach is simple and convenient. The problem appears later, when a rollback removes diagnostic information, a slow report increases API latency, or a user request begins carrying the weight of every downstream process that the business has attached to it.
PostgreSQL is excellent at transactional consistency because it ensures that related changes succeed or fail together. That behavior is exactly what you want when an order, payment, inventory adjustment, or account update must remain correct. However, there are real-world cases where the triggering transaction and the follow-on work should not share the same fate. An audit record should survive a rollback, a notification should not hold an HTTP request open, and an analytical report should not force a user to watch a spinner while PostgreSQL scans millions of rows.
PostgreSQL does not provide Oracle-style autonomous transactions as a built-in feature, so teams often create their own patterns around this gap. Some teams use dblink loopbacks to force work through another database connection, while other teams use LISTEN and NOTIFY with external workers, polling tables, cron jobs, or message queues. These approaches can work, and larger platforms may still need full orchestration layers when the workflow spans many services. However, when the work is fundamentally SQL that should run inside PostgreSQL, the extra infrastructure can feel like a small bridge built to cross a puddle.
pg_background addres
[...]Number of posts in the past two months
Number of posts in the past two months
Get in touch with the Planet PostgreSQL administrators at planet at postgresql.org.