I am generating a unique slug during creation of a record like so (based in this answer)
INSERT INTO ks.shares (
, slugbase
, slugindex
, slug
, userid
)
SELECT
, Pslugbase
, COALESCE((SELECT(max(slugindex) + 1)
FROM ks.shares s
WHERE s.slugbase = Pslugbase), 0)
, Pslugbase
|| COALESCE((SELECT '-'::text || (max(slugindex) + 1)::text
FROM ks.shares s
WHERE s.slugbase = Pslugbase), '')
, Puserid
RETURNING id, modified, slug INTO Vnewshare;
When testing under load this can raise a unique_violation
as two concurrent threads both assess what it he current slug index before one inserts a record with the next available slug, leaving the second thread with a now invalid slug.
I attempted to solve the issue by LOOPING over the insert and catching and unique constraint violations:
LOOP
BEGIN
INSERT INTO ks.shares (
, slugbase
, slugindex
, slug
, userid
)
SELECT
, Pslugbase
, COALESCE((SELECT(max(slugindex) + 1)
FROM ks.shares s
WHERE s.slugbase = Pslugbase), 0)
, Pslugbase
|| COALESCE((SELECT '-'::text || (max(slugindex) + 1)::text
FROM ks.shares s
WHERE s.slugbase = Pslugbase), '')
, Puserid
RETURNING id, modified, slug INTO Vnewshare;
-- INSERT into two other tables
RETURN json_build_object (
'data',json_build_object (
'id',lpad(Vnewshare.id::text,public.padding_constant(),'0'),
'modified',Vnewshare.modified,
'slug',Vnewshare.slug
)
);
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the INSERT again
END;
END LOOP;
(I took this pattern from an answer on implementing UPSERT but can't find it!)
However, when I deployed this function my DB instance got stuck twice running long queries that involved this function being called concurrently, so I seemed to have somehow replaced a race condition with a deadlock or an infinite loop
pid | duration | query | state
------+-----------------+---------------------------------------------------------------------------------+--------
8187 | 01:47:35.477316 | select ks.post_share($1::text,$2::text,$3::bigint) | active
1188 | 01:57:56.955747 | select ks.post_share($1::text,$2::text,$3::bigint) | active
(2 rows)
- Is the approach to use a LOOP here the wrong way to go?
- Is there any way to understand what is causing the query to get stuck? It does not happen during load testing but appeared twice on production (which seems counter intuitive)
- Should I just return the conflict to the calling process and let the application handle retry?
Any thoughts would be greatly appreciated.
on conflict
?ON CONFLICT
clause inside the loop and leave it empty?on confict
option.