Take the 2-minute tour ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

I have a postgres database which contains details on clusters of servers, such as server status (active, standby, etc). Active servers at any time may need to fail over to a standby, and I don't care which standby is used in particular.

I want a database query to change the status of a standby--JUST ONE--and return the server IP that is to be used.

Is it possible to limit my query to just one update?

Here is what I have so far:

UPDATE server_info SET status = 'active' 
WHERE status = 'standby' [[LIMIT 1???]] 
RETURNING server_ip;

Postgres doesn't like this. What could I do differently?

share|improve this question
    
Random, arbitrary or deterministic pick? –  Erwin Brandstetter Jul 1 at 23:19
    
Arbitrary--since the status of the server changes with the query, it doesn't matter which standby is selected. Your answer below works perfectly for me. :) –  vastlysuperiorman Jul 2 at 15:40

1 Answer 1

up vote 5 down vote accepted

Without concurrency

Use a subquery in the FROM clause of the UPDATE:

UPDATE server_info s
SET    status = 'active' 
FROM  (
   SELECT server_ip          -- your pk column or any (set of) unique column(s)
   FROM   server_info
   WHERE  status = 'standby'
   LIMIT  1                  -- arbitrary pick (cheapest)
   ) sub
WHERE  s.server_ip = sub.server_ip
RETURNING server_ip;

With concurrency

For concurrent write load, add FOR UPDATE to the subquery to lock and avoid race conditions:

UPDATE server_info s
SET    status = 'active' 
FROM  (
   SELECT server_ip
   FROM   server_info
   WHERE  status = 'standby'
   LIMIT  1
   FOR    UPDATE
   ) sub
WHERE  s.server_ip = sub.server_ip
RETURNING server_ip;

Concurrent SELECT .. FOR UPDATE block until the first transaction releases the lock.
If the first was rolled back, the next transaction takes the lock, the rest keeps waiting.

If the first committed, the WHERE conditions is not TRUE any more (status has changed) and the subquery (somewhat surprisingly) returns no row after re-testing the condition. Nothing happens. That would be the desired behavior if all transactions want to update the same row.
But not if each transaction wants to update the next row. And since we just want to update an arbitrary (or random) row, there is no point in waiting at all.

We can unblock the situation with the help of advisory locks:

UPDATE server_info s
SET    status = 'active' 
FROM  (
   SELECT server_ip
   FROM   server_info
   WHERE  status = 'standby'
   AND    pg_try_advisory_xact_lock(id)
   LIMIT  1
   FOR    UPDATE
   ) sub
WHERE  s.server_ip = sub.server_ip
RETURNING server_ip;

This way, the next row that is not locked yet will be updated. Each transaction gets a fresh row to work with. I had help from Czech Postgres Wiki for this trick.

If the command is part of a long transaction, consider session-level locks that can be (and have to be) released manually. So you can unlock as soon as you are done with the locked row: pg_try_advisory_lock() and pg_advisory_unlock().
Per documentation:

Once acquired at session level, an advisory lock is held until explicitly released or the session ends.

Related:

share|improve this answer
    
I think FOR UPDATE is a good idea no matter what, but IIRC (would need to double check) it's not necessarily deterministic when used with LIMIT. You should add another layer of subquery if using LIMIT, putting the FOR UPDATE outside it. –  Craig Ringer Jul 2 at 4:50
    
    
@kolypto: I added solutions for concurrency. –  Erwin Brandstetter yesterday

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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