This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Tarantool -- an efficient key/value data store".

The branch box-namespace_limit has been updated
       via  f74b8f1877963c179dfd8014694406064abc733d (commit)
      from  eade0287f8c53c3d490172af0ec44409d68750f8 (commit)

Summary of changes:
 mod/box/box.h             |   28 ++++++++++++------------
 mod/box/box.m             |   39 +++++++++++++++++++-------------
 mod/box/index.h           |   18 +++++++++------
 mod/box/index.m           |   53 ++++++++++++++++++++------------------------
 test/box/show.result      |    1 +
 test/box_big/limit.result |   17 ++++++++++++++
 test/box_big/limit.test   |   11 +++++++++
 7 files changed, 101 insertions(+), 66 deletions(-)

commit f74b8f1877963c179dfd8014694406064abc733d
Author: Roman Tokarev <[email protected]>
Date:   Fri Apr 15 17:44:03 2011 +0400

    A post-push fix for Blueprint: 'Limit the total amount of memory
     consumed by a namespace'.
    
    Don't update namespace usage during prepare phase, but capture
    all usage into a txn and update namespace usage at the end of
    commit phase.
    Rename tnt_namespace_update_usage to tnt_namespace_capture_usage.
    Add additional test cases for this blueprint.

diff --git a/mod/box/box.h b/mod/box/box.h
index 01bcf47..df27012 100644
--- a/mod/box/box.h
+++ b/mod/box/box.h
@@ -94,6 +94,8 @@ struct box_txn {
 
        bool in_recover;
        bool write_to_wal;
+
+       ssize_t usage;
 };
 
 enum tuple_flags {
@@ -147,21 +149,19 @@ enum box_mode {
 ENUM(messages, MESSAGES);
 
 static void __attribute__((unused, always_inline))
-tnt_namespace_update_usage(struct namespace *namespace, ssize_t bytes)
+tnt_namespace_capture_usage(struct box_txn *txn, ssize_t bytes)
 {
-       say_debug("usage = %zu limit = %zu bytes = %zi", namespace->usage, 
namespace->limit, bytes);
-
-       if (bytes < 0)
-               assert(namespace->usage >= -bytes);
-       else
-               assert(namespace->usage <= SIZE_MAX - bytes);
-       namespace->usage += bytes;
-
-       if (namespace->usage > namespace->limit)
-               if (bytes > 0) {
-                       tnt_raise(tnt_BoxException, reason:"namespace limit is 
exceeded"
-                                                   
errcode:ERR_CODE_NAMESPACE_LIMIT);
-               }
+       say_debug("namespace[%i]->usage = %zu namespace[%i]->limit = %zu "
+                 "txn->usage = %zi bytes = %zi",
+                 txn->n, namespace[txn->n].usage, txn->n, 
namespace[txn->n].limit,
+                 txn->usage, bytes);
+
+       txn->usage += bytes;
+
+       if (txn->usage > 0 && bytes > 0 &&
+           (namespace[txn->n].usage + txn->usage) > namespace[txn->n].limit)
+               tnt_raise(tnt_BoxException, reason:"namespace limit is exceeded"
+                                           errcode:ERR_CODE_NAMESPACE_LIMIT);
 }
 
 struct box_txn *txn_alloc(u32 flags);
diff --git a/mod/box/box.m b/mod/box/box.m
index 124b0d3..d2178f8 100644
--- a/mod/box/box.m
+++ b/mod/box/box.m
@@ -232,7 +232,7 @@ tuple_free(struct box_tuple *tuple)
 }
 
 static void
-tuple_ref(struct box_txn *txn, struct box_tuple *tuple, int count)
+tuple_ref(struct box_tuple *tuple, int count)
 {
        assert(tuple->refs + count >= 0);
        tuple->refs += count;
@@ -240,10 +240,8 @@ tuple_ref(struct box_txn *txn, struct box_tuple *tuple, 
int count)
        if (tuple->refs > 0)
                tuple->flags &= ~NEW;
 
-       if (tuple->refs == 0) {
-               tnt_namespace_update_usage(&namespace[txn->n], 
-SIZEOF_TUPLE(tuple));
+       if (tuple->refs == 0)
                tuple_free(tuple);
-       }
 }
 
 void
@@ -251,7 +249,7 @@ tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple)
 {
        say_debug("tuple_txn_ref(%p)", tuple);
        tbuf_append(txn->ref_tuples, &tuple, sizeof(struct box_tuple *));
-       tuple_ref(txn, tuple, +1);
+       tuple_ref(tuple, +1);
 }
 
 static void __attribute__((noinline))
