PGFS: Using Database as a Filesystem
A few days ago, I received a request from the Odoo community asking: “Databases support PITR (Point-in-Time Recovery), but is there a way to roll back the filesystem as well?”
Why the “PGFS” Idea?
From a veteran database engineer’s perspective, this is both a challenging and exciting question. We all know that for ERP systems like Odoo, the most valuable asset is indeed the core business data stored in a PostgreSQL database.
However, many “enterprise applications” inevitably deal with file operations - uploading attachments, storing images and documents, etc. While these files may not be as “mission-critical” as database data, having them rollback to the same point in time as the database would be excellent from security, data integrity, and convenience perspectives.
This led me to an interesting thought: Is there a way to give filesystems PITR capabilities similar to databases? Traditional approaches mostly point to expensive and complex CDP (Continuous Data Protection) solutions that require hardware appliances or block-level logging at the storage layer. But I wondered: for “poor folks,” could we solve this problem more cleverly using open-source technologies?
After much consideration, a combination that made me “slap my forehead” emerged: JuiceFS + PostgreSQL. By transforming PG into a filesystem, all file writes would enter the database, sharing the same WAL logs and enabling rollback to any historical point in time. This sounds fantastical, but don’t worry - it actually “works.” Let’s see how JuiceFS accomplishes this.
Meet JuiceFS: Turning Database into Filesystem
JuiceFS is a high-performance, cloud-native distributed filesystem that can mount object storage (like S3/MinIO) as a local POSIX filesystem. It’s extremely lightweight to install and use, requiring just a few commands for formatting, mounting, and read/write operations.
For example, these commands can use SQLite as JuiceFS’s metadata store and use local paths as object storage for testing:
The magic is: JuiceFS also supports using PostgreSQL as both metadata and object data storage backend! This means you only need to change JuiceFS’s backend to an existing PostgreSQL instance to get a database-based “filesystem.”
So if you have an existing PostgreSQL database (installed via Pigsty single-node setup, for example), you can spin up a “PGFS” with one command:
This way, any data written to the /data2 directory actually gets stored in PG’s jfs_blob table. In other words, this filesystem and the PG database have become one!
PGFS in Action: Filesystem PITR
Imagine we have an Odoo system that needs to store file data in directories like /var/lib/odoo. Traditionally, if we needed to restore Odoo’s database to a previous point in time, while the database could use WAL logs for point-in-time recovery, the filesystem would still rely on external snapshots or CDP.
But now, if we mount /var/lib/odoo on PGFS, all filesystem write operations become database write operations. The database no longer just stores SQL data - it simultaneously carries filesystem information. This means: when I perform PITR, not only can the database return to a certain point in time, but files can instantly “travel back with the database” to the same moment.
Some might ask, doesn’t ZFS support snapshots too? Yes, ZFS can create snapshots and rollback, but that’s still based on specific snapshot points. For precision down to specific seconds or minutes, you need true log-based solutions or CDP functionality. The JuiceFS+PG combination essentially writes file operation logs into the database’s WAL, which is exactly what PostgreSQL excels at naturally.
The following experimental workflow demonstrates everything. We write timestamps to the filesystem in a loop while continuously inserting heartbeat records into the database:
Then, verify the JuiceFS table in PostgreSQL:
When we decide to rollback to, say, one minute ago (2025-03-21 02:39:00), we simply execute:
What? Where did PITR and pgBackRest come from? Pigsty has already configured out-of-the-box monitoring, backup, high availability for you - just use it! You could set it up manually, but it would be somewhat troublesome.
Then when we check the filesystem logs and database heartbeat table again, both are frozen before the 02:39:00 timestamp:
This proves this approach works! We successfully achieved consistent FS/DB PITR through PGFS!
How’s the Performance?
So functionality exists, but what about performance?
I found a development server with SSD and tested it using the built-in juicefs bench. Results look decent - definitely more than sufficient for applications like Odoo.
Another sample: Aliyun ESSD PL1 budget disk test results
While throughput performance is certainly inferior to native FS, it’s sufficient for scenarios with small file volumes and low access frequency. After all, using “database as filesystem” isn’t meant for massive storage and high-concurrency writes, but to enable database and filesystem to “travel back in time together” - it just needs to work.
Completing the Puzzle: One-Click “Enterprise” Delivery
Next, let’s put this setup into a practical scenario - like one-click deployment of “enterprise-grade” Odoo, where files automatically have CDP capabilities.
Pigsty provides PG with external high availability, automatic backup, monitoring, PITR and other capabilities. Installing it is very easy:
Above is Pigsty’s standard installation process. Below we use playbooks to install Docker, create PGFS mount, and spin up stateless Odoo with Docker Compose:
Yes, it’s that simple - everything is ready. However, while the commands are simple, the key is the configuration file.
The configuration file pigsty.yml would look something like this, with the only modification being the addition of JuiceFS configuration, mounting PGFS to /data/odoo:
After completing these steps, you’ll have an “enterprise-grade” Odoo running on the same server: backend database managed by Pigsty, filesystem mounted by JuiceFS, and JuiceFS’s backend connected to PG. Once a “rollback need” arises, simply perform PITR on PG to get both files and database “back to the specified moment” together. This applies equally to applications with similar needs like Dify, Gitlab, Gitea, MatterMost, etc.
Looking back at all this, you’ll find: what originally required expensive, high-end storage hardware to achieve CDP can now be accomplished with a lightweight open-source combination. While it bears the DIY marks of “poor man’s engineering,” it’s indeed simple, stable, and sufficiently practical, worthy of exploration and experimentation in more scenarios.
