Service

Exposing service for PostgreSQL

Split read & write, route traffic to the right place, and achieve stable & reliable access to the PostgreSQL cluster.

Service is an abstraction to seal the details of the underlying cluster, especially during cluster failover/switchover.


Personal User

Service is meaningless to personal users. You can access the database with raw IP address directly or whatever method you like.

  1. psql postgres://dbuser_dba:DBUser.DBA@10.10.10.10/meta # dbsu direct connect
  2. psql postgres://dbuser_meta:DBUser.Meta@10.10.10.10/meta # bizuser direct connect

Service

Service is a logical abstraction for PostgreSQL cluster abilities. Which consist of:

  1. Access Point via NodePort
  2. Target Instances via Selectors

It’s quite like a Kubernetes service (NodePort mode), but it is implemented differently (haproxy on the nodes).

Here are the default PostgreSQL services and their definition:

serviceportdescription
primary5433PROD read/write, connect to primary 5432 or 6432
replica5434PROD read-only, connect to replicas 5432/6432
default5436admin or direct access to primary
offline5438OLAP, ETL, personal user, interactive queries
  1. - { name: primary ,port: 5433 ,dest: default ,check: /primary ,selector: "[]" }
  2. - { name: replica ,port: 5434 ,dest: default ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }
  3. - { name: default ,port: 5436 ,dest: postgres ,check: /primary ,selector: "[]" }
  4. - { name: offline ,port: 5438 ,dest: postgres ,check: /replica ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}

pgsql-ha

Take the default pg-meta cluster & meta database as an example, it will have four default services:

  1. psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5433/meta # pg-meta-primary : production read/write via primary pgbouncer(6432)
  2. psql postgres://dbuser_meta:DBUser.Meta@pg-meta:5434/meta # pg-meta-replica : production read-only via replica pgbouncer(6432)
  3. psql postgres://dbuser_dba:DBUser.DBA@pg-meta:5436/meta # pg-meta-default : Direct connect primary via primary postgres(5432)
  4. psql postgres://dbuser_stats:DBUser.Stats@pg-meta:5438/meta # pg-meta-offline : Direct connect offline via offline postgres(5432)

EVERY INSTANCE of pg-meta cluster will have these four services exposed; you can access service via ANY / ALL of them.


Primary Service

The primary service may be the most critical service for production usage.

It will route traffic to the primary instance, depending on pg_default_service_dest:

  • pgbouncer: route traffic to primary pgbouncer port (6432), which is the default behavior
  • postgres: route traffic to primary postgres port (5432) directly, if you don’t want to use pgbouncer
  1. - { name: primary ,port: 5433 ,dest: default ,check: /primary ,selector: "[]" }

It means all cluster members will be included in the primary service (selector: "[]"), but the one and only one instance that past health check (check: /primary) will be used as the primary instance. Patroni will guarantee that only one instance is primary at any time, so the primary service will always route traffic to THE primary instance.

Example: pg-test-primary haproxy config

  1. listen pg-test-primary
  2. bind *:5433
  3. mode tcp
  4. maxconn 5000
  5. balance roundrobin
  6. option httpchk
  7. option http-keep-alive
  8. http-check send meth OPTIONS uri /primary
  9. http-check expect status 200
  10. default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
  11. # servers
  12. server pg-test-1 10.10.10.11:6432 check port 8008 weight 100
  13. server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
  14. server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Replica Service

The replica service is used for production read-only traffics.

There may be many more read-only queries than read-write queries in real-world scenarios, you may have many replicas for that.

The replica service will route traffic to pgbouncer or postgres depending on pg_default_service_dest, just like primary service.

  1. - { name: replica ,port: 5434 ,dest: default ,check: /read-only ,selector: "[]" , backup: "[? pg_role == `primary` || pg_role == `offline` ]" }

The replica service traffic will try to use common pg instances with pg_role = replica to alleviate the load on the primary instance as much as possible. And it will try NOT to use instances with pg_role = offline to avoid mixing OLAP & OLTP queries as much as possible.

All cluster members will be included in the replica service (selector: "[]") when it passes the read-only health check (check: /read-only). While primary and offline instances are used as backup servers, which will take over in case of all replica instances are down.

Example: pg-test-replica haproxy config

  1. listen pg-test-replica
  2. bind *:5434
  3. mode tcp
  4. maxconn 5000
  5. balance roundrobin
  6. option httpchk
  7. option http-keep-alive
  8. http-check send meth OPTIONS uri /read-only
  9. http-check expect status 200
  10. default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
  11. # servers
  12. server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup
  13. server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
  14. server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Default Service

The default service will route to primary postgres (5432) by default.

It is quite like primary service, except that it will always bypass pgbouncer, regardless of pg_default_service_dest. Which is useful for administration connection, ETL writes, CDC changing data capture, etc…

  1. - { name: primary ,port: 5433 ,dest: default ,check: /primary ,selector: "[]" }

