Hi all!
This is my first post here. I am trying to understand locking and multithreading concepts with python and postgres. I have built a simple function to book a seat(say for an airline/theater) and I am trying to run it with 100 concurrent users. Here is some code for reference.
def book_seat(TRIP_ID, user_id, conn):
try:
with closing(conn.cursor()) as cur:
cur.execute("SELECT id FROM seats WHERE TRIP_ID = %s AND user_id is NULL ORDER BY id LIMIT 1 FOR UPDATE", (TRIP_ID))
seat = cur.fetchone()
seat_id = seat[0]
print("Trying to book seat {} for user {}".format(seat_id, user_id))
cur.execute("UPDATE seats SET user_id = %s WHERE TRIP_ID = %s AND id = %s", (user_id, TRIP_ID, seat_id))
if cur.rowcount == 0:
print("Failed to book seat {} for user {}".format(seat_id, user_id))
return False
print("Booked seat {} for user {}".format(seat_id, user_id))
conn.commit()
return True
except Exception as e:
print(f"Error in booking for user {user_id}: {e}")
finally:
pool.release_conn(conn)
My expectation was that, if I do not provide FOR UPDATE
in the select query, then only first few tickets will be booked back to back by all the threads, because all the threads would see the first empty seat and keep on booking it, till the time one of the threads commit it. This is working fine, but when I provide FOR UPDATE
, I am noticing the same behaviour!
I read upon psycopg2 documentation, and it states that v2.9.3 onwards, the connection object is thread safe, so we should be able to use same conn object with multiple cursors which I am doing in my code. But looks like something is missing ?
I have also tried using multiple connection objects(by creating a ThreadedConnectionPool
, but then my code is booking 1 seat at a time and taking lot of time, its almost like a sequential execution. I have initialized my pool with 100 workers.
Here is how my book_seat is being invoked for reference.
conn = pool.get_conn()
conn.autocommit = False # Also False by default though, doing this because i want to run my # entire function as a single transaction
with concurrent.futures.ThreadPoolExecutor(max_workers=180) as executor:
for user_id in users:
executor.submit(book_seat, TRIP_ID, user_id, conn)
I have tried a lot to debug and take AI help, but not able to understand this one, please give some pointers!
Thanks