Spring Boot with Oracle Sequence - 2 different applications get colliding SEQUENCE.NEXTVAL causing ORA-0001 unique constraint violation on primary key
Asked Answered
D

2

1

I have 2 applications (one is SpringBoot app and another is a C++ app) calling the same Oracle sequence SEQUENCE_A responsible for generating PK on the same table TABLE-A.

The sequence DDL is as:

CREATE SEQUENCE SEQUENCE_A START WITH 1 INCREMENT BY 1 ORDER;

So, SEQUENCE_A should start at 1 and then keep incrementing by 1, no cycling. This means that any client app inserting into TABLE_A will have SEQUENCE_A generate the next available sequence number which is 2, then 3, then 4 and so on. Based on Oracle documentation, this should happen even if the 2 apps try to generate the next sequence at the same time. (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Sequence-Pseudocolumns.html#GUID-D438E28B-3E30-4B12-8D52-8DA5CFE2E0FF)

My SpringBoot application (which uses ORM, so no SQL INSERT, but mapping to table entity like shown below) has entity using SEQUENCE_A for its PK field:

@Entity
@Table(name = "TABLE-A")
public class TableAEntity implements Serializable {

    private static final long serialVersionUID = 1234...L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "table-a_generator")
    @SequenceGenerator(name = "table-a_generator", sequenceName = "SEQUENCE_A", allocationSize = 1)
    @Column(name = "ID", nullable = false)
    private Long id;
    
    ...
}

This application will mostly do inserts into TABLE-A. The C++ app will call it and let it do the insertion. But if for whatever reason, the SpringBoot app is not responsive or down, after 60 seconds, C++ app will do the insert itself into TABLE-A using SEQUENCE-A to generate PK like:

INSERT ALL 
    INTO TABLE-A(ID, NAME) 
        VALUES(SEQUENCE_A.NEXTVAL, '%s') 
    INTO TABLE-A-LOG(ID, TABLE-A_ID, SUCCEEDED) 
        VALUES(SEQUENCE_B.NEXTVAL, SEQUENCE_A.CURRVAL, 0) 
SELECT * FROM DUAL", name

Given that both of these applications are using SEQUENCE_A, they should always get the next available sequence number. So, if we start with 1, it will be like:

APP   SEQ
App1 > 1
App1 > 2
App2 > 3
App1 > 4
App2 > 5
...

However, once in a while I would get a unique constraint violation on the TABLE-A PK in production environment. So, it looks like, one of the 2 application is trying to reuse a PK that already exists. But since both of the applications are using same Oracle sequence SEQUENCE-A defined as above, that should not be possible.

In our staging environment, I haven't seen this error but upon observation of the 2 years worth of inserted records, I noticed that:

  1. Spring Boot application has PKs with higher value. They are currently at 4 digits lengths and and the last 3 are 4122, 4123, and 4124.
  2. C++ application has PKS with much smaller value and they have currently all 2 digits lengths and the last 3 generated are 21, 41, and 61.

To my understanding, an Oracle sequence generates next available number regardless of from where it is called. So, in example above, the NEXTVAL should be the highest + 1, so 4124+1 = 4125. How is it possible that C++ app is generating 61 as NEXTVAL?

Obviously this will eventually lead to collision and unique constraint violation I get occasionally.

What I am seeing looks like each application "knows" its own generated highest sequence and than adds 1 to that.

Another strange thing I noticed is that when I inspect NEXTVAL and CURRVAL in Oracle by issuing:

SELECT SEQUENCE_A.NEXTVAL FROM DUAL;  // returns 62

SELECT SEQUENCE_A.CURRVAL FROM DUAL;  // returns 62

This seem to be more aligned with what C++ app is getting. This makes it even more confusing since why would then SpringBoot application get values larger than 81 by so much? It almost looks like SpringBoot is somehow cashing sequences it has ever generated before and than adding 1 on top of that.

I also inspected values for this sequence by issuing:

SELECT MIN_VALUE, MAX_VALUE, INCREMENT_BY, CYCLE_FLAG, CACHE_SIZE, LAST_NUMBER  
FROM ALL_SEQUENCES 
WHERE SEQUENCE_NAME = 'SEQUENCE_A';

, and result I get is:

MIN_VALUE|MAX_VALUE                   |INCREMENT_BY|CYCLE_FLAG|CACHE_SIZE|LAST_NUMBER|
---------+----------------------------+------------+----------+----------+-----------+
        1|9999999999999999999999999999|           1|N         |        20|        101|

Why would 2 applications inserting into same table and using same sequence result in such different sequence numbers generated?

Given above and that SpringBoot application has generated sequence that are much higher than sequences generated from C++ app and returned from Oracle table when querying DUAL and ALL_SEQUENCES, there seem to be that SpringBoot is somehow using its own sequences. But I dont know why? Every documentation I read confirms that my Entity annotations above are correct.