@@ -267,14 +265,16 @@ prepare_replace(struct box_txn *txn, size_t cardinality, 
struct tbuf *data)
 
        txn->tuple = tuple_alloc(data->len);
        tuple_txn_ref(txn, txn->tuple);
-       tnt_namespace_update_usage(&namespace[txn->n], 
SIZEOF_TUPLE(txn->tuple));
        txn->tuple->cardinality = cardinality;
        memcpy(txn->tuple->data, data->data, data->len);
 
        txn->old_tuple = txn->index->find_by_tuple(txn->index, txn->tuple);
 
-       if (txn->old_tuple != NULL)
+       if (txn->old_tuple != NULL) {
                tuple_txn_ref(txn, txn->old_tuple);
+               tnt_namespace_capture_usage(txn, -SIZEOF_TUPLE(txn->old_tuple));
+       }
+       tnt_namespace_capture_usage(txn, SIZEOF_TUPLE(txn->tuple));
 
        if (txn->flags & BOX_ADD && txn->old_tuple != NULL)
                tnt_raise(tnt_BoxException, reason:"tuple found" 
errcode:ERR_CODE_NODE_FOUND);
@@ -316,7 +316,7 @@ prepare_replace(struct box_txn *txn, size_t cardinality, 
struct tbuf *data)
                 * txn_commit().
                 */
                foreach_index(txn->n, index)
-                       index->replace(index, NULL, txn->tuple);
+                       index->replace(txn,index, NULL, txn->tuple);
        }
 
        if (!(txn->flags & BOX_QUIET)) {
@@ -334,14 +334,14 @@ commit_replace(struct box_txn *txn)
 {
        if (txn->old_tuple != NULL) {
                foreach_index(txn->n, index)
-                       index->replace(index, txn->old_tuple, txn->tuple);
+                       index->replace(txn, index, txn->old_tuple, txn->tuple);
 
-               tuple_ref(txn, txn->old_tuple, -1);
+               tuple_ref(txn->old_tuple, -1);
        }
 
        if (txn->tuple != NULL) {
                txn->tuple->flags &= ~GHOST;
-               tuple_ref(txn, txn->tuple, +1);
+               tuple_ref(txn->tuple, +1);
        }
 }
 
@@ -352,7 +352,7 @@ rollback_replace(struct box_txn *txn)
 
        if (txn->tuple && txn->tuple->flags & GHOST) {
                foreach_index(txn->n, index)
-                       index->remove(index, txn->tuple);
+                       index->remove(txn, index, txn->tuple);
        }
 }
 
@@ -504,6 +504,7 @@ prepare_update_fields(struct box_txn *txn, struct tbuf 
*data)
 
                goto out;
        }
+       tnt_namespace_capture_usage(txn, -SIZEOF_TUPLE(txn->old_tuple));
 
        lock_tuple(txn, txn->old_tuple);
 
@@ -568,7 +569,7 @@ prepare_update_fields(struct box_txn *txn, struct tbuf 
*data)
                bsize += fields[i]->len + varint32_sizeof(fields[i]->len);
        txn->tuple = tuple_alloc(bsize);
        tuple_txn_ref(txn, txn->tuple);
-       tnt_namespace_update_usage(&namespace[txn->n], 
SIZEOF_TUPLE(txn->tuple));
+       tnt_namespace_capture_usage(txn, SIZEOF_TUPLE(txn->tuple));
        txn->tuple->cardinality = txn->old_tuple->cardinality;
 
        uint8_t *p = txn->tuple->data;
@@ -723,8 +724,9 @@ commit_delete(struct box_txn *txn)
                return;
 
        foreach_index(txn->n, index)
