Use a hash table to store current sequence values.

This speeds up nextval() and currval(), when you touch a lot of different
sequences in the same backend.

David Rowley
This commit is contained in:
Heikki Linnakangas 2013-11-15 12:29:38 +02:00
parent 982b82d6b1
commit 21025d4a53
1 changed files with 35 additions and 36 deletions

View File

@ -60,15 +60,10 @@ typedef struct sequence_magic
* session. This is needed to hold onto nextval/currval state. (We can't * session. This is needed to hold onto nextval/currval state. (We can't
* rely on the relcache, since it's only, well, a cache, and may decide to * rely on the relcache, since it's only, well, a cache, and may decide to
* discard entries.) * discard entries.)
*
* XXX We use linear search to find pre-existing SeqTable entries. This is
* good when only a small number of sequences are touched in a session, but
* would suck with many different sequences. Perhaps use a hashtable someday.
*/ */
typedef struct SeqTableData typedef struct SeqTableData
{ {
struct SeqTableData *next; /* link to next SeqTable object */ Oid relid; /* pg_class OID of this sequence (hash key) */
Oid relid; /* pg_class OID of this sequence */
Oid filenode; /* last seen relfilenode of this sequence */ Oid filenode; /* last seen relfilenode of this sequence */
LocalTransactionId lxid; /* xact in which we last did a seq op */ LocalTransactionId lxid; /* xact in which we last did a seq op */
bool last_valid; /* do we have a valid "last" value? */ bool last_valid; /* do we have a valid "last" value? */
@ -81,7 +76,7 @@ typedef struct SeqTableData
typedef SeqTableData *SeqTable; typedef SeqTableData *SeqTable;
static SeqTable seqtab = NULL; /* Head of list of SeqTable items */ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
/* /*
* last_used_seq is updated by nextval() to point to the last used * last_used_seq is updated by nextval() to point to the last used
@ -92,6 +87,7 @@ static SeqTableData *last_used_seq = NULL;
static void fill_seq_with_data(Relation rel, HeapTuple tuple); static void fill_seq_with_data(Relation rel, HeapTuple tuple);
static int64 nextval_internal(Oid relid); static int64 nextval_internal(Oid relid);
static Relation open_share_lock(SeqTable seq); static Relation open_share_lock(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
static Form_pg_sequence read_seq_tuple(SeqTable elm, Relation rel, static Form_pg_sequence read_seq_tuple(SeqTable elm, Relation rel,
Buffer *buf, HeapTuple seqtuple); Buffer *buf, HeapTuple seqtuple);
@ -998,6 +994,23 @@ open_share_lock(SeqTable seq)
return relation_open(seq->relid, NoLock); return relation_open(seq->relid, NoLock);
} }
/*
* Creates the hash table for storing sequence data
*/
static void
create_seq_hashtable(void)
{
HASHCTL ctl;
memset(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(SeqTableData);
ctl.hash = oid_hash;
seqhashtab = hash_create("Sequence values", 16, &ctl,
HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
}
/* /*
* Given a relation OID, open and lock the sequence. p_elm and p_rel are * Given a relation OID, open and lock the sequence. p_elm and p_rel are
* output parameters. * output parameters.
@ -1007,39 +1020,28 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
{ {
SeqTable elm; SeqTable elm;
Relation seqrel; Relation seqrel;
bool found;
/* Look to see if we already have a seqtable entry for relation */ if (seqhashtab == NULL)
for (elm = seqtab; elm != NULL; elm = elm->next) create_seq_hashtable();
{
if (elm->relid == relid) elm = (SeqTable) hash_search(seqhashtab, &relid, HASH_ENTER, &found);
break;
}
/* /*
* Allocate new seqtable entry if we didn't find one. * Initalize the new hash table entry if it did not exist already.
* *
* NOTE: seqtable entries remain in the list for the life of a backend. If * NOTE: seqtable entries are stored for the life of a backend (unless
* the sequence itself is deleted then the entry becomes wasted memory, * explictly discarded with DISCARD). If the sequence itself is deleted
* but it's small enough that this should not matter. * then the entry becomes wasted memory, but it's small enough that this
* should not matter.
*/ */
if (elm == NULL) if (!found)
{ {
/* /* relid already filled in */
* Time to make a new seqtable entry. These entries live as long as
* the backend does, so we use plain malloc for them.
*/
elm = (SeqTable) malloc(sizeof(SeqTableData));
if (elm == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
elm->relid = relid;
elm->filenode = InvalidOid; elm->filenode = InvalidOid;
elm->lxid = InvalidLocalTransactionId; elm->lxid = InvalidLocalTransactionId;
elm->last_valid = false; elm->last_valid = false;
elm->last = elm->cached = elm->increment = 0; elm->last = elm->cached = elm->increment = 0;
elm->next = seqtab;
seqtab = elm;
} }
/* /*
@ -1609,13 +1611,10 @@ seq_redo(XLogRecPtr lsn, XLogRecord *record)
void void
ResetSequenceCaches(void) ResetSequenceCaches(void)
{ {
SeqTableData *next; if (seqhashtab)
while (seqtab != NULL)
{ {
next = seqtab->next; hash_destroy(seqhashtab);
free(seqtab); seqhashtab = NULL;
seqtab = next;
} }
last_used_seq = NULL; last_used_seq = NULL;