Over the years, I’ve had the opportunity to architect all sorts of configurations using Postgres as a backend. I’ve always found it very cool and satisfying to implement sophisticated business rules, often in more ways than one has fingers and toes. So, it’s not an understatement when I say that Citus is one of the more interesting technologies that I’ve come across when scaling PostgreSQL.
Citus is an extension that horizontally scales PostgreSQL across multiple machines by sharding and creating redundant copies of tables. Its query engine not only parallelizes incoming SQL queries for time series but also has the ability to create column-wise tables making it ideal for OLAP analysis duties.
Among its many capabilities, a Citus cluster can:
Pondering the case of high availability and redundancy, one replicates data by creating a replica via streaming replication.
Now let’s stretch our imagination and consider a second method of high availability, ala Citus.
The best way to describe the Citus way of doing things is to reflect how data is managed by a disk RAID array. Depending on the configuration, one can tune a hardware RAID for either performance or redundancy. The same can be said for Citus data sharding.
Here is an example of a table named replica2x, which has 2X redundancy across a cluster of four (4) nodes. The colors indicate duplicated shards of the table. For example, if node citus1 goes offline, the sharded table it holds still has copies on nodes citus2 and citus4. Likewise, it can be said that if node citus2 goes offline, the same data is still available on nodes 1, 3, and 4.

I’ll be upfront: I love working with Linux Containers, LXD. Much of what I will show you makes heavy use of them. You won’t need LXD to replicate this POC, of course, but I can’t say enough how flexible and versatile such an environment can be when prototyping a cluster of Postgres nodes, let alone an entire multi-data center infrastructure on a single workstation.
There are two parts to this POC;
Referring to this earlier blog, you’ll see how to get and install Citus into your environment.
The Citus cluster consists of a five (5) node cluster:
By using LXD, I created a single templated container with Citus on Ubuntu 20.04, where the various nodes were copied from this template.
|
1 |
for u in citus-coord-01 citus1 citus2 citus3 citus4<br>do<br> echo "==== $u ===="<br> lxc rm --force $u 2>/dev/null<br> lxc cp template-ubuntu-2004-citusdb $u<br> lxc start $u<br>done |
And here’s the resultant cluster:
|
1 |
lxc ls -c ns4 citus |
|
1 |
+----------------+---------+----------------------+<br>| NAME | STATE | IPV4 |<br>+----------------+---------+----------------------+<br>| citus1 | RUNNING | 10.231.38.140 (eth0) |<br>+----------------+---------+----------------------+<br>| citus2 | RUNNING | 10.231.38.151 (eth0) |<br>+----------------+---------+----------------------+<br>| citus3 | RUNNING | 10.231.38.171 (eth0) |<br>+----------------+---------+----------------------+<br>| citus4 | RUNNING | 10.231.38.204 (eth0) |<br>+----------------+---------+----------------------+<br>| citus-coord-01 | RUNNING | 10.231.38.34 (eth0) |<br>+----------------+---------+----------------------+<br> |
It’s understood that on each of the five nodes:
Inspecting the nodes confirms Citus has been correctly installed:
|
1 |
for u in citus1 citus2 citus3 citus4 citus-coord-01<br>do<br><br>echo "==== NODE: $u ===="<br><br>lxc exec $u -- su - postgres -c 'psql db01'<<_eof_<br>select extname, extversion from pg_extension;<br>_eof_<br><br>done | less -S |
|
1 |
==== NODE: citus1 ====<br> extname | extversion <br>----------------+------------<br> plpgsql | 1.0<br> citus_columnar | 11.1-1<br> citus | 11.1-1<br><br>==== NODE: citus2 ====<br> extname | extversion <br>----------------+------------<br> plpgsql | 1.0<br> citus_columnar | 11.1-1<br> citus | 11.1-1<br><br>==== NODE: citus3 ====<br> extname | extversion <br>----------------+------------<br> plpgsql | 1.0<br> citus_columnar | 11.1-1<br> citus | 11.1-1<br><br>==== NODE: citus4 ====<br> extname | extversion <br>----------------+------------<br> plpgsql | 1.0<br> citus_columnar | 11.1-1<br> citus | 11.1-1<br><br>==== NODE: citus-coord-01 ====<br> extname | extversion <br>----------------+------------<br> plpgsql | 1.0<br> citus_columnar | 11.1-1<br> citus | 11.1-1<br> |
Properly installed, the Citus runtime variables are now available:
|
1 |
lxc exec citus1 -- su postgres -c psql db01<<_eof_ | less -S<br> select name,setting,unit from pg_settings where name ~ 'citus' order by 1;<br>_eof_ |
|
1 |
name | setting | unit <br>----------------------------------------------------+-----------------+------<br> citus.all_modifications_commutative | off | <br> citus.background_task_queue_interval | 5000 | ms<br> citus.cluster_name | default | <br> citus.coordinator_aggregation_strategy | row-gather | <br> citus.count_distinct_error_rate | 0 | <br> citus.cpu_priority | 0 | <br> citus.cpu_priority_for_logical_replication_senders | inherit | <br> citus.defer_drop_after_shard_move | on | <br> citus.defer_drop_after_shard_split | on | <br> citus.defer_shard_delete_interval | 15000 | ms<br> citus.desired_percent_disk_available_after_move | 10 | <br> citus.distributed_deadlock_detection_factor | 2 | <br> citus.enable_binary_protocol | on | <br> citus.enable_create_role_propagation | on | <br> citus.enable_deadlock_prevention | on | <br> citus.enable_local_execution | on | <br> citus.enable_local_reference_table_foreign_keys | on | <br> citus.enable_repartition_joins | off | <br> citus.enable_statistics_collection | off | <br> citus.explain_all_tasks | off | <br> citus.explain_analyze_sort_method | execution-time | <br> citus.limit_clause_row_fetch_count | -1 | <br> citus.local_hostname | localhost | <br> citus.local_shared_pool_size | 50 | <br> citus.local_table_join_policy | auto | <br> citus.log_remote_commands | off | <br> citus.max_adaptive_executor_pool_size | 16 | <br> citus.max_cached_connection_lifetime | 600000 | ms<br> citus.max_cached_conns_per_worker | 1 | <br> citus.max_client_connections | -1 | <br> citus.max_high_priority_background_processes | 2 | <br> citus.max_intermediate_result_size | 1048576 | kB<br> citus.max_matview_size_to_auto_recreate | 1024 | MB<br> citus.max_shared_pool_size | 100 | <br> citus.max_worker_nodes_tracked | 2048 | <br> citus.multi_shard_modify_mode | parallel | <br> citus.multi_task_query_log_level | off | <br> citus.node_connection_timeout | 30000 | ms<br> citus.node_conninfo | sslmode=require | <br> citus.propagate_set_commands | none | <br> citus.recover_2pc_interval | 60000 | ms<br> citus.remote_task_check_interval | 10 | ms<br> citus.shard_count | 32 | <br> citus.shard_replication_factor | 1 | <br> citus.show_shards_for_app_name_prefixes | | <br> citus.skip_constraint_validation | off | <br> citus.skip_jsonb_validation_in_copy | on | <br> citus.stat_statements_track | none | <br> citus.task_assignment_policy | greedy | <br> citus.task_executor_type | adaptive | <br> citus.use_citus_managed_tables | off | <br> citus.use_secondary_nodes | never | <br> citus.values_materialization_threshold | 100 | <br> citus.version | 11.1.4 | <br> citus.worker_min_messages | notice | <br> citus.writable_standby_coordinator | off | <br> |
Log into the coordinator in order to declare and configure the cluster:
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_<br> select citus_set_coordinator_host('citus-coord-01', 5432);<br> insert into pg_dist_node(nodename)<br> values ('citus1')<br> ,('citus2')<br> ,('citus3')<br> ,('citus4');<br>_eof_ |
And here’s the cluster’s organization:
|
1 |
db01=# select nodeid,nodename,groupid,isactive from pg_dist_node order by 1;<br> nodeid | nodename | groupid | isactive <br>--------+----------------+---------+----------<br> 1 | citus-coord-01 | 0 | t<br> 2 | citus1 | 1 | t<br> 3 | citus2 | 2 | t<br> 4 | citus3 | 3 | t<br> 5 | citus4 | 4 | t<br> |
Table myevents is created, and the newly inserted records are evenly distributed across the cluster of nodes.
Login to the coordinator and execute the following commands. Notice that all DML and SQL statements are executed on the coordinator node:
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_<br><br>-- create the table<br> create table myevents (<br> device_id<br> bigint,<br> event_id<br> bigserial,<br> event_time<br> timestamptz default now(),<br> data<br> jsonb not null,<br> primary key (device_id, event_id)<br> );<br><br>-- distribute the events among the nodes<br> select create_distributed_table('myevents', 'device_id');<br><br>-- populate the table<br> insert into myevents (device_id, data)<br> select s % 100, ('{"measurement":'||random()||'}')::jsonb<br> from generate_series(1,1000000) s;<br>_eof_ |
Querying the coordinator:
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br>select *<br> from myevents<br> where device_id = 1<br> order by event_time desc, event_id desc<br> limit 10;<br>_eof_ |
|
1 |
device_id | event_id | event_time | data <br>----------+----------+-------------------------------+-------------------------------------<br> 1 | 999901 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.2868659956537316}<br> 1 | 999801 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.7931493079697731}<br> 1 | 999701 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.4875322951757288}<br> 1 | 999601 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.6491362745752653}<br> 1 | 999501 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.9629266554851366}<br> 1 | 999401 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.1185674800281864}<br> 1 | 999301 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.5133762596297742}<br> 1 | 999201 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.7307634886202119}<br> 1 | 999101 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.2997471209159892}<br> 1 | 999001 | 2023-08-16 14:51:12.618467-07 | {"measurement": 0.9692520484104021} |
This demonstrates clearly that table myevents is hash-sharded across every node member:
|
1 |
for u in citus1 citus2 citus3 citus4<br>do<br>echo "==== NODE: $u ===="<br>lxc exec $u -- su - postgres -c 'psql db01'<<_eof_<br> dt+<br>_eof_<br>done | less -S |
|
1 |
==== NODE: citus1 ====<br> List of relations<br>Schema | Name | Type | Owner | Persistence | Access method | Size | Description <br>-------+-----------------+-------+----------+-------------+---------------+---------+-------------<br>public | myevents_102008 | table | postgres | permanent | heap | 2832 kB | <br>public | myevents_102012 | table | postgres | permanent | heap | 2840 kB | <br>public | myevents_102016 | table | postgres | permanent | heap | 5624 kB | <br>public | myevents_102020 | table | postgres | permanent | heap | 2840 kB | <br>public | myevents_102024 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102028 | table | postgres | permanent | heap | 5632 kB | <br>public | myevents_102032 | table | postgres | permanent | heap | 2840 kB | <br>public | myevents_102036 | table | postgres | permanent | heap | 6560 kB | <br><br>==== NODE: citus2 ====<br> List of relations<br>Schema | Name | Type | Owner | Persistence | Access method | Size | Description <br>-------+-----------------+-------+----------+-------------+---------------+---------+-------------<br>public | myevents_102009 | table | postgres | permanent | heap | 4696 kB | <br>public | myevents_102013 | table | postgres | permanent | heap | 976 kB | <br>public | myevents_102017 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102021 | table | postgres | permanent | heap | 3768 kB | <br>public | myevents_102025 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102029 | table | postgres | permanent | heap | 2840 kB | <br>public | myevents_102033 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102037 | table | postgres | permanent | heap | 2840 kB | <br><br>==== NODE: citus3 ====<br> List of relations<br>Schema | Name | Type | Owner | Persistence | Access method | Size | Description <br>-------+-----------------+-------+----------+-------------+---------------+------------+-------------<br>public | myevents_102010 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102014 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102018 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102022 | table | postgres | permanent | heap | 4696 kB | <br>public | myevents_102026 | table | postgres | permanent | heap | 8192 bytes | <br>public | myevents_102030 | table | postgres | permanent | heap | 2832 kB | <br>public | myevents_102034 | table | postgres | permanent | heap | 976 kB | <br>public | myevents_102038 | table | postgres | permanent | heap | 4696 kB | <br><br>==== NODE: citus4 ====<br> List of relations<br>Schema | Name | Type | Owner | Persistence | Access method | Size | Description <br>-------+-----------------+-------+----------+-------------+---------------+---------+-------------<br>public | myevents_102011 | table | postgres | permanent | heap | 5632 kB | <br>public | myevents_102015 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102019 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102023 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102027 | table | postgres | permanent | heap | 2840 kB | <br>public | myevents_102031 | table | postgres | permanent | heap | 2832 kB | <br>public | myevents_102035 | table | postgres | permanent | heap | 1904 kB | <br>public | myevents_102039 | table | postgres | permanent | heap | 4696 kB |<br> |
ATTENTION: Please note that you may have to edit your own queries as the values may be different for your setup.
Update shard replication factor from 1X to 2X:
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_<br> show citus.shard_replication_factor;<br> alter system set citus.shard_replication_factor=2;<br> select pg_reload_conf();<br>_eof_ |
|
1 |
# validate<br>lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_<br> show citus.shard_replication_factor;<br>_eof_ |
|
1 |
citus.shard_replication_factor <br>--------------------------------<br>2 |
Table myevents2x is created and populated with a redundancy of 2X across the cluster:
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br>-- create a new table with 2X redundancy<br> create table myevents2x (<br> device_id<br> bigint,<br> event_id<br> bigserial,<br> event_time<br> timestamptz default now(),<br> data<br> jsonb not null,<br> primary key (device_id, event_id)<br> );<br><br>-- distribute the events among the nodes<br> select create_distributed_table('myevents2x', 'device_id');<br><br><br>-- confirm table has been added across the cluster<br> select * from master_get_active_worker_nodes() order by 1;<br><br>-- populate the table<br> insert into myevents2x (device_id, data)<br> select s % 100, ('{"measurement":'||random()||'}')::jsonb<br> from generate_series(1,1000000) s;<br>_eof_ |
Here’s the output:
|
1 |
CREATE TABLE<br> create_distributed_table <br>--------------------------<br> <br>(1 row)<br><br> node_name | node_port <br>-----------+-----------<br> citus1 | 5432<br> citus2 | 5432<br> citus3 | 5432<br> citus4 | 5432<br>(4 rows)<br><br>INSERT 0 1000000 |
Locate shard myevents2x_102040, which should be on nodes citus1 and citus2:
|
1 |
for u in citus1 citus2 citus3 citus4<br>do<br>echo "==== NODE: $u ===="<br>lxc exec $u -- su - postgres -c 'psql db01'<<_eof_<br> select tablename from pg_tables where tablename~'myevents2x' order by 1<br>_eof_<br>done | less -S<br> |
Here’s the output:
|
1 |
==== NODE: citus1 ====<br> tablename <br>-------------------<br> myevents2x_102040<br> myevents2x_102043<br> myevents2x_102044<br> myevents2x_102047<br> myevents2x_102048<br> myevents2x_102051<br> myevents2x_102052<br> myevents2x_102055<br> myevents2x_102056<br> myevents2x_102059<br> myevents2x_102060<br> myevents2x_102063<br> myevents2x_102064<br> myevents2x_102067<br> myevents2x_102068<br> myevents2x_102071<br>(16 rows)<br><br>==== NODE: citus2 ====<br> tablename <br>-------------------<br> myevents2x_102040<br> myevents2x_102041<br> myevents2x_102044<br> myevents2x_102045<br> myevents2x_102048<br> myevents2x_102049<br> myevents2x_102052<br> myevents2x_102053<br> myevents2x_102056<br> myevents2x_102057<br> myevents2x_102060<br> myevents2x_102061<br> myevents2x_102064<br> myevents2x_102065<br> myevents2x_102068<br> myevents2x_102069<br><br>==== NODE: citus3 ====<br> tablename <br>-------------------<br> myevents2x_102041<br> myevents2x_102042<br> myevents2x_102045<br> myevents2x_102046<br> myevents2x_102049<br> myevents2x_102050<br> myevents2x_102053<br> myevents2x_102054<br> myevents2x_102057<br> myevents2x_102058<br> myevents2x_102061<br> myevents2x_102062<br> myevents2x_102065<br> myevents2x_102066<br> myevents2x_102069<br> myevents2x_102070<br><br>==== NODE: citus4 ====<br> tablename <br>-------------------<br> myevents2x_102042<br> myevents2x_102043<br> myevents2x_102046<br> myevents2x_102047<br> myevents2x_102050<br> myevents2x_102051<br> myevents2x_102054<br> myevents2x_102055<br> myevents2x_102058<br> myevents2x_102059<br> myevents2x_102062<br> myevents2x_102063<br> myevents2x_102066<br> myevents2x_102067<br> myevents2x_102070<br> myevents2x_102071 |
Locate and return the first three records of shard myevents2x_102040 on nodes citus1 and citus2:
|
1 |
lxc exec citus1 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br> qecho ==== citus1 ====<br> qecho select * from myevents2x_102040 order by 1,2 limit 3<br> select * from myevents2x_102040 order by 1,2 limit 3;<br> c 'host=citus2 dbname=db01 user=postgres'<br> qecho ==== citus2 ====<br> qecho select * from myevents2x_102040 order by 1,2 limit 3<br> select * from myevents2x_102040 order by 1,2 limit 3;<br> c 'host=citus-coord-01 dbname=db01 user=postgres'<br> qecho ==== coordinator ====<br> qecho select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2;<br>_eof_ |
Here’s the output:
|
1 |
==== citus1 ====<br>select * from myevents2x_102040 order by 1,2 limit 3<br> device_id | event_id | event_time | data <br>-----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922}<br><br><br>You are now connected to database "db01" as user "postgres" on host "citus2" (address "fd42:cb6a:5384:9a60:216:3eff:fe38><br>==== citus2 ====<br>select * from myevents2x_102040 order by 1,2 limit 3<br> device_id | event_id | event_time | data <br>-----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922}<br><br><br>You are now connected to database "db01" as user "postgres" on host "citus-coord-01" (address "fd42:cb6a:5384:9a60:216:3><br>==== coordinator ====<br>select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> device_id | event_id | event_time | data <br>-----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922} |
The next few steps demonstrate our ability to continuously query and return those records found in shard myevents2x_102040.
|
1 |
lxc stop citus1 |
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br> qecho "==== citus1 is shutdown ===="<br> qecho ==== querying coordinator ====<br> qecho select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2;<br>_eof_<br> |
Here’s the output:
|
1 |
"==== citus1 is shutdown ===="<br>==== querying coordinator ====<br>select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> device_id | event_id | event_time | data <br>-----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922} |
|
1 |
lxc start citus1<br>lxc stop citus2 |
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br> qecho "==== citus2 is shutdown ===="<br> qecho ==== querying coordinator ====<br> qecho select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2;<br>_eof_<br> |
Here’s the output; note that it’s exactly the same as the previous test:
|
1 |
"==== citus2 is shutdown ===="<br>==== querying coordinator ====<br>select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> device_id | event_id | event_time | data <br>-----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922} |
|
1 |
lxc start citus2<br> |
|
1 |
lxc exec citus-coord-01 -- su - postgres -c 'psql db01'<<_eof_ | less -S<br> qecho "==== cluster restored ===="<br> qecho ==== querying coordinator ====<br> qecho select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br> select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2;<br>_eof_ |
Here’s the output, of course!
|
1 |
"==== cluster restored ===="<br>==== querying coordinator ====<br>select * from myevents2x where device_id=8 and event_id in (8,108,208) order by 1,2<br>device_id | event_id | event_time | data <br>----------+----------+-------------------------------+-------------------------------------<br> 8 | 8 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.5055415404961443}<br> 8 | 108 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.9145586163936634}<br> 8 | 208 | 2023-08-17 07:10:51.622082-07 | {"measurement": 0.8032392420487922} |
Data redundancy is the hallmark of high availability. But with Citus, we’ve raised the bar. Can you think of a better system that can stay up without losing time initiating failovers when a node fails?
Have fun!
Percona Distribution for PostgreSQL provides the best and most critical enterprise components from the open-source community in a single distribution, designed and tested to work together.