Use binary tree for fake cluster storage
authorMarko Kreen <markokr@gmail.com>
Tue, 27 Mar 2012 11:51:33 +0000 (14:51 +0300)
committerMarko Kreen <markokr@gmail.com>
Thu, 29 Mar 2012 11:06:54 +0000 (14:06 +0300)
- Add aatree.[ch]
- Dont run maintenance if no init is done
- Simplify header deps in Makefile
- Store fake cluster in binary tree

Makefile
src/aatree.c [new file with mode: 0644]
src/aatree.h [new file with mode: 0644]
src/cluster.c
src/main.c
src/plproxy.h

index 20ac511a44e0642f9b49c5e97bc3b55e7f9012ca..dc15e10adda1008b6c05c1c6c230f4b7a9f062a3 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -15,11 +15,13 @@ PQLIB = $(shell $(PG_CONFIG) --libdir)
 # module setup
 MODULE_big = $(EXTENSION)
 SRCS = src/cluster.c src/execute.c src/function.c src/main.c \
-       src/query.c src/result.c src/type.c src/poll_compat.c
+       src/query.c src/result.c src/type.c src/poll_compat.c src/aatree.c
 OBJS = src/scanner.o src/parser.tab.o $(SRCS:.c=.o)
 EXTRA_CLEAN = src/scanner.[ch] src/parser.tab.[ch] libplproxy.* plproxy.so
 SHLIB_LINK = -L$(PQLIB) -lpq
 
+HDRS = src/plproxy.h src/rowstamp.h src/aatree.h src/poll_compat.h
+
 # Server include must come before client include, because there could
 # be mismatching libpq-dev and postgresql-server-dev installed.
 PG_CPPFLAGS = -I$(PQINCSERVER) -I$(PQINC) -DNO_SELECT=$(NO_SELECT)
@@ -104,9 +106,8 @@ $(EXTSQL): $(PLPROXY_SQL)
        cat $^ > $@
 
 # dependencies
-$(OBJS): src/plproxy.h src/rowstamp.h
-src/execute.o: src/poll_compat.h
-src/poll_compat.o: src/poll_compat.h
+
+$(OBJS): $(HDRS)
 
 # utility rules
 
