The short answer to the title of this blog post is: yes, of course you can. The more important question is: should you? The PostgreSQL documentation about tablespaces contains an explicit warning: “Placing a tablespace on a temporary file system like a RAM disk risks the reliability of the entire cluster”. This is for sure true for tablespaces containing persistent data, but what about objects/files that are created in a temporary tablespace like temporary tables or sorts that are going to disk? Does the warning from the documentation apply to these as well? You can check the last blog about temporary tablespaces for getting an idea what actually goes to temporary tablespaces in PostgreSQL.

On Linux a RAM disk can be created quite easily:

postgres@centos8pg:/home/postgres/ [pgdev] sudo mkfs  /dev/my_ram_disk 81920
mke2fs 1.44.6 (5-Mar-2019)
Creating regular file /dev/my_ram_disk
Creating filesystem with 81920 1k blocks and 20480 inodes
Filesystem UUID: ecc6d90e-2d59-47f8-a598-7726c309c389
Superblock backups stored on blocks: 
        8193, 24577, 40961, 57345, 73729

Allocating group tables: done                            
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done 

Of course that needs to be mounted somewhere so that we can put a temporary tablespace on it:

postgres@centos8pg:/home/postgres/ [pgdev] sudo mkdir /my_ram_disk
postgres@centos8pg:/home/postgres/ [pgdev] sudo mount /dev/my_ram_disk /my_ram_disk/
postgres@centos8pg:/home/postgres/ [pgdev] df -h | grep my_ram
/dev/loop0            78M  1.6M   72M   3% /my_ram_disk
postgres@centos8pg:/home/postgres/ [pgdev] sudo chown postgres:postgres /my_ram_disk

I am going to create a new PostgreSQL cluster from scratch:

postgres@centos8pg:/home/postgres/ [pgdev] initdb -D /var/tmp/pg
postgres@centos8pg:/home/postgres/ [pgdev] export PGPORT=8888
postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl -D /var/tmp/pg/ start -l /dev/null

Preparing the temporary tablespace and setting it as the default:

postgres@centos8pg:/home/postgres/ [pgdev] psql -X -p 8888 postgres
psql (13devel)
Type "help" for help.

postgres=# ! mkdir /my_ram_disk/tbs/
postgres=# create tablespace my_temp_tbs location '/my_ram_disk/tbs/';
CREATE TABLESPACE
postgres=# db+
                                       List of tablespaces
    Name     |  Owner   |     Location     | Access privileges | Options |  Size   | Description 
-------------+----------+------------------+-------------------+---------+---------+-------------
 my_temp_tbs | postgres | /my_ram_disk/tbs |                   |         | 0 bytes | 
 pg_default  | postgres |                  |                   |         | 22 MB   | 
 pg_global   | postgres |                  |                   |         | 559 kB  | 
(3 rows)

postgres=# alter system set temp_tablespaces = 'my_temp_tbs';
ALTER SYSTEM
postgres=# select pg_reload_conf();
 pg_reload_conf 
----------------
 t
(1 row)

postgres=# show temp_tablespaces ;
 temp_tablespaces 
------------------
 my_temp_tbs
(1 row)

Creating a new temporary table should now create the underlying file on the RAM disk:

postgres=# create temporary table tmp1 ( a int, b date );
CREATE TABLE
postgres=# select pg_relation_filepath('tmp1');
              pg_relation_filepath              
------------------------------------------------
 pg_tblspc/16384/PG_13_202003051/12732/t3_16387
(1 row)
postgres-# ! ls -la /var/tmp/pg/pg_tblspc/
total 4
drwx------.  2 postgres postgres   19 Mar 16 21:32 .
drwx------. 19 postgres postgres 4096 Mar 16 21:32 ..
lrwxrwxrwx.  1 postgres postgres   16 Mar 16 21:32 16384 -> /my_ram_disk/tbs

