SlideShare a Scribd company logo
ADVANCED
INT->BIGINT
CONVERSIONS
Robert Treat
introduction
Robert Treat
“I help people Postgres”
@robtreat2
xzilla.net
ground rules
ok to ask questions
slides will be online
feel free to take notes
warning!
all code is based on
poorly written notes
derived from code written
under duress
during real production
outages
ymmv
Overflow
Overview
tldr;
ERROR: integer out of range
SMALLINT BIGINT
2 bytes
+32,767
INTEGER
4 bytes
+2,147,483,647
Goldilocks & the 3 data types
8 bytes
+9,223,372,036
,854,775,807
foreshadow: min value is not zero, but negative max
More practically…
Postgres “SERIAL” data type
● most applications want auto-generated unique values to use as a surrogate
primary key* aka “id serial primary key”
● SERIAL type creates an integer column and a sequence and ties them together
● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor
default in most schema creation tools
More practically…
Postgres “SERIAL” data type
● most applications want auto-generated unique values to use as a surrogate
primary key* aka “id serial primary key”
● SERIAL type creates an integer column and a sequence and ties them together
● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor
default in most schema creation tools
What about identity columns?
● “id integer primary key generated always as identity
● OKAY… but you still might be wrong. We’ll come back to that later.
More practically…
Postgres “SERIAL” data type
● most applications want auto-generated unique values to use as a surrogate
primary key* aka “id serial primary key”
● SERIAL type creates an integer column and a sequence and ties them together
● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor
default in most schema creation tools
What about identity columns?
● “id integer primary key generated always as identity
● OKAY… but you still might be wrong. We’ll come back to that later.
We are not going to debate logical vs surrogate keys in this talk,
nor are we going to discuss the merits of uuid based primary keys!!!
Please keep in mind…
The nature of integer overflow problems means typically
● Often surprising. Often have to be fixed under stress.
● May have taken years to get there. Institutional knowledge may be scarce.
● Lot’s of data. Like 2 billion rows of it maybe. Makes everything harder.
Avoiding the
Problem (or not)
Can we eliminate the problem?
use bigint where “needed”
- usually surprising
- bugs in ORM
- artificial escalation
Can we eliminate the problem?
artificial escalation => errors and rollbacks
create table x (y serial primary key not null, z jsonb not null);
BEGIN; insert into x values (default,'{}'::jsonb);
insert into x values (default,'{}'::jsonb);
insert into x values (default,'{}'::jsonb); ROLLBACK;
select count(*) from x;
count
-----
0
Can we eliminate the problem?
artificial escalation => errors and rollbacks
create table x (y serial primary key not null, z jsonb not null);
BEGIN; insert into x values (default,'{}'::jsonb);
insert into x values (default,'{}'::jsonb);
insert into x values (default,'{}'::jsonb); ROLLBACK;
select count(*) from x;
count
-----
0
select * from x_y_seq;
last_value | 3
log_cnt | 30
is_called | t
Can we eliminate the problem?
artificial escalation => insert … on conflict …
create table x (b int primary key not null, i serial);
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 5 ON CONFLICT DO NOTHING;
Can we eliminate the problem?
artificial escalation => insert … on conflict …
create table x (b int primary key not null, i serial);
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING;
INSERT INTO x (b) select 5 ON CONFLICT DO NOTHING;
select * from x;
b | i
---|---
1 | 1
2 | 2
3 | 3
5 | 10
select * from x_i_seq;
last_value | 10
log_cnt | 23
is_called | t
Can we eliminate the problem?
artificial escalation => on purpose
setval
alter sequence
Can we eliminate the problem?
use bigint where “needed”
- usually surprising
- bugs in ORM
- artificial escalation
use bigint everywhere?
- more space on disk (heap)
- more space on disk (index)
- more ram
- more swap
- more network usage
Can we eliminate the problem?
use bigint where “needed”
- usually surprising
- bugs in ORM
- artificial escalation
use bigint everywhere?
- more space on disk (heap)
- more space on disk (index)
- more ram
- more swap
- more network usage
But actually… other databases handle it this way (crdb)
We could use UUID based primary keys!
We could use UUID based primary keys!
But I already told you we aren’t here for that.
Ok, we can’t stop it a priori…
but I bet we can monitor the problem away!
Ok, we can’t stop it a priori…
but I bet we can monitor the problem away!
We work in complex distributed systems with incomplete mental models and constantly
changing inputs; The idea that it is possible to test comprehensively enough to avoid
production outages is a logical fallacy.
select max(id) from mesa;
probably fine
select max(id) from mesa;
probably fine
what about foreign keys?
select max(parent_id) from child_table;
need to build extra indexes
what about foreign keys?
select max(parent_id) from child_table;
need to build extra indexes
real world issues:
- in billion row systems, people often drop FK to work around
locking/performance issues.
- doesn’t account for integer arrays
- doesn’t account for externally referenced ID’s
- or any normal int columns not part of FK
WITH
cols AS (
select attrelid, attname, atttypid::regtype::text as type,
relname, nspname
from pg_attribute
JOIN pg_class ON (attrelid=oid)
JOIN pg_namespace ON (relnamespace=pg_namespace.oid)
Where relkind='r'
AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]')
),
intarrvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::text[]) a,
unnest(a::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
intvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
data AS (
select * from intvals
union all
select * from intarrvals
)
select tablename, attname, type, min, max from data;
WITH
cols AS (
select attrelid, attname, atttypid::regtype::text as type,
relname, nspname
from pg_attribute
JOIN pg_class ON (attrelid=oid)
JOIN pg_namespace ON (relnamespace=pg_namespace.oid)
Where relkind='r'
AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]')
),
intarrvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::text[]) a,
unnest(a::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
intvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
data AS (
select * from intvals
union all
select * from intarrvals
)
select tablename, attname, type, min, max from data;
Gimme all the columns that are
integer/bigint/int array
WITH
cols AS (
select attrelid, attname, atttypid::regtype::text as type,
relname, nspname
from pg_attribute
JOIN pg_class ON (attrelid=oid)
JOIN pg_namespace ON (relnamespace=pg_namespace.oid)
Where relkind='r'
AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]')
),
intarrvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND
s.attname=cols.attname),
unnest(histogram_bounds::text::text[]) a,
unnest(a::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
intvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
data AS (
select * from intvals
union all
select * from intarrvals
)
select tablename, attname, type, min, max from data;
Now grab the min/max values
from pg_stats that we have
collected from analyze
WITH
cols AS (
select attrelid, attname, atttypid::regtype::text as type,
relname, nspname
from pg_attribute
JOIN pg_class ON (attrelid=oid)
JOIN pg_namespace ON (relnamespace=pg_namespace.oid)
Where relkind='r'
AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]')
),
intarrvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::text[]) a,
unnest(a::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
intvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
data AS (
select * from intvals
union all
select * from intarrvals
)
select tablename, attname, type, min, max from data;
smash that data together and
then tell me where each table
stands
WITH
cols AS (
select attrelid, attname, atttypid::regtype::text as type,
relname, nspname
from pg_attribute
JOIN pg_class ON (attrelid=oid)
JOIN pg_namespace ON (relnamespace=pg_namespace.oid)
Where relkind='r'
AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]')
),
intarrvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::text[]) a,
unnest(a::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
intvals AS (
SELECT s.tablename, s.attname, cols.type, max(i), min(i)
FROM pg_stats s
JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname),
unnest(histogram_bounds::text::int[]) i
GROUP BY s.tablename, s.attname, cols.type
),
data AS (
select * from intvals
union all
select * from intarrvals
)
select tablename, attname, type, min, max from data;
Even with this query, be
careful!
- only as good as your last
analyze
- watch out for negatives
- still might not protect you
from artificial escalation
Dealing with
Overflow
ERROR: integer out of range
ERROR: integer out of range
💩
alter sequence @seqname
minvalue -2147483648
restart -2147483648;
This will flip your sequence negative and begin counting upwards.
You now have 2 billion transactions to FYS (fix your system).
Good luck! Oh yeah, this might break things if you do silly things
like rely on pk ordering. It might also break your apps, but we’ll
come back to that :-)
column-swappin’
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
y | integer | not null | nextval('m_y_seq'::regclass)
z | jsonb | |
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
y | integer | not null | nextval('m_y_seq'::regclass)
z | jsonb | |
db=> alter table m add column fut_y bigint;
ALTER TABLE
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
y | integer | not null | nextval('m_y_seq'::regclass)
z | jsonb | |
fut_y | bigint | |
db=> begin; alter table m rename to other_m;
db-> create view m as select coalesce(y::bigint,fut_y) as y, z from other_m; commit;
ALTER TABLE
CREATE VIEW
View "public.m"
Column | Type | Nullable | Default | Storage | Description
--------+--------+----------+---------+----------+-------------
y | bigint | | | plain |
z | jsonb | | | extended |
View definition:
SELECT COALESCE(other_m.y::bigint, other_m.fut_y) AS y,
other_m.z
FROM other_m;
*add trigger(s) for ins/upd/del on m { y := fut_y() }
⇒ backfill update other_m set fut_y = y;
db=> begin; drop view m;
db-> alter table other_m drop column y;
db-> alter table other_m rename column fut_y to y;
db-> alter table other_m rename to m; commit;
DROP VIEW
ALTER TABLE
ALTER TABLE
ALTER TABLE
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
y | bigint | not null | nextval('m_y_seq'::regclass)
z | jsonb | |
table-swappin’
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
x | bigint | not null | nextval('m_y_seq'::regclass)
y | integer | not null |
z | jsonb | |
db=> create table future_m (x bigint, y bigint, z jsonb);
CREATE TABLE
Table "public.future_m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
x | bigint | not null |
y | bigint | not null |
z | jsonb | |
db=> begin; alter table m rename to orig_m;
db-> create view m as select
db-> x, coalesce(o.y::bigint,f.y) as y, z
db-> from orig_m o join future_m f using (x); commit;
ALTER TABLE
CREATE VIEW
View "public.m"
Column | Type | Nullable | Default | Storage | Description
--------+--------+----------+---------+----------+-------------
x | bigint | | | plain |
y | bigint | | | plain |
z | jsonb | | | extended |
db=> begin; alter table m rename to orig_m;
db-> create view m as select
db-> x, coalesce(o.y::bigint,f.y) as y, z
db-> from orig_m o join future_m f using (x); commit;
ALTER TABLE
CREATE VIEW
View "public.m"
Column | Type | Nullable | Default | Storage | Description
--------+--------+----------+---------+----------+-------------
x | bigint | | | plain |
y | bigint | | | plain |
z | jsonb | | | extended |
db=> begin; alter table m rename to orig_m;
db-> create view m as select
db-> x, coalesce(o.y::bigint,f.y) as y, z
db-> from orig_m o join future_m f using (x); commit;
ALTER TABLE
CREATE VIEW
View "public.m"
Column | Type | Nullable | Default | Storage | Description
--------+--------+----------+---------+----------+-------------
x | bigint | | | plain |
y | bigint | | | plain |
z | jsonb | | | extended |
*add trigger(s) on m => ins/upd/del orig_m where x=$1
*add trigger(s) on orig_m => ins/upd/del future_m where x=$1
⇒ backfill update future_m set p=p where y=y;
db=> begin; drop view m;
db-> alter table future_m rename to m; commit;
DROP VIEW
ALTER TABLE
Table "public.m"
Column | Type | Nullable | Default
--------+---------+----------+------------------------------
x | bigint | not null | nextval('m_y_seq'::regclass)
y | bigint | not null |
z | jsonb | |
tip: you can play the same tricks as views
and new tables using logical replication or
FDW, it is just a bit more complex.
tip: I glossed over a lot of things like trigger
code, foreign keys, constraints, and similar
trickery. You can work it out, just takes more
time/effort.
Other Problems
to Consider
Won’t somebody think of the children?
By children we mean app code, because developers (just kidding!)
● was your app based on the original ORM schema
definition (ie. int)?
Won’t somebody think of the children?
● was your app based on the original ORM schema
definition (ie. int)?
● what number types does your language support?
○ unsigned int? (0 to 4294967295, oh my!)
Won’t somebody think of the children?
● was your app based on the original ORM schema
definition (ie. int)?
● what number types does your language support?
○ unsigned int? (0 to 4294967295, oh my!)
● modern systems are like ogre’s… they have layers
○ api?
○ compiled?
CREATE OR REPLACE FUNCTION public.generate_pk_id()
RETURNS bigint AS
$BODY$
DECLARE
per_mil int;
BEGIN
SELECT (random() * 100.0::FLOAT8)::INT INTO per_mil;
CASE
WHEN per_mil = 100 THEN
return nextval('pk_id_seq'::regclass);
ELSE
return nextval('ex_pk_id_seq'::regclass);
END CASE;
END
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
THANK YOU!
@robtreat2
Additional Readings
● https://doordash.engineering/2020/10/21/hot-swapping-
production-data-tables/
● https://doordash.engineering/2022/01/19/making-applic
ations-compatible-with-postgres-tables-bigint-update/
● https://medium.com/doctolib/how-to-change-a-column-t
ype-in-your-productions-postgresql-database-35d6fa19
4cb8