-               index->remove(index, txn->old_tuple);
-       tuple_ref(txn, txn->old_tuple, -1);
+               index->remove(txn, index, txn->old_tuple);
+       tuple_ref(txn->old_tuple, -1);
+       tnt_namespace_capture_usage(txn, -SIZEOF_TUPLE(txn->old_tuple));
 }
 
 static bool
@@ -739,6 +741,7 @@ txn_alloc(u32 flags)
        struct box_txn *txn = p0alloc(fiber->pool, sizeof(*txn));
        txn->ref_tuples = tbuf_alloc(fiber->pool);
        txn->flags = flags;
+       txn->usage = 0;
        return txn;
 }
 
@@ -795,7 +798,7 @@ txn_cleanup(struct box_txn *txn)
 
        while (i-- > 0) {
                say_debug("tuple_txn_unref(%p)", *tuple);
-               tuple_ref(txn, *tuple++, -1);
+               tuple_ref(*tuple++, -1);
        }
 
        /* mark txn as clean */
@@ -831,6 +834,8 @@ txn_commit(struct box_txn *txn)
                        commit_delete(txn);
                else
                        commit_replace(txn);
+
+               namespace[txn->n].usage += txn->usage;
        }
 
        if (txn->flags & BOX_QUIET)
@@ -1601,6 +1606,8 @@ mod_info(struct tbuf *out)
 
                tbuf_printf(out, "    namespace_%u:" CRLF, i);
                tbuf_printf(out, "      usage: %zu" CRLF, namespace[i].usage);
+               tbuf_printf(out, "      usage%%: %.1f" CRLF,
+                           (float)(100 * namespace[i].usage) / 
namespace[i].limit);
        }
 }
 