diff --git a/src/aatree.c b/src/aatree.c
new file mode 100644 (file)
index 0000000..6a51784
--- /dev/null
@@ -0,0 +1,313 @@
+/*
+ * AA-Tree - Binary tree with embeddable nodes.
+ * 
+ * Copyright (c) 2007-2009  Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Self-balancing binary tree.
+ *
+ * Here is an implementation of AA-tree (Arne Andersson tree)
+ * which is simplification of Red-Black tree.
+ *
+ * Red-black tree has following properties that must be kept:
+ * 1. A node is either red or black.
+ * 2. The root is black.
+ * 3. All leaves (NIL nodes) are black.
+ * 4. Both childen of red node are black.
+ * 5. Every path from root to leaf contains same number of black nodes.
+ *
+ * AA-tree adds additional property:
+ * 6. Red node can exist only as a right node.
+ *
+ * Red-black tree properties quarantee that the longest path is max 2x longer
+ * than shortest path (B-R-B-R-B-R-B vs. B-B-B-B) thus the tree will be roughly
+ * balanced.  Also it has good worst-case guarantees for insertion and deletion,
+ * which makes it good tool for real-time applications.
+ *
+ * AA-tree removes most special cases from RB-tree, thus making resulting
+ * code lot simpler.  It requires slightly more rotations when inserting
+ * and deleting but also keeps the tree more balanced.
+ */
+
+
+#include "aatree.h"
+
+typedef struct AATree Tree;
+typedef struct AANode Node;
+
+/*
+ * NIL node
+ */
+#define NIL ((struct AANode *)&_nil)
+static const struct AANode _nil = { NIL, NIL, 0 };
+
+/*
+ * Rebalancing.  AA-tree needs only 2 operations
+ * to keep the tree balanced.
+ */
+
+/*
+ * Fix red on left.
+ *
+ *     X          Y
+ *    /     -->    \
+ *   Y              X
+ *    \            /
+ *     a          a
+ */
+static inline Node * skew(Node *x)
+{
+       Node *y = x->left;
+       if (x->level == y->level && x != NIL) {
+               x->left = y->right;
+               y->right = x;
+               return y;
+       }
+       return x;
+}
+
+/*
+ * Fix 2 reds on right.
+ *
+ *    X                Y
+ *     \              / \
+ *      Y      -->   X   Z
+ *     / \            \
+ *    a   Z            a
+ */
+static inline Node * split(Node *x)
+{
+       Node *y = x->right;
+       if (x->level == y->right->level && x != NIL) {
+               x->right = y->left;
+               y->left = x;
+               y->level++;
+               return y;
+       }
+       return x;
+}
+
+/* insert is easy */
+static Node *rebalance_on_insert(Node *current)
+{
+       return split(skew(current));
+}
+
+/* remove is bit more tricky */
+static Node *rebalance_on_remove(Node *current)
+{
+       /*
+        * Removal can create a gap in levels,
+        * fix it by lowering current->level.
+        */
+       if (current->left->level < current->level - 1
+           || current->right->level < current->level - 1)
+       {
+               current->level--;
+
+               /* if ->right is red, change it's level too */
+               if (current->right->level > current->level)
+                       current->right->level = current->level;
+
+               /* reshape, ask Arne about those */
+               current = skew(current);
+               current->right = skew(current->right);
+               current->right->right = skew(current->right->right);
+               current = split(current);
+               current->right = split(current->right);
+       }
+       return current;
+}
+
+/*
+ * Recursive insertion
+ */
+
+static Node * insert_sub(Tree *tree, Node *current, uintptr_t value, Node *node)
+{
+       int cmp;
+
+       if (current == NIL) {
+               /*
+                * Init node as late as possible, to avoid corrupting
+                * the tree in case it is already added.
+                */
+               node->left = node->right = NIL;
+               node->level = 1;
+
+               tree->count++;
+               return node;
+       }
+
+       /* recursive insert */
+       cmp = tree->node_cmp(value, current);
+       if (cmp > 0)
+               current->right = insert_sub(tree, current->right, value, node);
+       else if (cmp < 0)
+               current->left = insert_sub(tree, current->left, value, node);
+       else
+               /* already exists? */
+               return current;
+
+       return rebalance_on_insert(current);
+}
+
+void aatree_insert(Tree *tree, uintptr_t value, Node *node)
+{
+       tree->root = insert_sub(tree, tree->root, value, node);
+}
+
+/*
+ * Recursive removal
+ */
+
+/* remove_sub could be used for that, but want to avoid comparisions */
+static Node *steal_leftmost(Tree *tree, Node *current, Node **save_p)
+{
+       if (current->left == NIL) {
+               *save_p = current;
+               return current->right;
+       }
+
+       current->left = steal_leftmost(tree, current->left, save_p);
+       return rebalance_on_remove(current);
+}
+
+/* drop this node from tree */
+static Node *drop_this_node(Tree *tree, Node *old)
+{
+       Node *new = NIL;
+
+       if (old->left == NIL)
+               new = old->right;
+       else if (old->right == NIL)
+               new = old->left;
+       else {
+               /*
+                * Picking nearest from right is better than from left,
+                * due to asymmetry of the AA-tree.  It will result in
+                * less tree operations in the long run,
+                */
+               old->right = steal_leftmost(tree, old->right, &new);
+
+               /* take old node's place */
+               *new = *old;
+       }
+
+       /* cleanup for old node */
+       if (tree->release_cb)
+               tree->release_cb(old, tree);
+       tree->count--;
+
+       return new;
+}
+
+static Node *remove_sub(Tree *tree, Node *current, uintptr_t value)
+{
+       int cmp;
+
+       /* not found? */
+       if (current == NIL)
+               return current;
+
+       cmp = tree->node_cmp(value, current);
+       if (cmp > 0)
+               current->right = remove_sub(tree, current->right, value);
+       else if (cmp < 0)
+               current->left = remove_sub(tree, current->left, value);
+       else
+               current = drop_this_node(tree, current);
+       return rebalance_on_remove(current);
+}
+
+void aatree_remove(Tree *tree, uintptr_t value)
+{
+       tree->root = remove_sub(tree, tree->root, value);
+}
+
+/*
+ * Walking all nodes
+ */
+
+static void walk_sub(Node *current, enum AATreeWalkType wtype,
+                    aatree_walker_f walker, void *arg)
+{
+       if (current == NIL)
+               return;
+
+       switch (wtype) {
+       case AA_WALK_IN_ORDER:
+               walk_sub(current->left, wtype, walker, arg);
+               walker(current, arg);
+               walk_sub(current->right, wtype, walker, arg);
+               break;
+       case AA_WALK_POST_ORDER:
+               walk_sub(current->left, wtype, walker, arg);
+               walk_sub(current->right, wtype, walker, arg);
+               walker(current, arg);
+               break;
+       case AA_WALK_PRE_ORDER:
+               walker(current, arg);
+               walk_sub(current->left, wtype, walker, arg);
+               walk_sub(current->right, wtype, walker, arg);
+               break;
+       }
+}
+
+/* walk tree in correct order */
+void aatree_walk(Tree *tree, enum AATreeWalkType wtype, aatree_walker_f walker, void *arg)
+{
+       walk_sub(tree->root, wtype, walker, arg);
+}
+
+/* walk tree in bottom-up order, so that walker can destroy the nodes */
+void aatree_destroy(Tree *tree)
+{
+       walk_sub(tree->root, AA_WALK_POST_ORDER, tree->release_cb, tree);
+
+       /* reset tree */
+       tree->root = NIL;
+       tree->count = 0;
+}
+
+/* prepare tree */
+void aatree_init(Tree *tree, aatree_cmp_f cmpfn, aatree_walker_f release_cb)
+{
+       tree->root = NIL;
+       tree->count = 0;
+       tree->node_cmp = cmpfn;
+       tree->release_cb = release_cb;
+}
+
+/*
+ * search function
+ */
+Node *aatree_search(Tree *tree, uintptr_t value)
+{
+       Node *current = tree->root;
+       while (current != NIL) {
+               int cmp = tree->node_cmp(value, current);
+               if (cmp > 0)
+                       current = current->right;
+               else if (cmp < 0)
+                       current = current->left;
+               else
+                       return current;
+       }
+       return NULL;
+}
+
diff --git a/src/aatree.h b/src/aatree.h
new file mode 100644 (file)
index 0000000..40e490f
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007-2009  Marko Kreen, Skype Technologies OÜ
+ * 
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/** @file
+ *
+ * AA-Tree - Binary tree with embeddable nodes.
+ *
+ * AA-Tree (Arne Andersson tree) is a simplified Red-Black tree.
+ */
+
+#ifndef _USUAL_AATREE_H_
+#define _USUAL_AATREE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct AATree;
+struct AANode;
+
+/** Callback for node comparision against value */
+typedef int (*aatree_cmp_f)(uintptr_t, struct AANode *node);
+
+/** Callback for walking the tree */
+typedef void (*aatree_walker_f)(struct AANode *n, void *arg);
+
+/**
+ * Tree header, for storing helper functions.
+ */
+struct AATree {
+       struct AANode *root;
+       int count;
+       aatree_cmp_f node_cmp;
+       aatree_walker_f release_cb;
+};
+
+/**
+ * Tree node.  Embeddable, parent structure should be taken
+ * with container_of().
+ *
+ * Techinally, the full level is not needed and 2-lowest
+ * bits of either ->left or ->right would be enough
+ * to keep track of structure.  Currently this is not
+ * done to keep code simple.
+ */
+struct AANode {
+       struct AANode *left;    /**<  smaller values */
+       struct AANode *right;   /**<  larger values */
+       int level;              /**<  number of black nodes to leaf */
+};
+
+/**
+ * Walk order types.
+ */
+enum AATreeWalkType {
+       AA_WALK_IN_ORDER = 0,   /* left->self->right */
+       AA_WALK_PRE_ORDER = 1,  /* self->left->right */
+       AA_WALK_POST_ORDER = 2, /* left->right->self */
+};
+
+/** Initialize structure */
+void aatree_init(struct AATree *tree, aatree_cmp_f cmpfn, aatree_walker_f release_cb);
+
+/** Search for node */
+struct AANode *aatree_search(struct AATree *tree, uintptr_t value);
+
+/** Insert new node */
+void aatree_insert(struct AATree *tree, uintptr_t value, struct AANode *node);
+
+/** Remote node */
+void aatree_remove(struct AATree *tree, uintptr_t value);
+
+/** Walk over all nodes */
+void aatree_walk(struct AATree *tree, enum AATreeWalkType wtype, aatree_walker_f walker, void *arg);
+
+/** Free */
+void aatree_destroy(struct AATree *tree);
+
+/** Check if terminal node. */
+static inline int aatree_is_nil_node(const struct AANode *node)
+{
+       return (node->left == node);
+}
+
+#endif
index c91592d3de72e5b23e5edca1e06cb8936f2f0f25..745ecda99dfe45aab562c913e3927e57f3a96b01 100644 (file)
@@ -41,7 +41,7 @@ static ProxyCluster *cluster_list = NULL;
  *
  * Cluster name will be actual connect string.
  */
-static ProxyCluster *fake_cluster_list = NULL;
+static struct AATree fake_cluster_tree;
 
 /* plan for fetching cluster version */
 static void *version_plan;
@@ -89,6 +89,14 @@ check_valid_partcount(int n)
        return (n > 0) && !(n & (n - 1));
 }
 
+static int cluster_name_cmp(uintptr_t val, struct AANode *node)
+{
+       const char *name = (const char *)val;
+       const ProxyCluster *cluster = (ProxyCluster *)node;
+
+       return strcmp(name, cluster->name);
+}
+
 /*
  * Create cache memory area and prepare plans
  */
@@ -104,6 +112,7 @@ plproxy_cluster_cache_init(void)
                                                                                ALLOCSET_SMALL_MINSIZE,
                                                                                ALLOCSET_SMALL_INITSIZE,
                                                                                ALLOCSET_SMALL_MAXSIZE);
+       aatree_init(&fake_cluster_tree, cluster_name_cmp, NULL);
 }
 
 /* initialize plans on demand */
@@ -827,16 +836,12 @@ fake_cluster(ProxyFunction *func, const char *connect_str)
        ProxyCluster *cluster;
        ProxyConnection *conn;
        MemoryContext old_ctx;
+       struct AANode *n;
 
        /* search if cached */
-       for (cluster = fake_cluster_list; cluster; cluster = cluster->next)
-       {
-               if (strcmp(cluster->name, connect_str) == 0)
-                       break;
-       }
-
-       if (cluster)
-               return cluster;
+       n = aatree_search(&fake_cluster_tree, (uintptr_t)connect_str);
+       if (n)
+               return (ProxyCluster *)n;
 
        /* create if not */
 
@@ -861,8 +866,7 @@ fake_cluster(ProxyFunction *func, const char *connect_str)
 
        MemoryContextSwitchTo(old_ctx);
 
-       cluster->next = fake_cluster_list;
-       fake_cluster_list = cluster;
+       aatree_insert(&fake_cluster_tree, (uintptr_t)connect_str, &cluster->node);
 
        return cluster;
 }
@@ -998,6 +1002,14 @@ clean_cluster(ProxyCluster *cluster, struct timeval * now)
 /*
  * Clean old connections and results from all clusters.
  */
+
+static void w_clean_cluster(struct AANode *n, void *arg)
+{
+       ProxyCluster *c = (ProxyCluster *)n;
+       struct timeval *now = arg;
+       clean_cluster(c, now);
+}
+
 void
 plproxy_cluster_maint(struct timeval * now)
 {
@@ -1005,6 +1017,5 @@ plproxy_cluster_maint(struct timeval * now)
 
        for (cluster = cluster_list; cluster; cluster = cluster->next)
                clean_cluster(cluster, now);
-       for (cluster = fake_cluster_list; cluster; cluster = cluster->next)
-               clean_cluster(cluster, now);
+       aatree_walk(&fake_cluster_tree, AA_WALK_IN_ORDER, w_clean_cluster, now);
 }
index 861557ddb525ad7953b8725b6270ba49343c43d4..2e0f736034c0c75c6bc85d810c8683af0f370a10 100644 (file)
@@ -122,11 +122,11 @@ plproxy_remote_error(ProxyFunction *func, ProxyConnection *conn, const PGresult
  * Library load-time initialization.
  * Do the initialization when SPI is active to simplify the code.
  */
+static bool initialized = false;
+
 static void
 plproxy_startup_init(void)
 {
-       static bool initialized = false;
-
        if (initialized)
                return;
 
@@ -146,6 +146,9 @@ run_maint(void)
        static struct timeval last = {0, 0};
        struct timeval now;
 
+       if (!initialized)
+               return;
+
        gettimeofday(&now, NULL);
        if (now.tv_sec - last.tv_sec < 2 * 60)
                return;
index dd193c3fd7775effe175ca31c72d58ce32ae086c..31ca4ef012ffc1feaa8c3b36fb5dc78679332397 100644 (file)
@@ -55,6 +55,7 @@
 #include <utils/memutils.h>
 #include <utils/syscache.h>
 
+#include "aatree.h"
 #include "rowstamp.h"
 
 #include <libpq-fe.h>
@@ -176,6 +177,7 @@ typedef struct ProxyConnection
 /* Info about one cluster */
 typedef struct ProxyCluster
 {
+       struct AANode node;
        struct ProxyCluster *next;      /* Pointer for building singly-linked list */
 
        const char *name;                       /* Cluster name */