More Related Content

PPTX
Python Traning presentation
PPT
SQL -PHP Tutorial
PDF
Rob Sullivan at Heroku's Waza 2013: Your Database -- A Story of Indifference
PDF
Rootkit on Linux X86 v2.6
DOCX
INPUT AND OUTPUT PROCESSINGPlease note that the material o.docx
PDF
DConf 2016 std.database (a proposed interface & implementation)
PPTX
Applying Compiler Techniques to Iterate At Blazing Speed
PPT
Rocky Nevin's presentation at eComm 2008
Python Traning presentation
SQL -PHP Tutorial
Rob Sullivan at Heroku's Waza 2013: Your Database -- A Story of Indifference
Rootkit on Linux X86 v2.6
INPUT AND OUTPUT PROCESSINGPlease note that the material o.docx
DConf 2016 std.database (a proposed interface & implementation)
Applying Compiler Techniques to Iterate At Blazing Speed
Rocky Nevin's presentation at eComm 2008

Similar to Advanced Int->Bigint Conversions (20)

PPTX
C++ lecture 01
PDF
Cc code cards
PDF
Rootkit on linux_x86_v2.6
PPTX
Price of an Error
PDF
cel shading as PDF and Python description
PDF
C Programming Interview Questions
PDF
What We Talk About When We Talk About Unit Testing
PPTX
Getting started cpp full
PPTX
Clean code
PDF
(ThoughtWorks Away Day 2009) one or two things you may not know about typesys...
PDF
Introduction to R Short course Fall 2016
PDF
OSCON Presentation: Developing High Performance Websites and Modern Apps with...
PPS
T02 a firstcprogram
PPS
T02 a firstcprogram
PPTX
A well-typed program never goes wrong
PPTX
Learning python
PPTX
Learning python
PPTX
Learning python
PPTX
Learning python
PPTX
Learning python
C++ lecture 01
Cc code cards
Rootkit on linux_x86_v2.6
Price of an Error
cel shading as PDF and Python description
C Programming Interview Questions
What We Talk About When We Talk About Unit Testing
Getting started cpp full
Clean code
(ThoughtWorks Away Day 2009) one or two things you may not know about typesys...
Introduction to R Short course Fall 2016
OSCON Presentation: Developing High Performance Websites and Modern Apps with...
T02 a firstcprogram
T02 a firstcprogram
A well-typed program never goes wrong
Learning python
Learning python
Learning python
Learning python
Learning python
Ad

