Overview

5 Improper feature usage

Unidirectional logical replication
CH05 F01 Angelakos
Logical replication in both directions
CH05 F02 Angelakos

Summary

  • SQL_ASCII is not a character encoding so much as the absence of one. If you don’t want to risk mixing encodings irreversibly and you enjoy the ease of automatic character set conversion, make sure you use UTF-8 for your database.
  • PostgreSQL RULEs are not related to rules as defined in other DBMSs. RULEs are complicated to understand and are mainly there as Postgres internal machinery. In most cases they don’t behave as the user expects them to and TRIGGERs are best used instead.
  • Using JSON(B) values with relational access patterns makes for less efficient SQL which is harder to read and might not perform as well. It’s better not to mix the SQL and NoSQL paradigms, but use each facility for what it’s best at doing.
  • uuids take up more storage space than even bigints and indexing them is less efficient. They may guarantee a range of values that you simply don’t require for your use case, and an integer index might be enough to guarantee uniqueness.
  • Bi-directional or multi-master replication is more complex than what appears at first sight. For practical use in a production system, there is a very long list of prerequisites, considerations and caveats. If your use case indeed justifies setting up a multi-master system, you are better off using an established multi-master solution whose developers have given thought to these issues rather than re-inventing the wheel.
  • All of the above apply to homemade distributed systems as well and it is not recommended to set out building them unless your scope is very narrowly focused. You need to consider the potential for distributed serialization anomalies and the very real trade-offs laid out in the CAP and PACELC theorems.

FAQ

What is SQL_ASCII in PostgreSQL and why is it risky?SQL_ASCII isn’t a real encoding. PostgreSQL treats bytes 0–127 as ASCII and leaves 128–255 uninterpreted. That means no validation and no server-side conversion. You can silently mix multiple encodings in the same column and later be unable to decode them reliably. Use UTF8 unless you must interoperate with a known legacy encoding.
How do I migrate a legacy SQL_ASCII database to UTF-8?- Create a new UTF8 database with proper locale: CREATE DATABASE ... ENCODING UTF8 LC_COLLATE '...UTF-8' LC_CTYPE '...UTF-8' TEMPLATE template0.
- For each text column, convert bytes using the true source encoding, for example: convert_from(convert(col::bytea, 'ISO-8859-1','UTF8'),'UTF8').
- Export/import or INSERT...SELECT into the new DB.
- Set client_encoding correctly and verify collations.
How do server and client encodings interact?PostgreSQL converts between server and client encodings automatically when the combination is supported and you set client_encoding (or the driver does). With SQL_ASCII, no conversion occurs and non-ASCII bytes are accepted unvalidated. Always ensure your application sends UTF-8 to a UTF8 database.
Should I use CREATE RULE or a trigger?Prefer triggers. Rules are an SQL rewriter with surprising side effects (e.g., duplicate evaluation, changed semantics). They’re hard to reason about and easy to misuse. The only common, safe use is to make views updatable; for table-side behavior, use BEFORE/AFTER triggers.
Why did my rule log the wrong ID or call nextval() twice?Rules rewrite your statement, so volatile expressions (like nextval() or now()) can be evaluated again in the expanded statements. That can advance sequences unexpectedly and produce mismatched values. Use a trigger that references NEW to capture the generated ID once.
Why didn’t my “DO INSTEAD NOTHING” rule block an UPDATE as expected?Rules don’t “block” queries; they transform them. Your original statement still runs, possibly with additional predicates injected by the rule. To enforce conditional logic (e.g., forbid updates on old rows), use a BEFORE trigger that RAISE EXCEPTIONs when the condition is met.
When should I store data in JSON/JSONB vs relational columns?Use JSONB for truly variable/sparse or schemaless payloads and for whole-object retrieval. Use relational columns when your data is structured and queried relationally (joins, constraints, typed predicates). Shoving relational data into JSON leads to unreadable queries, weak integrity, and heavier indexing.
Why are joins and filters over JSONB slow and hard to index?Predicates become expression-based (e.g., (doc->>'k')::int), which often prevents efficient use of simple btree indexes. GIN/GiST indexes can help but tend to be large and still yield nested loops/materialization for relational-style joins. If you’re indexing many JSON keys, it’s a signal to model the data relationally.
Are UUIDs good primary keys in Postgres? What are the trade-offs vs bigint?UUIDs are 16 bytes (vs 8 for bigint), produce larger indexes, slower inserts, and more random I/O; heavy use can push other columns to TOAST. Prefer int/bigint sequences for most workloads. Use UUIDs when you truly need globally unique, coordination-free IDs (e.g., multi-writer systems). Consider time-ordered IDs like Snowflake or UUIDv7 for better locality and sortability.
Can I build multi-master by pointing logical replication both ways? What will break?DIY bidirectional setups hit multiple pitfalls: replication-origin loops (must filter origins), primary-key conflicts from unsynchronized sequences, and replication failures on schema drift (DDL not coordinated). Even if it “works” briefly, it’s fragile. Use proven multi-master products (e.g., PGD/Spock) only when you truly need it (extreme availability, geo-distribution), otherwise stick to a single writer with replicas.
Is it safe to read from standbys while writing to the primary?Reads can be stale, breaking application-level atomicity and causing wrong decisions. Long standby queries can also be canceled due to recovery conflicts. Mitigations include: routing read-your-own-writes to primary, waiting for apply LSN, limiting replica lag, hot_standby_feedback (trades bloat), or using synchronous replication (trades latency/throughput). Avoid cross-node “transactions” unless you implement a robust 2PC strategy.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • PostgreSQL Mistakes and How to Avoid Them ebook for free