0

I have some services, which can execute insert to insert new elements into a table of my database (Postgresql). So in the view of the database, many inserts are executed concurrently.

My question is if it's possible to keep the insertion order with some column of the table.

For example, there are three inserts:

INSERT INTO Users (UserName, Id) VALUES ('Tom', 1);
INSERT INTO Users (UserName, Id) VALUES ('Jerry', 2);
INSERT INTO Users (UserName, Id) VALUES ('Joey', 3);

They are executing concurrently.

For some reason, the second insert is delayed. In this case, when the third insert is now executing, I'm expecting one of the two results as below:

  1. the insert fails;
  2. the insert succeed with a new Id 2.

Is there some mechanism to achieve this?

Why do I need this

There is a ID generator which generate IDs with snowflake algorithm. Concurrent inserts are executed in multi-threads. when reading data from the table, I want to make range queries like this:

select * from my_table where Id > a limit 5
select * from my_table where Id > b limit 5
select * from my_table where Id > c limit 5
...

a, b and c are the last Id coming from the previous select.

| --- a --- | --- b --- | --- c --- |

The problem is when the second select is executing, it still might be possible to make a insert in the a zone because of concurrent insert. If this happen, I will have no chance to read the delayed inserted data.

14
  • I think what you are looking for is Serializable isolation level but you will lose your concurrency. But I wondering why would you need such a mechanism when you can simply order by using ID once they are inserted in any order?
    – goodfella
    Commented Aug 27 at 9:03
  • @goodfella check my update. I explained why I need this.
    – Yves
    Commented Aug 27 at 10:16
  • Your use case doesn't make a ton of sense for a couple of reasons. Firstly, a table and the data inside it, has no definitive order. Only results of a query selecting against that table can be ordered with the ORDER BY clause. Secondly, when you use the LIMIT clause against your table without an ORDER BY then you're receiving the rows almost randomly because it's non-deterministic. Think of the table like a box of different colored balls, and LIMIT is like you stuck your hand in the box and pulled out 5 balls. You can get any colored balls that way since they're unordered.
    – J.D.
    Commented Aug 27 at 13:09
  • 1
    @Yves Even if you use sequence in database as a primary key instead of your snowflake ID there is still chance they are inserted out of order.
    – goodfella
    Commented Aug 28 at 2:18
  • 1
    @Yves Unfortunately even letting the database auto generate the ID doesn't guarantee either. I think the closest thing to doing that would be to handle it in the app: Step 1: Generate all the Snowflake IDs (or whatever algorithm you want to use) upfront for the total number of rows you plan to insert. Step 2: Asynchronously call a function to do your first batch of inserts that takes in a collection of a sequential subset of the IDs. Step 3: Repeat Step 2 for each group of inserts you need to do. Step 4: Write your query like: where Id <= a where a is your last Id for the a group, etc.
    – J.D.
    Commented Aug 28 at 2:41

1 Answer 1

0

There is no way to do that unless you serialize the inserts rather than performing them concurrently. This can affect your performance quite a bit.

One way to do that is to use a table to generate the id:

CREATE TABLE counter (c bigint);
INSERT INTO counter VALUES (0);

Then your inserts could be:

WITH x AS (
   UPDATE counter SET c = c + 1 RETURNING c
)
INSERT INTO users (username, id)
SELECT 'Jerry', c FROM x;

The update on the counter table locks the row, and the lock is only released when the transaction is done. As a consequence, the order of insert becomes the order of commit, and the order of insert is the same as the order defined by the counter.

4
  • What a nice solution. It helps me a lot. One more little thing: if I say the performance of update counter is not good because update is kind of heavy. Is there any other solution? I don't know if there is some mechanism such as a counter in RAM in terms of relational database.
    – Yves
    Commented Aug 29 at 3:53
  • Is it possible to use SEQUENCE with nextval to replace the UPDATE part?
    – Yves
    Commented Aug 29 at 6:14
  • No, you cannot use a sequence; that won't give you the guarantees you need. Yes, my solution will affect performance severely. Perhaps you should drop your requirement and solve the underlying problem in some other way; perhaps with a bookean flag on the row that you update once the row has been processed. Commented Aug 29 at 6:54
  • @Yves FWIW, you don't have to serialize the inserts if you go with what I suggested in the comments. Only the generation and assignment of IDs are serialized upfront. But the actual inserts run in parallel at least. From a performance standpoint, it probably works out pretty well.
    – J.D.
    Commented Aug 29 at 12:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.