More from Robert Treat (20)

PDF
Explaining Explain
PDF
the-lost-art-of-plpgsql
PDF
Managing Chaos In Production: Testing vs Monitoring
PDF
Managing Databases In A DevOps Environment 2016
PDF
Less Alarming Alerts - SRECon 2016
PDF
What Ops Can Learn From Design
PDF
Postgres 9.4 First Look
PDF
Less Alarming Alerts!
PDF
Past, Present, and Pachyderm - All Things Open - 2013
PDF
Big Bad "Upgraded" Postgres
PDF
Managing Databases In A DevOps Environment
PDF
The Essential PostgreSQL.conf
PDF
Pro Postgres 9
PDF
Advanced WAL File Management With OmniPITR
PDF
Scaling with Postgres (Highload++ 2010)
PDF
Intro to Postgres 9 Tutorial
PDF
Check Please!
PDF
Database Scalability Patterns
PDF
A Guide To PostgreSQL 9.0
PPT
Scaling With Postgres
Explaining Explain
the-lost-art-of-plpgsql
Managing Chaos In Production: Testing vs Monitoring
Managing Databases In A DevOps Environment 2016
Less Alarming Alerts - SRECon 2016
What Ops Can Learn From Design
Postgres 9.4 First Look
Less Alarming Alerts!
Past, Present, and Pachyderm - All Things Open - 2013
Big Bad "Upgraded" Postgres
Managing Databases In A DevOps Environment
The Essential PostgreSQL.conf
Pro Postgres 9
Advanced WAL File Management With OmniPITR
Scaling with Postgres (Highload++ 2010)
Intro to Postgres 9 Tutorial
Check Please!
Database Scalability Patterns
A Guide To PostgreSQL 9.0
Scaling With Postgres
Ad

Recently uploaded (20)

PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Empathic Computing: Creating Shared Understanding
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPT
“AI and Expert System Decision Support & Business Intelligence Systems”
PDF
Electronic commerce courselecture one. Pdf
PDF
Machine learning based COVID-19 study performance prediction
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPT
Teaching material agriculture food technology
PDF
Review of recent advances in non-invasive hemoglobin estimation
PPTX
Understanding_Digital_Forensics_Presentation.pptx
Spectral efficient network and resource selection model in 5G networks
Chapter 3 Spatial Domain Image Processing.pdf
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Diabetes mellitus diagnosis method based random forest with bat algorithm
Empathic Computing: Creating Shared Understanding
Reach Out and Touch Someone: Haptics and Empathic Computing
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Digital-Transformation-Roadmap-for-Companies.pptx
“AI and Expert System Decision Support & Business Intelligence Systems”
Electronic commerce courselecture one. Pdf
Machine learning based COVID-19 study performance prediction
NewMind AI Weekly Chronicles - August'25 Week I
Mobile App Security Testing_ A Comprehensive Guide.pdf
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
Dropbox Q2 2025 Financial Results & Investor Presentation
Teaching material agriculture food technology
Review of recent advances in non-invasive hemoglobin estimation
Understanding_Digital_Forensics_Presentation.pptx

