0

Pre-requisites

Postgresql

Spring boot with spring data jpa

Problem

I have 2 tables. Products and ProductsLocationCounter. Each product has a location_id and counter_value fields among others. location_id is also the primary key of ProductsLocationCounter. The ProductsLocationCounter is meant to keep a counter of the number of products grouped by a specific location_id whenever a new product is added. The problem is that I also need the counter value at that point in time to be attached to the product entity.

So the flow would be like

 1. create product
 2. counter_value = get counter
 3. increment counter
 4. product.counter_value = counter_value

Of course this has to be done in a concurrent matter. Now, i've read/tried different solutions.

  1. this stackoverflow post suggests that i should let the db to handle the concurrency, which sounds fine by me. But the trick is that I need the value of the counter in the same transaction. So I've created a trigger
CREATE FUNCTION maintain_location_product_count_fun() RETURNS TRIGGER AS
$$
DECLARE
counter_var BIGINT;
BEGIN
  IF TG_OP IN ('INSERT') THEN
    select product_location_count.counter into counter_var from product_location_count WHERE id = new.location_id FOR UPDATE;
    UPDATE product_location_count SET counter = counter + 1 WHERE id = new.location_id;
    UPDATE products SET counter_value = counter_var WHERE location_id = new.location_id;
  END IF;
  RETURN NULL;
END
$$
LANGUAGE plpgsql;

CREATE TRIGGER maintain_location_product_count_trig
AFTER INSERT ON products
FOR EACH ROW
EXECUTE PROCEDURE maintain_location_product_count_fun();

and tested it with a parallel stream

    IntStream.range(1, 5000)
        .parallel()
        .forEach(value -> {
          executeInsideTransactionTemplate(status -> {
            var location = locationRepository.findById(location.getId()).get();
            return addProductWithLocation(location)
          });
        });

Got no duplication on the counter_value column. Is this trigger safe for multi-threaded apps? Haven't worked with triggers/postgresql functions before. Not sure what to expect


  1. The second solution I tried was to add PESIMISTIC_WRITE on findById method of the ProductsLocationCounter entity but i ended up getting cannot execute SELECT FOR UPDATE in a read-only transaction even though i was executing the code in a @Transactional annotated method ( which by default has read-only false).

  2. The third one was to update and retrieve the value of the counter in the same statement but spring jpa doesn't allow that (nor the underlying db) as the update statement only return the number of rows affected

Is there any other solution or do i need to add something to the trigger function to make it threadsafe? Thank you

8
  • trigger seems to be OK (I would prefer to remove first select ... for update in favour of returning into in update stmt. though, and you actually need to create three triggers), however if product_location_count table is managed by HBN and it is not marked as @Immutable (or counter column is not marked as non-updatable), you may potentially loose some updates Commented Oct 25, 2022 at 11:28
  • why 3 triggers? Commented Oct 25, 2022 at 11:39
  • If I understand properly you need to keep value of products.counter_value in sync with product_location_count.counter, so, you need three triggers: one for insert, one for delete and one for update. Commented Oct 25, 2022 at 11:42
  • No, i don't need it to be synced. I need to get the atomic value of product_location_count.counter at moment of time t1, add the value to the product, update the product_location_count.counter = product_location_count.counter + 1 then release it for another thread/client to come pick up the next value Commented Oct 25, 2022 at 11:59
  • Before digging into the technical details I would suggest to think what these counters are for from the business perspective. Could you update the post, please, and provide more details? What product.counter is used for? Commented Oct 25, 2022 at 14:25

1 Answer 1

0

This is how I've achieved what i needed.

Long story short, i've used a sql function and I called it inside repository. I didn't need the trigger anymore.

https://stackoverflow.com/a/74208072/3018285

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.