Everything as expected. What could possibly go wrong with that? Will PostgreSQL start just fine if we put data in that table and then crash the postmaster?

postgres=# insert into tmp1 select * from generate_series(1,10000);
INSERT 0 10000
postgres=# ! ps -ef | grep postgres | grep "var/tmp"
postgres 25900     1  0 21:31 ?        00:00:00 /u01/app/postgres/product/DEV/db_1/bin/postgres -D /var/tmp/pg
postgres 25992 25911  0 21:47 pts/0    00:00:00 sh -c ps -ef | grep postgres | grep "var/tmp"
postgres 25995 25992  0 21:47 pts/0    00:00:00 grep var/tmp
postgres=# ! kill -9 25900
postgres=# select 1;
FATAL:  terminating connection due to unexpected postmaster exit
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?> select 1;
You are currently not connected to a database.
!?> 

Can we start normally from here on?

postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl -D /var/tmp/pg/ start
pg_ctl: another server might be running; trying to start server anyway
waiting for server to start....2020-03-16 21:49:33.034 CET [26010] LOG:  starting PostgreSQL 13devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.3.1 20190507 (Red Hat 8.3.1-4), 64-bit
2020-03-16 21:49:33.035 CET [26010] LOG:  listening on IPv6 address "::1", port 8888
2020-03-16 21:49:33.035 CET [26010] LOG:  listening on IPv4 address "127.0.0.1", port 8888
2020-03-16 21:49:33.037 CET [26010] LOG:  listening on Unix socket "/tmp/.s.PGSQL.8888"
2020-03-16 21:49:33.041 CET [26011] LOG:  database system was interrupted; last known up at 2020-03-16 21:36:18 CET
2020-03-16 21:49:33.263 CET [26011] LOG:  database system was not properly shut down; automatic recovery in progress
2020-03-16 21:49:33.264 CET [26011] LOG:  redo starts at 0/156D588
2020-03-16 21:49:33.264 CET [26011] LOG:  invalid record length at 0/1574240: wanted 24, got 0
2020-03-16 21:49:33.264 CET [26011] LOG:  redo done at 0/1574068
2020-03-16 21:49:33.277 CET [26010] LOG:  database system is ready to accept connections
 done
server started

All is fine. Of course the temporary table is gone but that would also have been the case if we just ended our session instead of killing the postmaster:

postgres@centos8pg:/home/postgres/ [pgdev] psql -X -p 8888 postgres
psql (13devel)
Type "help" for help.

postgres=# d tmp1
Did not find any relation named "tmp1".
postgres=# db+
                                        List of tablespaces
    Name     |  Owner   |     Location     | Access privileges | Options |    Size    | Description 
-------------+----------+------------------+-------------------+---------+------------+-------------
 my_temp_tbs | postgres | /my_ram_disk/tbs |                   |         | 1024 bytes | 
 pg_default  | postgres |                  |                   |         | 22 MB      | 
 pg_global   | postgres |                  |                   |         | 559 kB     | 
(3 rows)

postgres=# 

Sort operations will just work fine on that tablespace as well:

postgres=# select * from generate_series(1,1000000) order by random();
 generate_series 
-----------------
          943370
          301661
...

… but you need to be careful with the size of the RAM disk:

postgres=# select * from generate_series(1,5000000) order by random();
2020-03-16 22:04:47.738 CET [26026] ERROR:  could not write block 629 of temporary file: No space left on device
2020-03-16 22:04:47.738 CET [26026] STATEMENT:  select * from generate_series(1,5000000) order by random();
ERROR:  could not write block 629 of temporary file: No space left on device

So actually you can put a temporary tablespace on a RAM disk and I am not aware of serious issues, even if you lose the RAM disk that can easily be fixed:

postgres@centos8pg:/home/postgres/ [pgdev] sudo umount /my_ram_disk 
postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl -D /var/tmp/pg/ restart
waiting for server to shut down....2020-03-16 22:06:23.118 CET [26010] LOG:  received fast shutdown request
2020-03-16 22:06:23.124 CET [26010] LOG:  aborting any active transactions
2020-03-16 22:06:23.133 CET [26010] LOG:  background worker "logical replication launcher" (PID 26017) exited with exit code 1
2020-03-16 22:06:23.133 CET [26012] LOG:  shutting down
2020-03-16 22:06:23.148 CET [26010] LOG:  database system is shut down
 done
server stopped
waiting for server to start....2020-03-16 22:06:23.281 CET [26236] LOG:  starting PostgreSQL 13devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.3.1 20190507 (Red Hat 8.3.1-4), 64-bit
2020-03-16 22:06:23.282 CET [26236] LOG:  listening on IPv6 address "::1", port 8888
2020-03-16 22:06:23.282 CET [26236] LOG:  listening on IPv4 address "127.0.0.1", port 8888
2020-03-16 22:06:23.286 CET [26236] LOG:  listening on Unix socket "/tmp/.s.PGSQL.8888"
2020-03-16 22:06:23.289 CET [26236] LOG:  could not open directory "pg_tblspc/16384/PG_13_202003051": No such file or directory
2020-03-16 22:06:23.293 CET [26237] LOG:  database system was shut down at 2020-03-16 22:06:23 CET
2020-03-16 22:06:23.293 CET [26237] LOG:  could not open directory "pg_tblspc/16384/PG_13_202003051": No such file or directory
2020-03-16 22:06:23.299 CET [26236] LOG:  database system is ready to accept connections
 done
server started

PostreSQL will complain but it will start and once the RAM disk is avaialble again there is business as usual:

postgres@centos8pg:/home/postgres/ [pgdev] sudo mount /dev/my_ram_disk /my_ram_disk/
22:07:33 postgres@centos8pg:/home/postgres/ [pgdev] psql -X -p 8888 postgres
psql (13devel)
Type "help" for help.

postgres=# create temporary table tmp1 ( a int );
CREATE TABLE
postgres=# ! ls -la /my_ram_disk/
total 19
drwxr-xr-x.  4 postgres postgres  1024 Mar 16 21:31 .
dr-xr-xr-x. 23 root     root      4096 Mar 16 21:28 ..
drwx------.  2 root     root     12288 Mar 16 21:27 lost+found
drwx------.  3 postgres postgres  1024 Mar 16 21:32 tbs
postgres=# ! ls -la /my_ram_disk/tbs/
total 5
drwx------. 3 postgres postgres 1024 Mar 16 21:32 .
drwxr-xr-x. 4 postgres postgres 1024 Mar 16 21:31 ..
drwx------. 4 postgres postgres 1024 Mar 16 22:02 PG_13_202003051
postgres=# 

If you know any issues with that or have any comments I would be happy if you can share your thoughts.

Another option would by to use tmpfs:

postgres@centos8pg:/home/postgres/ [pgdev] sudo mkdir /my_ram_disk_2/
postgres@centos8pg:/home/postgres/ [pgdev] sudo mount -t tmpfs -o size=2G tmpfs /my_ram_disk_2/
postgres@centos8pg:/home/postgres/ [pgdev] df -h | grep disk_2
tmpfs                2.0G     0  2.0G   0% /my_ram_disk_2

This can be used for temporary tablespaces as well:

postgres@centos8pg:/home/postgres/ [pgdev] psql -X -p 8888 postgres
psql (13devel)
Type "help" for help.

postgres=# ! sudo chown postgres:postgres /my_ram_disk_2/
postgres=# ! mkdir /my_ram_disk_2/tbs2
postgres=# create tablespace my_temp_tbs2 location '/my_ram_disk_2/tbs2';
CREATE TABLESPACE
postgres=# db+
                                          List of tablespaces
     Name     |  Owner   |      Location       | Access privileges | Options |    Size    | Description 
