- •About…
- •About the Book
- •About the Author
- •Acknowledgements
- •About the organisation of the books
- •Structured Query Language
- •A First Use Case
- •Loading the Data Set
- •Application Code and SQL
- •Back to Discovering SQL
- •Computing Weekly Changes
- •Software Architecture
- •Why PostgreSQL?
- •The PostgreSQL Documentation
- •Getting Ready to read this Book
- •Business Logic
- •Every SQL query embeds some business logic
- •Business Logic Applies to Use Cases
- •Correctness
- •Efficiency
- •Stored Procedures — a Data Access API
- •Procedural Code and Stored Procedures
- •Where to Implement Business Logic?
- •A Small Application
- •Readme First Driven Development
- •Chinook Database
- •Top-N Artists by Genre
- •Intro to psql
- •The psqlrc Setup
- •Transactions and psql Behavior
- •Discovering a Schema
- •Interactive Query Editor
- •SQL is Code
- •SQL style guidelines
- •Comments
- •Unit Tests
- •Regression Tests
- •A Closer Look
- •Indexing Strategy
- •Indexing for Queries
- •Choosing Queries to Optimize
- •PostgreSQL Index Access Methods
- •Advanced Indexing
- •Adding Indexes
- •An Interview with Yohann Gabory
- •Get Some Data
- •Structured Query Language
- •Queries, DML, DDL, TCL, DCL
- •Select, From, Where
- •Anatomy of a Select Statement
- •Projection (output): Select
- •Restrictions: Where
- •Order By, Limit, No Offset
- •Ordering with Order By
- •kNN Ordering and GiST indexes
- •Top-N sorts: Limit
- •No Offset, and how to implement pagination
- •Group By, Having, With, Union All
- •Aggregates (aka Map/Reduce): Group By
- •Aggregates Without a Group By
- •Restrict Selected Groups: Having
- •Grouping Sets
- •Common Table Expressions: With
- •Distinct On
- •Result Sets Operations
- •Understanding Nulls
- •Three-Valued Logic
- •Not Null Constraints
- •Outer Joins Introducing Nulls
- •Using Null in Applications
- •Understanding Window Functions
- •Windows and Frames
- •Partitioning into Different Frames
- •Available Window Functions
- •When to Use Window Functions
- •Relations
- •SQL Join Types
- •An Interview with Markus Winand
- •Serialization and Deserialization
- •Some Relational Theory
- •Attribute Values, Data Domains and Data Types
- •Consistency and Data Type Behavior
- •PostgreSQL Data Types
- •Boolean
- •Character and Text
- •Server Encoding and Client Encoding
- •Numbers
- •Floating Point Numbers
- •Sequences and the Serial Pseudo Data Type
- •Universally Unique Identifier: UUID
- •Date/Time and Time Zones
- •Time Intervals
- •Date/Time Processing and Querying
- •Network Address Types
- •Denormalized Data Types
- •Arrays
- •Composite Types
- •Enum
- •PostgreSQL Extensions
- •An interview with Grégoire Hubert
- •Object Relational Mapping
- •Tooling for Database Modeling
- •How to Write a Database Model
- •Generating Random Data
- •Modeling Example
- •Normalization
- •Data Structures and Algorithms
- •Normal Forms
- •Database Anomalies
- •Modeling an Address Field
- •Primary Keys
- •Foreign Keys Constraints
- •Not Null Constraints
- •Check Constraints and Domains
- •Exclusion Constraints
- •Practical Use Case: Geonames
- •Features
- •Countries
- •Modelization Anti-Patterns
- •Entity Attribute Values
- •Multiple Values per Column
- •UUIDs
- •Denormalization
- •Premature Optimization
- •Functional Dependency Trade-Offs
- •Denormalization with PostgreSQL
- •Materialized Views
- •History Tables and Audit Trails
- •Validity Period as a Range
- •Pre-Computed Values
- •Enumerated Types
- •Multiple Values per Attribute
- •The Spare Matrix Model
- •Denormalize wih Care
- •Not Only SQL
- •Schemaless Design in PostgreSQL
- •Durability Trade-Offs
- •Another Small Application
- •Insert, Update, Delete
- •Insert Into
- •Insert Into … Select
- •Update
- •Inserting Some Tweets
- •Delete
- •Tuples and Rows
- •Deleting All the Rows: Truncate
- •Isolation and Locking
- •About SSI
- •Putting Concurrency to the Test
- •Computing and Caching in SQL
- •Views
- •Materialized Views
- •Triggers
- •Transactional Event Driven Processing
- •Trigger and Counters Anti-Pattern
- •Fixing the Behavior
- •Event Triggers
- •Listen and Notify
- •PostgreSQL Notifications
- •Notifications and Cache Maintenance
- •Listen and Notify Support in Drivers
- •Batch Update, MoMA Collection
- •Updating the Data
- •Concurrency Patterns
- •On Conflict Do Nothing
- •An Interview with Kris Jenkins
- •Installing and Using PostgreSQL Extensions
- •Finding PostgreSQL Extensions
- •A Short List of Noteworthy Extensions
- •Auditing Changes with hstore
- •Introduction to hstore
- •Comparing hstores
- •Auditing Changes with a Trigger
- •Testing the Audit Trigger
- •From hstore Back to a Regular Record
- •Last.fm Million Song Dataset
- •Using Trigrams For Typos
- •The pg_trgm PostgreSQL Extension
- •Trigrams, Similarity and Searches
- •Complete and Suggest Song Titles
- •Trigram Indexing
- •Denormalizing Tags with intarray
- •Advanced Tag Indexing
- •User-Defined Tags Made Easy
- •The Most Popular Pub Names
- •A Pub Names Database
- •Normalizing the Data
- •Geolocating the Nearest Pub (k-NN search)
- •How far is the nearest pub?
- •The earthdistance PostgreSQL contrib
- •Pubs and Cities
- •The Most Popular Pub Names by City
- •Geolocation with PostgreSQL
- •Geolocation Data Loading
- •Geolocation Metadata
- •Emergency Pub
- •Counting Distinct Users with HyperLogLog
- •HyperLogLog
- •Installing postgresql-hll
- •Counting Unique Tweet Visitors
- •Lossy Unique Count with HLL
- •Getting the Visits into Unique Counts
- •Scheduling Estimates Computations
- •Combining Unique Visitors
- •An Interview with Craig Kerstiens
Chapter 38 Triggers j 328
That’s the happy scenario where no problem occurs. Now, in the real life, here’s what will sometimes happen. It’s not always, mind you, but not never either. Concurrency bugs — they like to hide in plain sight.
1. |
The |
rst transaction of the day attempts to update the daily counters table |
||
|
for this day but |
nds no records because it’s the |
rst one. |
|
2. |
The second transaction of the day attempts to update the daily counters |
|||
|
table for this day, but nds no records, because the rst one isn’t there yet. |
|||
3. |
The second transaction of the day now proceeds to insert the rst value |
|||
|
for the day, because the job wasn’t done yet. |
|
||
4. |
The |
rst transaction of the day then inserts the |
rst value… and fails with a |
|
|
primary key con |
ict error because that insert has already been done. Sorry |
||
|
about that! |
|
|
|
There are several ways to address this issue, and the classic one is documented at A PL/pgSQL Trigger Procedure For Maintaining A Summary Table example in the PostgreSQL documentation.
The solution there is to loop over attempts at update then insert until one of those works, ignoring the UNIQUE_VIOLATION exceptions in the process. That allows implementing a fall back when another transaction did insert a value concurrently, i.e. in the middle of the NOT FOUND test and the consequent insert.
Starting in PostgreSQL 9.5 with support for the on conflict clause of the insert into command, there’s a much better way to address this problem.
Fixing the Behavior
While it’s easy to maintain a cache in an event driven fashion thanks to PostgreSQL and its trigger support, turning an insert into an update with contention on a single row is never a good idea. It’s even a classic anti-pattern.
Here’s a modern way to x the problem with the previous trigger implementation, this time applied to a per-message counter of retweet and favorite actions:
1begin;
2
3 create table twcache.counters
4(
|
|
Chapter 38 Triggers j 329 |
5 |
messageid |
bigint not null references tweet.message(messageid), |
6 |
rts |
bigint, |
7 |
favs |
bigint, |
8 |
|
|
9unique(messageid)
10 |
); |
11 |
|
12create or replace function twcache.tg_update_counters ()
13returns trigger
14language plpgsql
15as $$
16declare
17begin
18insert into twcache.counters(messageid, rts, favs)
19select NEW.messageid,
20 |
case |
when |
NEW.action |
= |
'rt' then 1 else 0 end, |
21 |
case |
when |
NEW.action |
= |
'fav' then 1 else 0 end |
22on conflict (messageid)
23do update
24 |
set rts = case when NEW.action = 'rt' |
25 |
then counters.rts + 1 |
26 |
|
27 |
when NEW.action = 'de-rt' |
28 |
then counters.rts - 1 |
29 |
|
30 |
else counters.rts |
31 |
end, |
32 |
|
33 |
favs = case when NEW.action = 'fav' |
34 |
then counters.favs + 1 |
35 |
|
36 |
when NEW.action = 'de-fav' |
37 |
then counters.favs - 1 |
38 |
|
39 |
else counters.favs |
40 |
end |
41 |
where counters.messageid = NEW.messageid; |
42 |
|
43RETURN NULL;
44end;
45$$;
46
47CREATE TRIGGER update_counters
48AFTER INSERT
49ON tweet.activity
50FOR EACH ROW
51EXECUTE PROCEDURE twcache.tg_update_counters();
52
53insert into tweet.activity(messageid, action)
54values (7, 'rt'),
55 |
(7, |
'fav'), |
56 |
(7, |
'de-fav'), |
Chapter 38 Triggers j 330
57 |
(8, 'rt'), |
58 |
(8, 'rt'), |
59 |
(8, 'rt'), |
60 |
(8, 'de-rt'), |
61 |
(8, 'rt'); |
62 |
|
63select messageid, rts, favs
64from twcache.counters;
65
66 rollback;
And here’s the result of running that le in psql, either from the command line with psql -f or with the interactive \i <path/to/file.sql command:
BEGIN
CREATE TABLE CREATE FUNCTION CREATE TRIGGER INSERT 0 8
messageid │ rts │ favs
═══════════╪═════╪══════
7 |
│ |
1 |
│ |
0 |
8 |
│ |
3 |
│ |
0 |
(2 rows) ROLLBACK
You might have noticed that the le ends with a ROLLBACK statement. That’s because we don’t really want to install such a trigger, it’s meant as an example only.
The reason why we don’t actually want to install it is that it would cancel all our previous e forts to model for tweet activity scalability by transforming every insert into tweet.activity into an update twcache.counters on the same messageid. We looked into that exact thing in the previous section and we saw that it would never scale to our requirements.
Event Triggers
Event triggers are another kind of triggers that only PostgreSQL supports, and theyallowone to implement triggers on anyevent that the source code integrates. Currently event triggers are mainly provided for DDL commands.
Have a look at “A Table Rewrite Event Trigger Example” in the PostgreSQL documentation for more information about event triggers, as they are not covered
Chapter 38 Triggers j 331
in this book.
