[tarantool-patches] [PATCH v3] Introduce replica local spaces

  • From: Vladimir Davydov <vdavydov.dev@xxxxxxxxx>
  • To: kostja@xxxxxxxxxxxxx
  • Date: Tue, 10 Jul 2018 12:36:10 +0300

This patch introduces a new space option, group_id, which defines how
the space is replicated. If it is 0 (default), the space is replicated
throughout the entire cluster. If it is 1, the space is replica local,
i.e. all changes made to it are invisible to other replicas in the
cluster. Currently, no other value is permitted, but in future we will
use this option for setting up arbitrary replication groups in a
cluster. The option can only be set on space creation and cannot be
altered.

Since the concept of replication groups hasn't been established yet,
group_id isn't exposed to Lua. Instead, we use is_local flag, both in
box.schema.space.create arguments and in box.space output.

Technically, to support this feature, we introduce a new header key,
IPROTO_GROUP_ID, which is set to the space group id for all rows
corresponding to a space, both in xlog and in snap. Relay won't send
snapshot rows whose group_id is 1. As for xlog rows, they are
transformed to IPROTO_NOP so as to promote vclock on replicas without
any actual data modification.

The feature is currently supported for memtx spaces only, but it should
be easy to implement it for vinyl spaces as well.

Closes #3443

@TarantoolBot document
Title: Document new space option - is_local
If a space is created with is_local flag set in options, changes made to
the space will be persisted, but won't be replicated.
---
https://github.com/tarantool/tarantool/issues/3443
https://github.com/tarantool/tarantool/commits/dv/gh-3443-replica-local-spaces

Changes in v3:
 - Do not expose group_id in Lua. Use is_local instead, both when
   creating a space and when showing its options to the user.

v2: 
https://www.freelists.org/post/tarantool-patches/PATCH-v2-04-Introduce-replica-local-spaces

Changes in v2:
 - Use group_id space option instead of is_local flag, as this is more
   flexible and will allow us to implement fully-functional replication
   groups in future (Kostja).
 - Refactor code used for committing NOP requests (Kostja).
 - Rename space_opts::temporary to is_temporary (Kostja).
 - Add a comment to request_create_from_tuple (Kostja).
 - Remove merged patches from the patch set (patches 1-3 from v1).

v1: 
https://www.freelists.org/post/tarantool-patches/PATCH-06-Introduce-replica-local-spaces

 src/box/alter.cc                       |  13 +++
 src/box/errcode.h                      |   1 +
 src/box/iproto_constants.c             |   4 +-
 src/box/iproto_constants.h             |   1 +
 src/box/lua/schema.lua                 |   8 +-
 src/box/lua/space.cc                   |   6 ++
 src/box/lua/xlog.c                     |   5 +
 src/box/memtx_engine.c                 |   8 +-
 src/box/relay.cc                       |  17 ++-
 src/box/replication.h                  |  14 +++
 src/box/space.h                        |   7 ++
 src/box/space_def.c                    |   2 +
 src/box/space_def.h                    |   5 +
 src/box/txn.c                          |   1 +
 src/box/vinyl.c                        |   5 +
 src/box/xrow.c                         |   9 ++
 src/box/xrow.h                         |   1 +
 test/box/misc.result                   |   5 +-
 test/engine/iterator.result            |   2 +-
 test/replication/local_spaces.result   | 183 +++++++++++++++++++++++++++++++++
 test/replication/local_spaces.test.lua |  67 ++++++++++++
 test/replication/suite.cfg             |   1 +
 test/vinyl/ddl.result                  |   5 +
 test/vinyl/ddl.test.lua                |   3 +
 24 files changed, 362 insertions(+), 11 deletions(-)
 create mode 100644 test/replication/local_spaces.result
 create mode 100644 test/replication/local_spaces.test.lua

diff --git a/src/box/alter.cc b/src/box/alter.cc
index c8761d3e..b7436932 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -560,6 +560,15 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t 
errcode,
        }
        struct space_opts opts;
        space_opts_decode(&opts, space_opts);
