2009/10/25 Jeff Davis <pg...@j-davis.com>: > On Mon, 2009-10-19 at 17:48 +0100, Dean Rasheed wrote: >> This is a WIP patch to replace the after-trigger queues with TID bitmaps >> to prevent them from using excessive amounts of memory. Each round of >> trigger executions is a modified bitmap heap scan. > > Can you please take a look at my patch here: > http://archives.postgresql.org/message-id/1256499249.12775.20.ca...@jdavis > > to make sure that we're not interfering with eachother? I implemented > deferred constraint checking in my operator exclusion constraints patch > (formerly "generalized index constraints"). >
Yes, I've been following this, and I'm looking forward to this new functionality. > After looking very briefly at your approach, I think that it's entirely > orthogonal, so I don't expect a problem. > I agree. I think that the 2 are orthogonal. Possibly they could both share some common bulk checking code, but I've not thought much about how to do that yet. > I have a git repo here: > http://git.postgresql.org/gitweb?p=users/jdavis/postgres.git;a=shortlog;h=refs/heads/operator-exclusion-constraints > > which may be helpful if you just want to look at the commit for deferred > constraint checking. Any comments welcome. > I did a quick bit of testing, and I think that there is a locking/concurrency problem :-( Attached is a (rather crappy) python script (using PyGreSQL) that I used to test consistency while I was working on the deferrable uniqueness constraints patch. Basically it just spawns a bunch of threads, each of which does random CRUD, with heavy contention and lots of constraint violations and deadlocks, which are rolled back. I modified the script to enforce uniqueness with an exclusion constraint, and the script is able to break the constraint, forcing invalid data into the table. I haven't looked at your code in depth, but I hope that this is not a difficult problem to fix. It seems like it ought to be similar to the btree code. - Dean
#!/usr/bin/python import sys, pg, threading, random, time, datetime num_threads = 10 num_loops = 1000 lock = threading.RLock() total_nontrans = 0 total_commits = 0 total_rollbacks = 0 total_inserts = 0 total_updates = 0 total_deletes = 0 total_violations = 0 total_deadlocks = 0 total_unknown_errors = 0 total_errors = 0 total_duplicates = 0 def open(): return pg.DB("pgdevel", "localhost", -1, "-c client_min_messages=WARNING", None, "pgdevel", "") def find_duplicates(db): result = db.query("SELECT max(c)-1 AS dups FROM "+\ "(SELECT count(*) AS c FROM foo GROUP BY a) AS foo") result = result.dictresult()[0]["dups"] if result == None: return 0 return result def setup(): db = open() db.query("DROP TABLE IF EXISTS foo") # db.query("CREATE TABLE foo(a int UNIQUE)") # db.query("CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY DEFERRED)") db.query("CREATE TABLE foo(a int)") db.query("ALTER TABLE foo ADD CONSTRAINT foo_u EXCLUSION"+\ " USING btree (a CHECK WITH =) DEFERRABLE INITIALLY DEFERRED") db.close() def do_crud(db): global total_nontrans, total_commits, total_rollbacks global total_inserts, total_updates, total_deletes global total_violations, total_deadlocks, total_unknown_errors global total_errors, total_duplicates inserts = 0 updates = 0 deletes = 0 do_trans = random.random() > 0.2 do_commit = random.random() > 0.2 do_loop = True duplicates = find_duplicates(db) lock.acquire() total_duplicates += duplicates if duplicates > 0: print "1> FOUND DUPLICATES" lock.release() if total_duplicates > 0: sys.exit(1) try: if do_trans: db.query("BEGIN") while do_loop: if random.random() > 0.5: val = int(random.random()*100) db.query("INSERT INTO foo VALUES("+str(val)+")") inserts += 1 if random.random() > 0.5: val1 = int(random.random()*100) val2 = int(random.random()*100) db.query("UPDATE foo SET a="+str(val2)+" WHERE a="+str(val1)) updates += 1 if random.random() > 0.5: val = int(random.random()*100) db.query("DELETE FROM foo WHERE a="+str(val)) deletes += 1 if random.random() > 0.5: do_loop = False if do_trans: if do_commit: db.query("COMMIT") else: db.query("ROLLBACK") inserts = 0 updates = 0 deletes = 0 duplicates = find_duplicates(db) lock.acquire() if do_trans: if do_commit: total_commits += 1 else: total_rollbacks += 1 else: total_nontrans += 1 total_inserts += inserts total_updates += updates total_deletes += deletes total_duplicates += duplicates if duplicates > 0: print "2> FOUND DUPLICATES" lock.release() if total_duplicates > 0: sys.exit(1) except pg.ProgrammingError, detail: if do_trans: db.query("ROLLBACK") duplicates = find_duplicates(db) lock.acquire() if str(detail).find("operator exclusion constraint violation detected") != -1: total_violations += 1 elif str(detail).find("deadlock detected") != -1: total_deadlocks += 1 else: print detail total_unknown_errors += 1 total_errors += 1 total_duplicates += duplicates if duplicates > 0: print "3> FOUND DUPLICATES" lock.release() if total_duplicates > 0: sys.exit(1) def do_cruds(): db = open() for ii in range(num_loops): do_crud(db) db.close() class Inserter(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): do_cruds() if __name__ == "__main__": start_time = time.time() if len(sys.argv) > 1: num_threads = int(sys.argv[1]) if len(sys.argv) > 2: num_loops = int(sys.argv[2]) print "Testing with %d threads, %d loops..." % (num_threads, num_loops) setup() inserters = [] for ii in range(num_threads): inserter = Inserter() inserter.start() inserters.append(inserter) for inserter in inserters: inserter.join() db = open() result = db.query("SELECT count(*) FROM foo;") count = result.dictresult()[0]["count"] duplicates = find_duplicates(db) total_duplicates += duplicates if duplicates > 0: print "4> FOUND DUPLICATES" db.close() print "" print "Ops outside of a transaction: %d" % total_nontrans print "Committed transactions: %d" % total_commits print "Rolled back transactions: %d" % total_rollbacks print " Unique key violations: %d" % total_violations print " Deadlocks: %d" % total_deadlocks print " Unexpected errors: %d" % total_unknown_errors print "Failed transactions: %d" % total_errors print "" print "Num inserts: %d" % total_inserts print "Num updates: %d" % total_updates print "Num deletes: %d" % total_deletes print "" print "Number of rows: %d" % count print "Number of duplicates: %d" % total_duplicates elapsed_time = time.time() - start_time print "Elapsed time: %s" % datetime.timedelta(seconds=elapsed_time) print "" if total_duplicates > 0 or total_unknown_errors > 0: print "FAILED (something dodgy happened)" else: print "PASSED (everything looks OK)"
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers