// -*- c++-mode -*- void example(void) { PGconn *conn = PQconnectdb(...); txcache::Cache cache(conn); cache.beginRO(); cout << a_wrapper(conn, 42) << endl; cache.commit(); } int a_wrapper(PGconn *conn, int x) { txcache::Cache *cache = txcache::getCacheForXaction(conn); if (!cache) { // We have no cache, or we're not in a RO transaction return a(conn, x); } else { txcache::Key key("a"); txcache::Value value; int res; key << x; if (cache->lookup_or_lock(key, value)) { value >> res; } else { txcache::ValidityInterval interval; // XXX Need to start the transaction if we haven't // // XXX Deal with nested cachable calls cache->resetValidityInterval(); res = a(x); interval = cache->getValidityInterval(); value << res; cache->put(key, value, interval); } return res; } } #define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&) /** * A connection to the txcache. This wraps a PGconn, but for the most * part, the PGconn can be used directly. */ class Cache { DISALLOW_EVIL_CONSTRUCTORS(Cache); public: /** * Construct a connection to the cache that wraps the specified * connection to the Postgres database. * * XXX This probably needs more arguments to specify how to connect * to the cache. */ explicit Cache(PGconn *conn); /** * Retrieve the Cache for the specified Postgres connection, but * only consider Caches that are currently in a read-only * transaction. This allows users of the library to mostly ignore * the presence of the cache and just pass around regular PGconn * objects. */ static Cache *getForXaction(PGconn *conn); /** * Begin a new read-only transaction that takes advantage of cached * data. This will execute the necessary SQL commands to begin this * transaction on behalf of the caller. * * XXX It would be good if we didn't have to even begin the * transaction until we got a cache miss, but what if the caller * issues SQL statements outside a cachable function before there's * a cache miss for this transaction? * * XXX Include a transaction class name for heuristic windowing? */ void beginRO(double freshness); /** * Begin a read-only transaction with the default freshness. */ void beginRO(void) { beginRO(defaultFreshness); } /** * Lookup the value of the given key in the time range for the * current transaction. If there's a hit in the cache, the interval * of this transaction will be narrowed to the interval of the * value, the given value buffer will be filled with the value, and * this will return true. If there's a miss in the cache, this will * place an advisory lock on the key for the running transaction's * current interval and return false. * * XXX Would the lock just get in the way? Disregarding network * latency, node failures, and heterogeneity, clearly it's fastest * to let the first node that misses in the cache fill the cache * line, but how well does this work in practice? */ bool lookupOrLock(const Key &key, Unmarshall &result); void put(const Key &key, const Marshall &value, const Interval &interval); private: PGconn *conn; }; /** * A key that can be looked up in the cache. This captures the name * of a cachable function as well as any input arguments. It is *not* * associated with a timestamp; this will happen when the key is * looked up in the cache. */ class Key; /** * A value either retrieved by looking up a key or given when setting * a key. */ class Value; /* * If we require cachable functions to return an int and return their * result in a final reference argument, just like how the 6.824 RPC * system does, then we can just use template magic. Cachable calls * would be made by instead calling a library function and passing the * function to call. It's a little annoying that this is at the * caller instead of the callee. We would also need some way to * capture which cachable function was being invoked. We could use * the function pointer itself, or we could probably use other * template magic plus a little CPP to construct a static mapping of * names of cachable functions (which would have the further advantage * of declaring what was cachable at the callee). */ typedef unsigned int Timestamp; class VersionStore { DISALLOW_EVIL_CONSTRUCTORS(VersionStore); public: VersionStore(); /** * XXX Since we're searching over an interval, this can yield * multiple results. * * @param[in/out] lo Lower bound to search for values. Upon return, * stores the actual lower bound of the result. * @param[in/out] hi Upper bound to search for values. Upon return, * stores the actual upper bound of the result. * @param[out] result Upon return, stores the value found for the * given key whose timestamp overlaps [lo, hi). */ bool lookupOrLock(const std::string &key, Timestamp *lo, Timestamp *hi, std::string &result); void put(const std::string &key, Timestamp lo, Timestamp hi, const std::string &value); };