Why Amazon RDS and Aurora PostgreSQL report "No space left on device" when storage looks free
This article explains why Amazon RDS for PostgreSQL and Amazon Aurora PostgreSQL can return "No space left on device" while CloudWatch still shows free storage
Overview
You may encounter a situation where your Amazon RDS for PostgreSQL or Amazon Aurora PostgreSQL instance reports an error similar to:
ERROR: could not open file "current_logfiles.tmp": No space left on device
or
ERROR: could not create file "base/16aa6/11261aaa71_vm": No space left on device
However, when you check CloudWatch metrics, available storage space still shows free capacity.
This article explains why this discrepancy occurs, how it differs between RDS PostgreSQL and Aurora PostgreSQL, and how to diagnose and resolve the issue.
How Storage Differs: RDS PostgreSQL vs Aurora PostgreSQL
| Aspect | RDS PostgreSQL | Aurora PostgreSQL |
|---|---|---|
| Data storage | EBS volume (gp2/gp3/io1/io2) with ext4 filesystem | Aurora distributed storage (no traditional filesystem) |
| Inode limit applicable? | Yes — ext4 has a fixed inode count | No — Aurora storage does not use inodes for data files |
| "No space left" with free space | Inode exhaustion or ext4 directory index full | Local storage full (temp files/logs) |
| Key CloudWatch metric | FreeStorageSpace (EBS bytes only — does NOT track inodes) | FreeLocalStorage |
Part 1: RDS PostgreSQL — Inode Exhaustion
What Are Inodes?
Every file on a Linux ext4 filesystem requires an inode (index node). It is a fixed-size metadata structure that stores file attributes and pointers to data blocks. The inode count is set at filesystem creation and is proportional to the volume size. When all inodes are consumed, no new files can be created even if disk space remains available.
Key points:
- Each file consumes exactly one inode, regardless of file size
- The inode count is fixed and cannot be increased without scaling storage (which grows the filesystem and adds inodes proportionally)
- CloudWatch FreeStorageSpace reports disk bytes only — it does NOT monitor inode availability
How PostgreSQL Uses Files
PostgreSQL uses a file-per-relation storage model. Each database object creates multiple files on disk:
| Component | File Suffix | Purpose |
|---|---|---|
| Main data fork | (none) | Actual row data or index entries |
| Free Space Map | _fsm | Tracks free space within pages |
| Visibility Map | _vm | Tracks all-visible pages for index-only scans |
| TOAST table | separate relfilenode | Stores oversized column values |
| TOAST index | separate relfilenode | B-tree index on TOAST table |
| Segments (>1 GB) | .1, .2, .3... | Each 1 GB data chunk is a separate file |
Temporary files and log files count too. On RDS PostgreSQL, temp files (from sorts/hashes that spill past work_mem, in 'base/pgsql_tmp') and server log files ('postgresql.log', retained per rds.log_retention_period) live on the same EBS ext4 volume as your data — so they consume both FreeStorageSpace bytes and inodes. Temp-file inode usage is transient (files are removed when the query finishes), so persistent inode exhaustion is still driven mainly by permanent relations (tables, indexes, TOAST, partitions); retained log files are longer-lived.
Diagnosing Inode Exhaustion (RDS PostgreSQL)
Check via Database Insights (Advanced) or Enhanced Monitoring
Database Insights Advanced and Enhanced Monitoring expose OS-level filesystem counters including inode usage:
| Metric | Console Label | What It Shows |
|---|---|---|
| usedFilePercent | Used Inodes | The percentage of available files in use. |
| maxFiles | Max Inodes | The maximum number of files that can be created for the file system. |
| usedFiles | Used% | The number of files in the file system. |
| usedPercent | Used Filesystem | The percentage of the file-system disk space in use. |
How to check in the RDS Console:
Option A — Enhanced Monitoring (Amazon RDS console)
- Open the Amazon RDS console → Databases.
- Choose your DB instance (for a cluster, select the specific writer/reader instance — OS metrics are per instance, not per cluster).
- Open the Monitoring tab.
- Use the Monitoring dropdown (top-right of the tab) and select Enhanced monitoring.
- Select Manage graphs.
- Scroll to the File system metric group and select:
usedFilePercent— the percentage of inodes in use. This is your primary signal — 99–100% = inode exhaustion.usedFiles— the raw count of inodes in use.usedPercent— the percentage of disk bytes used. In an inode-exhaustion case this stays low (for example 40–50%) — that's the paradox.
Option B — Database Insights (Amazon CloudWatch console)
- Open the CloudWatch Database Insights console → in the left nav.
- Select your database (filter by cluster/instance), then open the specific DB instance.
- Go to Database Telemetry and then click on Create widget.
- Under 'OS metrics', expand the File system group and select the same fields "Used File Percent", "Used Files" and "Used Percent" (Parameters Without rootfilesys would be for /rdsdbdata).
- Then click on Create Widget and monitor the metrics.
Confirmation pattern:
Used Inodes (usedFilePercent) ≈ 99-100% ← INODES EXHAUSTED
Used Filesystem (usedPercent) ≈ 40-50% ← Plenty of disk bytes free
Note: Database Insights Standard does not include these OS-level metrics. You need either Database Insights Advanced or Enhanced Monitoring enabled.
Check RDS Events
RDS emits low inodes events when usage exceeds thresholds either through AWS console or using below AWS CLI command:
aws rds describe-events \ --source-identifier <your-instance-id> \ --source-type db-instance \ --duration 10080
Look for messages like:
The free inodes count for DB Instance: <instance-name> is low at 5% of total
Identify What Is Consuming Inodes
Connect to each database and count relations:
-- Total relation count in the current database SELECT count(*) AS total_relations FROM pg_class; -- Breakdown by object type SELECT CASE relkind WHEN 'r' THEN 'table' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' ELSE relkind::text END AS object_type, count(*) AS count FROM pg_class GROUP BY relkind ORDER BY count DESC; -- List all databases (repeat the above for each) SELECT datname FROM pg_database WHERE datistemplate = false;
Check for ext4 Directory Index Full Scenario
If errors are intermittent (some file creates succeed, others fail), you may be hitting the ext4 hash tree (htree) directory limit — a per-directory cap that is separate from total inode count.
Characteristics:
- Occurs when a single database directory has millions of files
- usedFilePercent may still be below 100%
- Errors are intermittent, not consistent
- Reach out to AWS Support if needed
Resolution (RDS PostgreSQL)
Immediate: Scale Storage
Increasing allocated storage grows the filesystem, adding inodes proportionally:
aws rds modify-db-instance \ --db-instance-identifier <your-instance> \ --allocated-storage <new-size-in-GB> \ --apply-immediately
Important: Storage scaling is a one-way operation (cannot decrease). After scaling, the storage-full state clears and the instance becomes operational again.
Reduce File Count
-- Drop unused databases DROP DATABASE <unused_database>; -- Find unused indexes (candidates for removal) SELECT schemaname, indexrelname, idx_scan FROM pg_stat_user_indexes WHERE idx_scan = 0; -- Check for inactive replication slots SELECT slot_name, active FROM pg_replication_slots WHERE NOT active; SELECT pg_drop_replication_slot('<slot_name>');
Long-Term Recommendations
- Implement cleanup policies for ephemeral databases (CI/CD workloads)
- Reduce partition count where possible (each partition = separate set of files)
- Avoid heavy CREATE/DROP temporary table patterns (use TRUNCATE instead)
- Tune work_mem to reduce temp file generation
Prevention & Monitoring
| Action | Purpose |
|---|---|
| Enable Database Insights Advanced or Enhanced Monitoring | Get visibility into inode usage (usedFilePercent) |
| Alarm on usedFilePercent > 80% | Early warning before exhaustion |
| Provision storage for file count, not just data size | More storage = more inodes |
| Implement database cleanup policies | Prevent unbounded relation growth |
| Reduce log retention (rds.log_retention_period) | Reduces the number of log files |
| Set temp_file_limit parameter | Cap temp file generation per session |
| log_temp_files + pg_ls_tmpdir() | For diagnosing which sessions spill temp files |
| Increase work_mem | Reduce temp file spills |
Part 2: Aurora PostgreSQL — Local Storage Exhaustion
Inode exhaustion does NOT apply to Aurora PostgreSQL data files. Aurora uses a distributed storage layer without a traditional ext4 filesystem.
However, Aurora PostgreSQL CAN report "No space left on device" when local storage fills up with temporary files or logs.
What Causes "No Space Left" on Aurora PostgreSQL?
| Cause | Key Metric |
|---|---|
| Temp files from large sort/hash operations filling local storage | FreeLocalStorage → 0 |
| Excessive PostgreSQL logging filling local storage | FreeLocalStorage → 0 |
Diagnosing (Aurora PostgreSQL)
- Check FreeLocalStorage in CloudWatch or Database Insights — if it dropped to zero at the time of error, local storage exhaustion is confirmed.
- Check temp file usage:
SELECT datname, temp_files, temp_bytes FROM pg_stat_database WHERE temp_files > 0;
- Check Database Insights / Enhanced Monitoring — OS metrics show local filesystem usage breakdown.
Resolution (Aurora PostgreSQL)
| Action | Effect |
|---|---|
| Kill long-running queries generating temp files | Immediate relief |
| Scale to larger instance class | More local storage |
| Increase work_mem | Reduces temp file spills |
| Set temp_file_limit parameter | Prevents runaway queries from filling local storage |
Prevention & Monitoring
| Action | Purpose |
|---|---|
| Alarm on FreeLocalStorage < 5 GB | Early warning for local storage |
| Enable Database Insights Advanced | OS-level local filesystem visibility |
| Set temp_file_limit parameter | Cap temp file generation per session |
| Increase work_mem | Reduce temp file spills |
| Monitor Performance Insights wait events IO:BufFileWrite | Identify queries spilling to disk |
| Reduce log retention (rds.log_retention_period) | Reduces the number of files |
| log_temp_files + pg_ls_tmpdir() | For diagnosing which sessions spill temp files |
| Use Aurora Optimized Reads | Moves temp files to local NVMe |
Frequently Asked Questions
Q: Does inode exhaustion affect Aurora PostgreSQL?
A: No. Aurora uses a distributed storage layer without a traditional filesystem or inode limit for data files. "No space left" on Aurora is caused by local storage (temp files/logs) filling up.
Q: Does VACUUM free inodes?
A: Generally no — and not in the way you'd need to recover from inode exhaustion. Standard (non-FULL) VACUUM reclaims dead-tuple space inside existing files so pages can be reused; it doesn't reduce the file (relation) count. It can only shrink a relation's file footprint in the narrow case where it truncates all-empty trailing pages, which occasionally removes a trailing 1 GB segment file — but this rarely moves the inode count meaningfully.
VACUUM FULL is different: it rewrites the table (and its indexes) into a new relfilenode and drops the old files. That doesn't reduce the number of relations either, and while it runs it temporarily needs space for both the old and new copies — so it's not a remedy for inode exhaustion (and it takes an ACCESS EXCLUSIVE lock).
To actually reduce the inode count, you must reduce the number of relations: DROP TABLE, DROP INDEX, DROP DATABASE, or reduce partition count. If you're already out of inodes/space, scale storage first to get the instance operational, then drop unneeded objects.
Q: Will a reboot fix inode exhaustion?
A: No. A reboot does not free inodes. You must either scale storage or drop database objects.
Q: Can I check inode usage in Database Insights?
A: Yes, but only with Database Insights Advanced (which includes Enhanced Monitoring OS metrics). Navigate to Monitoring → OS Metrics → Filesystem to view usedFilePercent for the /rdsdbdata mount point.
References
- Topics
- Database
- Language
- English
Relevant content
asked a year ago
asked 3 years ago