+       /*
+        * Currently, only predefined replication groups
+        * are supported.
+        */
+       if (opts.group_id != GROUP_DEFAULT &&
+           opts.group_id != GROUP_LOCAL) {
+               tnt_raise(ClientError, ER_NO_SUCH_GROUP,
+                         int2str(opts.group_id));
+       }
        struct space_def *def =
                space_def_new_xc(id, uid, exact_field_count, name, name_len,
                                 engine_name, engine_name_len, &opts, fields,
@@ -1664,6 +1673,10 @@ on_replace_dd_space(struct trigger * /* trigger */, void 
*event)
                        tnt_raise(ClientError, ER_ALTER_SPACE,
                                  space_name(old_space),
                                  "can not change space engine");
+               if (def->opts.group_id != space_group_id(old_space))
+                       tnt_raise(ClientError, ER_ALTER_SPACE,
+                                 space_name(old_space),
+                                 "replication group is immutable");
                /*
                 * Allow change of space properties, but do it
                 * in WAL-error-safe mode.
diff --git a/src/box/errcode.h b/src/box/errcode.h
index f3987cf0..3d5f66af 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -207,6 +207,7 @@ struct errcode_record {
        /*152 */_(ER_NULLABLE_PRIMARY,          "Primary index of the space 
'%s' can not contain nullable parts") \
        /*153 */_(ER_NULLABLE_MISMATCH,         "Field %d is %s in space 
format, but %s in index parts") \
        /*154 */_(ER_TRANSACTION_YIELD,         "Transaction has been aborted 
by a fiber yield") \
+       /*155 */_(ER_NO_SUCH_GROUP,             "Replication group '%s' does 
not exist") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/iproto_constants.c b/src/box/iproto_constants.c
index 5c1d3a31..3bc965bd 100644
--- a/src/box/iproto_constants.c
+++ b/src/box/iproto_constants.c
@@ -40,10 +40,10 @@ const unsigned char iproto_key_type[IPROTO_KEY_MAX] =
                /* 0x04 */      MP_DOUBLE, /* IPROTO_TIMESTAMP */
                /* 0x05 */      MP_UINT,   /* IPROTO_SCHEMA_VERSION */
                /* 0x06 */      MP_UINT,   /* IPROTO_SERVER_VERSION */
+               /* 0x07 */      MP_UINT,   /* IPROTO_GROUP_ID */
        /* }}} */
 
        /* {{{ unused */
-               /* 0x07 */      MP_UINT,
                /* 0x08 */      MP_UINT,
                /* 0x09 */      MP_UINT,
                /* 0x0a */      MP_UINT,
@@ -133,7 +133,7 @@ const char *iproto_key_strs[IPROTO_KEY_MAX] = {
        "timestamp",        /* 0x04 */
        "schema version",   /* 0x05 */
        "server version",   /* 0x06 */
-       NULL,               /* 0x07 */
+       "group id",         /* 0x07 */
        NULL,               /* 0x08 */
        NULL,               /* 0x09 */
        NULL,               /* 0x0a */
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index d1320de7..ccbf2da5 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -58,6 +58,7 @@ enum iproto_key {
        IPROTO_TIMESTAMP = 0x04,
        IPROTO_SCHEMA_VERSION = 0x05,
        IPROTO_SERVER_VERSION = 0x06,
+       IPROTO_GROUP_ID = 0x07,
        /* Leave a gap for other keys in the header. */
        IPROTO_SPACE_ID = 0x10,
        IPROTO_INDEX_ID = 0x11,
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index dc18f71b..ef544c87 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -418,6 +418,7 @@ box.schema.space.create = function(name, options)
         field_count = 'number',
         user = 'string, number',
         format = 'table',
+        is_local = 'boolean',
         temporary = 'boolean',
     }
     local options_defaults = {
@@ -461,6 +462,7 @@ box.schema.space.create = function(name, options)
     format = update_format(format)
     -- filter out global parameters from the options array
     local space_options = setmap({
+        group_id = options.is_local and 1 or nil,
         temporary = options.temporary and true or nil,
     })
     _space:insert{id, uid, name, options.engine, options.field_count,
@@ -2369,7 +2371,11 @@ local function box_space_mt(tab)
     for k,v in pairs(tab) do
         -- skip system spaces and views
         if type(k) == 'string' and #k > 0 and k:sub(1,1) ~= '_' then
-            t[k] = { engine = v.engine, temporary = v.temporary }
+            t[k] = {
+                engine = v.engine,
+                is_local = v.is_local,
+                temporary = v.temporary,
+            }
         end
     end
     return t
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 52438275..580e0ea2 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -47,6 +47,7 @@ extern "C" {
 #include "box/vclock.h" /* VCLOCK_MAX */
 #include "box/sequence.h"
 #include "box/coll_id_cache.h"
+#include "box/replication.h" /* GROUP_LOCAL */
 
 /**
  * Trigger function for all spaces
@@ -160,6 +161,11 @@ lbox_fillspace(struct lua_State *L, struct space *space, 
int i)
        lua_pushnumber(L, space_id(space));
        lua_settable(L, i);
 
+       /* space.group_id */
+       lua_pushstring(L, "is_local");
+       lua_pushboolean(L, space_group_id(space) == GROUP_LOCAL);
+       lua_settable(L, i);
+
        /* space.is_temp */
        lua_pushstring(L, "temporary");
        lua_pushboolean(L, space_is_temporary(space));
diff --git a/src/box/lua/xlog.c b/src/box/lua/xlog.c
index 2271c829..3c7cab38 100644
--- a/src/box/lua/xlog.c
+++ b/src/box/lua/xlog.c
@@ -211,6 +211,11 @@ lbox_xlog_parser_iterate(struct lua_State *L)
                lua_pushinteger(L, row.replica_id);
                lua_settable(L, -3); /* replica_id */
        }
+       if (row.group_id != 0) {
+               lbox_xlog_pushkey(L, iproto_key_name(IPROTO_GROUP_ID));
+               lua_pushinteger(L, row.group_id);
+               lua_settable(L, -3); /* group_id */
+       }
        if (row.tm != 0) {
                lbox_xlog_pushkey(L, iproto_key_name(IPROTO_TIMESTAMP));
                lua_pushnumber(L, row.tm);
diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
index 74c6be8d..c210afbe 100644
--- a/src/box/memtx_engine.c
+++ b/src/box/memtx_engine.c
@@ -555,19 +555,20 @@ checkpoint_write_row(struct xlog *l, struct xrow_header 
*row)
 }
 
 static int
-checkpoint_write_tuple(struct xlog *l, uint32_t space_id,
+checkpoint_write_tuple(struct xlog *l, struct space *space,
                       const char *data, uint32_t size)
 {
        struct request_replace_body body;
        body.m_body = 0x82; /* map of two elements. */
        body.k_space_id = IPROTO_SPACE_ID;
        body.m_space_id = 0xce; /* uint32 */
-       body.v_space_id = mp_bswap_u32(space_id);
+       body.v_space_id = mp_bswap_u32(space_id(space));
        body.k_tuple = IPROTO_TUPLE;
 
        struct xrow_header row;
        memset(&row, 0, sizeof(struct xrow_header));
        row.type = IPROTO_INSERT;
+       row.group_id = space_group_id(space);
 
        row.bodycnt = 2;
        row.body[0].iov_base = &body;
@@ -692,8 +693,7 @@ checkpoint_f(va_list ap)
                struct snapshot_iterator *it = entry->iterator;
                for (data = it->next(it, &size); data != NULL;
                     data = it->next(it, &size)) {
-                       if (checkpoint_write_tuple(&snap,
-                                       space_id(entry->space),
+                       if (checkpoint_write_tuple(&snap, entry->space,
                                        data, size) != 0) {
                                xlog_close(&snap, false);
                                return -1;
diff --git a/src/box/relay.cc b/src/box/relay.cc
index c91e5aed..75c3d56a 100644
--- a/src/box/relay.cc
+++ b/src/box/relay.cc
@@ -620,7 +620,12 @@ static void
 relay_send_initial_join_row(struct xstream *stream, struct xrow_header *row)
 {
        struct relay *relay = container_of(stream, struct relay, stream);
-       relay_send(relay, row);
+       /*
+        * Ignore replica local requests as we don't need to promote
+        * vclock while sending a snapshot.
+        */
+       if (row->group_id != GROUP_LOCAL)
+               relay_send(relay, row);
 }
 
 /** Send a single row to the client. */
@@ -630,6 +635,16 @@ relay_send_row(struct xstream *stream, struct xrow_header 
*packet)
        struct relay *relay = container_of(stream, struct relay, stream);
        assert(iproto_type_is_dml(packet->type));
        /*
+        * Transform replica local requests to IPROTO_NOP so as to
+        * promote vclock on the replica without actually modifying
+        * any data.
+        */
+       if (packet->group_id == GROUP_LOCAL) {
+               packet->type = IPROTO_NOP;
+               packet->group_id = GROUP_DEFAULT;
+               packet->bodycnt = 0;
+       }
+       /*
         * We're feeding a WAL, thus responding to SUBSCRIBE request.
         * In that case, only send a row if it is not from the same replica
         * (i.e. don't send replica's own rows back) or if this row is
diff --git a/src/box/replication.h b/src/box/replication.h
index fdf995c3..6956837a 100644
--- a/src/box/replication.h
+++ b/src/box/replication.h
@@ -96,6 +96,20 @@ extern "C" {
 
 struct gc_consumer;
 
+/** Predefined replication group identifiers. */
+enum {
+       /**
+        * Default replication group: changes made to the space
+        * are replicated throughout the entire cluster.
+        */
+       GROUP_DEFAULT = 0,
+       /**
+        * Replica local space: changes made to the space are
+        * not replicated.
+        */
+       GROUP_LOCAL = 1,
+};
+
 static const int REPLICATION_CONNECT_QUORUM_ALL = INT_MAX;
 
 /**
diff --git a/src/box/space.h b/src/box/space.h
index 074e2462..ae32e6df 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -198,6 +198,13 @@ space_is_temporary(struct space *space)
        return space->def->opts.is_temporary;
 }
 
+/** Return replication group id of a space. */
+static inline bool
+space_group_id(struct space *space)
+{
+       return space->def->opts.group_id;
+}
+
 void
 space_run_triggers(struct space *space, bool yesno);
 
diff --git a/src/box/space_def.c b/src/box/space_def.c
index ff35cb20..6243c2c4 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -34,10 +34,12 @@
 #include "error.h"
 
 const struct space_opts space_opts_default = {
+       /* .group_id = */ 0,
        /* .is_temporary = */ false,
 };
 
 const struct opt_def space_opts_reg[] = {
+       OPT_DEF("group_id", OPT_UINT32, struct space_opts, group_id),
        OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary),
        OPT_END,
 };
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 6cee6ad8..f66417d9 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -41,6 +41,11 @@ extern "C" {
 
 /** Space options */
 struct space_opts {
+       /**
+        * Replication group identifier. Defines how changes
+        * made to a space are replicated.
+        */
+       uint32_t group_id;
         /**
         * The space is a temporary:
         * - it is empty at server start
diff --git a/src/box/txn.c b/src/box/txn.c
index 3a811ae5..cb301015 100644
--- a/src/box/txn.c
+++ b/src/box/txn.c
@@ -61,6 +61,7 @@ txn_add_redo(struct txn_stmt *stmt, struct request *request)
        /* Initialize members explicitly to save time on memset() */
        row->type = request->type;
        row->replica_id = 0;
+       row->group_id = stmt->space != NULL ? space_group_id(stmt->space) : 0;
        row->lsn = 0;
        row->sync = 0;
        row->tm = 0;
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 08a83bb5..8518e945 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -595,6 +595,11 @@ vinyl_engine_check_space_def(struct space_def *def)
                         def->name, "engine does not support temporary flag");
                return -1;
        }
+       if (def->opts.group_id != 0) {
+               diag_set(ClientError, ER_ALTER_SPACE, def->name,
+                        "engine does not support replication groups");
+               return -1;
+       }
        return 0;
 }
 
diff --git a/src/box/xrow.c b/src/box/xrow.c
index 64d845f7..11316906 100644
--- a/src/box/xrow.c
+++ b/src/box/xrow.c
@@ -97,6 +97,9 @@ error:
                case IPROTO_REPLICA_ID:
                        header->replica_id = mp_decode_uint(pos);
                        break;
+               case IPROTO_GROUP_ID:
+                       header->group_id = mp_decode_uint(pos);
+                       break;
                case IPROTO_LSN:
                        header->lsn = mp_decode_uint(pos);
                        break;
@@ -178,6 +181,12 @@ xrow_header_encode(const struct xrow_header *header, 
uint64_t sync,
                map_size++;
        }
 
+       if (header->group_id) {
+               d = mp_encode_uint(d, IPROTO_GROUP_ID);
+               d = mp_encode_uint(d, header->group_id);
+               map_size++;
+       }
+
        if (header->lsn) {
                d = mp_encode_uint(d, IPROTO_LSN);
                d = mp_encode_uint(d, header->lsn);
diff --git a/src/box/xrow.h b/src/box/xrow.h
index 1bb5f103..92ea3c97 100644
--- a/src/box/xrow.h
+++ b/src/box/xrow.h
@@ -57,6 +57,7 @@ struct xrow_header {
 
        uint32_t type;
        uint32_t replica_id;
+       uint32_t group_id;
        uint64_t sync;
        int64_t lsn; /* LSN must be signed for correct comparison */
        double tm;
diff --git a/test/box/misc.result b/test/box/misc.result
index 5c390ae8..9209c33a 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -398,8 +398,9 @@ t;
   - 'box.error.UPDATE_ARG_TYPE : 26'
   - 'box.error.CROSS_ENGINE_TRANSACTION : 81'
   - 'box.error.FORMAT_MISMATCH_INDEX_PART : 27'
-  - 'box.error.FUNCTION_TX_ACTIVE : 30'
   - 'box.error.injection : table: <address>
+  - 'box.error.FUNCTION_TX_ACTIVE : 30'
+  - 'box.error.ITERATOR_TYPE : 72'
   - 'box.error.TRANSACTION_YIELD : 154'
   - 'box.error.NO_SUCH_ENGINE : 57'
   - 'box.error.COMMIT_IN_SUB_STMT : 122'
@@ -449,7 +450,7 @@ t;
   - 'box.error.COMPRESSION : 119'
   - 'box.error.INVALID_ORDER : 68'
   - 'box.error.UNKNOWN : 0'
-  - 'box.error.ITERATOR_TYPE : 72'
+  - 'box.error.NO_SUCH_GROUP : 155'
   - 'box.error.TUPLE_FORMAT_LIMIT : 16'
   - 'box.error.DROP_PRIMARY_KEY : 17'
   - 'box.error.NULLABLE_PRIMARY : 152'
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 05d892df..10097edb 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -4213,7 +4213,7 @@ s:replace{35}
 ...
 state, value = gen(param,state)
 ---
-- error: 'builtin/box/schema.lua:1032: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:1034: usage: next(param, state)'
 ...
 value
 ---
diff --git a/test/replication/local_spaces.result 
b/test/replication/local_spaces.result
new file mode 100644
index 00000000..bbae2204
--- /dev/null
+++ b/test/replication/local_spaces.result
@@ -0,0 +1,183 @@
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+--
+-- gh-3443: Check that changes done to spaces marked as local
+-- are not replicated, but vclock is still promoted.
+--
+s1 = box.schema.space.create('test1')
+---
+...
+_ = s1:create_index('pk')
+---
+...
+s2 = box.schema.space.create('test2', {is_local = true})
+---
+...
+_ = s2:create_index('pk')
+---
+...
+s1.is_local
+---
+- false
+...
+s2.is_local
+---
+- true
+...
+-- Check is_local to group_id mapping.
+box.space._space:get(s1.id)[6]
+---
+- {}
+...
+box.space._space:get(s2.id)[6]
+---
+- {'group_id': 1}
+...
+-- Check that group_id is immutable.
+box.space._space:update(s1.id, {{'=', 6, {group_id = 1}}}) -- error
+---
+- error: 'Can''t modify space ''test1'': replication group is immutable'
+...
+box.space._space:update(s2.id, {{'=', 6, {group_id = 0}}}) -- error
+---
+- error: 'Can''t modify space ''test2'': replication group is immutable'
+...
+-- Currently, there are only two replication groups:
+-- 0 (global) and 1 (local)
+box.space._space:insert{9000, 1, 'test', 'memtx', 0, {group_id = 2}, {}} -- 
error
+---
+- error: Replication group '2' does not exist
+...
+_ = s1:insert{1}
+---
+...
+_ = s2:insert{1}
+---
+...
+box.snapshot()
+---
+- ok
+...
+_ = s1:insert{2}
+---
+...
+_ = s2:insert{2}
+---
+...
+box.schema.user.grant('guest', 'replication')
+---
+...
+test_run:cmd("create server replica with rpl_master=default, 
script='replication/replica.lua'")
+---
+- true
+...
+test_run:cmd("start server replica")
+---
+- true
+...
+test_run:cmd("switch replica")
+---
+- true
+...
+box.space.test1.is_local
+---
+- false
+...
+box.space.test2.is_local
+---
+- true
+...
+box.space.test1:select()
+---
+- - [1]
+  - [2]
+...
+box.space.test2:select()
+---
+- []
+...
+for i = 1, 3 do box.space.test2:insert{i, i} end
+---
+...
+test_run:cmd("switch default")
+---
+- true
+...
+_ = s1:insert{3}
+---
+...
+_ = s2:insert{3}
+---
+...
+vclock = test_run:get_vclock('default')
+---
+...
+_ = test_run:wait_vclock('replica', vclock)
+---
+...
+test_run:cmd("switch replica")
+---
+- true
+...
+box.space.test1:select()
+---
+- - [1]
+  - [2]
+  - [3]
+...
+box.space.test2:select()
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+...
+test_run:cmd("restart server replica")
+box.space.test1:select()
+---
+- - [1]
+  - [2]
+  - [3]
+...
+box.space.test2:select()
+---
+- - [1, 1]
+  - [2, 2]
+  - [3, 3]
+...
+test_run:cmd("switch default")
+---
+- true
+...
+test_run:cmd("stop server replica")
+---
+- true
+...
+test_run:cmd("cleanup server replica")
+---
+- true
+...
+box.schema.user.revoke('guest', 'replication')
+---
+...
+s1:select()
+---
+- - [1]
+  - [2]
+  - [3]
+...
+s2:select()
+---
+- - [1]
+  - [2]
+  - [3]
+...
+s1:drop()
+---
+...
+s2:drop()
+---
+...
diff --git a/test/replication/local_spaces.test.lua 
b/test/replication/local_spaces.test.lua
new file mode 100644
index 00000000..97443c03
--- /dev/null
+++ b/test/replication/local_spaces.test.lua
@@ -0,0 +1,67 @@
+env = require('test_run')
+test_run = env.new()
+
+--
+-- gh-3443: Check that changes done to spaces marked as local
+-- are not replicated, but vclock is still promoted.
+--
+
+s1 = box.schema.space.create('test1')
+_ = s1:create_index('pk')
+s2 = box.schema.space.create('test2', {is_local = true})
+_ = s2:create_index('pk')
+s1.is_local
+s2.is_local
+
+-- Check is_local to group_id mapping.
+box.space._space:get(s1.id)[6]
+box.space._space:get(s2.id)[6]
+
+-- Check that group_id is immutable.
+box.space._space:update(s1.id, {{'=', 6, {group_id = 1}}}) -- error
+box.space._space:update(s2.id, {{'=', 6, {group_id = 0}}}) -- error
+
+-- Currently, there are only two replication groups:
+-- 0 (global) and 1 (local)
+box.space._space:insert{9000, 1, 'test', 'memtx', 0, {group_id = 2}, {}} -- 
error
+
+_ = s1:insert{1}
+_ = s2:insert{1}
+box.snapshot()
+_ = s1:insert{2}
+_ = s2:insert{2}
+
+box.schema.user.grant('guest', 'replication')
+test_run:cmd("create server replica with rpl_master=default, 
script='replication/replica.lua'")
+test_run:cmd("start server replica")
+
+test_run:cmd("switch replica")
+box.space.test1.is_local
+box.space.test2.is_local
+box.space.test1:select()
+box.space.test2:select()
+for i = 1, 3 do box.space.test2:insert{i, i} end
+
+test_run:cmd("switch default")
+_ = s1:insert{3}
+_ = s2:insert{3}
+vclock = test_run:get_vclock('default')
+_ = test_run:wait_vclock('replica', vclock)
+
+test_run:cmd("switch replica")
+box.space.test1:select()
+box.space.test2:select()
+test_run:cmd("restart server replica")
+box.space.test1:select()
+box.space.test2:select()
+
+test_run:cmd("switch default")
+test_run:cmd("stop server replica")
+test_run:cmd("cleanup server replica")
+box.schema.user.revoke('guest', 'replication')
+
+s1:select()
+s2:select()
+
+s1:drop()
+s2:drop()
diff --git a/test/replication/suite.cfg b/test/replication/suite.cfg
index 95e94e5a..283edcad 100644
--- a/test/replication/suite.cfg
+++ b/test/replication/suite.cfg
@@ -6,6 +6,7 @@
     "wal_off.test.lua": {},
     "hot_standby.test.lua": {},
     "rebootstrap.test.lua": {},
+    "local_spaces.test.lua": {},
     "*": {
         "memtx": {"engine": "memtx"},
         "vinyl": {"engine": "vinyl"}
diff --git a/test/vinyl/ddl.result b/test/vinyl/ddl.result
index 3e65e232..5b49f51f 100644
--- a/test/vinyl/ddl.result
+++ b/test/vinyl/ddl.result
@@ -44,6 +44,11 @@ space:create_index('pk', {bloom_fpr = 1.1})
 space:drop()
 ---
 ...
+-- vinyl does not support replica local spaces
+space = box.schema.space.create('test', {engine = 'vinyl', is_local = true})
+---
+- error: 'Can''t modify space ''test'': engine does not support replication 
groups'
+...
 -- space secondary index create
 space = box.schema.space.create('test', { engine = 'vinyl' })
 ---
diff --git a/test/vinyl/ddl.test.lua b/test/vinyl/ddl.test.lua
index 45c5cf8e..44cfa2ac 100644
--- a/test/vinyl/ddl.test.lua
+++ b/test/vinyl/ddl.test.lua
@@ -12,6 +12,9 @@ space:create_index('pk', {bloom_fpr = 0})
 space:create_index('pk', {bloom_fpr = 1.1})
 space:drop()
 
+-- vinyl does not support replica local spaces
+space = box.schema.space.create('test', {engine = 'vinyl', is_local = true})
+
 -- space secondary index create
 space = box.schema.space.create('test', { engine = 'vinyl' })
 index1 = space:create_index('primary')
-- 
2.11.0


Other related posts:

  • » [tarantool-patches] [PATCH v3] Introduce replica local spaces - Vladimir Davydov