Dragonhead answered 18/4 at 18:53 Comment(13)
They wouldn't. If you are truly using the same sequence you can't possibly get the same value in two sessions. Double check to ensure they are both using not only the same sequence name, but the same schema too. There could be two sequences with the same name in different schemas. Follow the path of what each program does from the time it obtains the sequence and then tries to insert it - see if it changes it in any way. I suspect your Springboot app isn't using the sequence as you expect. If it's adding its own caching on top of Oracle's native sequence caching, that would be a problem.Hilliard
Spring framework doesn't cache ORACLE sequences so the most logical seems indeed that both clients don't use the same sequence. Another potential problem: "INSERT ALL": I don't remember this one guarantee the order of execution... which may lead to data corruption: the LOG table referencing the wrong row of the original table.Marceau
"the last 3 are 4122, 4123, and 4124" - those might be the highest PK values, but how do you know they are the last three? Do you have another column that reliably shows you the chronological order they were inserted? You said the sequence might have been recreated, so it sounds more likely both applications are now creating PKs < 100, and some of those clash and get ORA-0001, while others fit into gaps that happened to exist (from old rows deleted, or rolled back inserts). If it isn't logged, capture all the current PKs, wait for a Spring insert, see what new PK was actually created.Boccie
can you show us the INSERT for the spring boot applicationGuesswork
to p3consulting, Alex Pole- both clients use SEQUENCE_A as provided above. According to Oracle documentation provided, INSERT ALL should not be an issue at all. Yes, I do know they are last 3 since there is a column CREATED recording date and time. No, both applications are not recreating PKs < 100, currently only one is as explained above but yes, my suspicion is that they clash resulting in ORA-0001. Spring creates next highest sequence but it is much higher than whet SEQUENCE_A.NEXTVAL would return if executed directly in the database.Dragonhead
@Guesswork Spring uses ORM, so mapping to table entity as explained above, so no SQL INSERT (JPA). C++ uses just direct JDBC stuff using the SQL INSERTS in the code. Both are explained above and for both I provided already information.Dragonhead
@Dragonhead - my point was have you actually verified that the Spring application is currently still using values in the 4000s, or are you assuming it is that the highest values are the newest? Also are the ORA-0001 coming from inserts from Spring or C++ or both? As you said it should work so we're trying to understand where the disconnect it - that may be a wrong assumption, it may be caching (but shouldn't be), or some side effect of recreation, or a hidden schema mismatch.Boccie
@AlexPoole Yes, I have verified all that before even posting the question. The spring app is currently in 4000s and its PKs are generated by incrementing SEQUENCE_A by 1 (INCREMENT BY). But I dont get why it gets numbers in 4000s if NEXTVAL returns numbers much smaller and aligned by what C++ app gets. The error is caused by C++ app. And I dont understand why would Spring App get NEXTVAL in 4000s if SELECT SEQUENCE_A.NEXTVAL FROM DUAL returns value alligned with what C++ getsDragonhead
To summarize: SELECT SEQUENCE_A.NEXTVAL FROM DUAL returns 82. So, I would expect APP1 and APP2 to next generate 83, 84, 85, ... However, SpringBoot app receives 4000+ as next sequence and does not error While C++ app receives 83 but occasionally gets ORA-00001. This is most likely due to collision with older sequences generated by SpringBoot that were in 80s range. Again, both apps use same sequence as per original postDragonhead
... and C++ app attempts to do inserts only if SpringBoot app is not reachable for whatever reason, that explains why there are only few attempts to insert from the C++ app (I know which app does it because it is recorded in a table field). But regardless of that, both apps should be getting sequences that are close to each other, not with difference like 80s vs 4000s for app1 vs app2Dragonhead
i hope this answer clarifies your doubt #7172126Guesswork
@Guesswork It seem to be related but I do use allocationSize=1 as per original post. But that SO seem to be related to what I have, thank you. However, I still dont know how to fix itDragonhead
@Dragonhead may be setting this parameter will fix your issue,but before that you have to set your sequence current value to max (pk)+ some value #43264606Guesswork
U
3

I'll wager you are running on a RAC cluster in which case what you are seeing is normal, expected behavior. It is not causing the constraint violation error.

In a RAC environment, if you create a sequence with a CACHE size of 20 and NOORDER (which is the default), each node of the RAC cluster gets a separate cache of 20 sequence values. If application A connects to node 1 and does a nextval, the application will get a value of 1 and Oracle will cache 19 more values (2-20) on node 1. If application B then connects to node 2 and does a nextval, the application will get a value of 21 and Oracle will cache 19 more values (22-40) on node 2. Until those caches are exhausted or the cache gets purged from memory (which probably only happens when the node is restarted), subsequent nextval requests will get a value from the cache on whatever node that connection happens to be on. It is perfectly normal to get interleaved values. Oracle guarantees that a sequence produces unique values (unless it cycles) not that those values are monotonically increasing.