Advanced Int->Bigint Conversions

  • 2. introduction Robert Treat “I help people Postgres” @robtreat2 xzilla.net
  • 3. ground rules ok to ask questions slides will be online feel free to take notes
  • 4. warning! all code is based on poorly written notes derived from code written under duress during real production outages ymmv
  • 7. SMALLINT BIGINT 2 bytes +32,767 INTEGER 4 bytes +2,147,483,647 Goldilocks & the 3 data types 8 bytes +9,223,372,036 ,854,775,807 foreshadow: min value is not zero, but negative max
  • 8. More practically… Postgres “SERIAL” data type ● most applications want auto-generated unique values to use as a surrogate primary key* aka “id serial primary key” ● SERIAL type creates an integer column and a sequence and ties them together ● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor default in most schema creation tools
  • 9. More practically… Postgres “SERIAL” data type ● most applications want auto-generated unique values to use as a surrogate primary key* aka “id serial primary key” ● SERIAL type creates an integer column and a sequence and ties them together ● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor default in most schema creation tools What about identity columns? ● “id integer primary key generated always as identity ● OKAY… but you still might be wrong. We’ll come back to that later.
  • 10. More practically… Postgres “SERIAL” data type ● most applications want auto-generated unique values to use as a surrogate primary key* aka “id serial primary key” ● SERIAL type creates an integer column and a sequence and ties them together ● There is a “BIGSERIAL” type which ties to bigint, but it isn’t as widely known nor default in most schema creation tools What about identity columns? ● “id integer primary key generated always as identity ● OKAY… but you still might be wrong. We’ll come back to that later. We are not going to debate logical vs surrogate keys in this talk, nor are we going to discuss the merits of uuid based primary keys!!!
  • 11. Please keep in mind… The nature of integer overflow problems means typically ● Often surprising. Often have to be fixed under stress. ● May have taken years to get there. Institutional knowledge may be scarce. ● Lot’s of data. Like 2 billion rows of it maybe. Makes everything harder.
  • 13. Can we eliminate the problem? use bigint where “needed” - usually surprising - bugs in ORM - artificial escalation
  • 14. Can we eliminate the problem? artificial escalation => errors and rollbacks create table x (y serial primary key not null, z jsonb not null); BEGIN; insert into x values (default,'{}'::jsonb); insert into x values (default,'{}'::jsonb); insert into x values (default,'{}'::jsonb); ROLLBACK; select count(*) from x; count ----- 0
  • 15. Can we eliminate the problem? artificial escalation => errors and rollbacks create table x (y serial primary key not null, z jsonb not null); BEGIN; insert into x values (default,'{}'::jsonb); insert into x values (default,'{}'::jsonb); insert into x values (default,'{}'::jsonb); ROLLBACK; select count(*) from x; count ----- 0 select * from x_y_seq; last_value | 3 log_cnt | 30 is_called | t
  • 16. Can we eliminate the problem? artificial escalation => insert … on conflict … create table x (b int primary key not null, i serial); INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 5 ON CONFLICT DO NOTHING;
  • 17. Can we eliminate the problem? artificial escalation => insert … on conflict … create table x (b int primary key not null, i serial); INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 1 union all select 2 union all select 3 ON CONFLICT DO NOTHING; INSERT INTO x (b) select 5 ON CONFLICT DO NOTHING; select * from x; b | i ---|--- 1 | 1 2 | 2 3 | 3 5 | 10 select * from x_i_seq; last_value | 10 log_cnt | 23 is_called | t
  • 18. Can we eliminate the problem? artificial escalation => on purpose setval alter sequence
  • 19. Can we eliminate the problem? use bigint where “needed” - usually surprising - bugs in ORM - artificial escalation use bigint everywhere? - more space on disk (heap) - more space on disk (index) - more ram - more swap - more network usage
  • 20. Can we eliminate the problem? use bigint where “needed” - usually surprising - bugs in ORM - artificial escalation use bigint everywhere? - more space on disk (heap) - more space on disk (index) - more ram - more swap - more network usage But actually… other databases handle it this way (crdb)
  • 21. We could use UUID based primary keys!
  • 22. We could use UUID based primary keys! But I already told you we aren’t here for that.
  • 23. Ok, we can’t stop it a priori… but I bet we can monitor the problem away!
  • 24. Ok, we can’t stop it a priori… but I bet we can monitor the problem away! We work in complex distributed systems with incomplete mental models and constantly changing inputs; The idea that it is possible to test comprehensively enough to avoid production outages is a logical fallacy.
  • 25. select max(id) from mesa; probably fine
  • 26. select max(id) from mesa; probably fine what about foreign keys? select max(parent_id) from child_table; need to build extra indexes
  • 27. what about foreign keys? select max(parent_id) from child_table; need to build extra indexes real world issues: - in billion row systems, people often drop FK to work around locking/performance issues. - doesn’t account for integer arrays - doesn’t account for externally referenced ID’s - or any normal int columns not part of FK
  • 28. WITH cols AS ( select attrelid, attname, atttypid::regtype::text as type, relname, nspname from pg_attribute JOIN pg_class ON (attrelid=oid) JOIN pg_namespace ON (relnamespace=pg_namespace.oid) Where relkind='r' AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]') ), intarrvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::text[]) a, unnest(a::int[]) i GROUP BY s.tablename, s.attname, cols.type ), intvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::int[]) i GROUP BY s.tablename, s.attname, cols.type ), data AS ( select * from intvals union all select * from intarrvals ) select tablename, attname, type, min, max from data;
  • 29. WITH cols AS ( select attrelid, attname, atttypid::regtype::text as type, relname, nspname from pg_attribute JOIN pg_class ON (attrelid=oid) JOIN pg_namespace ON (relnamespace=pg_namespace.oid) Where relkind='r' AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]') ), intarrvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::text[]) a, unnest(a::int[]) i GROUP BY s.tablename, s.attname, cols.type ), intvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::int[]) i GROUP BY s.tablename, s.attname, cols.type ), data AS ( select * from intvals union all select * from intarrvals ) select tablename, attname, type, min, max from data; Gimme all the columns that are integer/bigint/int array
  • 30. WITH cols AS ( select attrelid, attname, atttypid::regtype::text as type, relname, nspname from pg_attribute JOIN pg_class ON (attrelid=oid) JOIN pg_namespace ON (relnamespace=pg_namespace.oid) Where relkind='r' AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]') ), intarrvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::text[]) a, unnest(a::int[]) i GROUP BY s.tablename, s.attname, cols.type ), intvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::int[]) i GROUP BY s.tablename, s.attname, cols.type ), data AS ( select * from intvals union all select * from intarrvals ) select tablename, attname, type, min, max from data; Now grab the min/max values from pg_stats that we have collected from analyze
  • 31. WITH cols AS ( select attrelid, attname, atttypid::regtype::text as type, relname, nspname from pg_attribute JOIN pg_class ON (attrelid=oid) JOIN pg_namespace ON (relnamespace=pg_namespace.oid) Where relkind='r' AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]') ), intarrvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::text[]) a, unnest(a::int[]) i GROUP BY s.tablename, s.attname, cols.type ), intvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::int[]) i GROUP BY s.tablename, s.attname, cols.type ), data AS ( select * from intvals union all select * from intarrvals ) select tablename, attname, type, min, max from data; smash that data together and then tell me where each table stands
  • 32. WITH cols AS ( select attrelid, attname, atttypid::regtype::text as type, relname, nspname from pg_attribute JOIN pg_class ON (attrelid=oid) JOIN pg_namespace ON (relnamespace=pg_namespace.oid) Where relkind='r' AND atttypid::regtype::text IN ('integer', 'bigint', 'integer[]') ), intarrvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer[]' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::text[]) a, unnest(a::int[]) i GROUP BY s.tablename, s.attname, cols.type ), intvals AS ( SELECT s.tablename, s.attname, cols.type, max(i), min(i) FROM pg_stats s JOIN cols ON (cols.type = 'integer' AND s.schemaname = cols.nspname AND s.tablename = cols.relname AND s.attname=cols.attname), unnest(histogram_bounds::text::int[]) i GROUP BY s.tablename, s.attname, cols.type ), data AS ( select * from intvals union all select * from intarrvals ) select tablename, attname, type, min, max from data; Even with this query, be careful! - only as good as your last analyze - watch out for negatives - still might not protect you from artificial escalation
  • 34. ERROR: integer out of range
  • 35. ERROR: integer out of range 💩
  • 36. alter sequence @seqname minvalue -2147483648 restart -2147483648; This will flip your sequence negative and begin counting upwards. You now have 2 billion transactions to FYS (fix your system). Good luck! Oh yeah, this might break things if you do silly things like rely on pk ordering. It might also break your apps, but we’ll come back to that :-)
  • 38. Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ y | integer | not null | nextval('m_y_seq'::regclass) z | jsonb | |
  • 39. Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ y | integer | not null | nextval('m_y_seq'::regclass) z | jsonb | | db=> alter table m add column fut_y bigint; ALTER TABLE Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ y | integer | not null | nextval('m_y_seq'::regclass) z | jsonb | | fut_y | bigint | |
  • 40. db=> begin; alter table m rename to other_m; db-> create view m as select coalesce(y::bigint,fut_y) as y, z from other_m; commit; ALTER TABLE CREATE VIEW View "public.m" Column | Type | Nullable | Default | Storage | Description --------+--------+----------+---------+----------+------------- y | bigint | | | plain | z | jsonb | | | extended | View definition: SELECT COALESCE(other_m.y::bigint, other_m.fut_y) AS y, other_m.z FROM other_m; *add trigger(s) for ins/upd/del on m { y := fut_y() }
  • 41. ⇒ backfill update other_m set fut_y = y; db=> begin; drop view m; db-> alter table other_m drop column y; db-> alter table other_m rename column fut_y to y; db-> alter table other_m rename to m; commit; DROP VIEW ALTER TABLE ALTER TABLE ALTER TABLE Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ y | bigint | not null | nextval('m_y_seq'::regclass) z | jsonb | |
  • 43. Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ x | bigint | not null | nextval('m_y_seq'::regclass) y | integer | not null | z | jsonb | | db=> create table future_m (x bigint, y bigint, z jsonb); CREATE TABLE Table "public.future_m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ x | bigint | not null | y | bigint | not null | z | jsonb | |
  • 44. db=> begin; alter table m rename to orig_m; db-> create view m as select db-> x, coalesce(o.y::bigint,f.y) as y, z db-> from orig_m o join future_m f using (x); commit; ALTER TABLE CREATE VIEW View "public.m" Column | Type | Nullable | Default | Storage | Description --------+--------+----------+---------+----------+------------- x | bigint | | | plain | y | bigint | | | plain | z | jsonb | | | extended |
  • 45. db=> begin; alter table m rename to orig_m; db-> create view m as select db-> x, coalesce(o.y::bigint,f.y) as y, z db-> from orig_m o join future_m f using (x); commit; ALTER TABLE CREATE VIEW View "public.m" Column | Type | Nullable | Default | Storage | Description --------+--------+----------+---------+----------+------------- x | bigint | | | plain | y | bigint | | | plain | z | jsonb | | | extended |
  • 46. db=> begin; alter table m rename to orig_m; db-> create view m as select db-> x, coalesce(o.y::bigint,f.y) as y, z db-> from orig_m o join future_m f using (x); commit; ALTER TABLE CREATE VIEW View "public.m" Column | Type | Nullable | Default | Storage | Description --------+--------+----------+---------+----------+------------- x | bigint | | | plain | y | bigint | | | plain | z | jsonb | | | extended | *add trigger(s) on m => ins/upd/del orig_m where x=$1 *add trigger(s) on orig_m => ins/upd/del future_m where x=$1
  • 47. ⇒ backfill update future_m set p=p where y=y; db=> begin; drop view m; db-> alter table future_m rename to m; commit; DROP VIEW ALTER TABLE Table "public.m" Column | Type | Nullable | Default --------+---------+----------+------------------------------ x | bigint | not null | nextval('m_y_seq'::regclass) y | bigint | not null | z | jsonb | |
  • 48. tip: you can play the same tricks as views and new tables using logical replication or FDW, it is just a bit more complex.
  • 49. tip: I glossed over a lot of things like trigger code, foreign keys, constraints, and similar trickery. You can work it out, just takes more time/effort.
  • 51. Won’t somebody think of the children? By children we mean app code, because developers (just kidding!) ● was your app based on the original ORM schema definition (ie. int)?
  • 52. Won’t somebody think of the children? ● was your app based on the original ORM schema definition (ie. int)? ● what number types does your language support? ○ unsigned int? (0 to 4294967295, oh my!)
  • 53. Won’t somebody think of the children? ● was your app based on the original ORM schema definition (ie. int)? ● what number types does your language support? ○ unsigned int? (0 to 4294967295, oh my!) ● modern systems are like ogre’s… they have layers ○ api? ○ compiled?
  • 54. CREATE OR REPLACE FUNCTION public.generate_pk_id() RETURNS bigint AS $BODY$ DECLARE per_mil int; BEGIN SELECT (random() * 100.0::FLOAT8)::INT INTO per_mil; CASE WHEN per_mil = 100 THEN return nextval('pk_id_seq'::regclass); ELSE return nextval('ex_pk_id_seq'::regclass); END CASE; END $BODY$ LANGUAGE 'plpgsql' VOLATILE;
  • 56. Additional Readings ● https://doordash.engineering/2020/10/21/hot-swapping- production-data-tables/ ● https://doordash.engineering/2022/01/19/making-applic ations-compatible-with-postgres-tables-bigint-update/ ● https://medium.com/doctolib/how-to-change-a-column-t ype-in-your-productions-postgresql-database-35d6fa19 4cb8