Live data from Hacker News

Postgres sequences can skip 32 unexpectedly

incident.io

61–70 of 81 posts

Re: Postgres sequences can skip 32 unexpectedly

#61

Good writeup! It's an interesting gotcha because Postgres and SQLite docs expressly disclaim that their sequences/AUTOINCREMENT are gapless but experienced and talented programmers still use them as such. Is the type of thing that doesn't bite you until production. Postgres docs-- https://www.postgresql.org/docs/13/sql-createsequence.html > Because nextval and setval calls are never rolled back, sequence objects cann…

[deleted]

Re: Postgres sequences can skip 32 unexpectedly

#62

We have a similar system (multi-tenant database) where each tenant (account) has objects that have unique identifiers for that specific account (customers, locations, jobs, invoices, etc). Customer #C1010 may have 2 locations #L1899 and #L8443 and many invoices #IN1940 and #IN2399 for example. When we first built the system, I considered using native Postgres sequences to track these, but decided against them because…

>We have an event in our ORM to automatically generate the next sequence value as part of the transaction so if the transaction is rolled back, the next sequence value is as well. I assume that means you also have to only allow one TX to be in-flight at a time adding a new record whose ID is generated from a given sequence?

Or bounce an exception for insert duplicate on a unique index.

Re: Postgres sequences can skip 32 unexpectedly

#63
post #15

The initial design was quite flawed, in addition to not using sequences they should not use one DB object per organization, but rather a single object with an "organization" field.

> but rather a single object with an "organization" field.

In your opinion. There are advantages of sharding tables by tenant boundaries. Data isolation and query speed to name a couple.

Re: Postgres sequences can skip 32 unexpectedly

#64

Earlier quoted context omitted.

Years ago, I bought some software. Its serial number had quite a few digits. I found out later that I had been the first customer. I guess the idea was that customers might be more confident to jump in the water, if they saw others there already.

This is a technique used in other ways too. For instance, when opening a new checking account, it’s common to start with an arbitrary number for the first check. And often new businesses will start with an arbitrary invoice number.

There's a fair number of ecommerce setups that default to numerically ascending order numbers also.

Re: Postgres sequences can skip 32 unexpectedly

#65

Earlier quoted context omitted.

Do you mean enumeration, whereby an attacker starts at some ID and tries several in sequence? It has never been a problem for me and my applications. Just because you know a record exists, doesn't mean you can see it. For example, if you are authorized to view https://www.example.com/records/100 , and you decide to try https://www.example.com/records/101 , then the code will check to see if you're authorized to see r…

I've certainly seen this be a problem - even if you and every future programmer who touches your code gets the authorization check right every time over the years the app is live, and over the hundreds or thousands of endpoints it exposes, plenty of apps don't get it right every time. Using random id's is a nice and low effort additional layer of security there.

Depending, very much, on that balance between usability and security. For the use case in the story, I'd way rather talk about (and write down) incidents 122 through 124 with my team, rather than incidents 549697a9-dd6a-4a90-a5f4-b2ff1a1d9289, d6150929-a692-414b-ba9e-ccbcf5e48a59, and 2756998d-035f-42bc-a97d-4135529e85d9.

Re: Postgres sequences can skip 32 unexpectedly

#66

This is why you should never expose your database IDs to the customer. They just complain about it, and it invites them wanting to assign meaning and have control over the values.

Customer IDs should have at least one or two checksum digits to help spotcheck for data entry errors anyway.

Indeed, if you skip this you're going to have a weird "bug" some time in the next years where a customer accidentally got the ID wrong and the system accepted it.

Google Analytics either doesn't do this or got really unlucky. But some day I had to troubleshoot an issue where the data for a totally unrelated website ended up in someone's GA data set. Not just the usual spam, but millions of visits to the wrong website which had an account ID very similar to the client's.

Re: Postgres sequences can skip 32 unexpectedly

#67

This is why you should never expose your database IDs to the customer. They just complain about it, and it invites them wanting to assign meaning and have control over the values.

Years ago, I bought some software. Its serial number had quite a few digits. I found out later that I had been the first customer. I guess the idea was that customers might be more confident to jump in the water, if they saw others there already.

I remember getting a large-ish IBM storage product 10-15 years ago with a serial number of 1. They actually updated it once someone realized!

Re: Postgres sequences can skip 32 unexpectedly

#68

Earlier quoted context omitted.

Years ago, I bought some software. Its serial number had quite a few digits. I found out later that I had been the first customer. I guess the idea was that customers might be more confident to jump in the water, if they saw others there already.

This is a technique used in other ways too. For instance, when opening a new checking account, it’s common to start with an arbitrary number for the first check. And often new businesses will start with an arbitrary invoice number.

It used to be common for businesses to refuse to take a check if it had a low check number, the assumption was the account was new and had a higher chance of bouncing. Eventually the banks let you pick or set high numbers so you wouldn't have to deal with that inconvenience.

Re: Postgres sequences can skip 32 unexpectedly

#69
I'm sure someone will correct me if I'm wrong, but isn't it simpler to manage this sort of functionality by putting a counter in a table and using a CTE to increment it along with the insert? Something like

  with u as (
    update organizations
    set last_external_id = last_external_id + 1
    where id = 123
    returning last_external_id
  )
  insert into incidents (organization_id, external_id, title)
  select 123, last_external_id, 'Blah blah blah'
  from u;
The risk here of course is running into lock contention around the organization table there's a high volume of incident creation for the same organization, but considering the context (incident management) that seems pretty low risk.

Re: Postgres sequences can skip 32 unexpectedly

#70
post #29

Earlier quoted context omitted.

That may be your opinion, but an invoice in at least the Netherlands needs an monotonically increasing number. And it needs to be on the invoice and your customer needs to be able to see it. [edit] It's even EU-wide, see article 226(2) of directive 2006/112/EC: > a sequential number, based on one or more series, which uniquely identifies the invoice;

Nothing stops you from using the "...or more series" part to generate numbers that are specific to the day, the hour or, if you feel like it, the minute. Today's first invoice could be 2021-07-16-001, the second one 2021-07-16-002, etc. If you really don't want people to be able to guess your invoice volume from numbers alone, there are various ways to do that while still being compliant to EU laws.

I think e.g. 2021-07-16-123 followed by 2021-07-17-001 wouldn't fall under "sequential number" though because, well, they're not sequential.

The Italian authorities seem to see this the same way - https://vatdesk.eu/en/eu-vat-news/italy-mandatory-mentions-o...

(Annoyingly the directive doesn't give a definition for "a sequential number" itself.)

Post reply on HN