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

Reply via email to