If you are consistently seeing that application A and application B are interleaving rows when they are both running, my suspicion would be that your DBA has configured the system to preferentially route application A connections to node 1 and application B connections to node 2 as a form of load balancing.

You could choose to build the sequence as ORDER rather than NOORDER if you want to guarantee that nextval returns monotonically increasing values. There is a large performance cost to this, however. Rather than being able to satisfy this request from a local cache, Oracle would have to communicate with the other node(s) in the cluster over the network, acquire an exclusive lock, get the next value from the sequence, and send that back over the network to the requesting node. That is very fast in human terms but is orders of magnitude slower than a local cache lookup. It is rarely worth the trade-off.

Finally, note that currval is defined to return the last value of the sequence that was returned to the current session. If you do a nextval in session 1 at 1pm, session 2 does 1000 nextval calls in the next hour, and you both ask for the currval in your session, session 1 would get whatever value was returned by nextval at noon to session 1. Session 2's subsequent nextval calls would have no impact.

All this is to say that the behaviors you are describing do not explain the duplicate key issue that you are receiving.

Univalent answered 18/4 at 19:17 Comment(7)
NOORDER wouldn't permit duplicate values, it simply wouldn't ensure that each value was monotonically increasing across all nodes, which is rarely an important constraint to impose on a sequence, as sequences are typically used as surrogate keys only in which the value itself doesn't matter as long as it's unique. NOORDER is recommended in RAC, in fact, because of the concurrency penalty for ORDER over the interconnect. Nor would RAC explain the difference by 4000 between one app and the other, with a cache size of only 20. I think SpringBoot is doing its own thing.Hilliard
@PaulW - I agree and I thought I made it clear that noorder wouldn't cause duplicates just interleaved values. If I said the opposite somewhere or didn't make that clear, I'd welcome an edit. It's certainly possible that one or the other application isn't really using the sequence to generate its PKs and I'd suspect that it is SpringBoot that is doing something weird like asking for 1000 nextval's in short order that it caches outside the database. But if it's using something completely different I would expect to be getting a lot more than very occasional duplicatesUnivalent
Thank you Justin, Paul. Just to clarify if you missed that from the posted DDL, my CREATE SEQUENCE DDL uses ORDER. Also, based on the bottom of my question where I query DUAL and ALL_SEQUENCES in Oracle, it shows that C++ app is getting correct values. I feel you are right when you say that it is most likely SpringBoot issue as its values are by far larger. But everything I read about INSERT ALL (which is what C++ app uses), Sequences and Spring boot/hibernate (which uses TableAEntity and field annotations as per question above) confirms that what I am doing is correct.Dragonhead
@pixel, I think you need to dig into Springboot and see if you can disable any caching or manipulation it may be doing. It sounds like it uses an Oracle sequence to start but then tries to maintain its own sequence outside the database, which would be incompatible with anyone else needing to insert into that table. Or maybe it isn't using the Oracle sequence at all but something entirely its own?Hilliard
Would it be possible that if the SEQUENCE_A was dropped and recreated at some point in past, it would cause this issue (as per psaraj12 answer below)? I dont see how that would be possible. The collision for sure as the sequence would simply start from the begining leading to collisions but it would not have so much different sequence numbers per applicationDragonhead
Also, to add an additional detail, the C++ app will only attempt to do insert if the SpringBoot app failed to do so on its own. The C++ app calls SpringBoot app and tells it to do the insert but if for whatever reason (say the Spring Boot app is down), then C++ app will do the insert on its own. Not sure if that matters but thought it might. So, if SpringBoot app does not respond within say 60sec, the C++ will insert the INSERT ALL statement to do the insert. I updated original post to reflect thisDragonhead
@JustinCave and yes, it is on RAC cluster but I still dont understand how would that result in such behavior? Thank youDragonhead
G
0

If the Spring boot Application is older than the C++ Application then there might be chances that your sequence might have been dropped and recreated at some point in time.

Please ascertain whether this is the issue. If so you can increase the current sequence value to Spring boot application higher value + some number to resolve the issue.

Guesswork answered 18/4 at 19:19 Comment(3)
Why would the age of app matter if they both use same sequence on same table. The sequence is define by the provided CREATE SEQUENCE DDL. Also, if sequence was dropped then recreated (which might have been the case), how would that affect one but not the other application? I would expect in that case that the sequence generation will start from the begining at 1 and any application doing insert would get 2, 3, 4, 5, .... as NEXTVAL. This is not case here since 2 applications are getting very different far-appart valuesDragonhead
if you have PK with the mixture of 2 digits for one App and 4 digits for another App and you are not getting this issue frequently means your table has large number of Gaps in PK values. These gaps could have been due the reason i pointed out. Also while recreating a sequence you can start with initial number greater than 1.Guesswork
i hope this answer clarifies your doubt #7172126Guesswork

© 2022 - 2024 — McMap. All rights reserved.