--------------+----------+---------------------+-------------------+---------+------------+-------------
 my_temp_tbs  | postgres | /my_ram_disk/tbs    |                   |         | 2048 bytes | 
 my_temp_tbs2 | postgres | /my_ram_disk_2/tbs2 |                   |         | 0 bytes    | 
 pg_default   | postgres |                     |                   |         | 22 MB      | 
 pg_global    | postgres |                     |                   |         | 559 kB     | 
(4 rows)

postgres=# alter system set temp_tablespaces = 'my_temp_tbs2';
ALTER SYSTEM
postgres=# select pg_reload_conf();
 pg_reload_conf 
----------------
 t
(1 row)

postgres=# show temp_tablespaces ;
 temp_tablespaces 
------------------
 my_temp_tbs2
(1 row)

Same test as above:

postgres=# create temporary table tmp3 ( a int );
CREATE TABLE
postgres=# insert into tmp3 select * from generate_series(1,10000);
INSERT 0 10000
postgres=# select pg_relation_filepath('tmp3');
              pg_relation_filepath              
------------------------------------------------
 pg_tblspc/24583/PG_13_202003051/12732/t3_24587
(1 row)

postgres=# ! ls -la /var/tmp/pg/pg_tblspc/
total 4
drwx------.  2 postgres postgres   32 Mar 16 22:31 .
drwx------. 19 postgres postgres 4096 Mar 16 22:32 ..
lrwxrwxrwx.  1 postgres postgres   16 Mar 16 21:32 16384 -> /my_ram_disk/tbs
lrwxrwxrwx.  1 postgres postgres   19 Mar 16 22:31 24583 -> /my_ram_disk_2/tbs2
postgres=# ! ps -ef | grep postgres | grep "var/tmp"
postgres 26236     1  0 22:06 ?        00:00:00 /u01/app/postgres/product/DEV/db_1/bin/postgres -D /var/tmp/pg
postgres 26412 26379  0 22:35 pts/0    00:00:00 sh -c ps -ef | grep postgres | grep "var/tmp"
postgres 26415 26412  0 22:35 pts/0    00:00:00 grep var/tmp
postgres=# ! kill -9 26236
postgres=# select 1;
FATAL:  terminating connection due to unexpected postmaster exit
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
!?> select 1;
You are currently not connected to a database.
!?> 

PostgreSQL will start just fine:

postgres@centos8pg:/home/postgres/ [pgdev] pg_ctl -D /var/tmp/pg/ start
pg_ctl: another server might be running; trying to start server anyway
waiting for server to start....2020-03-16 22:37:44.903 CET [26431] LOG:  starting PostgreSQL 13devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.3.1 20190507 (Red Hat 8.3.1-4), 64-bit
2020-03-16 22:37:44.903 CET [26431] LOG:  listening on IPv6 address "::1", port 8888
2020-03-16 22:37:44.903 CET [26431] LOG:  listening on IPv4 address "127.0.0.1", port 8888
2020-03-16 22:37:44.906 CET [26431] LOG:  listening on Unix socket "/tmp/.s.PGSQL.8888"
2020-03-16 22:37:44.910 CET [26432] LOG:  database system was interrupted; last known up at 2020-03-16 22:31:24 CET
2020-03-16 22:37:45.172 CET [26432] LOG:  database system was not properly shut down; automatic recovery in progress
2020-03-16 22:37:45.173 CET [26432] LOG:  redo starts at 0/15A1EC8
2020-03-16 22:37:45.174 CET [26432] LOG:  invalid record length at 0/15B9E88: wanted 24, got 0
2020-03-16 22:37:45.174 CET [26432] LOG:  redo done at 0/15B9CD0
2020-03-16 22:37:45.195 CET [26431] LOG:  database system is ready to accept connections
 done
server started

To conclude: I am not sure if I would do this in real life but it seems to work quite well. As long as no persistent objects go into these tablespaces all should be fine. I’ve asked the mailing list for any experience with this, you can follow that here.