Example: pg-test-default haproxy config

  1. listen pg-test-default
  2. bind *:5436
  3. mode tcp
  4. maxconn 5000
  5. balance roundrobin
  6. option httpchk
  7. option http-keep-alive
  8. http-check send meth OPTIONS uri /primary
  9. http-check expect status 200
  10. default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
  11. # servers
  12. server pg-test-1 10.10.10.11:5432 check port 8008 weight 100
  13. server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
  14. server pg-test-2 10.10.10.12:5432 check port 8008 weight 100

Offline Service

The Offline service will route traffic to dedicate postgres instance directly.

Which could be a pg_role = offline instance, or a pg_offline_query flagged instance.

If no such instance is found, it will fall back to any replica instances. the bottom line is: it will never route traffic to the primary instance.

  1. - { name: offline ,port: 5438 ,dest: postgres ,check: /replica ,selector: "[? pg_role == `offline` || pg_offline_query ]" , backup: "[? pg_role == `replica` && !pg_offline_query]"}
  1. listen pg-test-offline
  2. bind *:5438
  3. mode tcp
  4. maxconn 5000
  5. balance roundrobin
  6. option httpchk
  7. option http-keep-alive
  8. http-check send meth OPTIONS uri /replica
  9. http-check expect status 200
  10. default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
  11. # servers
  12. server pg-test-3 10.10.10.13:5432 check port 8008 weight 100
  13. server pg-test-2 10.10.10.12:5432 check port 8008 weight 100 backup

Define Service

The default services are defined in pg_default_services.

While you can define your extra PostgreSQL services with pg_services @ the global or cluster level.

These two parameters are both arrays of service objects. Each service definition will be rendered as a haproxy config in /etc/haproxy/<svcname>.cfg, check service.j2 for details.

Here is an example of an extra service definition: standby

  1. - name: standby # required, service name, the actual svc name will be prefixed with `pg_cluster`, e.g: pg-meta-standby
  2. port: 5435 # required, service exposed port (work as kubernetes service node port mode)
  3. ip: "*" # optional, service bind ip address, `*` for all ip by default
  4. selector: "[]" # required, service member selector, use JMESPath to filter inventory
  5. dest: default # optional, destination port, default|postgres|pgbouncer|<port_number>, 'default' by default
  6. check: /sync # optional, health check url path, / by default
  7. backup: "[? pg_role == `primary`]" # backup server selector
  8. maxconn: 3000 # optional, max allowed front-end connection
  9. balance: roundrobin # optional, haproxy load balance algorithm (roundrobin by default, other: leastconn)
  10. options: 'inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100'

And it will be translated to a haproxy config file /etc/haproxy/pg-test-standby.conf:

  1. #---------------------------------------------------------------------
  2. # service: pg-test-standby @ 10.10.10.11:5435
  3. #---------------------------------------------------------------------
  4. # service instances 10.10.10.11, 10.10.10.13, 10.10.10.12
  5. # service backups 10.10.10.11
  6. listen pg-test-standby
  7. bind *:5435
  8. mode tcp
  9. maxconn 5000
  10. balance roundrobin
  11. option httpchk
  12. option http-keep-alive
  13. http-check send meth OPTIONS uri /
  14. http-check expect status 200
  15. default-server inter 3s fastinter 1s downinter 5s rise 3 fall 3 on-marked-down shutdown-sessions slowstart 30s maxconn 3000 maxqueue 128 weight 100
  16. # servers
  17. server pg-test-1 10.10.10.11:6432 check port 8008 weight 100 backup
  18. server pg-test-3 10.10.10.13:6432 check port 8008 weight 100
  19. server pg-test-2 10.10.10.12:6432 check port 8008 weight 100

Reload Service

When cluster membership has changed, such as append / remove replicas, switchover/failover, or adjust relative weight, You have to reload service to make the changes take effect.

  1. bin/pgsql-svc <cls> [ip...] # reload service for lb cluster or lb instance

Access Service

Pigsty expose service with haproxy. Which is enabled on all nodes by default.

haproxy load balancers are idempotent among same pg cluster by default, you use ANY / ALL of them by all means.

The typical method is access via cluster domain name, which resolve to cluster L2 VIP, or all instances ip address in a round-robin manner.

Service can be implemented in different ways, You can even implement you own access method such as L4 LVS, F5, etc… instead of haproxy.

You can use different combination of host and port, they are provide PostgreSQL service in different ways.

Host

typesampledescription
Cluster Domain Namepg-testvia cluster domain name (resolved by dnsmasq @ infra nodes)
Cluster VIP Address10.10.10.3via a L2 VIP address managed by vip-manager, bind to primary
Instance Hostnamepg-test-1Access via any instance hostname (resolved by dnsmasq @ infra nodes)
Instance IP Address10.10.10.11Access any instance ip address

