ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
MMappedUUIDHashTable.h
Go to the documentation of this file.
1/**
2 * @file MMappedUUIDHashTable.h
3 * @brief Open-addressing hash table mapping UUIDs to integers, backed by an mmap file.
4 *
5 * @c MMappedUUIDHashTable provides a persistent hash table that maps
6 * 128-bit UUID keys to sequential unsigned-long integers (used as gate
7 * indices into the @c MMappedVector of @c GateInformation). The table
8 * is stored in a memory-mapped file so that it survives PostgreSQL
9 * restarts and is accessible by multiple processes.
10 *
11 * Design constraints:
12 * - **Append-only**: elements can be added but never removed.
13 * - **Open addressing**: collisions are resolved by linear probing.
14 * - **Trivial hash**: the first 8 bytes of the UUID are reinterpreted as
15 * a 64-bit integer and taken modulo the table capacity. UUIDs are
16 * generated uniformly at random (version 4), so this is effectively
17 * uniform.
18 * - **Automatic growth**: when the load factor exceeds @c MAXIMUM_LOAD_FACTOR
19 * the table is doubled in size and rehashed.
20 *
21 * Access to the table from multiple processes is serialised via the
22 * ProvSQL LWLock in @c provsqlSharedState.
23 */
24#ifndef MMAPPED_UUID_HASH_TABLE_H
25#define MMAPPED_UUID_HASH_TABLE_H
26
27#include <cstddef>
28#include <cstdint>
29#include <string>
30#include <utility>
31
32#include "MappedRegion.h"
33
34extern "C" {
35#include "provsql_utils.h"
36}
37
38/**
39 * @brief Persistent open-addressing hash table mapping UUIDs to integers.
40 */
42{
43/** @brief One slot in the hash table: a UUID key and its associated integer value. */
44struct value_t {
45 pg_uuid_t uuid; ///< Key
46 unsigned long value; ///< Associated integer (gate index), or 0 if slot is empty
47};
48
49/**
50 * @brief On-disk layout of the hash table stored in the mmap file.
51 *
52 * The header fields are followed by a flexible array of @c value_t slots.
53 */
54struct table_t {
55 /**
56 * @brief Compute the file size required for a table with @c 2^ls slots.
57 * @param ls Log2 of the desired slot count.
58 * @return Required file size in bytes.
59 */
60 static constexpr std::size_t sizeForLogSize(unsigned ls) {
61 return offsetof(table_t, t) + (1 << ls)*sizeof(value_t);
62 }
63 /**
64 * @brief Compute the log2 of the slot count from the file size.
65 * @param size File size in bytes.
66 * @return Log2 of the number of slots that fit in @p size.
67 */
68 static constexpr unsigned logSizeForSize(std::size_t size) {
69 size -= offsetof(table_t, t);
70 size /= sizeof(value_t);
71 size >>= 1;
72 unsigned log_size=0;
73 while(size) {
74 size >>= 1;
75 ++log_size;
76 }
77 return log_size;
78 }
79 /**
80 * @brief Maximum number of slots in the table (@c 2^log_size).
81 * @return Current capacity (number of available hash-table slots).
82 */
83 constexpr unsigned long capacity() {
84 return 1u << log_size;
85 }
86
87 uint64_t magic; ///< File-type identifier
88 uint16_t version; ///< Format version (currently 1)
89 uint16_t elem_size; ///< sizeof(value_t) at write time
90 uint32_t flags; ///< Bit 0: opened for writing and not closed since
91 unsigned log_size; ///< log2 of the number of slots
92 unsigned long nb_elements; ///< Current number of stored key-value pairs
93 unsigned long next_value; ///< Next integer value to assign to a new UUID
94 value_t t[]; ///< Flexible array of hash-table slots
95};
96
97MappedRegion region; ///< Backing storage (shared mmap, or heap buffer)
98table_t *table; ///< Typed view of @c region.base()
99std::string path_; ///< Backing file, for the atomic rehash in @c grow()
100bool read_only_ = false;///< Mapped read-only: the header must not be written
101bool unclean_ = false;///< The previous run left the dirty bit set
102
103/** @brief Initial log2 capacity (65 536 slots). */
104static constexpr unsigned STARTING_LOG_SIZE=16;
105/** @brief Rehash when this fraction of slots is occupied. */
106static constexpr double MAXIMUM_LOAD_FACTOR=.5;
107
108/**
109 * @brief Compute the starting slot index for UUID @p u.
110 *
111 * Reinterprets the first 8 bytes of @p u as a 64-bit integer and takes
112 * it modulo the current capacity.
113 * @param u UUID to hash.
114 * @return Slot index in [0, capacity).
115 */
116inline unsigned long hash(pg_uuid_t u) const {
117 return *reinterpret_cast<unsigned long*>(&u) % (1 << table->log_size);
118};
119
120/**
121 * @brief Find the slot index of @p u, or @c NOTHING if absent.
122 * @param u UUID to look up.
123 * @return Slot index, or @c NOTHING if @p u is not in the table.
124 */
125unsigned long find(pg_uuid_t u) const;
126/** @brief Double the table capacity and rehash all existing entries. */
127void grow();
128
129public:
130/** @brief Sentinel returned by @c operator[]() when the UUID is not present. */
131static constexpr unsigned long NOTHING=static_cast<unsigned long>(-1);
132
133/**
134 * @brief Open (or create) the mmap-backed hash table.
135 *
136 * @param filename Path to the backing file (created if absent).
137 * @param read_only If @c true, map the file read-only (no new entries
138 * can be inserted).
139 * @param magic Expected magic value for format validation.
140 */
141MMappedUUIDHashTable(const char *filename, bool read_only, uint64_t magic);
142/** @brief Sync and unmap the file. */
144
145/**
146 * @brief Bit of @c table_t::flags set while the file is open for writing.
147 *
148 * Cleared on a clean close, so a file found with it still set was left
149 * behind by a process that died (or by an immediate shutdown). Reported
150 * by @c uncleanShutdown; the header layout is unchanged, since the bit
151 * lives in what used to be reserved padding.
152 */
153static constexpr uint32_t FLAG_DIRTY = 1u;
154
155/** @brief Whether this file still had @c FLAG_DIRTY set when it was
156 * opened, i.e. whoever wrote it last did not close it. */
157inline bool uncleanShutdown() const {
158 return unclean_;
159}
160
161/**
162 * @brief Look up the integer index for UUID @p u.
163 *
164 * @param u The UUID to look up.
165 * @return The associated integer, or @c NOTHING if @p u is absent.
166 */
167unsigned long operator[](pg_uuid_t u) const;
168
169/**
170 * @brief Insert UUID @p u, assigning it the next available integer.
171 *
172 * If @p u is already present the existing value is returned without
173 * modification.
174 *
175 * @param u UUID to insert.
176 * @return A pair @c {value, inserted} where @c inserted is @c true if
177 * a new entry was created.
178 */
179std::pair<unsigned long,bool> add(pg_uuid_t u);
180
181/**
182 * @brief Insert UUID @p u with a caller-chosen value.
183 *
184 * Lets the caller build the record @p value indexes *before* the key that
185 * points at it becomes visible, so that a process killed between the two
186 * leaves an unreferenced record rather than a mapping entry pointing past
187 * the end of the record vector -- which would shift every later gate for
188 * the rest of the store's life. The slot's @c value field is written
189 * last, and an aligned 8-byte store is atomic on the supported platforms,
190 * so a reader sees either @c NOTHING or the complete entry.
191 *
192 * @param u UUID to insert.
193 * @param value Value to associate with it.
194 * @return A pair @c {value, inserted}; @c inserted is @c false (and
195 * the existing value returned) when @p u was already there.
196 */
197std::pair<unsigned long,bool> publish(pg_uuid_t u, unsigned long value);
198
199/** @brief The value the next @c add would assign. Kept equal to the
200 * number of gate records by @c MMappedCircuit. */
201inline unsigned long nextValue() const {
202 return table->next_value;
203}
204
205/** @brief The number of slots the table currently has. */
206inline unsigned long capacity() const {
207 return table->capacity();
208}
209
210/** @brief The @p k-th slot's value, or @c NOTHING when the slot is empty.
211 * For consistency checks and for the clean-up's mark phase. */
212inline unsigned long slotValue(unsigned long k) const {
213 return table->t[k].value;
214}
215
216/** @brief The @p k-th slot's key; only meaningful when @c slotValue(k)
217 * is not @c NOTHING. */
218inline pg_uuid_t slotKey(unsigned long k) const {
219 return table->t[k].uuid;
220}
221
222/**
223 * @brief Return the number of UUID→integer pairs currently stored.
224 * @return Element count.
225 */
226inline unsigned long nbElements() const {
227 return table->nb_elements;
228}
229
230/**
231 * @brief Flush the backing region to its file (@c MappedRegion::sync()).
232 */
233void sync();
234
235/** @brief Force the backing file to stable storage
236 * (@c MappedRegion::flush()). */
237void flush();
238};
239
240 #endif /* MMAPPED_UUID_HASH_TABLE_H */
File-backed memory region with two interchangeable backends.
bool read_only_
Mapped read-only: the header must not be written.
unsigned long hash(pg_uuid_t u) const
Compute the starting slot index for UUID u.
std::pair< unsigned long, bool > publish(pg_uuid_t u, unsigned long value)
Insert UUID u with a caller-chosen value.
static constexpr uint32_t FLAG_DIRTY
Bit of table_t::flags set while the file is open for writing.
void grow()
Double the table capacity and rehash all existing entries.
std::pair< unsigned long, bool > add(pg_uuid_t u)
Insert UUID u, assigning it the next available integer.
MMappedUUIDHashTable(const char *filename, bool read_only, uint64_t magic)
Open (or create) the mmap-backed hash table.
bool unclean_
The previous run left the dirty bit set.
static constexpr unsigned STARTING_LOG_SIZE
Initial log2 capacity (65 536 slots).
unsigned long find(pg_uuid_t u) const
Find the slot index of u, or NOTHING if absent.
unsigned long operator[](pg_uuid_t u) const
Look up the integer index for UUID u.
unsigned long capacity() const
The number of slots the table currently has.
unsigned long nbElements() const
Return the number of UUID→integer pairs currently stored.
std::string path_
Backing file, for the atomic rehash in grow().
bool uncleanShutdown() const
Whether this file still had FLAG_DIRTY set when it was opened, i.e.
~MMappedUUIDHashTable()
Sync and unmap the file.
MappedRegion region
Backing storage (shared mmap, or heap buffer).
void sync()
Flush the backing region to its file (MappedRegion::sync()).
unsigned long nextValue() const
The value the next add would assign.
unsigned long slotValue(unsigned long k) const
The k-th slot's value, or NOTHING when the slot is empty.
pg_uuid_t slotKey(unsigned long k) const
The k-th slot's key; only meaningful when slotValue(k) is not NOTHING.
static constexpr unsigned long NOTHING
Sentinel returned by operator[]() when the UUID is not present.
void flush()
Force the backing file to stable storage (MappedRegion::flush()).
static constexpr double MAXIMUM_LOAD_FACTOR
Rehash when this fraction of slots is occupied.
table_t * table
Typed view of region.base().
Core types, constants, and utilities shared across ProvSQL.
On-disk layout of the hash table stored in the mmap file.
static constexpr unsigned logSizeForSize(std::size_t size)
Compute the log2 of the slot count from the file size.
value_t t[]
Flexible array of hash-table slots.
uint32_t flags
Bit 0: opened for writing and not closed since.
static constexpr std::size_t sizeForLogSize(unsigned ls)
Compute the file size required for a table with 2^ls slots.
uint64_t magic
File-type identifier.
unsigned log_size
log2 of the number of slots
uint16_t elem_size
sizeof(value_t) at write time
unsigned long nb_elements
Current number of stored key-value pairs.
unsigned long next_value
Next integer value to assign to a new UUID.
uint16_t version
Format version (currently 1).
constexpr unsigned long capacity()
Maximum number of slots in the table (2^log_size).
One slot in the hash table: a UUID key and its associated integer value.
unsigned long value
Associated integer (gate index), or 0 if slot is empty.