diff --git a/mod/box/index.h b/mod/box/index.h
index 7a0a2d1..54bdbd5 100644
--- a/mod/box/index.h
+++ b/mod/box/index.h
@@ -59,20 +59,25 @@ struct tree_index_member {
 #define SIZEOF_TREE_INDEX_MEMBER(index) \
        (sizeof(struct tree_index_member) + sizeof(struct field) * 
(index)->key_cardinality)
 
+#define SIZEOF_HASH_INDEX_MEMBER(index) \
+       (sizeof((index)->idx.hash->keys[0]) + 
sizeof((index)->idx.hash->vals[0]))
+
 #include <third_party/sptree.h>
 SPTREE_DEF(str_t, realloc);
 
+struct box_txn;
+
 struct index {
        bool enabled;
 
        bool unique;
 
-       struct box_tuple *(*find) (struct index * index, void *key);    /* only 
for unique lookups */
-       struct box_tuple *(*find_by_tuple) (struct index * index, struct 
box_tuple * pattern);
-       void (*remove) (struct index * index, struct box_tuple *);
-       void (*replace) (struct index * index, struct box_tuple *, struct 
box_tuple *);
-       void (*iterator_init) (struct index *, struct tree_index_member * 
pattern);
-       struct box_tuple *(*iterator_next) (struct index *, struct 
tree_index_member * pattern);
+       struct box_tuple *(*find) (struct index * index, void *key); /* only 
for unique lookups */
+       struct box_tuple *(*find_by_tuple) (struct index *, struct box_tuple * 
pattern);
+       void (*remove) (struct box_txn *, struct index *, struct box_tuple *);
+       void (*replace) (struct box_txn *, struct index *, struct box_tuple *, 
struct box_tuple *);
+       void (*iterator_init) (struct index *, struct tree_index_member *);
+       struct box_tuple *(*iterator_next) (struct index *, struct 
tree_index_member *);
        union {
                khash_t(lstr_ptr_map) * str_hash;
                khash_t(int_ptr_map) * int_hash;
@@ -116,7 +121,6 @@ struct tree_index_member * alloc_search_pattern(struct 
index *index, int key_car
 void index_iterator_init_tree_str(struct index *self, struct tree_index_member 
*pattern);
 struct box_tuple * index_iterator_next_tree_str(struct index *self, struct 
tree_index_member *pattern);
 
-struct box_txn;
 void validate_indeces(struct box_txn *txn);
 void build_indexes(void);
 
diff --git a/mod/box/index.m b/mod/box/index.m
index e6a2fd1..5f360aa 100644
--- a/mod/box/index.m
+++ b/mod/box/index.m
@@ -296,7 +296,7 @@ index_find_tree_by_tuple(struct index *self, struct 
box_tuple *tuple)
 }
 
 static void
-index_remove_hash_num(struct index *self, struct box_tuple *tuple)
+index_remove_hash_num(struct box_txn *txn, struct index *self, struct 
box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
        unsigned int key_size = load_varint32(&key);
@@ -306,14 +306,14 @@ index_remove_hash_num(struct index *self, struct 
box_tuple *tuple)
                tnt_raise(tnt_BoxException,
                          reason:"key is not u32" 
errcode:ERR_CODE_ILLEGAL_PARAMS);
        assoc_delete(int_ptr_map, self->idx.int_hash, num);
-       tnt_namespace_update_usage(self->namespace, -(sizeof(num) + 
sizeof(tuple)));
+       tnt_namespace_capture_usage(txn, -SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        say_debug("index_remove_hash_num(self:%p, key:%i)", self, num);
 #endif
 }
 
 static void
-index_remove_hash_num64(struct index *self, struct box_tuple *tuple)
+index_remove_hash_num64(struct box_txn *txn, struct index *self, struct 
box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
        unsigned int key_size = load_varint32(&key);
@@ -323,18 +323,18 @@ index_remove_hash_num64(struct index *self, struct 
box_tuple *tuple)
                tnt_raise(tnt_BoxException,
                          reason:"key is not u64" 
errcode:ERR_CODE_ILLEGAL_PARAMS);
        assoc_delete(int64_ptr_map, self->idx.int64_hash, num);
-       tnt_namespace_update_usage(self->namespace, -(sizeof(num) + 
sizeof(tuple)));
+       tnt_namespace_capture_usage(txn, -SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        say_debug("index_remove_hash_num(self:%p, key:%"PRIu64")", self, num);
 #endif
 }
 
 static void
-index_remove_hash_str(struct index *self, struct box_tuple *tuple)
+index_remove_hash_str(struct box_txn *txn, struct index *self, struct 
box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
        assoc_delete(lstr_ptr_map, self->idx.str_hash, key);
-       tnt_namespace_update_usage(self->namespace, -(sizeof(key) + 
sizeof(tuple)));
+       tnt_namespace_capture_usage(txn, -SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        u32 size = load_varint32(&key);
        say_debug("index_remove_hash_str(self:%p, key:'%.*s')", self, size, (u8 
*)key);
@@ -342,23 +342,21 @@ index_remove_hash_str(struct index *self, struct 
box_tuple *tuple)
 }
 
 static void
-index_remove_tree_str(struct index *self, struct box_tuple *tuple)
+index_remove_tree_str(struct box_txn *txn, struct index *self, struct 
box_tuple *tuple)
 {
        struct tree_index_member *member = tuple2tree_index_member(self, tuple, 
NULL);
        sptree_str_t_delete(self->idx.tree, member);
-       tnt_namespace_update_usage(self->namespace, 
-SIZEOF_TREE_INDEX_MEMBER(self));
+       tnt_namespace_capture_usage(txn, -SIZEOF_TREE_INDEX_MEMBER(self));
 }
 
 static void
-index_replace_hash_num(struct index *self, struct box_tuple *old_tuple, struct 
box_tuple *tuple)
+index_replace_hash_num(struct box_txn *txn, struct index *self,
+                      struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
        u32 key_size = load_varint32(&key);
        u32 num = *(u32 *)key;
 
-       /* see index_replace_tree_str for comments */
-       tnt_namespace_update_usage(self->namespace, sizeof(num) + 
sizeof(tuple));
-
        if (key_size != 4)
                tnt_raise(tnt_BoxException,
                          reason:"key is not u32" 
errcode:ERR_CODE_ILLEGAL_PARAMS);
@@ -368,9 +366,11 @@ index_replace_hash_num(struct index *self, struct 
box_tuple *old_tuple, struct b
                load_varint32(&old_key);
                u32 old_num = *(u32 *)old_key;
                assoc_delete(int_ptr_map, self->idx.int_hash, old_num);
+               tnt_namespace_capture_usage(txn, 
-SIZEOF_HASH_INDEX_MEMBER(self));
        }
 
        assoc_replace(int_ptr_map, self->idx.int_hash, num, tuple);
+       tnt_namespace_capture_usage(txn, SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        say_debug("index_replace_hash_num(self:%p, old_tuple:%p, tuple:%p) 
key:%i", self, old_tuple,
                  tuple, num);
@@ -378,15 +378,13 @@ index_replace_hash_num(struct index *self, struct 
box_tuple *old_tuple, struct b
 }
 
 static void
-index_replace_hash_num64(struct index *self, struct box_tuple *old_tuple, 
struct box_tuple *tuple)
+index_replace_hash_num64(struct box_txn *txn, struct index *self,
+                        struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
        u32 key_size = load_varint32(&key);
        u64 num = *(u64 *)key;
 
-       /* see index_replace_tree_str for comments */
-       tnt_namespace_update_usage(self->namespace, sizeof(num) + 
sizeof(tuple));
-
        if (key_size != 8)
                tnt_raise(tnt_BoxException,
                          reason:"key is not u64" 
errcode:ERR_CODE_ILLEGAL_PARAMS);
@@ -396,9 +394,11 @@ index_replace_hash_num64(struct index *self, struct 
box_tuple *old_tuple, struct
                load_varint32(&old_key);
                u64 old_num = *(u64 *)old_key;
                assoc_delete(int64_ptr_map, self->idx.int64_hash, old_num);
+               tnt_namespace_capture_usage(txn, 
-SIZEOF_HASH_INDEX_MEMBER(self));
        }
 
        assoc_replace(int64_ptr_map, self->idx.int64_hash, num, tuple);
+       tnt_namespace_capture_usage(txn, SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        say_debug("index_replace_hash_num(self:%p, old_tuple:%p, tuple:%p) 
key:%"PRIu64, self, old_tuple,
                  tuple, num);
@@ -406,13 +406,11 @@ index_replace_hash_num64(struct index *self, struct 
box_tuple *old_tuple, struct
 }
 
 static void
-index_replace_hash_str(struct index *self, struct box_tuple *old_tuple, struct 
box_tuple *tuple)
+index_replace_hash_str(struct box_txn *txn, struct index *self,
+                      struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
        void *key = tuple_field(tuple, self->key_field->fieldno);
 
-       /* see index_replace_tree_str for comments */
-       tnt_namespace_update_usage(self->namespace, sizeof(key) + 
sizeof(tuple));
-
        if (key == NULL)
                tnt_raise(tnt_BoxException,
                          reason:"Supplied tuple misses a field which is part 
of an index"
@@ -421,9 +419,11 @@ index_replace_hash_str(struct index *self, struct 
box_tuple *old_tuple, struct b
        if (old_tuple != NULL) {
                void *old_key = tuple_field(old_tuple, 
self->key_field->fieldno);
                assoc_delete(lstr_ptr_map, self->idx.str_hash, old_key);
+               tnt_namespace_capture_usage(txn, 
-SIZEOF_HASH_INDEX_MEMBER(self));
        }
 
        assoc_replace(lstr_ptr_map, self->idx.str_hash, key, tuple);
+       tnt_namespace_capture_usage(txn, SIZEOF_HASH_INDEX_MEMBER(self));
 #ifdef DEBUG
        u32 size = load_varint32(&key);
        say_debug("index_replace_hash_str(self:%p, old_tuple:%p, tuple:%p) 
key:'%.*s'", self,
@@ -432,15 +432,9 @@ index_replace_hash_str(struct index *self, struct 
box_tuple *old_tuple, struct b
 }
 
 static void
-index_replace_tree_str(struct index *self, struct box_tuple *old_tuple, struct 
box_tuple *tuple)
+index_replace_tree_str(struct box_txn *txn, struct index *self,
+                      struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
-       /*
-        * Update usage at the beggining because if any error have been
-        * occured there is no way to determine whether update of index was
-        * successfull. So we always decrease usage during index cleanup.
-        */
-       tnt_namespace_update_usage(self->namespace, 
SIZEOF_TREE_INDEX_MEMBER(self));
-
        if (tuple->cardinality < self->field_cmp_order_cnt)
                tnt_raise(tnt_BoxException,
                          reason:"Supplied tuple misses a field which is part 
of an index"
@@ -449,8 +443,9 @@ index_replace_tree_str(struct index *self, struct box_tuple 
*old_tuple, struct b
        struct tree_index_member *member = tuple2tree_index_member(self, tuple, 
NULL);
 
        if (old_tuple)
-               index_remove_tree_str(self, old_tuple);
+               index_remove_tree_str(txn, self, old_tuple);
        sptree_str_t_insert(self->idx.tree, member);
+       tnt_namespace_capture_usage(txn, SIZEOF_TREE_INDEX_MEMBER(self));
 }
 
 void
diff --git a/test/box/show.result b/test/box/show.result
index 8860e40..647bcce 100644
--- a/test/box/show.result
+++ b/test/box/show.result
@@ -109,4 +109,5 @@ info:
   namespace_stat:
     namespace_0:
       usage: 0
+      usage%: 0.0
 ...
diff --git a/test/box_big/limit.result b/test/box_big/limit.result
index cce04ab..90a16d2 100644
--- a/test/box_big/limit.result
+++ b/test/box_big/limit.result
@@ -31,6 +31,7 @@ An error occurred: ERR_CODE_NAMESPACE_LIMIT, 'Namespace limit 
has been exceeded'
 insert into t0 values (9, 'tuple')
 An error occurred: ERR_CODE_NAMESPACE_LIMIT, 'Namespace limit has been 
exceeded'
 usage = 376
+usage% = 89.7
 
 #
 #  free some space in namespace
@@ -43,11 +44,13 @@ Delete OK, 1 row affected
 delete from t0 where k0 = 2
 Delete OK, 1 row affected
 usage = 235
+usage% = 56.1
 insert into t0 values (8, 'tuple')
 Insert OK, 1 row affected
 insert into t0 values (9, 'tuple')
 Insert OK, 1 row affected
 usage = 329
+usage% = 78.5
 
 #
 #  try to decrease namespace limit in runtime
@@ -93,3 +96,17 @@ Insert OK, 1 row affected
 insert into t0 values (19, 'tuple')
 Insert OK, 1 row affected
 usage = 799
+usage% = 76.2
+
+#
+#  check usage updates after replace/update
+#
+
+insert into t0 values (19, 'bigger tuple')
+Insert OK, 1 row affected
+usage = 806
+usage% = 76.9
+update t0 set k1 = 'tuple' where k0 = 19
+Update OK, 1 row affected
+usage = 799
+usage% = 76.2
diff --git a/test/box_big/limit.test b/test/box_big/limit.test
index f4dd088..8423a41 100644
--- a/test/box_big/limit.test
+++ b/test/box_big/limit.test
@@ -4,6 +4,7 @@ import yaml
 def print_usage():
   info = yaml.load(admin.execute("show info\n"))["info"]
   print "usage = " + str(info["namespace_stat"]["namespace_0"]["usage"])
+  print "usage% = " + str(info["namespace_stat"]["namespace_0"]["usage%"])
 
 print """
 #
@@ -61,6 +62,16 @@ for i in range(10, 20):
   exec sql "insert into t0 values ({0}, 'tuple')".format(i)
 print_usage()
 
+print """
+#
+#  check usage updates after replace/update
+#
+"""
+exec sql "insert into t0 values (19, 'bigger tuple')"
+print_usage()
+exec sql "update t0 set k1 = 'tuple' where k0 = 19"
+print_usage()
+
 # restore default server
 server.stop()
 server.deploy(self.suite_ini["config"])

-- 
Tarantool -- an efficient key/value data store

_______________________________________________
Mailing list: https://launchpad.net/~tarantool-developers
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~tarantool-developers
More help   : https://help.launchpad.net/ListHelp

Reply via email to