summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/time/combocid.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/time/combocid.c')
-rw-r--r--src/backend/utils/time/combocid.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
new file mode 100644
index 0000000..44fe2f3
--- /dev/null
+++ b/src/backend/utils/time/combocid.c
@@ -0,0 +1,364 @@
+/*-------------------------------------------------------------------------
+ *
+ * combocid.c
+ * Combo command ID support routines
+ *
+ * Before version 8.3, HeapTupleHeaderData had separate fields for cmin
+ * and cmax. To reduce the header size, cmin and cmax are now overlayed
+ * in the same field in the header. That usually works because you rarely
+ * insert and delete a tuple in the same transaction, and we don't need
+ * either field to remain valid after the originating transaction exits.
+ * To make it work when the inserting transaction does delete the tuple,
+ * we create a "combo" command ID and store that in the tuple header
+ * instead of cmin and cmax. The combo command ID can be mapped to the
+ * real cmin and cmax using a backend-private array, which is managed by
+ * this module.
+ *
+ * To allow reusing existing combo CIDs, we also keep a hash table that
+ * maps cmin,cmax pairs to combo CIDs. This keeps the data structure size
+ * reasonable in most cases, since the number of unique pairs used by any
+ * one transaction is likely to be small.
+ *
+ * With a 32-bit combo command id we can represent 2^32 distinct cmin,cmax
+ * combinations. In the most perverse case where each command deletes a tuple
+ * generated by every previous command, the number of combo command ids
+ * required for N commands is N*(N+1)/2. That means that in the worst case,
+ * that's enough for 92682 commands. In practice, you'll run out of memory
+ * and/or disk space way before you reach that limit.
+ *
+ * The array and hash table are kept in TopTransactionContext, and are
+ * destroyed at the end of each transaction.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/time/combocid.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "miscadmin.h"
+#include "storage/shmem.h"
+#include "utils/combocid.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+
+/* Hash table to lookup combo CIDs by cmin and cmax */
+static HTAB *comboHash = NULL;
+
+/* Key and entry structures for the hash table */
+typedef struct
+{
+ CommandId cmin;
+ CommandId cmax;
+} ComboCidKeyData;
+
+typedef ComboCidKeyData *ComboCidKey;
+
+typedef struct
+{
+ ComboCidKeyData key;
+ CommandId combocid;
+} ComboCidEntryData;
+
+typedef ComboCidEntryData *ComboCidEntry;
+
+/* Initial size of the hash table */
+#define CCID_HASH_SIZE 100
+
+
+/*
+ * An array of cmin,cmax pairs, indexed by combo command id.
+ * To convert a combo CID to cmin and cmax, you do a simple array lookup.
+ */
+static ComboCidKey comboCids = NULL;
+static int usedComboCids = 0; /* number of elements in comboCids */
+static int sizeComboCids = 0; /* allocated size of array */
+
+/* Initial size of the array */
+#define CCID_ARRAY_SIZE 100
+
+
+/* prototypes for internal functions */
+static CommandId GetComboCommandId(CommandId cmin, CommandId cmax);
+static CommandId GetRealCmin(CommandId combocid);
+static CommandId GetRealCmax(CommandId combocid);
+
+
+/**** External API ****/
+
+/*
+ * GetCmin and GetCmax assert that they are only called in situations where
+ * they make sense, that is, can deliver a useful answer. If you have
+ * reason to examine a tuple's t_cid field from a transaction other than
+ * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
+ */
+
+CommandId
+HeapTupleHeaderGetCmin(HeapTupleHeader tup)
+{
+ CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
+
+ Assert(!(tup->t_infomask & HEAP_MOVED));
+ Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
+
+ if (tup->t_infomask & HEAP_COMBOCID)
+ return GetRealCmin(cid);
+ else
+ return cid;
+}
+
+CommandId
+HeapTupleHeaderGetCmax(HeapTupleHeader tup)
+{
+ CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
+
+ Assert(!(tup->t_infomask & HEAP_MOVED));
+
+ /*
+ * Because GetUpdateXid() performs memory allocations if xmax is a
+ * multixact we can't Assert() if we're inside a critical section. This
+ * weakens the check, but not using GetCmax() inside one would complicate
+ * things too much.
+ */
+ Assert(CritSectionCount > 0 ||
+ TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
+
+ if (tup->t_infomask & HEAP_COMBOCID)
+ return GetRealCmax(cid);
+ else
+ return cid;
+}
+
+/*
+ * Given a tuple we are about to delete, determine the correct value to store
+ * into its t_cid field.
+ *
+ * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to
+ * false. If we do need one, *cmax is replaced by a combo CID and *iscombo
+ * is set to true.
+ *
+ * The reason this is separate from the actual HeapTupleHeaderSetCmax()
+ * operation is that this could fail due to out-of-memory conditions. Hence
+ * we need to do this before entering the critical section that actually
+ * changes the tuple in shared buffers.
+ */
+void
+HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+ CommandId *cmax,
+ bool *iscombo)
+{
+ /*
+ * If we're marking a tuple deleted that was inserted by (any
+ * subtransaction of) our transaction, we need to use a combo command id.
+ * Test for HeapTupleHeaderXminCommitted() first, because it's cheaper
+ * than a TransactionIdIsCurrentTransactionId call.
+ */
+ if (!HeapTupleHeaderXminCommitted(tup) &&
+ TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup)))
+ {
+ CommandId cmin = HeapTupleHeaderGetCmin(tup);
+
+ *cmax = GetComboCommandId(cmin, *cmax);
+ *iscombo = true;
+ }
+ else
+ {
+ *iscombo = false;
+ }
+}
+
+/*
+ * Combo command ids are only interesting to the inserting and deleting
+ * transaction, so we can forget about them at the end of transaction.
+ */
+void
+AtEOXact_ComboCid(void)
+{
+ /*
+ * Don't bother to pfree. These are allocated in TopTransactionContext, so
+ * they're going to go away at the end of transaction anyway.
+ */
+ comboHash = NULL;
+
+ comboCids = NULL;
+ usedComboCids = 0;
+ sizeComboCids = 0;
+}
+
+
+/**** Internal routines ****/
+
+/*
+ * Get a combo command id that maps to cmin and cmax.
+ *
+ * We try to reuse old combo command ids when possible.
+ */
+static CommandId
+GetComboCommandId(CommandId cmin, CommandId cmax)
+{
+ CommandId combocid;
+ ComboCidKeyData key;
+ ComboCidEntry entry;
+ bool found;
+
+ /*
+ * Create the hash table and array the first time we need to use combo
+ * cids in the transaction.
+ */
+ if (comboHash == NULL)
+ {
+ HASHCTL hash_ctl;
+
+ /* Make array first; existence of hash table asserts array exists */
+ comboCids = (ComboCidKeyData *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE);
+ sizeComboCids = CCID_ARRAY_SIZE;
+ usedComboCids = 0;
+
+ hash_ctl.keysize = sizeof(ComboCidKeyData);
+ hash_ctl.entrysize = sizeof(ComboCidEntryData);
+ hash_ctl.hcxt = TopTransactionContext;
+
+ comboHash = hash_create("Combo CIDs",
+ CCID_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ }
+
+ /*
+ * Grow the array if there's not at least one free slot. We must do this
+ * before possibly entering a new hashtable entry, else failure to
+ * repalloc would leave a corrupt hashtable entry behind.
+ */
+ if (usedComboCids >= sizeComboCids)
+ {
+ int newsize = sizeComboCids * 2;
+
+ comboCids = (ComboCidKeyData *)
+ repalloc(comboCids, sizeof(ComboCidKeyData) * newsize);
+ sizeComboCids = newsize;
+ }
+
+ /* Lookup or create a hash entry with the desired cmin/cmax */
+
+ /* We assume there is no struct padding in ComboCidKeyData! */
+ key.cmin = cmin;
+ key.cmax = cmax;
+ entry = (ComboCidEntry) hash_search(comboHash,
+ (void *) &key,
+ HASH_ENTER,
+ &found);
+
+ if (found)
+ {
+ /* Reuse an existing combo CID */
+ return entry->combocid;
+ }
+
+ /* We have to create a new combo CID; we already made room in the array */
+ combocid = usedComboCids;
+
+ comboCids[combocid].cmin = cmin;
+ comboCids[combocid].cmax = cmax;
+ usedComboCids++;
+
+ entry->combocid = combocid;
+
+ return combocid;
+}
+
+static CommandId
+GetRealCmin(CommandId combocid)
+{
+ Assert(combocid < usedComboCids);
+ return comboCids[combocid].cmin;
+}
+
+static CommandId
+GetRealCmax(CommandId combocid)
+{
+ Assert(combocid < usedComboCids);
+ return comboCids[combocid].cmax;
+}
+
+/*
+ * Estimate the amount of space required to serialize the current combo CID
+ * state.
+ */
+Size
+EstimateComboCIDStateSpace(void)
+{
+ Size size;
+
+ /* Add space required for saving usedComboCids */
+ size = sizeof(int);
+
+ /* Add space required for saving ComboCidKeyData */
+ size = add_size(size, mul_size(sizeof(ComboCidKeyData), usedComboCids));
+
+ return size;
+}
+
+/*
+ * Serialize the combo CID state into the memory, beginning at start_address.
+ * maxsize should be at least as large as the value returned by
+ * EstimateComboCIDStateSpace.
+ */
+void
+SerializeComboCIDState(Size maxsize, char *start_address)
+{
+ char *endptr;
+
+ /* First, we store the number of currently-existing combo CIDs. */
+ *(int *) start_address = usedComboCids;
+
+ /* If maxsize is too small, throw an error. */
+ endptr = start_address + sizeof(int) +
+ (sizeof(ComboCidKeyData) * usedComboCids);
+ if (endptr < start_address || endptr > start_address + maxsize)
+ elog(ERROR, "not enough space to serialize ComboCID state");
+
+ /* Now, copy the actual cmin/cmax pairs. */
+ if (usedComboCids > 0)
+ memcpy(start_address + sizeof(int), comboCids,
+ (sizeof(ComboCidKeyData) * usedComboCids));
+}
+
+/*
+ * Read the combo CID state at the specified address and initialize this
+ * backend with the same combo CIDs. This is only valid in a backend that
+ * currently has no combo CIDs (and only makes sense if the transaction state
+ * is serialized and restored as well).
+ */
+void
+RestoreComboCIDState(char *comboCIDstate)
+{
+ int num_elements;
+ ComboCidKeyData *keydata;
+ int i;
+ CommandId cid;
+
+ Assert(!comboCids && !comboHash);
+
+ /* First, we retrieve the number of combo CIDs that were serialized. */
+ num_elements = *(int *) comboCIDstate;
+ keydata = (ComboCidKeyData *) (comboCIDstate + sizeof(int));
+
+ /* Use GetComboCommandId to restore each combo CID. */
+ for (i = 0; i < num_elements; i++)
+ {
+ cid = GetComboCommandId(keydata[i].cmin, keydata[i].cmax);
+
+ /* Verify that we got the expected answer. */
+ if (cid != i)
+ elog(ERROR, "unexpected command ID while restoring combo CIDs");
+ }
+}