Port

Pigsty uses different ports to distinguish between pg services

portservicetypedescription
5432postgresdatabaseDirect access to postgres server
6432pgbouncermiddlewareGo through connection pool middleware before postgres
5433primaryserviceAccess primary pgbouncer (or postgres)
5434replicaserviceAccess replica pgbouncer (or postgres)
5436defaultserviceAccess primary postgres
5438offlineserviceAccess offline postgres

Combinations

  1. # Access via cluster domain
  2. postgres://test@pg-test:5432/test # DNS -> L2 VIP -> primary direct connection
  3. postgres://test@pg-test:6432/test # DNS -> L2 VIP -> primary connection pool -> primary
  4. postgres://test@pg-test:5433/test # DNS -> L2 VIP -> HAProxy -> Primary Connection Pool -> Primary
  5. postgres://test@pg-test:5434/test # DNS -> L2 VIP -> HAProxy -> Replica Connection Pool -> Replica
  6. postgres://dbuser_dba@pg-test:5436/test # DNS -> L2 VIP -> HAProxy -> Primary direct connection (for Admin)
  7. postgres://dbuser_stats@pg-test:5438/test # DNS -> L2 VIP -> HAProxy -> offline direct connection (for ETL/personal queries)
  8. # Direct access via cluster VIP
  9. postgres://test@10.10.10.3:5432/test # L2 VIP -> Primary direct access
  10. postgres://test@10.10.10.3:6432/test # L2 VIP -> Primary Connection Pool -> Primary
  11. postgres://test@10.10.10.3:5433/test # L2 VIP -> HAProxy -> Primary Connection Pool -> Primary
  12. postgres://test@10.10.10.3:5434/test # L2 VIP -> HAProxy -> Repilca Connection Pool -> Replica
  13. postgres://dbuser_dba@10.10.10.3:5436/test # L2 VIP -> HAProxy -> Primary direct connection (for Admin)
  14. postgres://dbuser_stats@10.10.10.3::5438/test # L2 VIP -> HAProxy -> offline direct connect (for ETL/personal queries)
  15. # Specify any cluster instance name directly
  16. postgres://test@pg-test-1:5432/test # DNS -> Database Instance Direct Connect (singleton access)
  17. postgres://test@pg-test-1:6432/test # DNS -> connection pool -> database
  18. postgres://test@pg-test-1:5433/test # DNS -> HAProxy -> connection pool -> database read/write
  19. postgres://test@pg-test-1:5434/test # DNS -> HAProxy -> connection pool -> database read-only
  20. postgres://dbuser_dba@pg-test-1:5436/test # DNS -> HAProxy -> database direct connect
  21. postgres://dbuser_stats@pg-test-1:5438/test # DNS -> HAProxy -> database offline read/write
  22. # Directly specify any cluster instance IP access
  23. postgres://test@10.10.10.11:5432/test # Database instance direct connection (directly specify instance, no automatic traffic distribution)
  24. postgres://test@10.10.10.11:6432/test # Connection Pool -> Database
  25. postgres://test@10.10.10.11:5433/test # HAProxy -> connection pool -> database read/write
  26. postgres://test@10.10.10.11:5434/test # HAProxy -> connection pool -> database read-only
  27. postgres://dbuser_dba@10.10.10.11:5436/test # HAProxy -> Database Direct Connections
  28. postgres://dbuser_stats@10.10.10.11:5438/test # HAProxy -> database offline read-write
  29. # Directly specify any cluster instance IP access
  30. postgres://test@10.10.10.11:5432/test # Database instance direct connection (directly specify instance, no automatic traffic distribution)
  31. postgres://test@10.10.10.11:6432/test # Connection pool -> database
  32. postgres://test@10.10.10.11:5433/test # HAProxy -> connection pool -> database read/write
  33. postgres://test@10.10.10.11:5434/test # HAProxy -> connection pool -> database read-only
  34. postgres://dbuser_dba@10.10.10.11:5436/test # HAProxy -> Database Direct Connections
  35. postgres://dbuser_stats@10.10.10.11:5438/test # HAProxy -> database offline read-write
  36. # Smart client automatic read/write separation (connection pooling)
  37. postgres://test@10.10.10.11:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=primary
  38. postgres://test@10.10.10.11:6432,10.10.10.12:6432,10.10.10.13:6432/test?target_session_attrs=prefer-standby
  39. # Intelligent client automatic read/write separation (database)
  40. postgres://test@10.10.10.11:5432,10.10.10.12:5432,10.10.10.13:5432/test?target_session_attrs=primary
  41. postgres://test@10.10.10.11:5432,10.10.10.12:5432,10.10.10.13:5432/test?target_session_attrs=prefer-standby

Last modified 2023-02-27: add v2.0 images and docs (5b09f12)