diff --git a/Makefile b/Makefile
index 8d65c18b059a645b16bc8b78ab40c5da4e55835f..cad767c45e5d61337c092c18a1b8bec724427605 100644
--- a/Makefile
+++ b/Makefile
@@ -5,39 +5,29 @@
 ECHO=/bin/echo
 CAT=/bin/cat
 
-# make magick
+# make magic
 SUB_MAKE:=$(filter _%,$(notdir $(CURDIR)))
 OBJDIR:=$(shell $(ECHO) $(filter _%,$(MAKECMDGOALS)) | tr ' ' '\n' | cut -d/ -f1 | sort | uniq)
 ifeq (,$(SUB_MAKE))
-ifneq (,$(OBJDIR))
-.SUFFIXES:
-MAKEFLAGS += -rR --no-print-directory SRCDIR=$(CURDIR) VPATH=$(CURDIR)
-FILTERED_MAKECMDGOALS=$(subst $@/,,$(filter $@/%,$(MAKECMDGOALS)))
-MODULE=$(subst tarantool_,,$(FILTERED_MAKECMDGOALS))
-.PHONY: $(OBJDIR)
-$(OBJDIR):
+  ifneq (,$(OBJDIR))
+    .SUFFIXES:
+    MAKEFLAGS += -rR --no-print-directory VPATH=$(CURDIR)
+    FILTERED_MAKECMDGOALS=$(subst $@/,,$(filter $@/%,$(MAKECMDGOALS)))
+    module=$(subst tarantool_,,$(FILTERED_MAKECMDGOALS))
+    .PHONY: $(OBJDIR)
+    $(OBJDIR):
 	+@mkdir -p $@
-	+@$(MAKE) -C $@ -f $(CURDIR)/Makefile OBJDIR=$@ module=$(MODULE) $(FILTERED_MAKECMDGOALS)
-
-Makefile: ;
-%.mk :: ;
-% :: $(OBJDIR) ; @:
-else
-SRCDIR:=$(CURDIR)
-endif
-endif
-
-# this global rules are always defined
-ifeq (,$(module))
-all: 
-	@echo "Valid targets are:"
-	@echo "	_release_box/tarantool_silverbox"
-	@echo "	_release_feeder/tarantool_feeder"
-	@echo "	_debug_box/tarantool_silverbox"
-	@echo "	_debug_feeder/tarantool_feeder"
-	@echo "	clean"
+	+@$(MAKE) -C $@ -f $(CURDIR)/Makefile SRCDIR=$(CURDIR) OBJDIR=$@ module=$(module) $(FILTERED_MAKECMDGOALS)
+
+    Makefile: ;
+    %.mk :: ;
+    % :: $(OBJDIR) ; @:
+  else
+    SRCDIR:=$(CURDIR)
+    include $(SRCDIR)/scripts/rules.mk
+  endif
 else
-all: tarantool_$(module)
+  include $(SRCDIR)/scripts/rules.mk
 endif
 
 ifeq ("$(origin module)", "command line")
@@ -51,104 +41,9 @@ clean:
 	@for mod in mod/*; do $(MAKE) --no-print-directory module=`basename $$mod` clean; done
 endif
 .PHONY: TAGS
-tags:
+TAGS:
 	ctags -R -e -f TAGS
 
-# then SRCDIR is defined, build type is selected and it's time to define build rules
-ifneq (,$(SRCDIR))
--include $(SRCDIR)/config.mk $(SRCDIR)/scripts/config.mk
-include $(SRCDIR)/scripts/config_def.mk
-
-ifeq (_debug,$(OBJDIR))
-  DEBUG=0
-else ifeq (_test,$(OBJDIR))
- CFLAGS += --coverage -DCOVERAGE -DNDEBUG
-else ifeq (_coverage,$(OBJDIR))
- CFLAGS += --coverage -DCOVERAGE
-endif
-
-# build dir includes going first
-ifneq (,$(OBJDIR))
-CFLAGS += -I$(SRCDIR)/$(OBJDIR) -I$(SRCDIR)/$(OBJDIR)/include
-endif
-CFLAGS += -I$(SRCDIR) -I$(SRCDIR)/include
-LIBS += -lm
-
-subdirs += third_party
-ifneq (,$(module))
-  subdirs += mod/$(module)
-endif
-subdirs += cfg core
-include $(foreach dir,$(subdirs),$(SRCDIR)/$(dir)/Makefile)
-
-tarantool_$(module): $(obj)
-	$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) $^ -o $@
-
-ifdef I
-$(info * build with $(filter -D%,$(CFLAGS)))
-endif
-
-# makefile change will force rebuild
-$(obj): $(wildcard ../*.mk) $(wildcard ../scripts/*.mk)
-$(obj): $(foreach dir,$(subdirs),$(SRCDIR)/$(dir)/Makefile)
-$(obj): Makefile
-
-dep = $(patsubst %.o,%.d,$(obj)) $(patsubst %.o,%.pd,$(obj))
--include $(dep)
-
-ifneq (,$(OBJDIR))
-%.o: %.c
-	@mkdir -p $(dir $@)
-	$(CC) $(CFLAGS) -MD -c $< -o $@
-	@sed -n -f $(SRCDIR)/scripts/slurp.sed \
-		-f $(SRCDIR)/scripts/fixdep.sed \
-		-e 's!$(SRCDIR)/!!; p' \
-		< $(@:.o=.d) > $(@:.o=.pd)
-else
-%.o: %.c
-	@mkdir -p $(dir $@)
-	$(CC) $(CFLAGS) -MD -c $< -o $@
-endif
-
-# code gen targets
-.PRECIOUS: %.h %.c %.dot
-ifeq ($(HAVE_RAGEL),1)
-%.c: %.rl
-	@mkdir -p $(dir $@)
-	$(RAGEL) -G2 $< -o $@
-
-%.dot: %.rl
-	@mkdir -p $(dir $@)
-	$(RAGEL) -p -V $< -o $(basename $@).dot
-
-%.png: %.dot
-	@mkdir -p $(dir $@)
-	$(DOT) -Tpng $< -o $(basename $@).png
-endif
-ifeq ($(HAVE_CONFETTI),1)
-%.cfg: %.cfg_tmpl
-	@mkdir -p $(dir $@)
-	$(CONFETTI) -i $< -n tarantool_cfg -f $@
-
-%.h: %.cfg_tmpl
-	@mkdir -p $(dir $@)
-	$(CONFETTI) -i $< -n tarantool_cfg -h $@
-
-%.c: %.cfg_tmpl
-	@mkdir -p $(dir $@)
-	$(CONFETTI) -i $< -n tarantool_cfg -c $@
-endif
-
-ifeq ($(HAVE_GIT),1)
-tarantool_version.h: FORCE
-	@echo -n "const char tarantool_version_string[] = " > $@_
-	@git show HEAD | sed 's/commit \(.*\)/\"\1/;q' | tr -d \\n >> $@_
-	@git diff --quiet || (echo -n ' AND'; git diff --shortstat) | tr -d \\n >> $@_
-	@echo '";' >> $@_
-	@diff -q $@ $@_ 2>/dev/null >/dev/null || ($(ECHO) "	GEN	" $(notdir $@); cp $@_ $@)
-FORCE:
-endif
-
 
 ifeq ("$(origin V)", "command line")
   VERBOSE = $(V)
@@ -160,5 +55,3 @@ ifeq (,$(VERBOSE))
   $(eval override CONFETTI = @$(ECHO) "	CNF	" $$@; $(CONFETTI))
   $(eval override CAT = @$(ECHO) "	CAT	" $$@; $(CAT))
 endif
-
-endif
diff --git a/README b/README
index 276427712ef40001b6ca12bbdc231d320ab76e03..3406cf84d257205124676a34af12da1470982ef0 100644
--- a/README
+++ b/README
@@ -19,8 +19,8 @@ Cons:
 
 How to run:
 
-1) compile
-	make _release_box/tarantool_silverbox
+1) compile (note GNU make is required)
+	(g)make _release_box/tarantool_silverbox
 2) customize config
 	emacs cfg/tarantool_silverbox_cfg.cfg
 3) initialize storage
diff --git a/cfg/core_cfg.cfg_tmpl b/cfg/core_cfg.cfg_tmpl
index f2383cb0c1858ee91a7b0c10d00b9fe4fc5fdcf4..b2b930d91892b63d1aa571caac9200e175402b7b 100644
--- a/cfg/core_cfg.cfg_tmpl
+++ b/cfg/core_cfg.cfg_tmpl
@@ -37,9 +37,6 @@ logger_nonblock=1
 # delay between loop iteraions
 io_collect_interval=0.0
 
-# do not write snapshot faster then snap_io_rate_limit MBytes/sec
-snap_io_rate_limit=0.0
-
 # size of listen backlog
 backlog=1024
 
diff --git a/cfg/tarantool_feeder_cfg.c b/cfg/tarantool_feeder_cfg.c
index a57bacbdd887d931f2ac69482b9891230492dca8..4b8515cb66abaf240c2658bee87fc9c2a2166835 100644
--- a/cfg/tarantool_feeder_cfg.c
+++ b/cfg/tarantool_feeder_cfg.c
@@ -40,12 +40,12 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	c->logger = NULL;
 	c->logger_nonblock = 1;
 	c->io_collect_interval = 0;
-	c->snap_io_rate_limit = 0;
 	c->backlog = 1024;
 	c->readahead = 16320;
 	c->wal_feeder_bind_ipaddr = NULL;
 	c->wal_feeder_bind_port = 0;
 	c->wal_feeder_dir = NULL;
+	c->custom_proc_title = NULL;
 	return 0;
 }
 
@@ -85,9 +85,6 @@ static NameAtom _name__logger_nonblock[] = {
 static NameAtom _name__io_collect_interval[] = {
 	{ "io_collect_interval", -1, NULL }
 };
-static NameAtom _name__snap_io_rate_limit[] = {
-	{ "snap_io_rate_limit", -1, NULL }
-};
 static NameAtom _name__backlog[] = {
 	{ "backlog", -1, NULL }
 };
@@ -103,6 +100,9 @@ static NameAtom _name__wal_feeder_bind_port[] = {
 static NameAtom _name__wal_feeder_dir[] = {
 	{ "wal_feeder_dir", -1, NULL }
 };
+static NameAtom _name__custom_proc_title[] = {
+	{ "custom_proc_title", -1, NULL }
+};
 
 #define ARRAYALLOC(x,n,t)  do {                                     \
    int l = 0, ar;                                                   \
@@ -244,14 +244,6 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		if ( (c->io_collect_interval == 0 || c->io_collect_interval == -HUGE_VAL || c->io_collect_interval == HUGE_VAL) && errno == ERANGE)
 			return CNF_WRONGRANGE;
 	}
-	else if ( cmpNameAtoms( opt->name, _name__snap_io_rate_limit) ) {
-		if (opt->paramType != numberType )
-			return CNF_WRONGTYPE;
-		errno = 0;
-		c->snap_io_rate_limit = strtod(opt->paramValue.numberval, NULL);
-		if ( (c->snap_io_rate_limit == 0 || c->snap_io_rate_limit == -HUGE_VAL || c->snap_io_rate_limit == HUGE_VAL) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-	}
 	else if ( cmpNameAtoms( opt->name, _name__backlog) ) {
 		if (opt->paramType != numberType )
 			return CNF_WRONGTYPE;
@@ -301,6 +293,14 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		 if (opt->paramValue.stringval && c->wal_feeder_dir == NULL)
 			return CNF_NOMEMORY;
 	}
+	else if ( cmpNameAtoms( opt->name, _name__custom_proc_title) ) {
+		if (opt->paramType != stringType )
+			return CNF_WRONGTYPE;
+		errno = 0;
+		c->custom_proc_title = (opt->paramValue.stringval) ? strdup(opt->paramValue.stringval) : NULL;
+		 if (opt->paramValue.stringval && c->custom_proc_title == NULL)
+			return CNF_NOMEMORY;
+	}
 	else {
 		return CNF_MISSED;
 	}
@@ -366,6 +366,10 @@ acceptCfgDef(tarantool_cfg *c, OptDef *opt, int check_rdonly, int *n_accepted, i
 				out_warning(r, "Not enough memory to accept '%s' option", dumpOptDef(opt->name));
 				if (n_skipped) (*n_skipped)++;
 				break;
+			case CNF_NOTSET:
+				out_warning(r, "Option '%s' is not set (or has a default value)", dumpOptDef(opt->name));
+				if (n_skipped) (*n_skipped)++;
+				break;
 			default:
 				out_warning(r, "Unknown error for '%s' option", dumpOptDef(opt->name));
 				if (n_skipped) (*n_skipped)++;
@@ -409,12 +413,12 @@ typedef enum IteratorState {
 	S_name__logger,
 	S_name__logger_nonblock,
 	S_name__io_collect_interval,
-	S_name__snap_io_rate_limit,
 	S_name__backlog,
 	S_name__readahead,
 	S_name__wal_feeder_bind_ipaddr,
 	S_name__wal_feeder_bind_port,
 	S_name__wal_feeder_dir,
+	S_name__custom_proc_title,
 	_S_Finished
 } IteratorState;
 
@@ -565,17 +569,6 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 			}
 			sprintf(*v, "%g", c->io_collect_interval);
 			snprintf(buf, PRINTBUFLEN-1, "io_collect_interval");
-			i->state = S_name__snap_io_rate_limit;
-			return buf;
-		case S_name__snap_io_rate_limit:
-			*v = malloc(32);
-			if (*v == NULL) {
-				free(i);
-				out_warning(CNF_NOMEMORY, "No memory to output value");
-				return NULL;
-			}
-			sprintf(*v, "%g", c->snap_io_rate_limit);
-			snprintf(buf, PRINTBUFLEN-1, "snap_io_rate_limit");
 			i->state = S_name__backlog;
 			return buf;
 		case S_name__backlog:
@@ -629,6 +622,16 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 				return NULL;
 			}
 			snprintf(buf, PRINTBUFLEN-1, "wal_feeder_dir");
+			i->state = S_name__custom_proc_title;
+			return buf;
+		case S_name__custom_proc_title:
+			*v = (c->custom_proc_title) ? strdup(c->custom_proc_title) : NULL;
+			if (*v == NULL && c->custom_proc_title) {
+				free(i);
+				out_warning(CNF_NOMEMORY, "No memory to output value");
+				return NULL;
+			}
+			snprintf(buf, PRINTBUFLEN-1, "custom_proc_title");
 			i->state = _S_Finished;
 			return buf;
 		case _S_Finished:
@@ -641,3 +644,12 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 	return NULL;
 }
 
+/************** Checking of required fields  **************/
+int
+check_cfg_tarantool_cfg(tarantool_cfg *c) {
+	tarantool_cfg_iterator_t iterator, *i = &iterator;
+	int	res = 0;
+
+	return res;
+}
+
diff --git a/cfg/tarantool_feeder_cfg.cfg b/cfg/tarantool_feeder_cfg.cfg
index 7fbe2a5823ee627b633368ed1d2823444ca66928..f6c7690bdd38b73ad4c9aadaae7208fdeda82204 100644
--- a/cfg/tarantool_feeder_cfg.cfg
+++ b/cfg/tarantool_feeder_cfg.cfg
@@ -39,9 +39,6 @@ logger_nonblock = 1
 # delay between loop iteraions
 io_collect_interval = 0
 
-# do not write snapshot faster then snap_io_rate_limit MBytes/sec
-snap_io_rate_limit = 0
-
 # size of listen backlog
 backlog = 1024
 
@@ -55,3 +52,6 @@ wal_feeder_bind_port = 0
 
 # Directory with WAL files to serve
 wal_feeder_dir = NULL
+
+# custom proc title is appended after normal
+custom_proc_title = NULL
diff --git a/cfg/tarantool_feeder_cfg.cfg_tmpl b/cfg/tarantool_feeder_cfg.cfg_tmpl
index c34b958f35bd639dccb175a702422ec84aeac1d0..c87a3980a1a4d85110054b9be70f25c0f598a697 100644
--- a/cfg/tarantool_feeder_cfg.cfg_tmpl
+++ b/cfg/tarantool_feeder_cfg.cfg_tmpl
@@ -42,9 +42,6 @@ logger_nonblock=1
 # delay between loop iteraions
 io_collect_interval=0.0
 
-# do not write snapshot faster then snap_io_rate_limit MBytes/sec
-snap_io_rate_limit=0.0
-
 # size of listen backlog
 backlog=1024
 
@@ -57,3 +54,6 @@ wal_feeder_bind_port=0
 
 # Directory with WAL files to serve
 wal_feeder_dir=NULL
+
+# custom proc title is appended after normal
+custom_proc_title=NULL
diff --git a/cfg/tarantool_feeder_cfg.h b/cfg/tarantool_feeder_cfg.h
index 57955aed2eea86e8b01325242a7b66e314074cd6..f7b61f65ff2df127086848c9101199177bd23edf 100644
--- a/cfg/tarantool_feeder_cfg.h
+++ b/cfg/tarantool_feeder_cfg.h
@@ -54,9 +54,6 @@ typedef struct tarantool_cfg {
 	/* delay between loop iteraions */
 	double	io_collect_interval;
 
-	/* do not write snapshot faster then snap_io_rate_limit MBytes/sec */
-	double	snap_io_rate_limit;
-
 	/* size of listen backlog */
 	int32_t	backlog;
 
@@ -72,6 +69,9 @@ typedef struct tarantool_cfg {
 
 	/* Directory with WAL files to serve */
 	char*	wal_feeder_dir;
+
+	/* custom proc title is appended after normal */
+	char*	custom_proc_title;
 } tarantool_cfg;
 
 int fill_default_tarantool_cfg(tarantool_cfg *c);
@@ -79,6 +79,8 @@ void parse_cfg_file_tarantool_cfg(tarantool_cfg *c, FILE *fh, int check_rdonly,
 
 void parse_cfg_buffer_tarantool_cfg(tarantool_cfg *c, char *buffer, int check_rdonly, int *n_accepted, int *n_skipped);
 
+int check_cfg_tarantool_cfg(tarantool_cfg *c);
+
 typedef struct tarantool_cfg_iterator_t tarantool_cfg_iterator_t;
 tarantool_cfg_iterator_t* tarantool_cfg_iterator_init();
 char* tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char **v);
diff --git a/cfg/tarantool_silverbox_cfg.c b/cfg/tarantool_silverbox_cfg.c
index d38647366dd89ef0e0188c162bde493d84aa25fe..02db5416d2f4b4329b9e8b15d298ff0bfc47b1d6 100644
--- a/cfg/tarantool_silverbox_cfg.c
+++ b/cfg/tarantool_silverbox_cfg.c
@@ -40,7 +40,6 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	c->logger = NULL;
 	c->logger_nonblock = 1;
 	c->io_collect_interval = 0;
-	c->snap_io_rate_limit = 0;
 	c->backlog = 1024;
 	c->readahead = 16320;
 	c->snap_dir = strdup(".");
@@ -55,11 +54,14 @@ fill_default_tarantool_cfg(tarantool_cfg *c) {
 	c->memcached_namespace = 23;
 	c->memcached_expire_per_loop = 1024;
 	c->memcached_expire_full_sweep = 3600;
+	c->snap_io_rate_limit = 0;
 	c->rows_per_wal = 500000;
 	c->wal_fsync_delay = 0;
 	c->wal_writer_inbox_size = 128;
 	c->local_hot_standby = 0;
 	c->wal_dir_rescan_delay = 0.1;
+	c->panic_on_snap_error = 1;
+	c->panic_on_wal_error = 0;
 	c->remote_hot_standby = 0;
 	c->wal_feeder_ipaddr = NULL;
 	c->wal_feeder_port = 0;
@@ -78,9 +80,18 @@ acceptDefault_name__namespace(tarantool_cfg_namespace *c) {
 
 static int
 acceptDefault_name__namespace__index(tarantool_cfg_namespace_index *c) {
-	c->type = strdup("NUM");
+	c->type = strdup("");
+	if (c->type == NULL) return CNF_NOMEMORY;
+	c->unique = -1;
+	c->key_field = NULL;
+	return 0;
+}
+
+static int
+acceptDefault_name__namespace__index__key_field(tarantool_cfg_namespace_index_key_field *c) {
+	c->fieldno = -1;
+	c->type = strdup("");
 	if (c->type == NULL) return CNF_NOMEMORY;
-	c->key_position = -1;
 	return 0;
 }
 
@@ -120,9 +131,6 @@ static NameAtom _name__logger_nonblock[] = {
 static NameAtom _name__io_collect_interval[] = {
 	{ "io_collect_interval", -1, NULL }
 };
-static NameAtom _name__snap_io_rate_limit[] = {
-	{ "snap_io_rate_limit", -1, NULL }
-};
 static NameAtom _name__backlog[] = {
 	{ "backlog", -1, NULL }
 };
@@ -159,6 +167,9 @@ static NameAtom _name__memcached_expire_per_loop[] = {
 static NameAtom _name__memcached_expire_full_sweep[] = {
 	{ "memcached_expire_full_sweep", -1, NULL }
 };
+static NameAtom _name__snap_io_rate_limit[] = {
+	{ "snap_io_rate_limit", -1, NULL }
+};
 static NameAtom _name__rows_per_wal[] = {
 	{ "rows_per_wal", -1, NULL }
 };
@@ -174,6 +185,12 @@ static NameAtom _name__local_hot_standby[] = {
 static NameAtom _name__wal_dir_rescan_delay[] = {
 	{ "wal_dir_rescan_delay", -1, NULL }
 };
+static NameAtom _name__panic_on_snap_error[] = {
+	{ "panic_on_snap_error", -1, NULL }
+};
+static NameAtom _name__panic_on_wal_error[] = {
+	{ "panic_on_wal_error", -1, NULL }
+};
 static NameAtom _name__remote_hot_standby[] = {
 	{ "remote_hot_standby", -1, NULL }
 };
@@ -200,10 +217,22 @@ static NameAtom _name__namespace__index__type[] = {
 	{ "index", -1, _name__namespace__index__type + 2 },
 	{ "type", -1, NULL }
 };
-static NameAtom _name__namespace__index__key_position[] = {
-	{ "namespace", -1, _name__namespace__index__key_position + 1 },
-	{ "index", -1, _name__namespace__index__key_position + 2 },
-	{ "key_position", -1, NULL }
+static NameAtom _name__namespace__index__unique[] = {
+	{ "namespace", -1, _name__namespace__index__unique + 1 },
+	{ "index", -1, _name__namespace__index__unique + 2 },
+	{ "unique", -1, NULL }
+};
+static NameAtom _name__namespace__index__key_field__fieldno[] = {
+	{ "namespace", -1, _name__namespace__index__key_field__fieldno + 1 },
+	{ "index", -1, _name__namespace__index__key_field__fieldno + 2 },
+	{ "key_field", -1, _name__namespace__index__key_field__fieldno + 3 },
+	{ "fieldno", -1, NULL }
+};
+static NameAtom _name__namespace__index__key_field__type[] = {
+	{ "namespace", -1, _name__namespace__index__key_field__type + 1 },
+	{ "index", -1, _name__namespace__index__key_field__type + 2 },
+	{ "key_field", -1, _name__namespace__index__key_field__type + 3 },
+	{ "type", -1, NULL }
 };
 
 #define ARRAYALLOC(x,n,t)  do {                                     \
@@ -346,14 +375,6 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		if ( (c->io_collect_interval == 0 || c->io_collect_interval == -HUGE_VAL || c->io_collect_interval == HUGE_VAL) && errno == ERANGE)
 			return CNF_WRONGRANGE;
 	}
-	else if ( cmpNameAtoms( opt->name, _name__snap_io_rate_limit) ) {
-		if (opt->paramType != numberType )
-			return CNF_WRONGTYPE;
-		errno = 0;
-		c->snap_io_rate_limit = strtod(opt->paramValue.numberval, NULL);
-		if ( (c->snap_io_rate_limit == 0 || c->snap_io_rate_limit == -HUGE_VAL || c->snap_io_rate_limit == HUGE_VAL) && errno == ERANGE)
-			return CNF_WRONGRANGE;
-	}
 	else if ( cmpNameAtoms( opt->name, _name__backlog) ) {
 		if (opt->paramType != numberType )
 			return CNF_WRONGTYPE;
@@ -474,6 +495,14 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 			return CNF_WRONGRANGE;
 		c->memcached_expire_full_sweep = i32;
 	}
+	else if ( cmpNameAtoms( opt->name, _name__snap_io_rate_limit) ) {
+		if (opt->paramType != numberType )
+			return CNF_WRONGTYPE;
+		errno = 0;
+		c->snap_io_rate_limit = strtod(opt->paramValue.numberval, NULL);
+		if ( (c->snap_io_rate_limit == 0 || c->snap_io_rate_limit == -HUGE_VAL || c->snap_io_rate_limit == HUGE_VAL) && errno == ERANGE)
+			return CNF_WRONGRANGE;
+	}
 	else if ( cmpNameAtoms( opt->name, _name__rows_per_wal) ) {
 		if (opt->paramType != numberType )
 			return CNF_WRONGTYPE;
@@ -526,6 +555,28 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		if ( (c->wal_dir_rescan_delay == 0 || c->wal_dir_rescan_delay == -HUGE_VAL || c->wal_dir_rescan_delay == HUGE_VAL) && errno == ERANGE)
 			return CNF_WRONGRANGE;
 	}
+	else if ( cmpNameAtoms( opt->name, _name__panic_on_snap_error) ) {
+		if (opt->paramType != numberType )
+			return CNF_WRONGTYPE;
+		errno = 0;
+		long int i32 = strtol(opt->paramValue.numberval, NULL, 10);
+		if (i32 == 0 && errno == EINVAL)
+			return CNF_WRONGINT;
+		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
+			return CNF_WRONGRANGE;
+		c->panic_on_snap_error = i32;
+	}
+	else if ( cmpNameAtoms( opt->name, _name__panic_on_wal_error) ) {
+		if (opt->paramType != numberType )
+			return CNF_WRONGTYPE;
+		errno = 0;
+		long int i32 = strtol(opt->paramValue.numberval, NULL, 10);
+		if (i32 == 0 && errno == EINVAL)
+			return CNF_WRONGINT;
+		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
+			return CNF_WRONGRANGE;
+		c->panic_on_wal_error = i32;
+	}
 	else if ( cmpNameAtoms( opt->name, _name__remote_hot_standby) ) {
 		if (opt->paramType != numberType )
 			return CNF_WRONGTYPE;
@@ -602,18 +653,43 @@ acceptValue(tarantool_cfg* c, OptDef* opt, int check_rdonly) {
 		 if (opt->paramValue.stringval && c->namespace[opt->name->index]->index[opt->name->next->index]->type == NULL)
 			return CNF_NOMEMORY;
 	}
-	else if ( cmpNameAtoms( opt->name, _name__namespace__index__key_position) ) {
+	else if ( cmpNameAtoms( opt->name, _name__namespace__index__unique) ) {
+		if (opt->paramType != numberType )
+			return CNF_WRONGTYPE;
+		ARRAYALLOC(c->namespace, opt->name->index + 1, _name__namespace);
+		ARRAYALLOC(c->namespace[opt->name->index]->index, opt->name->next->index + 1, _name__namespace__index);
+		errno = 0;
+		long int i32 = strtol(opt->paramValue.numberval, NULL, 10);
+		if (i32 == 0 && errno == EINVAL)
+			return CNF_WRONGINT;
+		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
+			return CNF_WRONGRANGE;
+		c->namespace[opt->name->index]->index[opt->name->next->index]->unique = i32;
+	}
+	else if ( cmpNameAtoms( opt->name, _name__namespace__index__key_field__fieldno) ) {
 		if (opt->paramType != numberType )
 			return CNF_WRONGTYPE;
 		ARRAYALLOC(c->namespace, opt->name->index + 1, _name__namespace);
 		ARRAYALLOC(c->namespace[opt->name->index]->index, opt->name->next->index + 1, _name__namespace__index);
+		ARRAYALLOC(c->namespace[opt->name->index]->index[opt->name->next->index]->key_field, opt->name->next->next->index + 1, _name__namespace__index__key_field);
 		errno = 0;
 		long int i32 = strtol(opt->paramValue.numberval, NULL, 10);
 		if (i32 == 0 && errno == EINVAL)
 			return CNF_WRONGINT;
 		if ( (i32 == LONG_MIN || i32 == LONG_MAX) && errno == ERANGE)
 			return CNF_WRONGRANGE;
-		c->namespace[opt->name->index]->index[opt->name->next->index]->key_position = i32;
+		c->namespace[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->fieldno = i32;
+	}
+	else if ( cmpNameAtoms( opt->name, _name__namespace__index__key_field__type) ) {
+		if (opt->paramType != stringType )
+			return CNF_WRONGTYPE;
+		ARRAYALLOC(c->namespace, opt->name->index + 1, _name__namespace);
+		ARRAYALLOC(c->namespace[opt->name->index]->index, opt->name->next->index + 1, _name__namespace__index);
+		ARRAYALLOC(c->namespace[opt->name->index]->index[opt->name->next->index]->key_field, opt->name->next->next->index + 1, _name__namespace__index__key_field);
+		errno = 0;
+		c->namespace[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type = (opt->paramValue.stringval) ? strdup(opt->paramValue.stringval) : NULL;
+		 if (opt->paramValue.stringval && c->namespace[opt->name->index]->index[opt->name->next->index]->key_field[opt->name->next->next->index]->type == NULL)
+			return CNF_NOMEMORY;
 	}
 	else {
 		return CNF_MISSED;
@@ -680,6 +756,10 @@ acceptCfgDef(tarantool_cfg *c, OptDef *opt, int check_rdonly, int *n_accepted, i
 				out_warning(r, "Not enough memory to accept '%s' option", dumpOptDef(opt->name));
 				if (n_skipped) (*n_skipped)++;
 				break;
+			case CNF_NOTSET:
+				out_warning(r, "Option '%s' is not set (or has a default value)", dumpOptDef(opt->name));
+				if (n_skipped) (*n_skipped)++;
+				break;
 			default:
 				out_warning(r, "Unknown error for '%s' option", dumpOptDef(opt->name));
 				if (n_skipped) (*n_skipped)++;
@@ -723,7 +803,6 @@ typedef enum IteratorState {
 	S_name__logger,
 	S_name__logger_nonblock,
 	S_name__io_collect_interval,
-	S_name__snap_io_rate_limit,
 	S_name__backlog,
 	S_name__readahead,
 	S_name__snap_dir,
@@ -736,11 +815,14 @@ typedef enum IteratorState {
 	S_name__memcached_namespace,
 	S_name__memcached_expire_per_loop,
 	S_name__memcached_expire_full_sweep,
+	S_name__snap_io_rate_limit,
 	S_name__rows_per_wal,
 	S_name__wal_fsync_delay,
 	S_name__wal_writer_inbox_size,
 	S_name__local_hot_standby,
 	S_name__wal_dir_rescan_delay,
+	S_name__panic_on_snap_error,
+	S_name__panic_on_wal_error,
 	S_name__remote_hot_standby,
 	S_name__wal_feeder_ipaddr,
 	S_name__wal_feeder_port,
@@ -750,7 +832,10 @@ typedef enum IteratorState {
 	S_name__namespace__estimated_rows,
 	S_name__namespace__index,
 	S_name__namespace__index__type,
-	S_name__namespace__index__key_position,
+	S_name__namespace__index__unique,
+	S_name__namespace__index__key_field,
+	S_name__namespace__index__key_field__fieldno,
+	S_name__namespace__index__key_field__type,
 	_S_Finished
 } IteratorState;
 
@@ -758,6 +843,7 @@ struct tarantool_cfg_iterator_t {
 	IteratorState	state;
 	int	idx_name__namespace;
 	int	idx_name__namespace__index;
+	int	idx_name__namespace__index__key_field;
 };
 
 tarantool_cfg_iterator_t*
@@ -903,17 +989,6 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 			}
 			sprintf(*v, "%g", c->io_collect_interval);
 			snprintf(buf, PRINTBUFLEN-1, "io_collect_interval");
-			i->state = S_name__snap_io_rate_limit;
-			return buf;
-		case S_name__snap_io_rate_limit:
-			*v = malloc(32);
-			if (*v == NULL) {
-				free(i);
-				out_warning(CNF_NOMEMORY, "No memory to output value");
-				return NULL;
-			}
-			sprintf(*v, "%g", c->snap_io_rate_limit);
-			snprintf(buf, PRINTBUFLEN-1, "snap_io_rate_limit");
 			i->state = S_name__backlog;
 			return buf;
 		case S_name__backlog:
@@ -1043,6 +1118,17 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 			}
 			sprintf(*v, "%"PRId32, c->memcached_expire_full_sweep);
 			snprintf(buf, PRINTBUFLEN-1, "memcached_expire_full_sweep");
+			i->state = S_name__snap_io_rate_limit;
+			return buf;
+		case S_name__snap_io_rate_limit:
+			*v = malloc(32);
+			if (*v == NULL) {
+				free(i);
+				out_warning(CNF_NOMEMORY, "No memory to output value");
+				return NULL;
+			}
+			sprintf(*v, "%g", c->snap_io_rate_limit);
+			snprintf(buf, PRINTBUFLEN-1, "snap_io_rate_limit");
 			i->state = S_name__rows_per_wal;
 			return buf;
 		case S_name__rows_per_wal:
@@ -1098,6 +1184,28 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 			}
 			sprintf(*v, "%g", c->wal_dir_rescan_delay);
 			snprintf(buf, PRINTBUFLEN-1, "wal_dir_rescan_delay");
+			i->state = S_name__panic_on_snap_error;
+			return buf;
+		case S_name__panic_on_snap_error:
+			*v = malloc(32);
+			if (*v == NULL) {
+				free(i);
+				out_warning(CNF_NOMEMORY, "No memory to output value");
+				return NULL;
+			}
+			sprintf(*v, "%"PRId32, c->panic_on_snap_error);
+			snprintf(buf, PRINTBUFLEN-1, "panic_on_snap_error");
+			i->state = S_name__panic_on_wal_error;
+			return buf;
+		case S_name__panic_on_wal_error:
+			*v = malloc(32);
+			if (*v == NULL) {
+				free(i);
+				out_warning(CNF_NOMEMORY, "No memory to output value");
+				return NULL;
+			}
+			sprintf(*v, "%"PRId32, c->panic_on_wal_error);
+			snprintf(buf, PRINTBUFLEN-1, "panic_on_wal_error");
 			i->state = S_name__remote_hot_standby;
 			return buf;
 		case S_name__remote_hot_standby:
@@ -1139,7 +1247,10 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 		case S_name__namespace__estimated_rows:
 		case S_name__namespace__index:
 		case S_name__namespace__index__type:
-		case S_name__namespace__index__key_position:
+		case S_name__namespace__index__unique:
+		case S_name__namespace__index__key_field:
+		case S_name__namespace__index__key_field__fieldno:
+		case S_name__namespace__index__key_field__type:
 			if (c->namespace && c->namespace[i->idx_name__namespace]) {
 				switch(i->state) {
 					case S_name__namespace:
@@ -1179,7 +1290,10 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 					case S_name__namespace__index:
 						i->state = S_name__namespace__index;
 					case S_name__namespace__index__type:
-					case S_name__namespace__index__key_position:
+					case S_name__namespace__index__unique:
+					case S_name__namespace__index__key_field:
+					case S_name__namespace__index__key_field__fieldno:
+					case S_name__namespace__index__key_field__type:
 						if (c->namespace[i->idx_name__namespace]->index && c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]) {
 							switch(i->state) {
 								case S_name__namespace__index:
@@ -1191,20 +1305,58 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 										return NULL;
 									}
 									snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].type", i->idx_name__namespace, i->idx_name__namespace__index);
-									i->state = S_name__namespace__index__key_position;
+									i->state = S_name__namespace__index__unique;
 									return buf;
-								case S_name__namespace__index__key_position:
+								case S_name__namespace__index__unique:
 									*v = malloc(32);
 									if (*v == NULL) {
 										free(i);
 										out_warning(CNF_NOMEMORY, "No memory to output value");
 										return NULL;
 									}
-									sprintf(*v, "%"PRId32, c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_position);
-									snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].key_position", i->idx_name__namespace, i->idx_name__namespace__index);
-									i->state = S_name__namespace__index;
-									i->idx_name__namespace__index++;
+									sprintf(*v, "%"PRId32, c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->unique);
+									snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].unique", i->idx_name__namespace, i->idx_name__namespace__index);
+									i->state = S_name__namespace__index__key_field;
 									return buf;
+								case S_name__namespace__index__key_field:
+									i->state = S_name__namespace__index__key_field;
+								case S_name__namespace__index__key_field__fieldno:
+								case S_name__namespace__index__key_field__type:
+									if (c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field && c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]) {
+										switch(i->state) {
+											case S_name__namespace__index__key_field:
+											case S_name__namespace__index__key_field__fieldno:
+												*v = malloc(32);
+												if (*v == NULL) {
+													free(i);
+													out_warning(CNF_NOMEMORY, "No memory to output value");
+													return NULL;
+												}
+												sprintf(*v, "%"PRId32, c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]->fieldno);
+												snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].key_field[%d].fieldno", i->idx_name__namespace, i->idx_name__namespace__index, i->idx_name__namespace__index__key_field);
+												i->state = S_name__namespace__index__key_field__type;
+												return buf;
+											case S_name__namespace__index__key_field__type:
+												*v = (c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]->type) ? strdup(c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]->type) : NULL;
+												if (*v == NULL && c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]->type) {
+													free(i);
+													out_warning(CNF_NOMEMORY, "No memory to output value");
+													return NULL;
+												}
+												snprintf(buf, PRINTBUFLEN-1, "namespace[%d].index[%d].key_field[%d].type", i->idx_name__namespace, i->idx_name__namespace__index, i->idx_name__namespace__index__key_field);
+												i->state = S_name__namespace__index__key_field;
+												i->idx_name__namespace__index__key_field++;
+												return buf;
+											default:
+												break;
+										}
+									}
+									else {
+										i->state = S_name__namespace__index;
+										i->idx_name__namespace__index++;
+										i->idx_name__namespace__index__key_field = 0;
+										goto again;
+									}
 								default:
 									break;
 							}
@@ -1213,6 +1365,7 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 							i->state = S_name__namespace;
 							i->idx_name__namespace++;
 							i->idx_name__namespace__index = 0;
+							i->idx_name__namespace__index__key_field = 0;
 							goto again;
 						}
 					default:
@@ -1229,3 +1382,27 @@ tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char
 	return NULL;
 }
 
+/************** Checking of required fields  **************/
+int
+check_cfg_tarantool_cfg(tarantool_cfg *c) {
+	tarantool_cfg_iterator_t iterator, *i = &iterator;
+	int	res = 0;
+
+	i->idx_name__namespace = 0;
+	while (c->namespace && c->namespace[i->idx_name__namespace]) {
+		i->idx_name__namespace__index = 0;
+		while (c->namespace[i->idx_name__namespace]->index && c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]) {
+			i->idx_name__namespace__index__key_field = 0;
+			while (c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field && c->namespace[i->idx_name__namespace]->index[i->idx_name__namespace__index]->key_field[i->idx_name__namespace__index__key_field]) {
+				i->idx_name__namespace__index__key_field++;
+			}
+
+			i->idx_name__namespace__index++;
+		}
+
+		i->idx_name__namespace++;
+	}
+
+	return res;
+}
+
diff --git a/cfg/tarantool_silverbox_cfg.cfg b/cfg/tarantool_silverbox_cfg.cfg
index 4477cd880b9cf8aae197eb05cd76ad41ee4470dc..de8d1edb3e11d37d50ef0f03a2871435303cb78c 100644
--- a/cfg/tarantool_silverbox_cfg.cfg
+++ b/cfg/tarantool_silverbox_cfg.cfg
@@ -39,9 +39,6 @@ logger_nonblock = 1
 # delay between loop iteraions
 io_collect_interval = 0
 
-# do not write snapshot faster then snap_io_rate_limit MBytes/sec
-snap_io_rate_limit = 0
-
 # size of listen backlog
 backlog = 1024
 
@@ -79,6 +76,9 @@ memcached_expire_per_loop = 1024
 # tarantool will try iterate all rows within this time
 memcached_expire_full_sweep = 3600
 
+# do not write snapshot faster then snap_io_rate_limit MBytes/sec
+snap_io_rate_limit = 0
+
 # Write no more rows in WAL
 rows_per_wal = 500000
 
@@ -96,6 +96,11 @@ local_hot_standby = 0
 # delay in fractional seconds between successive re-readings of wal_dir
 wal_dir_rescan_delay = 0.1
 
+# panic if where is error reading snap or wal
+# be default panic any snapshot reading error  and ignore errors then reading wals
+panic_on_snap_error = 1
+panic_on_wal_error = 0
+
 # Remote hot standby (if enabled server will run in hot standby mode
 # continuously fetching WAL records from wal_feeder_ipaddr:wal_feeder_port
 remote_hot_standby = 0
@@ -108,8 +113,14 @@ namespace = [
         estimated_rows = 0
         index = [
             {
-                type = "NUM"
-                key_position = -1
+                type = ""
+                unique = -1
+                key_field = [
+                    {
+                        fieldno = -1
+                        type = ""
+                    }
+                ]
             }
         ]
     }
diff --git a/cfg/tarantool_silverbox_cfg.cfg_tmpl b/cfg/tarantool_silverbox_cfg.cfg_tmpl
index 2de00d690252e5f7f3558a0cc06cc34c92cf3763..444d681863f65c09089cae2923c0d7424b930969 100644
--- a/cfg/tarantool_silverbox_cfg.cfg_tmpl
+++ b/cfg/tarantool_silverbox_cfg.cfg_tmpl
@@ -42,9 +42,6 @@ logger_nonblock=1
 # delay between loop iteraions
 io_collect_interval=0.0
 
-# do not write snapshot faster then snap_io_rate_limit MBytes/sec
-snap_io_rate_limit=0.0
-
 # size of listen backlog
 backlog=1024
 
@@ -79,6 +76,10 @@ memcached_expire_per_loop=1024
 # tarantool will try iterate all rows within this time
 memcached_expire_full_sweep=3600
 
+
+# do not write snapshot faster then snap_io_rate_limit MBytes/sec
+snap_io_rate_limit=0.0
+
 # Write no more rows in WAL
 rows_per_wal=500000
 
@@ -95,6 +96,12 @@ local_hot_standby=0
 # delay in fractional seconds between successive re-readings of wal_dir
 wal_dir_rescan_delay=0.1
 
+
+# panic if where is error reading snap or wal
+# be default panic any snapshot reading error  and ignore errors then reading wals
+panic_on_snap_error=1
+panic_on_wal_error=0
+
 # Remote hot standby (if enabled server will run in hot standby mode
 # continuously fetching WAL records from wal_feeder_ipaddr:wal_feeder_port
 remote_hot_standby=0
@@ -107,7 +114,11 @@ namespace = [
   cardinality = -1
   estimated_rows = 0
   index = [
-    type = "NUM"
-    key_position = -1
+    type = ""
+    unique = -1
+    key_field = [
+      fieldno = -1
+      type = ""
+    ]
   ]
-]
\ No newline at end of file
+]
diff --git a/cfg/tarantool_silverbox_cfg.h b/cfg/tarantool_silverbox_cfg.h
index 98d51bf16e16dcca3942463eb5543a175c0f635c..ae2d7add9a067cd0d1df67099ba39db2b15d23fe 100644
--- a/cfg/tarantool_silverbox_cfg.h
+++ b/cfg/tarantool_silverbox_cfg.h
@@ -8,9 +8,15 @@
  * Autogenerated file, do not edit it!
  */
 
+typedef struct tarantool_cfg_namespace_index_key_field {
+	int32_t	fieldno;
+	char*	type;
+} tarantool_cfg_namespace_index_key_field;
+
 typedef struct tarantool_cfg_namespace_index {
 	char*	type;
-	int32_t	key_position;
+	int32_t	unique;
+	tarantool_cfg_namespace_index_key_field**	key_field;
 } tarantool_cfg_namespace_index;
 
 typedef struct tarantool_cfg_namespace {
@@ -66,9 +72,6 @@ typedef struct tarantool_cfg {
 	/* delay between loop iteraions */
 	double	io_collect_interval;
 
-	/* do not write snapshot faster then snap_io_rate_limit MBytes/sec */
-	double	snap_io_rate_limit;
-
 	/* size of listen backlog */
 	int32_t	backlog;
 
@@ -108,6 +111,9 @@ typedef struct tarantool_cfg {
 	/* tarantool will try iterate all rows within this time */
 	int32_t	memcached_expire_full_sweep;
 
+	/* do not write snapshot faster then snap_io_rate_limit MBytes/sec */
+	double	snap_io_rate_limit;
+
 	/* Write no more rows in WAL */
 	int32_t	rows_per_wal;
 
@@ -129,6 +135,13 @@ typedef struct tarantool_cfg {
 	/* delay in fractional seconds between successive re-readings of wal_dir */
 	double	wal_dir_rescan_delay;
 
+	/*
+	 * panic if where is error reading snap or wal
+	 * be default panic any snapshot reading error  and ignore errors then reading wals
+	 */
+	int32_t	panic_on_snap_error;
+	int32_t	panic_on_wal_error;
+
 	/*
 	 * Remote hot standby (if enabled server will run in hot standby mode
 	 * continuously fetching WAL records from wal_feeder_ipaddr:wal_feeder_port
@@ -144,6 +157,8 @@ void parse_cfg_file_tarantool_cfg(tarantool_cfg *c, FILE *fh, int check_rdonly,
 
 void parse_cfg_buffer_tarantool_cfg(tarantool_cfg *c, char *buffer, int check_rdonly, int *n_accepted, int *n_skipped);
 
+int check_cfg_tarantool_cfg(tarantool_cfg *c);
+
 typedef struct tarantool_cfg_iterator_t tarantool_cfg_iterator_t;
 tarantool_cfg_iterator_t* tarantool_cfg_iterator_init();
 char* tarantool_cfg_iterator_next(tarantool_cfg_iterator_t* i, tarantool_cfg *c, char **v);
diff --git a/cfg/warning.c b/cfg/warning.c
index 1318f951b58864310d97227b58bdb9ca7547c57d..fbfbf0fc8a929234416c7a84c1d498214651bab5 100644
--- a/cfg/warning.c
+++ b/cfg/warning.c
@@ -10,5 +10,14 @@ out_warning(ConfettyError v, char *format, ...)
 	(void)v; /* make gcc happy */
 	va_list ap;
 	va_start(ap, format);
-	vsay(S_WARN, NULL, format, ap);
+	switch (v) {
+	case CNF_NOTSET:
+		vsay(S_FATAL, NULL, format, ap);
+		panic("can't read config");
+
+		break;
+
+	default:
+		vsay(S_WARN, NULL, format, ap);
+	}
 }
diff --git a/core/admin.c b/core/admin.c
index 3081f6122cf43d0b7d50e5f50e84c58bd991de52..d4923b101af8913acf6f2eb13c3f5188ee9c8c44 100644
--- a/core/admin.c
+++ b/core/admin.c
@@ -38,12 +38,13 @@
 #include <say.h>
 #include <stat.h>
 #include <tarantool.h>
+#include <tbuf.h>
 #include <util.h>
 
 static const char help[] =
 	"available commands:\r\n"
 	"help\r\n"
-	"quit\r\n"
+	"exit\r\n"
 	"show info\r\n"
 	"show fiber\r\n"
 	"show configuration\r\n"
@@ -51,21 +52,23 @@ static const char help[] =
 	"show palloc\r\n"
 	"show stat\r\n"
 	"save coredump\r\n"
-	"save snapshot\r\n";
+	"save snapshot\r\n"
+	"exec module command\r\n"
+	;
 
 
 static const char unknown_command[] = "unknown command. try typing help.\r\n";
 
 
-#line 61 "core/admin.c"
+#line 64 "core/admin.c"
 static const int admin_start = 1;
-static const int admin_first_final = 81;
+static const int admin_first_final = 88;
 static const int admin_error = 0;
 
 static const int admin_en_main = 1;
 
 
-#line 60 "core/admin.rl"
+#line 63 "core/admin.rl"
 
 
 static void
@@ -86,6 +89,7 @@ admin_dispatch(void)
 	struct tbuf *out = tbuf_alloc(fiber->pool);
 	int cs;
 	char *p, *pe;
+	char *strstart, *strend;
 
 	while ((pe = memchr(fiber->rbuf->data, '\n', fiber->rbuf->len)) == NULL) {
 		if (fiber_bread(fiber->rbuf, 1) <= 0)
@@ -96,12 +100,12 @@ admin_dispatch(void)
 	p = fiber->rbuf->data;
 
 	
-#line 100 "core/admin.c"
+#line 104 "core/admin.c"
 	{
 	cs = admin_start;
 	}
 
-#line 105 "core/admin.c"
+#line 109 "core/admin.c"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -110,9 +114,10 @@ admin_dispatch(void)
 case 1:
 	switch( (*p) ) {
 		case 99: goto st2;
-		case 104: goto st13;
-		case 113: goto st17;
-		case 115: goto st21;
+		case 101: goto st13;
+		case 104: goto st22;
+		case 113: goto st26;
+		case 115: goto st28;
 	}
 	goto st0;
 st0:
@@ -138,8 +143,10 @@ case 3:
 	if ( ++p == pe )
 		goto _test_eof4;
 case 4:
-	if ( (*p) == 115 )
-		goto st5;
+	switch( (*p) ) {
+		case 32: goto st4;
+		case 115: goto st5;
+	}
 	goto st0;
 st5:
 	if ( ++p == pe )
@@ -153,33 +160,39 @@ case 5:
 		goto _test_eof6;
 case 6:
 	switch( (*p) ) {
-		case 10: goto tr10;
-		case 13: goto tr11;
+		case 10: goto tr11;
+		case 13: goto tr12;
 		case 97: goto st8;
 	}
 	goto st0;
-tr10:
-#line 133 "core/admin.rl"
+tr11:
+#line 140 "core/admin.rl"
 	{slab_validate(); ok(out);}
-	goto st81;
-tr17:
-#line 123 "core/admin.rl"
-	{tbuf_append(out, help, sizeof(help));}
-	goto st81;
-tr22:
-#line 124 "core/admin.rl"
+	goto st88;
+tr18:
+#line 130 "core/admin.rl"
 	{return 0;}
-	goto st81;
-tr34:
-#line 131 "core/admin.rl"
+	goto st88;
+tr27:
+#line 127 "core/admin.rl"
+	{strend = p;}
+#line 139 "core/admin.rl"
+	{mod_exec(strstart, strend - strstart, out); end(out);}
+	goto st88;
+tr31:
+#line 129 "core/admin.rl"
+	{tbuf_append(out, help, sizeof(help));}
+	goto st88;
+tr44:
+#line 137 "core/admin.rl"
 	{coredump(60); ok(out);}
-	goto st81;
-tr43:
-#line 132 "core/admin.rl"
+	goto st88;
+tr53:
+#line 138 "core/admin.rl"
 	{snapshot(NULL, 0); ok(out);}
-	goto st81;
-tr60:
-#line 90 "core/admin.rl"
+	goto st88;
+tr70:
+#line 94 "core/admin.rl"
 	{
 			tarantool_cfg_iterator_t *i;
 			char *key, *value;
@@ -196,55 +209,61 @@ case 6:
 			}
 			end(out);
 		}
-	goto st81;
-tr74:
-#line 126 "core/admin.rl"
+	goto st88;
+tr84:
+#line 132 "core/admin.rl"
 	{fiber_info(out);end(out);}
-	goto st81;
-tr80:
-#line 125 "core/admin.rl"
+	goto st88;
+tr90:
+#line 131 "core/admin.rl"
 	{mod_info(out); end(out);}
-	goto st81;
-tr85:
-#line 129 "core/admin.rl"
+	goto st88;
+tr95:
+#line 135 "core/admin.rl"
 	{palloc_stat(out);end(out);}
-	goto st81;
-tr93:
-#line 128 "core/admin.rl"
+	goto st88;
+tr103:
+#line 134 "core/admin.rl"
 	{slab_stat(out);end(out);}
-	goto st81;
-tr97:
-#line 130 "core/admin.rl"
+	goto st88;
+tr107:
+#line 136 "core/admin.rl"
 	{stat_print(out);end(out);}
-	goto st81;
-st81:
+	goto st88;
+st88:
 	if ( ++p == pe )
-		goto _test_eof81;
-case 81:
-#line 225 "core/admin.c"
+		goto _test_eof88;
+case 88:
+#line 238 "core/admin.c"
 	goto st0;
-tr11:
-#line 133 "core/admin.rl"
+tr12:
+#line 140 "core/admin.rl"
 	{slab_validate(); ok(out);}
 	goto st7;
-tr18:
-#line 123 "core/admin.rl"
-	{tbuf_append(out, help, sizeof(help));}
-	goto st7;
-tr23:
-#line 124 "core/admin.rl"
+tr19:
+#line 130 "core/admin.rl"
 	{return 0;}
 	goto st7;
-tr35:
-#line 131 "core/admin.rl"
+tr28:
+#line 127 "core/admin.rl"
+	{strend = p;}
+#line 139 "core/admin.rl"
+	{mod_exec(strstart, strend - strstart, out); end(out);}
+	goto st7;
+tr32:
+#line 129 "core/admin.rl"
+	{tbuf_append(out, help, sizeof(help));}
+	goto st7;
+tr45:
+#line 137 "core/admin.rl"
 	{coredump(60); ok(out);}
 	goto st7;
-tr44:
-#line 132 "core/admin.rl"
+tr54:
+#line 138 "core/admin.rl"
 	{snapshot(NULL, 0); ok(out);}
 	goto st7;
-tr61:
-#line 90 "core/admin.rl"
+tr71:
+#line 94 "core/admin.rl"
 	{
 			tarantool_cfg_iterator_t *i;
 			char *key, *value;
@@ -262,41 +281,41 @@ case 81:
 			end(out);
 		}
 	goto st7;
-tr75:
-#line 126 "core/admin.rl"
+tr85:
+#line 132 "core/admin.rl"
 	{fiber_info(out);end(out);}
 	goto st7;
-tr81:
-#line 125 "core/admin.rl"
+tr91:
+#line 131 "core/admin.rl"
 	{mod_info(out); end(out);}
 	goto st7;
-tr86:
-#line 129 "core/admin.rl"
+tr96:
+#line 135 "core/admin.rl"
 	{palloc_stat(out);end(out);}
 	goto st7;
-tr94:
-#line 128 "core/admin.rl"
+tr104:
+#line 134 "core/admin.rl"
 	{slab_stat(out);end(out);}
 	goto st7;
-tr98:
-#line 130 "core/admin.rl"
+tr108:
+#line 136 "core/admin.rl"
 	{stat_print(out);end(out);}
 	goto st7;
 st7:
 	if ( ++p == pe )
 		goto _test_eof7;
 case 7:
-#line 290 "core/admin.c"
+#line 309 "core/admin.c"
 	if ( (*p) == 10 )
-		goto st81;
+		goto st88;
 	goto st0;
 st8:
 	if ( ++p == pe )
 		goto _test_eof8;
 case 8:
 	switch( (*p) ) {
-		case 10: goto tr10;
-		case 13: goto tr11;
+		case 10: goto tr11;
+		case 13: goto tr12;
 		case 98: goto st9;
 	}
 	goto st0;
@@ -305,8 +324,8 @@ case 8:
 		goto _test_eof9;
 case 9:
 	switch( (*p) ) {
-		case 10: goto tr10;
-		case 13: goto tr11;
+		case 10: goto tr11;
+		case 13: goto tr12;
 	}
 	goto st0;
 st10:
@@ -339,9 +358,9 @@ case 12:
 		goto _test_eof13;
 case 13:
 	switch( (*p) ) {
-		case 10: goto tr17;
-		case 13: goto tr18;
-		case 101: goto st14;
+		case 10: goto tr18;
+		case 13: goto tr19;
+		case 120: goto st14;
 	}
 	goto st0;
 st14:
@@ -349,9 +368,11 @@ case 13:
 		goto _test_eof14;
 case 14:
 	switch( (*p) ) {
-		case 10: goto tr17;
-		case 13: goto tr18;
-		case 108: goto st15;
+		case 10: goto tr18;
+		case 13: goto tr19;
+		case 32: goto st15;
+		case 101: goto st18;
+		case 105: goto st20;
 	}
 	goto st0;
 st15:
@@ -359,57 +380,64 @@ case 14:
 		goto _test_eof15;
 case 15:
 	switch( (*p) ) {
-		case 10: goto tr17;
-		case 13: goto tr18;
-		case 112: goto st16;
+		case 10: goto st0;
+		case 13: goto st0;
+		case 32: goto tr25;
 	}
-	goto st0;
+	goto tr24;
+tr24:
+#line 127 "core/admin.rl"
+	{strstart = p;}
+	goto st16;
 st16:
 	if ( ++p == pe )
 		goto _test_eof16;
 case 16:
+#line 397 "core/admin.c"
 	switch( (*p) ) {
-		case 10: goto tr17;
-		case 13: goto tr18;
+		case 10: goto tr27;
+		case 13: goto tr28;
 	}
-	goto st0;
+	goto st16;
+tr25:
+#line 127 "core/admin.rl"
+	{strstart = p;}
+	goto st17;
 st17:
 	if ( ++p == pe )
 		goto _test_eof17;
 case 17:
+#line 411 "core/admin.c"
 	switch( (*p) ) {
-		case 10: goto tr22;
-		case 13: goto tr23;
-		case 117: goto st18;
+		case 10: goto tr27;
+		case 13: goto tr28;
+		case 32: goto tr25;
 	}
-	goto st0;
+	goto tr24;
 st18:
 	if ( ++p == pe )
 		goto _test_eof18;
 case 18:
 	switch( (*p) ) {
-		case 10: goto tr22;
-		case 13: goto tr23;
-		case 105: goto st19;
+		case 32: goto st15;
+		case 99: goto st19;
 	}
 	goto st0;
 st19:
 	if ( ++p == pe )
 		goto _test_eof19;
 case 19:
-	switch( (*p) ) {
-		case 10: goto tr22;
-		case 13: goto tr23;
-		case 116: goto st20;
-	}
+	if ( (*p) == 32 )
+		goto st15;
 	goto st0;
 st20:
 	if ( ++p == pe )
 		goto _test_eof20;
 case 20:
 	switch( (*p) ) {
-		case 10: goto tr22;
-		case 13: goto tr23;
+		case 10: goto tr18;
+		case 13: goto tr19;
+		case 116: goto st21;
 	}
 	goto st0;
 st21:
@@ -417,8 +445,8 @@ case 20:
 		goto _test_eof21;
 case 21:
 	switch( (*p) ) {
-		case 97: goto st22;
-		case 104: goto st42;
+		case 10: goto tr18;
+		case 13: goto tr19;
 	}
 	goto st0;
 st22:
@@ -426,8 +454,9 @@ case 21:
 		goto _test_eof22;
 case 22:
 	switch( (*p) ) {
-		case 32: goto st23;
-		case 118: goto st40;
+		case 10: goto tr31;
+		case 13: goto tr32;
+		case 101: goto st23;
 	}
 	goto st0;
 st23:
@@ -435,25 +464,28 @@ case 22:
 		goto _test_eof23;
 case 23:
 	switch( (*p) ) {
-		case 99: goto st24;
-		case 115: goto st32;
+		case 10: goto tr31;
+		case 13: goto tr32;
+		case 108: goto st24;
 	}
 	goto st0;
 st24:
 	if ( ++p == pe )
 		goto _test_eof24;
 case 24:
-	if ( (*p) == 111 )
-		goto st25;
+	switch( (*p) ) {
+		case 10: goto tr31;
+		case 13: goto tr32;
+		case 112: goto st25;
+	}
 	goto st0;
 st25:
 	if ( ++p == pe )
 		goto _test_eof25;
 case 25:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 114: goto st26;
+		case 10: goto tr31;
+		case 13: goto tr32;
 	}
 	goto st0;
 st26:
@@ -461,9 +493,9 @@ case 25:
 		goto _test_eof26;
 case 26:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 101: goto st27;
+		case 10: goto tr18;
+		case 13: goto tr19;
+		case 117: goto st27;
 	}
 	goto st0;
 st27:
@@ -471,9 +503,9 @@ case 26:
 		goto _test_eof27;
 case 27:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 100: goto st28;
+		case 10: goto tr18;
+		case 13: goto tr19;
+		case 105: goto st20;
 	}
 	goto st0;
 st28:
@@ -481,9 +513,8 @@ case 27:
 		goto _test_eof28;
 case 28:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 117: goto st29;
+		case 97: goto st29;
+		case 104: goto st49;
 	}
 	goto st0;
 st29:
@@ -491,9 +522,8 @@ case 28:
 		goto _test_eof29;
 case 29:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 109: goto st30;
+		case 32: goto st30;
+		case 118: goto st47;
 	}
 	goto st0;
 st30:
@@ -501,35 +531,36 @@ case 29:
 		goto _test_eof30;
 case 30:
 	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-		case 112: goto st31;
+		case 32: goto st30;
+		case 99: goto st31;
+		case 115: goto st39;
 	}
 	goto st0;
 st31:
 	if ( ++p == pe )
 		goto _test_eof31;
 case 31:
-	switch( (*p) ) {
-		case 10: goto tr34;
-		case 13: goto tr35;
-	}
+	if ( (*p) == 111 )
+		goto st32;
 	goto st0;
 st32:
 	if ( ++p == pe )
 		goto _test_eof32;
 case 32:
-	if ( (*p) == 110 )
-		goto st33;
+	switch( (*p) ) {
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 114: goto st33;
+	}
 	goto st0;
 st33:
 	if ( ++p == pe )
 		goto _test_eof33;
 case 33:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 97: goto st34;
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 101: goto st34;
 	}
 	goto st0;
 st34:
@@ -537,9 +568,9 @@ case 33:
 		goto _test_eof34;
 case 34:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 112: goto st35;
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 100: goto st35;
 	}
 	goto st0;
 st35:
@@ -547,9 +578,9 @@ case 34:
 		goto _test_eof35;
 case 35:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 115: goto st36;
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 117: goto st36;
 	}
 	goto st0;
 st36:
@@ -557,9 +588,9 @@ case 35:
 		goto _test_eof36;
 case 36:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 104: goto st37;
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 109: goto st37;
 	}
 	goto st0;
 st37:
@@ -567,9 +598,9 @@ case 36:
 		goto _test_eof37;
 case 37:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 111: goto st38;
+		case 10: goto tr44;
+		case 13: goto tr45;
+		case 112: goto st38;
 	}
 	goto st0;
 st38:
@@ -577,43 +608,45 @@ case 37:
 		goto _test_eof38;
 case 38:
 	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-		case 116: goto st39;
+		case 10: goto tr44;
+		case 13: goto tr45;
 	}
 	goto st0;
 st39:
 	if ( ++p == pe )
 		goto _test_eof39;
 case 39:
-	switch( (*p) ) {
-		case 10: goto tr43;
-		case 13: goto tr44;
-	}
+	if ( (*p) == 110 )
+		goto st40;
 	goto st0;
 st40:
 	if ( ++p == pe )
 		goto _test_eof40;
 case 40:
 	switch( (*p) ) {
-		case 32: goto st23;
-		case 101: goto st41;
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 97: goto st41;
 	}
 	goto st0;
 st41:
 	if ( ++p == pe )
 		goto _test_eof41;
 case 41:
-	if ( (*p) == 32 )
-		goto st23;
+	switch( (*p) ) {
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 112: goto st42;
+	}
 	goto st0;
 st42:
 	if ( ++p == pe )
 		goto _test_eof42;
 case 42:
 	switch( (*p) ) {
-		case 32: goto st43;
-		case 111: goto st79;
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 115: goto st43;
 	}
 	goto st0;
 st43:
@@ -621,28 +654,29 @@ case 42:
 		goto _test_eof43;
 case 43:
 	switch( (*p) ) {
-		case 99: goto st44;
-		case 102: goto st57;
-		case 105: goto st62;
-		case 112: goto st66;
-		case 115: goto st72;
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 104: goto st44;
 	}
 	goto st0;
 st44:
 	if ( ++p == pe )
 		goto _test_eof44;
 case 44:
-	if ( (*p) == 111 )
-		goto st45;
+	switch( (*p) ) {
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 111: goto st45;
+	}
 	goto st0;
 st45:
 	if ( ++p == pe )
 		goto _test_eof45;
 case 45:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 110: goto st46;
+		case 10: goto tr53;
+		case 13: goto tr54;
+		case 116: goto st46;
 	}
 	goto st0;
 st46:
@@ -650,9 +684,8 @@ case 45:
 		goto _test_eof46;
 case 46:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 102: goto st47;
+		case 10: goto tr53;
+		case 13: goto tr54;
 	}
 	goto st0;
 st47:
@@ -660,29 +693,24 @@ case 46:
 		goto _test_eof47;
 case 47:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 105: goto st48;
+		case 32: goto st30;
+		case 101: goto st48;
 	}
 	goto st0;
 st48:
 	if ( ++p == pe )
 		goto _test_eof48;
 case 48:
-	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 103: goto st49;
-	}
+	if ( (*p) == 32 )
+		goto st30;
 	goto st0;
 st49:
 	if ( ++p == pe )
 		goto _test_eof49;
 case 49:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 117: goto st50;
+		case 32: goto st50;
+		case 111: goto st86;
 	}
 	goto st0;
 st50:
@@ -690,29 +718,29 @@ case 49:
 		goto _test_eof50;
 case 50:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 114: goto st51;
+		case 32: goto st50;
+		case 99: goto st51;
+		case 102: goto st64;
+		case 105: goto st69;
+		case 112: goto st73;
+		case 115: goto st79;
 	}
 	goto st0;
 st51:
 	if ( ++p == pe )
 		goto _test_eof51;
 case 51:
-	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 97: goto st52;
-	}
+	if ( (*p) == 111 )
+		goto st52;
 	goto st0;
 st52:
 	if ( ++p == pe )
 		goto _test_eof52;
 case 52:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 116: goto st53;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 110: goto st53;
 	}
 	goto st0;
 st53:
@@ -720,9 +748,9 @@ case 52:
 		goto _test_eof53;
 case 53:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 105: goto st54;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 102: goto st54;
 	}
 	goto st0;
 st54:
@@ -730,9 +758,9 @@ case 53:
 		goto _test_eof54;
 case 54:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 111: goto st55;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 105: goto st55;
 	}
 	goto st0;
 st55:
@@ -740,9 +768,9 @@ case 54:
 		goto _test_eof55;
 case 55:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
-		case 110: goto st56;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 103: goto st56;
 	}
 	goto st0;
 st56:
@@ -750,25 +778,29 @@ case 55:
 		goto _test_eof56;
 case 56:
 	switch( (*p) ) {
-		case 10: goto tr60;
-		case 13: goto tr61;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 117: goto st57;
 	}
 	goto st0;
 st57:
 	if ( ++p == pe )
 		goto _test_eof57;
 case 57:
-	if ( (*p) == 105 )
-		goto st58;
+	switch( (*p) ) {
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 114: goto st58;
+	}
 	goto st0;
 st58:
 	if ( ++p == pe )
 		goto _test_eof58;
 case 58:
 	switch( (*p) ) {
-		case 10: goto tr74;
-		case 13: goto tr75;
-		case 98: goto st59;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 97: goto st59;
 	}
 	goto st0;
 st59:
@@ -776,9 +808,9 @@ case 58:
 		goto _test_eof59;
 case 59:
 	switch( (*p) ) {
-		case 10: goto tr74;
-		case 13: goto tr75;
-		case 101: goto st60;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 116: goto st60;
 	}
 	goto st0;
 st60:
@@ -786,9 +818,9 @@ case 59:
 		goto _test_eof60;
 case 60:
 	switch( (*p) ) {
-		case 10: goto tr74;
-		case 13: goto tr75;
-		case 114: goto st61;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 105: goto st61;
 	}
 	goto st0;
 st61:
@@ -796,61 +828,65 @@ case 60:
 		goto _test_eof61;
 case 61:
 	switch( (*p) ) {
-		case 10: goto tr74;
-		case 13: goto tr75;
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 111: goto st62;
 	}
 	goto st0;
 st62:
 	if ( ++p == pe )
 		goto _test_eof62;
 case 62:
-	if ( (*p) == 110 )
-		goto st63;
+	switch( (*p) ) {
+		case 10: goto tr70;
+		case 13: goto tr71;
+		case 110: goto st63;
+	}
 	goto st0;
 st63:
 	if ( ++p == pe )
 		goto _test_eof63;
 case 63:
 	switch( (*p) ) {
-		case 10: goto tr80;
-		case 13: goto tr81;
-		case 102: goto st64;
+		case 10: goto tr70;
+		case 13: goto tr71;
 	}
 	goto st0;
 st64:
 	if ( ++p == pe )
 		goto _test_eof64;
 case 64:
-	switch( (*p) ) {
-		case 10: goto tr80;
-		case 13: goto tr81;
-		case 111: goto st65;
-	}
+	if ( (*p) == 105 )
+		goto st65;
 	goto st0;
 st65:
 	if ( ++p == pe )
 		goto _test_eof65;
 case 65:
 	switch( (*p) ) {
-		case 10: goto tr80;
-		case 13: goto tr81;
+		case 10: goto tr84;
+		case 13: goto tr85;
+		case 98: goto st66;
 	}
 	goto st0;
 st66:
 	if ( ++p == pe )
 		goto _test_eof66;
 case 66:
-	if ( (*p) == 97 )
-		goto st67;
+	switch( (*p) ) {
+		case 10: goto tr84;
+		case 13: goto tr85;
+		case 101: goto st67;
+	}
 	goto st0;
 st67:
 	if ( ++p == pe )
 		goto _test_eof67;
 case 67:
 	switch( (*p) ) {
-		case 10: goto tr85;
-		case 13: goto tr86;
-		case 108: goto st68;
+		case 10: goto tr84;
+		case 13: goto tr85;
+		case 114: goto st68;
 	}
 	goto st0;
 st68:
@@ -858,29 +894,25 @@ case 67:
 		goto _test_eof68;
 case 68:
 	switch( (*p) ) {
-		case 10: goto tr85;
-		case 13: goto tr86;
-		case 108: goto st69;
+		case 10: goto tr84;
+		case 13: goto tr85;
 	}
 	goto st0;
 st69:
 	if ( ++p == pe )
 		goto _test_eof69;
 case 69:
-	switch( (*p) ) {
-		case 10: goto tr85;
-		case 13: goto tr86;
-		case 111: goto st70;
-	}
+	if ( (*p) == 110 )
+		goto st70;
 	goto st0;
 st70:
 	if ( ++p == pe )
 		goto _test_eof70;
 case 70:
 	switch( (*p) ) {
-		case 10: goto tr85;
-		case 13: goto tr86;
-		case 99: goto st71;
+		case 10: goto tr90;
+		case 13: goto tr91;
+		case 102: goto st71;
 	}
 	goto st0;
 st71:
@@ -888,8 +920,9 @@ case 70:
 		goto _test_eof71;
 case 71:
 	switch( (*p) ) {
-		case 10: goto tr85;
-		case 13: goto tr86;
+		case 10: goto tr90;
+		case 13: goto tr91;
+		case 111: goto st72;
 	}
 	goto st0;
 st72:
@@ -897,28 +930,25 @@ case 71:
 		goto _test_eof72;
 case 72:
 	switch( (*p) ) {
-		case 108: goto st73;
-		case 116: goto st76;
+		case 10: goto tr90;
+		case 13: goto tr91;
 	}
 	goto st0;
 st73:
 	if ( ++p == pe )
 		goto _test_eof73;
 case 73:
-	switch( (*p) ) {
-		case 10: goto tr93;
-		case 13: goto tr94;
-		case 97: goto st74;
-	}
+	if ( (*p) == 97 )
+		goto st74;
 	goto st0;
 st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
 	switch( (*p) ) {
-		case 10: goto tr93;
-		case 13: goto tr94;
-		case 98: goto st75;
+		case 10: goto tr95;
+		case 13: goto tr96;
+		case 108: goto st75;
 	}
 	goto st0;
 st75:
@@ -926,8 +956,9 @@ case 74:
 		goto _test_eof75;
 case 75:
 	switch( (*p) ) {
-		case 10: goto tr93;
-		case 13: goto tr94;
+		case 10: goto tr95;
+		case 13: goto tr96;
+		case 108: goto st76;
 	}
 	goto st0;
 st76:
@@ -935,9 +966,9 @@ case 75:
 		goto _test_eof76;
 case 76:
 	switch( (*p) ) {
-		case 10: goto tr97;
-		case 13: goto tr98;
-		case 97: goto st77;
+		case 10: goto tr95;
+		case 13: goto tr96;
+		case 111: goto st77;
 	}
 	goto st0;
 st77:
@@ -945,9 +976,9 @@ case 76:
 		goto _test_eof77;
 case 77:
 	switch( (*p) ) {
-		case 10: goto tr97;
-		case 13: goto tr98;
-		case 116: goto st78;
+		case 10: goto tr95;
+		case 13: goto tr96;
+		case 99: goto st78;
 	}
 	goto st0;
 st78:
@@ -955,8 +986,8 @@ case 77:
 		goto _test_eof78;
 case 78:
 	switch( (*p) ) {
-		case 10: goto tr97;
-		case 13: goto tr98;
+		case 10: goto tr95;
+		case 13: goto tr96;
 	}
 	goto st0;
 st79:
@@ -964,16 +995,83 @@ case 78:
 		goto _test_eof79;
 case 79:
 	switch( (*p) ) {
-		case 32: goto st43;
-		case 119: goto st80;
+		case 108: goto st80;
+		case 116: goto st83;
 	}
 	goto st0;
 st80:
 	if ( ++p == pe )
 		goto _test_eof80;
 case 80:
+	switch( (*p) ) {
+		case 10: goto tr103;
+		case 13: goto tr104;
+		case 97: goto st81;
+	}
+	goto st0;
+st81:
+	if ( ++p == pe )
+		goto _test_eof81;
+case 81:
+	switch( (*p) ) {
+		case 10: goto tr103;
+		case 13: goto tr104;
+		case 98: goto st82;
+	}
+	goto st0;
+st82:
+	if ( ++p == pe )
+		goto _test_eof82;
+case 82:
+	switch( (*p) ) {
+		case 10: goto tr103;
+		case 13: goto tr104;
+	}
+	goto st0;
+st83:
+	if ( ++p == pe )
+		goto _test_eof83;
+case 83:
+	switch( (*p) ) {
+		case 10: goto tr107;
+		case 13: goto tr108;
+		case 97: goto st84;
+	}
+	goto st0;
+st84:
+	if ( ++p == pe )
+		goto _test_eof84;
+case 84:
+	switch( (*p) ) {
+		case 10: goto tr107;
+		case 13: goto tr108;
+		case 116: goto st85;
+	}
+	goto st0;
+st85:
+	if ( ++p == pe )
+		goto _test_eof85;
+case 85:
+	switch( (*p) ) {
+		case 10: goto tr107;
+		case 13: goto tr108;
+	}
+	goto st0;
+st86:
+	if ( ++p == pe )
+		goto _test_eof86;
+case 86:
+	switch( (*p) ) {
+		case 32: goto st50;
+		case 119: goto st87;
+	}
+	goto st0;
+st87:
+	if ( ++p == pe )
+		goto _test_eof87;
+case 87:
 	if ( (*p) == 32 )
-		goto st43;
+		goto st50;
 	goto st0;
 	}
 	_test_eof2: cs = 2; goto _test_eof; 
@@ -981,7 +1079,7 @@ case 80:
 	_test_eof4: cs = 4; goto _test_eof; 
 	_test_eof5: cs = 5; goto _test_eof; 
 	_test_eof6: cs = 6; goto _test_eof; 
-	_test_eof81: cs = 81; goto _test_eof; 
+	_test_eof88: cs = 88; goto _test_eof; 
 	_test_eof7: cs = 7; goto _test_eof; 
 	_test_eof8: cs = 8; goto _test_eof; 
 	_test_eof9: cs = 9; goto _test_eof; 
@@ -1056,12 +1154,19 @@ case 80:
 	_test_eof78: cs = 78; goto _test_eof; 
 	_test_eof79: cs = 79; goto _test_eof; 
 	_test_eof80: cs = 80; goto _test_eof; 
+	_test_eof81: cs = 81; goto _test_eof; 
+	_test_eof82: cs = 82; goto _test_eof; 
+	_test_eof83: cs = 83; goto _test_eof; 
+	_test_eof84: cs = 84; goto _test_eof; 
+	_test_eof85: cs = 85; goto _test_eof; 
+	_test_eof86: cs = 86; goto _test_eof; 
+	_test_eof87: cs = 87; goto _test_eof; 
 
 	_test_eof: {}
 	_out: {}
 	}
 
-#line 138 "core/admin.rl"
+#line 145 "core/admin.rl"
 
 
 	fiber->rbuf->len -= (void *)pe - (void *)fiber->rbuf->data;
diff --git a/core/admin.rl b/core/admin.rl
index ca4c6d05d00b55348fec134be95b3ac39102ecc4..a73061c705f18e6f42652bc3689a6bd95280101f 100644
--- a/core/admin.rl
+++ b/core/admin.rl
@@ -36,12 +36,13 @@
 #include <say.h>
 #include <stat.h>
 #include <tarantool.h>
+#include <tbuf.h>
 #include <util.h>
 
 static const char help[] =
 	"available commands:\r\n"
 	"help\r\n"
-	"quit\r\n"
+	"exit\r\n"
 	"show info\r\n"
 	"show fiber\r\n"
 	"show configuration\r\n"
@@ -49,7 +50,9 @@ static const char help[] =
 	"show palloc\r\n"
 	"show stat\r\n"
 	"save coredump\r\n"
-	"save snapshot\r\n";
+	"save snapshot\r\n"
+	"exec module command\r\n"
+	;
 
 
 static const char unknown_command[] = "unknown command. try typing help.\r\n";
@@ -77,6 +80,7 @@ admin_dispatch(void)
 	struct tbuf *out = tbuf_alloc(fiber->pool);
 	int cs;
 	char *p, *pe;
+	char *strstart, *strend;
 
 	while ((pe = memchr(fiber->rbuf->data, '\n', fiber->rbuf->len)) == NULL) {
 		if (fiber_bread(fiber->rbuf, 1) <= 0)
@@ -115,22 +119,25 @@ admin_dispatch(void)
 		palloc = "pa"("l"("l"("o"("c")?)?)?)?;
 		stat = "st"("a"("t")?)?;
 		help = "h"("e"("l"("p")?)?)?;
-		quit = "q"("u"("i"("t")?)?)?;
+		exit = "e"("x"("i"("t")?)?)? | "q"("u"("i"("t")?)?)?;
 		save = "sa"("v"("e")?)?;
 		coredump = "co"("r"("e"("d"("u"("m"("p")?)?)?)?)?)?;
 		snapshot = "sn"("a"("p"("s"("h"("o"("t")?)?)?)?)?)?;
-
-		commands = (help			%{tbuf_append(out, help, sizeof(help));}|
-			    quit			%{return 0;}				|
-			    show " " info		%{mod_info(out); end(out);}		|
-			    show " " fiber		%{fiber_info(out);end(out);}		|
-			    show " " configuration 	%show_configuration			|
-			    show " " slab		%{slab_stat(out);end(out);}		|
-			    show " " palloc		%{palloc_stat(out);end(out);}		|
-			    show " " stat		%{stat_print(out);end(out);}		|
-			    save " " coredump		%{coredump(60); ok(out);}		|
-			    save " " snapshot		%{snapshot(NULL, 0); ok(out);}		|
-			    check " " slab		%{slab_validate(); ok(out);});
+		exec = "ex"("e"("c")?)?;
+		string = [^\r\n]+ >{strstart = p;}  %{strend = p;};
+
+		commands = (help			%{tbuf_append(out, help, sizeof(help));}		|
+			    exit			%{return 0;}						|
+			    show " "+ info		%{mod_info(out); end(out);}				|
+			    show " "+ fiber		%{fiber_info(out);end(out);}				|
+			    show " "+ configuration 	%show_configuration					|
+			    show " "+ slab		%{slab_stat(out);end(out);}				|
+			    show " "+ palloc		%{palloc_stat(out);end(out);}				|
+			    show " "+ stat		%{stat_print(out);end(out);}				|
+			    save " "+ coredump		%{coredump(60); ok(out);}				|
+			    save " "+ snapshot		%{snapshot(NULL, 0); ok(out);}				|
+			    exec " "+ string		%{mod_exec(strstart, strend - strstart, out); end(out);}|
+			    check " "+ slab		%{slab_validate(); ok(out);});
 
 	        main := commands eol;
 		write init;
diff --git a/core/coro.c b/core/coro.c
index 0d840cb82633aed445d1e3f09ea135d975706ed3..2d35cbe9b2d7f01afe292b2142aa8dc50cebe605 100644
--- a/core/coro.c
+++ b/core/coro.c
@@ -41,7 +41,7 @@
 extern void *main_stack_frame;
 
 struct tarantool_coro *
-tarantool_coro_create(struct tarantool_coro *coro, void (*f)(void *), void *data)
+tarantool_coro_create(struct tarantool_coro *coro, void (*f) (void *), void *data)
 {
 	const int page = sysconf(_SC_PAGESIZE);
 
@@ -56,7 +56,7 @@ tarantool_coro_create(struct tarantool_coro *coro, void (*f)(void *), void *data
 	/* TODO: guard pages */
 	coro->stack_size = page * 16;
 	coro->stack = mmap(0, coro->stack_size, PROT_READ | PROT_WRITE | PROT_EXEC,
-			  MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+			   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
 
 	if (coro->stack == MAP_FAILED)
 		return NULL;
diff --git a/core/fiber.c b/core/fiber.c
index 72e6f0a07a8c5f2fb9e5c100414d461a01754de3..1e9b74136eb0da590d350a7f3b0446bb20bb1618 100644
--- a/core/fiber.c
+++ b/core/fiber.c
@@ -43,7 +43,6 @@
 #include <third_party/khash.h>
 
 #include <debug.h>
-#include <bert.h>
 #include <fiber.h>
 #include <palloc.h>
 #include <salloc.h>
@@ -92,7 +91,6 @@ static
 khash_t(fid2fiber) *
     fibers_registry;
 
-
 void
 fiber_call(struct fiber *callee)
 {
@@ -172,7 +170,6 @@ ev_schedule(ev_watcher *watcher, int event __unused__)
 	fiber_call(watcher->data);
 }
 
-
 static struct fiber *
 fid2fiber(int fid)
 {
@@ -251,6 +248,9 @@ fiber_gc(void)
 
 	fiber_cleanup();
 
+	if (palloc_allocated(fiber->pool) < 128 * 1024)
+		return;
+
 	tmp = fiber->pool;
 	fiber->pool = ex_pool;
 	ex_pool = tmp;
@@ -295,7 +295,6 @@ fiber_zombificate(struct fiber *f)
 	SLIST_INSERT_HEAD(&zombie_fibers, f, zombie_link);
 }
 
-
 static void
 fiber_loop(void *data __unused__)
 {
@@ -362,7 +361,6 @@ fiber_create(const char *restrict name, int fd, int inbox_size, void (*f) (void
 	return fiber;
 }
 
-
 char *
 fiber_peer_name(struct fiber *fiber)
 {
@@ -386,6 +384,8 @@ fiber_peer_name(struct fiber *fiber)
 	snprintf(fiber->peer_name, sizeof(fiber->peer_name),
 		 "%s:%d", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
 
+	fiber->cookie = 0;
+	memcpy(&fiber->cookie, &peer, MIN(sizeof(peer), sizeof(fiber->cookie)));
 	return fiber->peer_name;
 }
 
@@ -412,13 +412,13 @@ ring_size(struct ring *inbox)
 }
 
 int
-inbox_size(struct fiber * recipient)
+inbox_size(struct fiber *recipient)
 {
-        return ring_size(recipient->inbox);
+	return ring_size(recipient->inbox);
 }
 
 void
-wait_inbox(struct fiber * recipient)
+wait_inbox(struct fiber *recipient)
 {
 	while (ring_size(recipient->inbox) == 0) {
 		recipient->reading_inbox = true;
@@ -428,7 +428,7 @@ wait_inbox(struct fiber * recipient)
 }
 
 bool
-write_inbox(struct fiber * recipient, struct tbuf * msg)
+write_inbox(struct fiber *recipient, struct tbuf *msg)
 {
 	struct ring *inbox = recipient->inbox;
 	if (ring_size(inbox) == inbox->size - 1)
@@ -485,8 +485,6 @@ fiber_bread(struct tbuf *buf, size_t at_least)
 	return r;
 }
 
-
-
 void
 add_iov_dup(void *buf, size_t len)
 {
@@ -531,7 +529,7 @@ fiber_flush_output(void)
 		for (int i = 0; i < iov_cnt; i++)
 			rem += iov[i].iov_len;
 
-		say_syserror("client unexpectedly gone, %"PRI_SZ" bytes unwritten", rem);
+		say_syserror("client unexpectedly gone, %" PRI_SZ " bytes unwritten", rem);
 		result = r;
 	} else
 		result = bytes;
@@ -602,7 +600,7 @@ fiber_connect(struct sockaddr_in *addr)
 	if (set_nonblock(fiber->fd) < 0)
 		goto error;
 
-	if (connect(fiber->fd, (struct sockaddr*)addr, sizeof(*addr)) < 0) {
+	if (connect(fiber->fd, (struct sockaddr *)addr, sizeof(*addr)) < 0) {
 		if (errno != EINPROGRESS)
 			goto error;
 	}
@@ -620,7 +618,7 @@ fiber_connect(struct sockaddr_in *addr)
 
 	unwait(EV_WRITE);
 	return fiber->fd;
-error:
+      error:
 	unwait(EV_WRITE);
 	fiber_close();
 	return fiber->fd;
@@ -687,7 +685,6 @@ blocking_loop(int fd, struct tbuf *(*handler) (void *state, struct tbuf *), void
 			break;
 		}
 		*request_size = ntohl(*request_size);
-		assert(*request_size < 4 * 1024 * 1024);
 
 		if (read_atleast(fd, request, *request_size) < 0) {
 			result = EXIT_SUCCESS;
@@ -792,7 +789,8 @@ sock2inbox(void *_data __unused__)
 }
 
 struct child *
-spawn_child(const char *name, int inbox_size, struct tbuf *(*handler) (void *, struct tbuf *), void *state)
+spawn_child(const char *name, int inbox_size, struct tbuf *(*handler) (void *, struct tbuf *),
+	    void *state)
 {
 	char *proxy_name, *child_name;
 	int socks[2];
@@ -887,7 +885,7 @@ tcp_server_handler(void *data)
 		say_info("bound to TCP port %i", server->port);
 		break;
 
-	sleep_and_retry:
+	      sleep_and_retry:
 		if (!warning_said) {
 			say_warn("port %i is already in use, "
 				 "will retry binding after 0.1 seconds.", server->port);
@@ -959,7 +957,7 @@ udp_server_handler(void *data)
 		say_info("bound to UDP port %i", server->port);
 		break;
 
-	sleep_and_retry:
+	      sleep_and_retry:
 		if (!warning_said) {
 			say_warn("port %i is already in use, "
 				 "will retry binding after 0.1 seconds.", server->port);
@@ -973,16 +971,17 @@ udp_server_handler(void *data)
 
 	while (1) {
 #define MAXUDPPACKETLEN	128
-		char		buf[MAXUDPPACKETLEN];
-		struct		sockaddr_in addr;
-		socklen_t	addrlen;
-		ssize_t		sz;
+		char buf[MAXUDPPACKETLEN];
+		struct sockaddr_in addr;
+		socklen_t addrlen;
+		ssize_t sz;
 
 		wait_for(EV_READ);
 
-		for(;;) {
+		for (;;) {
 			addrlen = sizeof(addr);
-			sz = recvfrom(fiber->fd, buf, MAXUDPPACKETLEN, MSG_DONTWAIT,  (struct sockaddr *)&addr, &addrlen);
+			sz = recvfrom(fiber->fd, buf, MAXUDPPACKETLEN, MSG_DONTWAIT,
+				      (struct sockaddr *)&addr, &addrlen);
 
 			if (sz <= 0) {
 				if (!(errno == EAGAIN || errno == EWOULDBLOCK))
@@ -992,7 +991,7 @@ udp_server_handler(void *data)
 				if (server->handler) {
 					server->handler(data);
 				} else {
-					void (*f)(char *, int) = data;
+					void (*f) (char *, int) = data;
 					f(buf, (int)sz);
 				}
 			}
@@ -1001,8 +1000,8 @@ udp_server_handler(void *data)
 }
 
 struct fiber *
-fiber_server(fiber_server_type type, int port, void (*handler)(void *data), void *data,
-	     void (*on_bind)(void *data))
+fiber_server(fiber_server_type type, int port, void (*handler) (void *data), void *data,
+	     void (*on_bind) (void *data))
 {
 	char *server_name;
 	struct fiber_server *server;
@@ -1010,7 +1009,8 @@ fiber_server(fiber_server_type type, int port, void (*handler)(void *data), void
 
 	server_name = palloc(eter_pool, 64);
 	snprintf(server_name, 64, "%i/acceptor", port);
-	s = fiber_create(server_name, -1, -1, (type == tcp_server) ? tcp_server_handler : udp_server_handler, data);
+	s = fiber_create(server_name, -1, -1,
+			 (type == tcp_server) ? tcp_server_handler : udp_server_handler, data);
 	s->data = server = palloc(eter_pool, sizeof(struct fiber_server));
 	assert(server != NULL);
 	server->port = port;
@@ -1042,8 +1042,7 @@ fiber_info(struct tbuf *out)
 
 		struct frame *frame = fiber->rbp;
 		tbuf_printf(out, "    backtrace:\n");
-		while (stack_bottom < (void *)frame && (void *)frame < stack_top)
-		{
+		while (stack_bottom < (void *)frame && (void *)frame < stack_top) {
 			tbuf_printf(out, "        - { frame: %p, pc: %p }\n",
 				    frame + 2 * sizeof(void *), frame->ret);
 			frame = frame->rbp;
@@ -1052,7 +1051,6 @@ fiber_info(struct tbuf *out)
 	}
 }
 
-
 void
 fiber_init(void)
 {
@@ -1070,4 +1068,3 @@ fiber_init(void)
 	fiber = &sched;
 	last_used_fid = 100;
 }
-
diff --git a/core/iproto.c b/core/iproto.c
index 05135e8fdcbafed7cb7ae0c54ce66d12ed6c2fb5..820a8aa4125efb09fd6e989a9ad0958d1c989b33 100644
--- a/core/iproto.c
+++ b/core/iproto.c
@@ -50,7 +50,7 @@ iproto_parse(struct tbuf *in)
 void
 iproto_interact(void *data)
 {
-	uint32_t(*callback) (uint32_t msg, struct tbuf *requst_data) = data;
+	uint32_t (*callback) (uint32_t msg, struct tbuf *requst_data) = data;
 	struct tbuf *request;
 	struct iproto_header_retcode *reply;
 	ssize_t r;
diff --git a/core/log_io.c b/core/log_io.c
index e3f15ae98e5b13c30cf5dd6019ca13f28d2c7e39..4b557d80119b805917a2dba4e7c9078cec8a1e07 100644
--- a/core/log_io.c
+++ b/core/log_io.c
@@ -39,28 +39,34 @@
 
 #include <fiber.h>
 #include <log_io.h>
-#include "log_io_internal.h"
 #include <palloc.h>
 #include <say.h>
 #include <third_party/crc32.h>
 #include <util.h>
 #include <pickle.h>
+#include <tbuf.h>
 
+const u16 snap_tag = -1;
+const u16 wal_tag = -2;
+const u64 default_cookie = 0;
+const u32 default_version = 11;
 const u32 snap_marker_v04 = -1U;
 const u64 xlog_marker_v04 = -1ULL;
 const u64 xlog_eof_marker_v04 = 0;
-const u32 marker_v05 = 0xba0babed;
-const u32 marker_v05_eof = 0x10adab1e;
+const u32 marker_v11 = 0xba0babed;
+const u32 eof_marker_v11 = 0x10adab1e;
 const char *snap_suffix = ".snap";
 const char *xlog_suffix = ".xlog";
 const char *v04 = "0.04\n";
 const char *v03 = "0.03\n";
+const char *v11 = "0.11\n";
 const char *snap_mark = "SNAP\n";
 const char *xlog_mark = "XLOG\n";
 
 #define ROW_EOF (void *)1
 
-static struct tbuf *row_reader_v04(FILE * f, struct palloc_pool *pool);
+static struct tbuf *row_reader_v04(FILE *f, struct palloc_pool *pool);
+static struct tbuf *row_reader_v11(FILE *f, struct palloc_pool *pool);
 
 struct log_io_iter {
 	struct tarantool_coro coro;
@@ -72,18 +78,18 @@ struct log_io_iter {
 	int io_rate_limit;
 };
 
+struct row_v04 {
+	i64 lsn;		/* this used to be tid */
+	u16 type;
+	u32 len;
+	u8 data[];
+} __packed__;
 
-i64
-confirmed_lsn(struct recovery_state *r)
+static inline struct row_v04 *row_v04(const struct tbuf *t)
 {
-	return r->confirmed_lsn;
+	return (struct row_v04 *)t->data;
 }
 
-struct child *
-wal_writer(struct recovery_state *r)
-{
-	return r->wal_writer;
-}
 
 int
 confirm_lsn(struct recovery_state *r, i64 lsn)
@@ -92,14 +98,13 @@ confirm_lsn(struct recovery_state *r, i64 lsn)
 
 	if (r->confirmed_lsn < lsn) {
 		if (r->confirmed_lsn + 1 != lsn)
-			say_warn("non consecutive lsn, last confirmed:%"PRIi64
-				 " new:%"PRIi64" diff: %"PRIi64,
+			say_warn("non consecutive lsn, last confirmed:%" PRIi64
+				 " new:%" PRIi64 " diff: %" PRIi64,
 				 r->confirmed_lsn, lsn, lsn - r->confirmed_lsn);
 		r->confirmed_lsn = lsn;
 		return 0;
 	} else {
-		say_warn("lsn double confirmed:%"PRIi64,
-			    r->confirmed_lsn);
+		say_warn("lsn double confirmed:%" PRIi64, r->confirmed_lsn);
 	}
 
 	return -1;
@@ -113,43 +118,89 @@ next_lsn(struct recovery_state *r, i64 new_lsn)
 	else
 		r->lsn = new_lsn;
 
+	say_debug("next_lsn(%p, %" PRIi64 ") => %" PRIi64, r, new_lsn, r->lsn);
 	return r->lsn;
 }
 
-static void
-snap_class(struct log_io_class *c)
-{
-	c->suffix = snap_suffix;
-	c->filetype = snap_mark;
-	c->version = v03;
-	c->eof_marker_size = 0;	/* no end marker */
-	c->marker = snap_marker_v04;
-	c->marker_size = sizeof(snap_marker_v04);
-	c->rows_per_file = 0;
-}
-
-static i64 row_v04_lsn(const struct tbuf *t)
-{
-	return row_v04(t)->lsn;
-}
-
 static void
 xlog04_class(struct log_io_class *c)
 {
 	c->suffix = xlog_suffix;
 	c->filetype = xlog_mark;
 	c->version = v04;
-	c->row_lsn = row_v04_lsn;
 	c->reader = row_reader_v04;
 	c->marker = xlog_marker_v04;
 	c->marker_size = sizeof(xlog_marker_v04);
 	c->eof_marker = xlog_eof_marker_v04;
 	c->eof_marker_size = sizeof(xlog_eof_marker_v04);
 
-	c->rows_per_file = 50000; /* sane defaults */
+	c->rows_per_file = 50000;	/* sane defaults */
 	c->fsync_delay = 0;
 }
 
+static void
+v11_class(struct log_io_class *c)
+{
+	c->suffix = xlog_suffix;
+	c->filetype = xlog_mark;
+	c->version = v11;
+	c->reader = row_reader_v11;
+	c->marker = marker_v11;
+	c->marker_size = sizeof(marker_v11);
+	c->eof_marker = eof_marker_v11;
+	c->eof_marker_size = sizeof(eof_marker_v11);
+
+	c->fsync_delay = 0;
+}
+
+static struct log_io_class **
+snap_classes(row_reader snap_row_reader, const char *dirname)
+{
+	struct log_io_class **c = calloc(3, sizeof(*c));
+	if (c == NULL)
+		panic("calloc");
+
+	c[0] = calloc(1, sizeof(**c));
+	c[1] = calloc(1, sizeof(**c));
+	if (c[0] == NULL || c[1] == NULL)
+		panic("calloc");
+
+	c[0]->suffix = snap_suffix;
+	c[0]->filetype = snap_mark;
+	c[0]->version = v03;
+	c[0]->eof_marker_size = 0;	/* no end marker */
+	c[0]->marker = snap_marker_v04;
+	c[0]->marker_size = sizeof(snap_marker_v04);
+	c[0]->rows_per_file = 0;
+	c[0]->reader = snap_row_reader;
+
+	v11_class(c[1]);
+	c[1]->filetype = c[0]->filetype;
+	c[1]->suffix = c[0]->suffix;
+
+	c[0]->dirname = c[1]->dirname = dirname;
+	return c;
+}
+
+static struct log_io_class **
+xlog_classes(const char *dirname)
+{
+	struct log_io_class **c = calloc(3, sizeof(*c));
+	if (c == NULL)
+		panic("calloc");
+
+	c[0] = calloc(1, sizeof(**c));
+	c[1] = calloc(1, sizeof(**c));
+	if (c[0] == NULL || c[1] == NULL)
+		panic("calloc");
+
+	xlog04_class(c[0]);
+	v11_class(c[1]);
+
+	c[0]->dirname = c[1]->dirname = dirname;
+	return c;
+}
+
 static void *
 iter_inner(struct log_io_iter *i, void *data)
 {
@@ -172,7 +223,6 @@ close_iter(struct log_io_iter *i)
 	tarantool_coro_destroy(&i->coro);
 }
 
-
 static void
 read_rows(struct log_io_iter *i)
 {
@@ -189,32 +239,37 @@ read_rows(struct log_io_iter *i)
 		  l->class->marker, l->class->marker_size);
 
 	good_offset = ftello(l->f);
-restart:
+      restart:
 	if (marker_offset > 0)
 		fseeko(l->f, marker_offset + 1, SEEK_SET);
 
 	for (;;) {
-		say_debug("read_rows: loop start offt %" PRI_OFFT, ftello(l->f));
+		say_debug("read_rows: loop start offt 0x%08" PRI_XFFT, ftello(l->f));
 		if (fread(&magic, l->class->marker_size, 1, l->f) != 1)
 			goto eof;
 
 		while ((magic & marker_mask) != l->class->marker) {
 			int c = fgetc(l->f);
-			if (c == EOF)
+			if (c == EOF) {
+				say_debug("eof while looking for magic");
 				goto eof;
-			magic = (magic << 8) + (c & 0xff);
+			}
+			magic >>= 8;
+			magic |= (((u64)c & 0xff) << ((l->class->marker_size - 1) * 8));
 		}
 		marker_offset = ftello(l->f) - l->class->marker_size;
 		if (good_offset != marker_offset)
-			say_warn("skipped %"PRI_OFFT" bytes after %"PRI_OFFT" offset",
+			say_warn("skipped %" PRI_OFFT " bytes after 0x%08" PRI_XFFT " offset",
 				 marker_offset - good_offset, good_offset);
-		say_debug("magic found at %" PRI_OFFT, marker_offset);
+		say_debug("magic found at 0x%08" PRI_XFFT, marker_offset);
 
 		row = l->class->reader(l->f, fiber->pool);
 		if (row == ROW_EOF)
 			goto eof;
 
 		if (row == NULL) {
+			if (l->class->panic_if_error)
+				panic("failed to read row");
 			say_warn("failed to read row");
 			goto restart;
 		}
@@ -226,15 +281,12 @@ read_rows(struct log_io_iter *i)
 			goto out;
 		}
 
-		if (row_count % 1000 == 0)
-			prelease(fiber->pool);
+		prelease_after(fiber->pool, 128 * 1024);
 
-		if (++row_count % 100000 == 0) {
-			ev_now_update();
+		if (++row_count % 100000 == 0)
 			say_info("%.1fM rows processed", row_count / 1000000.);
-		}
 	}
-eof:
+      eof:
 	/*
 	 * then only two cases of fully read file:
 	 * 1. eof_marker_size > 0 and it is the last record in file
@@ -259,7 +311,7 @@ read_rows(struct log_io_iter *i)
 		goto out;
 	}
 
-out:
+      out:
 	l->rows += row_count;
 
 	fseeko(l->f, good_offset, SEEK_SET);	/* seek back to last known good offset */
@@ -274,23 +326,24 @@ read_rows(struct log_io_iter *i)
 }
 
 static void
-iter_open(struct log_io *l, struct log_io_iter *i, void (*iterator)(struct log_io_iter *i))
+iter_open(struct log_io *l, struct log_io_iter *i, void (*iterator) (struct log_io_iter * i))
 {
 	memset(i, 0, sizeof(*i));
 	i->log = l;
 	tarantool_coro_create(&i->coro, (void *)iterator, i);
 }
 
-
 static int
 cmp_i64(const void *_a, const void *_b)
 {
 	const i64 *a = _a, *b = _b;
-	return *a - *b;
+	if (*a == *b)
+		return 0;
+	return (*a > *b) ? 1 : -1;
 }
 
 static ssize_t
-scan_dir(struct log_io_class *class, i64 ** ret_lsn)
+scan_dir(struct log_io_class *class, i64 **ret_lsn)
 {
 	DIR *dh = NULL;
 	struct dirent *dent;
@@ -323,8 +376,11 @@ scan_dir(struct log_io_class *class, i64 ** ret_lsn)
 			continue;
 		}
 
-		if (lsn[i] == LLONG_MAX || lsn[i] == LLONG_MIN)
-			goto out;
+		if (lsn[i] == LLONG_MAX || lsn[i] == LLONG_MIN) {
+			say_warn("can't parse `%s', skipping", dent->d_name);
+			continue;
+		}
+
 		i++;
 		if (i == size) {
 			i64 *n = palloc(fiber->pool, sizeof(i64) * size * 2);
@@ -340,7 +396,7 @@ scan_dir(struct log_io_class *class, i64 ** ret_lsn)
 
 	*ret_lsn = lsn;
 	result = i;
-out:
+      out:
 	if (errno != 0)
 		say_syserror("error reading directory `%s'", class->dirname);
 
@@ -385,13 +441,28 @@ find_including_file(struct log_io_class *class, i64 target_lsn)
 	 * is not known beforehand. so, we simply return the last one.
 	 */
 
-out:
+      out:
 	return *lsn;
 }
 
+struct tbuf *
+convert_to_v11(struct tbuf *orig, u16 tag, const u64 cookie, i64 lsn)
+{
+	struct tbuf *row = tbuf_alloc(orig->pool);
+	tbuf_ensure(row, sizeof(struct row_v11));
+	row->len = sizeof(struct row_v11);
+	row_v11(row)->lsn = lsn;
+	row_v11(row)->tm = 0;
+	row_v11(row)->len = orig->len + sizeof(tag) + sizeof(cookie);
+
+	tbuf_append(row, &tag, sizeof(tag));
+	tbuf_append(row, &cookie, sizeof(cookie));
+	tbuf_append(row, orig->data, orig->len);
+	return row;
+}
 
 static struct tbuf *
-row_reader_v04(FILE * f, struct palloc_pool *pool)
+row_reader_v04(FILE *f, struct palloc_pool *pool)
 {
 	const int header_size = offsetof(struct row_v04, data);
 	struct tbuf *m = tbuf_alloc(pool);
@@ -428,7 +499,51 @@ row_reader_v04(FILE * f, struct palloc_pool *pool)
 		return NULL;
 	}
 
-	say_debug("read row success lsn:%" PRIi64, row_v04(m)->lsn);
+	say_debug("read row v04 success lsn:%" PRIi64, row_v04(m)->lsn);
+
+	/* we're copying row data twice here, it's ok since this is legacy function */
+	struct tbuf *data = tbuf_alloc(pool);
+	tbuf_append(data, &row_v04(m)->type, sizeof(row_v04(m)->type));
+	tbuf_append(data, row_v04(m)->data, row_v04(m)->len);
+
+	return convert_to_v11(data, wal_tag, default_cookie, row_v04(m)->lsn);
+}
+
+static struct tbuf *
+row_reader_v11(FILE *f, struct palloc_pool *pool)
+{
+	struct tbuf *m = tbuf_alloc(pool);
+
+	u32 header_crc, data_crc;
+
+	tbuf_ensure(m, sizeof(struct row_v11));
+	if (fread(m->data, sizeof(struct row_v11), 1, f) != 1)
+		return ROW_EOF;
+
+	m->len = offsetof(struct row_v11, data);
+
+	/* header crc32c calculated on <lsn, tm, len, data_crc32c> */
+	header_crc = crc32c(0, m->data + offsetof(struct row_v11, lsn),
+			    sizeof(struct row_v11) - offsetof(struct row_v11, lsn));
+
+	if (row_v11(m)->header_crc32c != header_crc) {
+		say_error("header crc32c mismatch");
+		return NULL;
+	}
+
+	tbuf_ensure(m, m->len + row_v11(m)->len);
+	if (fread(row_v11(m)->data, row_v11(m)->len, 1, f) != 1)
+		return ROW_EOF;
+
+	m->len += row_v11(m)->len;
+
+	data_crc = crc32c(0, row_v11(m)->data, row_v11(m)->len);
+	if (row_v11(m)->data_crc32c != data_crc) {
+		say_error("data crc32c mismatch");
+		return NULL;
+	}
+
+	say_debug("read row v11 success lsn:%" PRIi64, row_v11(m)->lsn);
 	return m;
 }
 
@@ -456,22 +571,9 @@ close_log(struct log_io **lptr)
 static int
 flush_log(struct log_io *l)
 {
-
-	static double last = 0;
-	double now;
-	struct timeval t;
-
 	if (fflush(l->f) < 0)
 		return -1;
 
-	if (gettimeofday(&t, NULL) < 0) {
-		say_syserror("gettimeofday");
-		return -1;
-	}
-	now = t.tv_sec + t.tv_usec / 1000000.;
-
-	if (l->class->fsync_delay == 0 || now - last < l->class->fsync_delay)
-		return 0;
 #ifdef Linux
 	if (fdatasync(fileno(l->f)) < 0) {
 		say_syserror("fdatasync");
@@ -483,7 +585,6 @@ flush_log(struct log_io *l)
 		return -1;
 	}
 #endif
-	last = now;
 	return 0;
 }
 
@@ -499,12 +600,15 @@ write_header(struct log_io *l)
 	if (fwrite(l->class->version, strlen(l->class->version), 1, l->f) != 1)
 		return -1;
 
-	time(&tm);
-	ctime_r(&tm, buf);
-	/* 20 bytes is hardcoded timestring length in silverspoon */
-	//buf[19] = '\n';
-	if (fwrite(buf, strlen(buf), 1, l->f) != 1)
-		return -1;
+	if (strcmp(l->class->version, v11) == 0) {
+		if (fwrite("\n", 1, 1, l->f) != 1)
+			return -1;
+	} else {
+		time(&tm);
+		ctime_r(&tm, buf);
+		if (fwrite(buf, strlen(buf), 1, l->f) != 1)
+			return -1;
+	}
 
 	return 0;
 }
@@ -518,7 +622,8 @@ format_filename(char *filename, struct log_io_class *class, i64 lsn, int suffix)
 		filename = buf;
 
 	switch (suffix) {
-	case 0: snprintf(filename, PATH_MAX, "%s/%020" PRIi64 "%s",
+	case 0:
+		snprintf(filename, PATH_MAX, "%s/%020" PRIi64 "%s",
 			 class->dirname, lsn, class->suffix);
 		break;
 	case -1:
@@ -534,9 +639,10 @@ format_filename(char *filename, struct log_io_class *class, i64 lsn, int suffix)
 }
 
 static struct log_io *
-open_for_read(struct recovery_state *recover, struct log_io_class *class, i64 lsn, int suffix, const char *filename)
+open_for_read(struct recovery_state *recover, struct log_io_class **class, i64 lsn, int suffix,
+	      const char *filename)
 {
-	char filetype[32], version[32], buf[32];
+	char filetype[32], version[32], buf[256];
 	struct log_io *l = NULL;
 	char *r;
 	char *error = "unknown error";
@@ -546,13 +652,12 @@ open_for_read(struct recovery_state *recover, struct log_io_class *class, i64 ls
 		goto error;
 	memset(l, 0, sizeof(*l));
 	l->mode = LOG_READ;
-	l->class = class;
 	l->stat.data = recover;
 
 	/* when filename is not null it is forced open for debug reading */
 	if (filename == NULL) {
 		assert(lsn != 0);
-		format_filename(l->filename, class, lsn, suffix);
+		format_filename(l->filename, *class, lsn, suffix);
 	} else {
 		assert(lsn == 0);
 		strncpy(l->filename, filename, PATH_MAX);
@@ -578,24 +683,43 @@ open_for_read(struct recovery_state *recover, struct log_io_class *class, i64 ls
 		goto error;
 	}
 
-	if (strcmp(class->filetype, filetype) != 0) {
+	if (strcmp((*class)->filetype, filetype) != 0) {
 		error = "unknown filetype";
 		goto error;
 	}
 
-	if (strcmp(class->version, version) != 0) {
-		error = "unknown version";
-		goto error;
+	while (*class) {
+		if (strcmp((*class)->version, version) == 0)
+			break;
+		class++;
 	}
 
-	r = fgets(buf, sizeof(buf), l->f);	/* skip line with time */
-	if (r == NULL) {
-		error = "header reading failed";
+	if (*class == NULL) {
+		error = "unknown version";
 		goto error;
 	}
+	l->class = *class;
+
+	if (strcmp(version, v11) == 0) {
+		for (;;) {
+			r = fgets(buf, sizeof(buf), l->f);
+			if (r == NULL) {
+				error = "header reading failed";
+				goto error;
+			}
+			if (strcmp(r, "\n") == 0 || strcmp(r, "\r\n") == 0)
+				break;
+		}
+	} else {
+		r = fgets(buf, sizeof(buf), l->f);	/* skip line with time */
+		if (r == NULL) {
+			error = "header reading failed";
+			goto error;
+		}
+	}
 
 	return l;
-error:
+      error:
 	say_error("open_for_read: failed to open `%s': %s", l->filename, error);
 	if (l != NULL) {
 		if (l->f != NULL)
@@ -625,7 +749,7 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 	format_filename(l->filename, class, lsn, suffix);
 	say_debug("find_log for writing `%s'", l->filename);
 
-	fd = open(l->filename, O_WRONLY|O_CREAT|O_EXCL|O_APPEND, 0664);
+	fd = open(l->filename, O_WRONLY | O_CREAT | O_EXCL | O_APPEND, 0664);
 	if (fd < 0) {
 		error = strerror(errno);
 		goto error;
@@ -640,7 +764,7 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 	say_info("creating `%s'", l->filename);
 	write_header(l);
 	return l;
-error:
+      error:
 	say_error("find_log: failed to open `%s': %s", l->filename, error);
 	if (l != NULL) {
 		if (l->f != NULL)
@@ -653,39 +777,32 @@ open_for_write(struct recovery_state *recover, struct log_io_class *class, i64 l
 /* this little hole shouldn't be used too much */
 int
 read_log(const char *filename, row_reader reader,
-	 row_handler xlog_handler, row_handler snap_handler, void *state)
+	 row_handler *xlog_handler, row_handler *snap_handler, void *state)
 {
 	struct log_io_iter i;
 	struct log_io *l;
-	struct log_io_class class;
+	struct log_io_class **c;
 	struct tbuf *row;
-
-	memset(&i, 0, sizeof(i));
-	memset(&class, 0, sizeof(class));
+	row_handler *h;
 
 	if (strstr(filename, xlog_suffix)) {
-		class.handler = xlog_handler;
-		xlog04_class(&class);
-	}
-	if (strstr(filename, snap_suffix)) {
-		class.handler = snap_handler;
-		snap_class(&class);
-		if (reader)
-			class.reader = reader;
-	}
-
-	if (class.filetype == NULL) {
+		c = xlog_classes(NULL);
+		h = xlog_handler;
+	} else if (strstr(filename, snap_suffix)) {
+		c = snap_classes(reader, NULL);
+		h = snap_handler;
+	} else {
 		say_error("don't know what how to read `%s'", filename);
 		return -1;
 	}
 
-	l = open_for_read(NULL, &class, 0, 0, filename);
+	l = open_for_read(NULL, c, 0, 0, filename);
 	iter_open(l, &i, read_rows);
 	while ((row = iter_inner(&i, (void *)1)))
-		class.handler(state, row);
+		h(state, row);
 
 	if (i.error != 0)
-		say_error("log `%s' wasn't correctly closed", filename);
+		say_error("binary log `%s' wasn't correctly closed", filename);
 
 	close_iter(&i);
 	return i.error;
@@ -707,14 +824,14 @@ recover_snap(struct recovery_state *r)
 		goto out;
 	}
 
-	lsn = greatest_lsn(&r->snap_class);
+	lsn = greatest_lsn(r->snap_prefered_class);
 
 	if (lsn <= 0) {
 		say_error("can't find snapshot");
 		goto out;
 	}
 
-	snap = open_for_read(r, &r->snap_class, lsn, 0, NULL);
+	snap = open_for_read(r, r->snap_class, lsn, 0, NULL);
 	if (snap == NULL) {
 		say_error("can't find/open snapshot");
 		goto out;
@@ -724,7 +841,7 @@ recover_snap(struct recovery_state *r)
 	say_info("recover from `%s'", snap->filename);
 
 	while ((row = iter_inner(&i, (void *)1))) {
-		if (snap->class->handler(r, row) < 0) {
+		if (r->row_handler(r, row) < 0) {
 			result = -1;
 			goto out;
 		}
@@ -732,7 +849,7 @@ recover_snap(struct recovery_state *r)
 	result = i.error;
 	if (result == 0)
 		r->lsn = r->confirmed_lsn = lsn;
-out:
+      out:
 	if (result != 0)
 		say_error("failure reading snapshot");
 
@@ -759,31 +876,38 @@ static int
 recover_wal(struct recovery_state *r, struct log_io *l)
 {
 	struct log_io_iter i;
-	struct tbuf *row;
+	struct tbuf *row = NULL;
 	int result;
 
+	if (setjmp(fiber->exc) != 0) {
+		result = -1;
+		goto out;
+	}
+
 	memset(&i, 0, sizeof(i));
 	iter_open(l, &i, read_rows);
 
 	while ((row = iter_inner(&i, (void *)1))) {
-		if (r && l->class->row_lsn(row) <= confirmed_lsn(r)) {
+		i64 lsn = row_v11(row)->lsn;
+		if (r && lsn <= r->confirmed_lsn) {
 			say_debug("skipping too young row");
 			continue;
 		}
 
-		if (l->class->handler(r, row) < 0) {
+		/*  after handler(r, row) returned, row may be modified, do not use it */
+		if (r->row_handler(r, row) < 0) {
 			say_error("row_handler returned error");
 			result = -1;
 			goto out;
 		}
 
 		if (r) {
-			next_lsn(r, l->class->row_lsn(row));
-			confirm_lsn(r, l->class->row_lsn(row));
+			next_lsn(r, lsn);
+			confirm_lsn(r, lsn);
 		}
 	}
 	result = i.error;
-out:
+      out:
 	/*
 	 * since we don't close log_io
 	 * we must rewind log_io to last known
@@ -818,27 +942,29 @@ recover_remaining_wals(struct recovery_state *r)
 	i64 current_lsn, wal_greatest_lsn;
 	size_t rows_before;
 
-	current_lsn = confirmed_lsn(r) + 1;
-	wal_greatest_lsn = greatest_lsn(&r->wal_class);
+	current_lsn = r->confirmed_lsn + 1;
+	wal_greatest_lsn = greatest_lsn(r->wal_prefered_class);
 
 	/* if the caller already opened WAL for us, recover from it first */
 	if (r->current_wal != NULL)
 		goto recover_current_wal;
 
-	while (confirmed_lsn(r) < wal_greatest_lsn) {
+	while (r->confirmed_lsn < wal_greatest_lsn) {
 		/* if newer WAL appeared in directory before current_wal was fully read try reread last */
 		if (r->current_wal != NULL) {
 			if (r->current_wal->retry++ < 3) {
-				say_warn("try reread `%s' despite newer WAL exists", r->current_wal->filename);
+				say_warn("try reread `%s' despite newer WAL exists",
+					 r->current_wal->filename);
 				goto recover_current_wal;
 			} else {
-				say_warn("wal `%s' wasn't correctly closed", r->current_wal->filename);
+				say_warn("wal `%s' wasn't correctly closed",
+					 r->current_wal->filename);
 				close_log(&r->current_wal);
 			}
 		}
 
-		current_lsn = confirmed_lsn(r) + 1; /* TODO: find better way looking for next xlog */
-		next_wal = open_for_read(r, &r->wal_class, current_lsn, suffix, NULL);
+		current_lsn = r->confirmed_lsn + 1;	/* TODO: find better way looking for next xlog */
+		next_wal = open_for_read(r, r->wal_class, current_lsn, suffix, NULL);
 		if (next_wal == NULL) {
 			if (suffix++ < 10)
 				continue;
@@ -849,7 +975,7 @@ recover_remaining_wals(struct recovery_state *r)
 		r->current_wal = next_wal;
 		say_info("recover from `%s'", r->current_wal->filename);
 
-	recover_current_wal:
+	      recover_current_wal:
 		rows_before = r->current_wal->rows;
 		result = recover_wal(r, r->current_wal);
 		if (result < 0) {
@@ -869,11 +995,12 @@ recover_remaining_wals(struct recovery_state *r)
 			if (suffix++ < 10)
 				continue;
 
-			say_error("too many filename confilcters");
+			say_error("too many filename conflicters");
 			result = -1;
 			break;
 		} else {
-			name = format_filename(NULL, &r->wal_class, current_lsn, suffix + 1);
+			name = format_filename(NULL, r->wal_prefered_class,
+					       current_lsn, suffix + 1);
 			if (access(name, F_OK) == 0) {
 				say_error("found conflicter `%s' after successful reading", name);
 				result = -1;
@@ -882,7 +1009,8 @@ recover_remaining_wals(struct recovery_state *r)
 		}
 
 		if (result == LOG_EOF) {
-			say_info("done `%s' confirmed_lsn:%"PRIi64, r->current_wal->filename, confirmed_lsn(r));
+			say_info("done `%s' confirmed_lsn:%" PRIi64, r->current_wal->filename,
+				 r->confirmed_lsn);
 			close_log(&r->current_wal);
 		}
 		suffix = 0;
@@ -892,7 +1020,7 @@ recover_remaining_wals(struct recovery_state *r)
 	 * it's not a fatal error then last wal is empty
 	 * but if we lost some logs it is fatal error
 	 */
-	if (wal_greatest_lsn > confirmed_lsn(r) + 1) {
+	if (wal_greatest_lsn > r->confirmed_lsn + 1) {
 		say_error("not all wals have been successfuly read");
 		result = -1;
 	}
@@ -915,13 +1043,13 @@ recover(struct recovery_state *r, i64 lsn)
 	if (lsn == 0) {
 		result = recover_snap(r);
 		if (result < 0) {
-			if (greatest_lsn(&r->snap_class) <= 0) {
+			if (greatest_lsn(r->snap_prefered_class) <= 0) {
 				say_crit("don't you forget to initialize storage with --init_storage switch?");
 				_exit(1);
 			}
 			panic("snapshot recovery failed");
 		}
-		say_info("snapshot recovered, confirmed lsn:%"PRIi64, confirmed_lsn(r));
+		say_info("snapshot recovered, confirmed lsn:%" PRIi64, r->confirmed_lsn);
 	} else {
 		/*
 		 * note, that recovery start with lsn _NEXT_ to confirmed one
@@ -934,14 +1062,14 @@ recover(struct recovery_state *r, i64 lsn)
 	 * so find wal which contains record with next lsn
 	 */
 	if (r->current_wal == NULL) {
-		i64 next_lsn = confirmed_lsn(r) + 1;
-		i64 lsn = find_including_file(&r->wal_class, next_lsn);
+		i64 next_lsn = r->confirmed_lsn + 1;
+		i64 lsn = find_including_file(r->wal_prefered_class, next_lsn);
 		if (lsn <= 0) {
-			say_error("can't find wal containing record with lsn:%"PRIi64, next_lsn);
+			say_error("can't find wal containing record with lsn:%" PRIi64, next_lsn);
 			result = -1;
 			goto out;
 		} else {
-			r->current_wal = open_for_read(r, &r->wal_class, lsn, 0, NULL);
+			r->current_wal = open_for_read(r, r->wal_class, lsn, 0, NULL);
 			if (r->current_wal == NULL) {
 				result = -1;
 				goto out;
@@ -950,17 +1078,18 @@ recover(struct recovery_state *r, i64 lsn)
 	}
 
 	result = recover_remaining_wals(r);
-	say_info("wals recovered, confirmed lsn: %"PRIi64, confirmed_lsn(r));
-out:
+	if (result < 0)
+		panic("recover failed");
+	say_info("wals recovered, confirmed lsn: %" PRIi64, r->confirmed_lsn);
+      out:
 	prelease(fiber->pool);
 	return result;
 }
 
-
-static void recover_follow_file(ev_stat * w, int revents __unused__);
+static void recover_follow_file(ev_stat *w, int revents __unused__);
 
 static void
-recover_follow_dir(ev_timer * w, int revents __unused__)
+recover_follow_dir(ev_timer *w, int revents __unused__)
 {
 	struct recovery_state *r = w->data;
 	struct log_io *wal = r->current_wal;
@@ -977,15 +1106,16 @@ recover_follow_dir(ev_timer * w, int revents __unused__)
 }
 
 static void
-recover_follow_file(ev_stat * w, int revents __unused__)
+recover_follow_file(ev_stat *w, int revents __unused__)
 {
 	struct recovery_state *r = w->data;
 	int result;
 	result = recover_wal(r, r->current_wal);
 	if (result < 0)
-		panic("recover failed: %i", result);
+		panic("recover failed");
 	if (result == LOG_EOF) {
-		say_info("done `%s' confirmed_lsn:%"PRIi64, r->current_wal->filename, confirmed_lsn(r));
+		say_info("done `%s' confirmed_lsn:%" PRIi64, r->current_wal->filename,
+			 r->confirmed_lsn);
 		close_log(&r->current_wal);
 		recover_follow_dir((ev_timer *)w, 0);
 	}
@@ -994,9 +1124,9 @@ recover_follow_file(ev_stat * w, int revents __unused__)
 void
 recover_follow(struct recovery_state *r, ev_tstamp wal_dir_rescan_delay)
 {
-	ev_timer_init(&r->wal_class.timer, recover_follow_dir,
+	ev_timer_init(&r->wal_timer, recover_follow_dir,
 		      wal_dir_rescan_delay, wal_dir_rescan_delay);
-	ev_timer_start(&r->wal_class.timer);
+	ev_timer_start(&r->wal_timer);
 	if (r->current_wal != NULL) {
 		ev_stat *stat = &r->current_wal->stat;
 		ev_stat_init(stat, recover_follow_file, r->current_wal->filename, 0.);
@@ -1007,16 +1137,24 @@ recover_follow(struct recovery_state *r, ev_tstamp wal_dir_rescan_delay)
 void
 recover_finalize(struct recovery_state *r)
 {
-	if (ev_is_active(&r->wal_class.timer))
-		ev_timer_stop(&r->wal_class.timer);
+	int result;
+
+	if (ev_is_active(&r->wal_timer))
+		ev_timer_stop(&r->wal_timer);
 
 	if (r->current_wal != NULL) {
 		if (ev_is_active(&r->current_wal->stat))
 			ev_stat_stop(&r->current_wal->stat);
 	}
 
-	if (recover_remaining_wals(r) < 0)
+	result = recover_remaining_wals(r);
+	if (result < 0)
 		panic("unable to scucessfully finalize recovery");
+
+	if (r->current_wal != NULL && result != LOG_EOF) {
+		say_warn("wal `%s' wasn't correctly closed", r->current_wal->filename);
+		close_log(&r->current_wal);
+	}
 }
 
 static struct wal_write_request *
@@ -1026,17 +1164,19 @@ wal_write_request(const struct tbuf *t)
 }
 
 static struct tbuf *
-write_to_disk_v04(void *_state, struct tbuf *t)
+write_to_disk(void *_state, struct tbuf *t)
 {
 	static struct log_io *wal = NULL, *wal_to_close = NULL;
+	static ev_tstamp last_flush = 0;
 	static size_t rows = 0;
-	struct tbuf *reply;
-	u32 calculated_crc;
+	struct tbuf *reply, *header;
 	struct recovery_state *r = _state;
 	u32 result = 0;
 	int suffix = 0;
 
+	/* we're not running inside ev_loop, so update ev_now manually */
 	ev_now_update();
+
 	/* caller requested termination */
 	if (t == NULL) {
 		if (wal != NULL)
@@ -1048,7 +1188,7 @@ write_to_disk_v04(void *_state, struct tbuf *t)
 
 	/* if there is filename conflict, try filename with lager suffix */
 	while (wal == NULL && suffix < 10) {
-		wal = open_for_write(r, &r->wal_class, wal_write_request(t)->lsn, suffix);
+		wal = open_for_write(r, r->wal_prefered_class, wal_write_request(t)->lsn, suffix);
 		suffix++;
 	}
 	if (wal_to_close != NULL) {
@@ -1063,26 +1203,47 @@ write_to_disk_v04(void *_state, struct tbuf *t)
 		say_syserror("can't write marker to wal");
 		goto fail;
 	}
-	if (fwrite(wal_write_request(t)->data, wal_write_request(t)->len, 1, wal->f) != 1) {
-		say_syserror("can't write data to wal");
+
+	header = tbuf_alloc(t->pool);
+	tbuf_ensure(header, sizeof(struct row_v11));
+	header->len = sizeof(struct row_v11);
+
+	row_v11(header)->lsn = wal_write_request(t)->lsn;
+	row_v11(header)->tm = ev_now();
+	row_v11(header)->len = wal_write_request(t)->len;
+	row_v11(header)->data_crc32c =
+		crc32c(0, wal_write_request(t)->data, wal_write_request(t)->len);
+	row_v11(header)->header_crc32c =
+		crc32c(0, header->data + field_sizeof(struct row_v11, header_crc32c),
+		       sizeof(struct row_v11) - field_sizeof(struct row_v11, header_crc32c));
+
+	if (fwrite(header->data, header->len, 1, wal->f) != 1) {
+		say_syserror("can't write row header to wal");
 		goto fail;
 	}
 
-	calculated_crc = crc32(wal_write_request(t)->data, wal_write_request(t)->len);
-	if (fwrite(&calculated_crc, sizeof(calculated_crc), 1, wal->f) != 1) {
-		say_syserror("can't write crc to wal");
+	if (fwrite(wal_write_request(t)->data, wal_write_request(t)->len, 1, wal->f) != 1) {
+		say_syserror("can't write row data to wal");
 		goto fail;
 	}
 
-	if (flush_log(wal) < 0) {
+	/* flush stdio buffer to keep feeder in sync */
+	if (fflush(wal->f) < 0) {
 		say_syserror("can't flush wal");
 		goto fail;
 	}
 
+	if (wal->class->fsync_delay > 0 && ev_now() - last_flush >= wal->class->fsync_delay) {
+		if (flush_log(wal) < 0) {
+			say_syserror("can't flush wal");
+			goto fail;
+		}
+		last_flush = ev_now();
+	}
+
 	rows++;
 	if (wal->class->rows_per_file <= rows ||
-	    (wal_write_request(t)->lsn + 1) % wal->class->rows_per_file == 0 )
-	{
+	    (wal_write_request(t)->lsn + 1) % wal->class->rows_per_file == 0) {
 		wal_to_close = wal;
 		wal = NULL;
 		rows = 0;
@@ -1092,23 +1253,26 @@ write_to_disk_v04(void *_state, struct tbuf *t)
 	tbuf_append(reply, &result, sizeof(result));
 	return reply;
 
-fail:
+      fail:
 	result = 1;
 	tbuf_append(reply, &result, sizeof(result));
 	return reply;
 }
 
 bool
-wal_write(struct recovery_state *r, i64 lsn, struct tbuf *data)
+wal_write(struct recovery_state *r, u16 tag, u64 cookie, i64 lsn, struct tbuf *row)
 {
-	struct tbuf *m = tbuf_alloc(data->pool);
+	struct tbuf *m = tbuf_alloc(row->pool);
 	struct msg *a;
 
-	say_debug("wal_write lsn=%"PRIi64, lsn);
-	tbuf_reserve(m, sizeof(struct wal_write_request) + data->len);
+	say_debug("wal_write lsn=%" PRIi64, lsn);
+	tbuf_reserve(m, sizeof(struct wal_write_request) + sizeof(tag) + sizeof(cookie) + row->len);
+	m->len = sizeof(struct wal_write_request);
 	wal_write_request(m)->lsn = lsn;
-	wal_write_request(m)->len = data->len;
-	memcpy(wal_write_request(m)->data, data->data, data->len);
+	wal_write_request(m)->len = row->len + sizeof(tag) + sizeof(cookie);
+	tbuf_append(m, &tag, sizeof(tag));
+	tbuf_append(m, &cookie, sizeof(cookie));
+	tbuf_append(m, row->data, row->len);
 
 	if (write_inbox(r->wal_writer->out, m) == false) {
 		say_warn("wal writer inbox is full");
@@ -1117,119 +1281,130 @@ wal_write(struct recovery_state *r, i64 lsn, struct tbuf *data)
 	a = read_inbox();
 
 	u32 reply = read_u32(a->msg);
-	say_debug("wal_write reply=%"PRIu32, reply);
+	say_debug("wal_write reply=%" PRIu32, reply);
 	if (reply != 0)
 		say_warn("wal writer returned error status");
 	return reply == 0;
 }
 
-bool
-wal_write_v04(struct recovery_state * r, int op, const u8 *data, size_t len)
-{
-	i64 lsn = next_lsn(r, 0);
-	struct tbuf *m = tbuf_alloc(fiber->pool);
-	tbuf_reserve(m, sizeof(struct row_v04) + len);
-	row_v04(m)->lsn = lsn;
-	row_v04(m)->type = op;
-	row_v04(m)->len = len;
-	memcpy(row_v04(m)->data, data, row_v04(m)->len);
-
-	if (wal_write(r, lsn, m)) {
-		confirm_lsn(r, lsn);
-		return true;
-	}
-
-	say_warn("wal_write failed, txn lsn:%"PRIi64 " aborted", lsn);
-	return false;
-}
-
 struct recovery_state *
 recover_init(const char *snap_dirname, const char *wal_dirname,
-	     row_reader snap_row_reader, row_handler snap_row_handler, row_handler wal_row_handler,
-	     int rows_per_file, double fsync_delay, double snap_io_rate_limit,
+	     row_reader snap_row_reader, row_handler row_handler,
+	     int rows_per_file, double fsync_delay,
 	     int inbox_size, int flags, void *data)
 {
-	struct recovery_state *r = malloc(sizeof(*r));	/* let it leak */
-	memset(r, 0, sizeof(*r));
+	struct recovery_state *r = p0alloc(eter_pool, sizeof(*r));
 
-	snap_class(&r->snap_class);
-	r->snap_class.dirname = snap_dirname;
-	r->snap_class.reader = snap_row_reader;
-	r->snap_class.handler = snap_row_handler;
+	r->wal_timer.data = r;
+	r->row_handler = row_handler;
+	r->data = data;
 
-	xlog04_class(&r->wal_class);
-	r->wal_class.dirname = wal_dirname;
-	r->wal_class.handler = wal_row_handler;
-	r->wal_class.timer.data = r;
-	r->wal_class.rows_per_file = rows_per_file;
-	r->wal_class.fsync_delay = fsync_delay;
+	r->snap_class = snap_classes(snap_row_reader, snap_dirname);
+	r->snap_prefered_class = r->snap_class[1];
+
+	r->wal_class = xlog_classes(wal_dirname);
+	r->wal_prefered_class = r->wal_class[1];
+	r->wal_prefered_class->rows_per_file = rows_per_file;
+	r->wal_prefered_class->fsync_delay = fsync_delay;
+
+	if ((flags & RECOVER_READONLY) == 0)
+		r->wal_writer = spawn_child("wal_writer", inbox_size, write_to_disk, r);
 
-	r->data = data;
-	if ((flags & RECOVER_READONLY) == 0) {
-		r->wal_writer = spawn_child("wal_writer", inbox_size, write_to_disk_v04, r);
-		r->snap_io_rate_limit = snap_io_rate_limit * 1024 * 1024;
-	}
 	return r;
 }
 
+void
+recovery_setup_panic(struct recovery_state *r, bool on_snap_error, bool on_wal_error)
+{
+	struct log_io_class **class;
+
+	for (class = r->wal_class; *class; class++)
+		(*class)->panic_if_error = on_wal_error;
+
+	for (class = r->snap_class; *class; class++)
+		(*class)->panic_if_error = on_snap_error;
+}
 
 static void
 write_rows(struct log_io_iter *i)
 {
 	struct log_io *l = i->log;
-	struct tbuf *row;
+	struct tbuf *row, *data;
+
+	row = tbuf_alloc(eter_pool);
+	tbuf_ensure(row, sizeof(struct row_v11));
+	row->len = sizeof(struct row_v11);
 
 	goto start;
 	for (;;) {
 		coro_transfer(&i->coro.ctx, &fiber->coro.ctx);
-	start:
-		row = i->to;
+	      start:
+		data = i->to;
 
 		if (fwrite(&l->class->marker, l->class->marker_size, 1, l->f) != 1)
 			panic("fwrite");
 
+		row_v11(row)->lsn = 0;	/* unused */
+		row_v11(row)->tm = ev_now();
+		row_v11(row)->len = data->len;
+		row_v11(row)->data_crc32c = crc32c(0, data->data, data->len);
+		row_v11(row)->header_crc32c =
+			crc32c(0, row->data + field_sizeof(struct row_v11, header_crc32c),
+			       sizeof(struct row_v11) - field_sizeof(struct row_v11,
+								     header_crc32c));
+
 		if (fwrite(row->data, row->len, 1, l->f) != 1)
 			panic("fwrite");
+
+		if (fwrite(data->data, data->len, 1, l->f) != 1)
+			panic("fwrite");
+
+		prelease_after(fiber->pool, 128 * 1024);
 	}
 }
 
-
 void
-snapshot_write_row(struct log_io_iter *i, struct tbuf *row)
+snapshot_write_row(struct log_io_iter *i, u16 tag, u64 cookie, struct tbuf *row)
 {
 	static int rows;
 	static int bytes;
-	static struct timeval last;
+	ev_tstamp elapsed;
+	static ev_tstamp last = 0;
+	struct tbuf *wal_row = tbuf_alloc(fiber->pool);
+
+	tbuf_append(wal_row, &tag, sizeof(tag));
+	tbuf_append(wal_row, &cookie, sizeof(cookie));
+	tbuf_append(wal_row, row->data, row->len);
 
-	i->to = row;
+	i->to = wal_row;
 	if (i->io_rate_limit > 0) {
-		if (last.tv_sec == 0)
-			gettimeofday(&last, NULL);
-		bytes += row->len;
+		if (last == 0) {
+			ev_now_update();
+			last = ev_now();
+		}
 
-		while (bytes >= i->io_rate_limit) {
-			struct timeval now;
-			useconds_t elapsed;
+		bytes += row->len + sizeof(struct row_v11);
 
-			gettimeofday(&now, NULL);
-			elapsed = (now.tv_sec - last.tv_sec) * 1000000 + now.tv_usec - last.tv_usec;
+		while (bytes >= i->io_rate_limit) {
+			flush_log(i->log);
 
-			if (elapsed < 1000000)
-				usleep(1000000 - elapsed);
+			ev_now_update();
+			elapsed = ev_now() - last;
+			if (elapsed < 1)
+				usleep(((1 - elapsed) * 1000000));
 
-			gettimeofday(&last, NULL);
+			ev_now_update();
+			last = ev_now();
 			bytes -= i->io_rate_limit;
 		}
 	}
 	coro_transfer(&fiber->coro.ctx, &i->coro.ctx);
-	if (++rows % 100000 == 0) {
-		ev_now_update();
+	if (++rows % 100000 == 0)
 		say_crit("%.1fM rows written", rows / 1000000.);
-	}
 }
 
 void
-snapshot_save(struct recovery_state *r, void (*f)(struct log_io_iter *))
+snapshot_save(struct recovery_state *r, void (*f) (struct log_io_iter *))
 {
 	struct log_io_iter i;
 	struct log_io *snap;
@@ -1238,7 +1413,7 @@ snapshot_save(struct recovery_state *r, void (*f)(struct log_io_iter *))
 
 	memset(&i, 0, sizeof(i));
 
-	snap = open_for_write(r, &r->snap_class, confirmed_lsn(r), -1);
+	snap = open_for_write(r, r->snap_prefered_class, r->confirmed_lsn, -1);
 	if (snap == NULL)
 		panic("can't open snap for writing");
 
@@ -1251,11 +1426,10 @@ snapshot_save(struct recovery_state *r, void (*f)(struct log_io_iter *))
 	dot = strrchr(final_filename, '.');
 	*dot = 0;
 
-	ev_now_update();
 	say_info("saving snapshot `%s'", final_filename);
 	f(&i);
 
-        if (fsync(fileno(snap->f)) < 0)
+	if (fsync(fileno(snap->f)) < 0)
 		panic("fsync");
 
 	if (rename(snap->filename, final_filename) != 0)
@@ -1263,6 +1437,5 @@ snapshot_save(struct recovery_state *r, void (*f)(struct log_io_iter *))
 
 	close_log(&snap);
 
-	ev_now_update();
 	say_info("done");
 }
diff --git a/core/log_io_internal.h b/core/log_io_internal.h
deleted file mode 100644
index 84090e2d841fc3947d4f444fe9fb2e9823195115..0000000000000000000000000000000000000000
--- a/core/log_io_internal.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef TARANTOOL_LOG_IO_INTERNAL_H
-#define TARANTOOL_LOG_IO_INTERNAL_H
-
-enum log_mode {
-	LOG_READ,
-	LOG_WRITE
-};
-
-struct log_io_class {
-	ev_timer timer;
-
-	i64 (*row_lsn)(const struct tbuf *);
-	row_handler handler;
-	row_reader reader;
-	u64 marker, eof_marker;
-	size_t marker_size, eof_marker_size;
-	size_t rows_per_file;
-	double fsync_delay;
-
-	const char *filetype;
-	const char *version;
-	const char *suffix;
-	const char *dirname;
-};
-
-struct log_io {
-	struct log_io_class *class;
-	FILE *f;
-
-	ev_stat stat;
-	enum log_mode mode;
-	size_t rows;
-	size_t retry;
-	char filename[PATH_MAX + 1];
-};
-
-struct recovery_state {
-	i64 lsn, confirmed_lsn;
-
-	struct log_io *current_wal; /* the WAL we'r currently reading/writing from/to */
-	struct log_io_class snap_class, wal_class;
-	struct child *wal_writer;
-	void *data;
-	int snap_io_rate_limit;
-};
-
-struct wal_write_request {
-	i64 lsn;
-	u32 len;
-	u8 data[];
-} __packed__;
-
-bool wal_write(struct recovery_state *r, i64 lsn, struct tbuf *data);
-
-#endif
diff --git a/core/log_io_remote.c b/core/log_io_remote.c
index e0134cd41a8cd577b8403c60fbaa2b505301e98f..fc8713702ef8ff31a3d0abf353f6a8565dd87910 100644
--- a/core/log_io_remote.c
+++ b/core/log_io_remote.c
@@ -36,103 +36,172 @@
 
 #include <say.h>
 #include <log_io.h>
-#include "log_io_internal.h"
+#include <pickle.h>
 
+struct remote_state {
+	struct recovery_state *r;
+	int (*handler) (struct recovery_state * r, struct tbuf *row);
+};
+
+static u32
+row_v11_len(struct tbuf *r)
+{
+	if (r->len < sizeof(struct row_v11))
+		return 0;
+
+	if (r->len < sizeof(struct row_v11) + row_v11(r)->len)
+		return 0;
+
+	return sizeof(struct row_v11) + row_v11(r)->len;
+}
 
 static struct tbuf *
-row_reader_v04(struct palloc_pool *pool)
+row_reader_v11()
 {
-	const int header_size = offsetof(struct row_v04, data);
-	struct tbuf *m = tbuf_alloc(pool);
+	const int header_size = sizeof(struct row_v11);
+	struct tbuf *m;
 
-	if (fiber_read(m->data, header_size) != header_size) {
-		say_error("unexpected eof reading row header");
-		return NULL;
-	}
-	m->len = header_size;
+	for (;;) {
+		if (row_v11_len(fiber->rbuf) != 0) {
+			m = tbuf_split(fiber->rbuf, row_v11_len(fiber->rbuf));
+			say_debug("read row bytes:%" PRIu32 " %s", m->len, tbuf_to_hex(m));
+			return m;
+		}
 
-	tbuf_ensure(m, header_size + row_v04(m)->len);
-	if (fiber_read(row_v04(m)->data, row_v04(m)->len) != row_v04(m)->len) {
-		say_error("unexpected eof reading row body");
-		return NULL;
+		if (fiber_bread(fiber->rbuf, header_size) <= 0) {
+			say_error("unexpected eof reading row header");
+			return NULL;
+		}
 	}
-	m->len += row_v04(m)->len;
-
-	say_debug("read row bytes:%"PRIu32" %s", m->len, tbuf_to_hex(m));
-	return m;
 }
 
-
-static void
-pull_from_remote(void *state)
+static struct tbuf *
+remote_read_row(i64 initial_lsn)
 {
-	struct recovery_state *r = state;
 	struct tbuf *row;
-	i64 lsn;
-	int rows = 0;
 	bool warning_said = false;
 	const int reconnect_delay = 1;
+	const char *err = NULL;
+	u32 version;
 
 	for (;;) {
 		if (fiber->fd < 0) {
 			if (fiber_connect(fiber->data) < 0) {
-				if (!warning_said) {
-					say_syserror("can't connect to feeder");
-					say_info("will retry every %i second", reconnect_delay);
-					warning_said = true;
-				}
-				fiber_sleep(reconnect_delay);
-				continue;
+				err = "can't connect to feeder";
+				goto err;
+			}
+
+			if (fiber_write(&initial_lsn, sizeof(initial_lsn)) != sizeof(initial_lsn)) {
+				err = "can't write version";
+				goto err;
 			}
-			say_crit("succefully connected to feeder");
 
-			lsn = confirmed_lsn(r) + 1;
-			fiber_write(&lsn, sizeof(lsn));
+			if (fiber_read(&version, sizeof(version)) != sizeof(version)) {
+				err = "can't read version";
+				goto err;
+			}
+
+			if (version != default_version) {
+				err = "remote version mismatch";
+				goto err;
+			}
 
-			say_crit("starting remote recovery from lsn:%"PRIi64, lsn);
+			say_crit("succefully connected to feeder");
+			say_crit("starting remote recovery from lsn:%" PRIi64, initial_lsn);
 			warning_said = false;
+			err = NULL;
 		}
 
-		row = row_reader_v04(fiber->pool);
+		row = row_reader_v11(fiber->pool);
 		if (row == NULL) {
-			fiber_close();
-			fiber_sleep(reconnect_delay);
-			continue;
+			err = "can't read row";
+			goto err;
 		}
 
-		if (r->wal_class.handler(r, row) < 0)
-			panic("replication failure: can't apply row");
+		return row;
 
-		if (wal_write(r, r->wal_class.row_lsn(row), row) == false)
-			panic("replication failure: can't write row to WAL");
+	      err:
+		if (err != NULL && !warning_said) {
+			say_info("%s", err);
+			say_info("will retry every %i second", reconnect_delay);
+			warning_said = true;
+		}
+		fiber_close();
+		fiber_sleep(reconnect_delay);
+	}
+}
+
+static void
+pull_from_remote(void *state)
+{
+	struct remote_state *h = state;
+	struct tbuf *row;
+
+	if (setjmp(fiber->exc) != 0)
+		fiber_close();
 
-		next_lsn(r, r->wal_class.row_lsn(row));
-		confirm_lsn(r, r->wal_class.row_lsn(row));
+	for (;;) {
+		row = remote_read_row(h->r->confirmed_lsn + 1);
+		h->r->recovery_lag = ev_now() - row_v11(row)->tm;
+		h->r->recovery_last_update_tstamp = ev_now();
 
-		if (rows++ % 1000 == 0) {
-			prelease(fiber->pool);
-			rows = 0;
+		if (h->handler(h->r, row) < 0) {
+			fiber_close();
+			continue;
 		}
+
+		fiber_gc();
 	}
 }
 
+int
+default_remote_row_handler(struct recovery_state *r, struct tbuf *row)
+{
+	struct tbuf *data;
+	i64 lsn = row_v11(row)->lsn;
+	u16 tag;
+
+	/* save row data since wal_row_handler may clobber it */
+	data = tbuf_alloc(row->pool);
+	tbuf_append(data, row_v11(row)->data, row_v11(row)->len);
+
+	if (r->row_handler(r, row) < 0)
+		panic("replication failure: can't apply row");
+
+	tag = read_u16(data);
+	(void)read_u64(data); /* drop the cookie */
+
+	if (wal_write(r, tag, r->cookie, lsn, data) == false)
+		panic("replication failure: can't write row to WAL");
+
+	next_lsn(r, lsn);
+	confirm_lsn(r, lsn);
+
+	return 0;
+}
 
 struct fiber *
-recover_follow_remote(struct recovery_state *r, char *ip_addr, int port)
+recover_follow_remote(struct recovery_state *r, char *ip_addr, int port,
+		      int (*handler) (struct recovery_state *r, struct tbuf *row))
 {
 	char *name;
 	struct fiber *f;
 	struct in_addr server;
 	struct sockaddr_in *addr;
+	struct remote_state *h;
 
 	say_crit("initializing remote hot standby, WAL feeder %s:%i", ip_addr, port);
 	name = palloc(eter_pool, 64);
 	snprintf(name, 64, "remote_hot_standby/%s:%i", ip_addr, port);
-	f = fiber_create(name, -1, -1, pull_from_remote, r);
+
+	h = palloc(eter_pool, sizeof(*h));
+	h->r = r;
+	h->handler = handler;
+
+	f = fiber_create(name, -1, -1, pull_from_remote, h);
 	if (f == NULL)
 		return NULL;
 
-
 	if (inet_aton(ip_addr, &server) < 0) {
 		say_syserror("inet_aton: %s", ip_addr);
 		return NULL;
@@ -144,6 +213,7 @@ recover_follow_remote(struct recovery_state *r, char *ip_addr, int port)
 	memcpy(&addr->sin_addr.s_addr, &server, sizeof(server));
 	addr->sin_port = htons(port);
 	f->data = addr;
+	memcpy(&r->cookie, &addr, MIN(sizeof(r->cookie), sizeof(addr)));
 	fiber_call(f);
 	return f;
 }
diff --git a/core/palloc.c b/core/palloc.c
index 728914ce7734337198b7b490c5de100df733a9ba..ee82319c10c8ca92ee0619d57fc143abf2c12705 100644
--- a/core/palloc.c
+++ b/core/palloc.c
@@ -40,21 +40,21 @@
 
 struct chunk {
 	uint32_t magic;
+	void *brk;
+	size_t free;
 	size_t size;
-	size_t allocated;
 
-	struct chunk_class * restrict class;
+	struct chunk_class *class;
 	 SLIST_ENTRY(chunk) busy_link;
 	 SLIST_ENTRY(chunk) free_link;
-
-	unsigned char data[0];
 };
 
 SLIST_HEAD(chunk_list_head, chunk);
 
 struct chunk_class {
 	int i;
-	i64 size;
+	u32 size;
+	int chunks_count;
 	struct chunk_list_head chunks;
 	 TAILQ_ENTRY(chunk_class) link;
 };
@@ -86,12 +86,31 @@ struct palloc_pool *eter_pool;
 #define PALLOC_POISON
 #endif
 
-size_t
+static size_t
 palloc_greatest_size(void)
 {
 	return (1 << 22) - sizeof(struct chunk);
 }
 
+
+static struct chunk_class *
+class_init(size_t size)
+{
+	struct chunk_class *class;
+
+	class = malloc(sizeof(struct chunk_class));
+	if (class == NULL)
+		return NULL;
+
+	class->i = class_count++;
+	class->chunks_count = 0;
+	class->size = size;
+	SLIST_INIT(&class->chunks);
+	TAILQ_INSERT_TAIL(&classes, class, link);
+
+	return class;
+}
+
 int
 palloc_init(void)
 {
@@ -100,24 +119,17 @@ palloc_init(void)
 	class_count = 0;
 	TAILQ_INIT(&classes);
 
-	for (size_t size = 4096; size <= 1 << 16; size *= 2) {
-		class = malloc(sizeof(struct chunk_class));
-		class->i = class_count++;
-		if (class == NULL)
+	for (size_t size = 4096 * 8; size <= 1 << 16; size *= 2) {
+		if (class_init(size) == NULL)
 			return 0;
-
-		class->size = size - sizeof(struct chunk);
-		SLIST_INIT(&class->chunks);
-		TAILQ_INSERT_TAIL(&classes, class, link);
 	}
 
-	class = malloc(sizeof(struct chunk_class));
-	class->i = class_count++;
-	if (class == NULL)
+	if (class_init(palloc_greatest_size()) == NULL)
 		return 0;
-	class->size = palloc_greatest_size();
-	SLIST_INIT(&class->chunks);
-	TAILQ_INSERT_TAIL(&classes, class, link);
+
+	if ((class = class_init(-1)) == NULL)
+		return 0;
+
 	TAILQ_NEXT(class, link) = NULL;
 
 	eter_pool = palloc_create_pool("eter_pool");
@@ -129,27 +141,30 @@ poison_chunk(struct chunk *chunk)
 {
 	(void)chunk;		/* arg used */
 #ifdef PALLOC_POISON
-	memset(chunk->data, poison_char, chunk->size);
+	memset((void *)chunk + sizeof(struct chunk), poison_char, chunk->size);
 	VALGRIND_MAKE_MEM_NOACCESS(chunk->data, chunk->size);
 #endif
 }
 
 static struct chunk *
-next_chunk_for(struct palloc_pool * restrict pool, size_t size)
+next_chunk_for(struct palloc_pool *restrict pool, size_t size)
 {
-	struct chunk * restrict chunk = SLIST_FIRST(&pool->chunks);
-	struct chunk_class * restrict class;
+	struct chunk *restrict chunk = SLIST_FIRST(&pool->chunks);
+	struct chunk_class *restrict class;
+	size_t chunk_size;
 
 	if (chunk != NULL)
 		class = chunk->class;
 	else
 		class = TAILQ_FIRST(&classes);
 
+	if (class->size == -1)
+		class = TAILQ_PREV(class, class_tailq_head, link);
+
 	while (class != NULL && class->size < size)
 		class = TAILQ_NEXT(class, link);
 
-	if (class == NULL)
-		return NULL;
+	assert(class != NULL);
 
 	chunk = SLIST_FIRST(&class->chunks);
 	if (chunk != NULL) {
@@ -157,14 +172,21 @@ next_chunk_for(struct palloc_pool * restrict pool, size_t size)
 		goto found;
 	}
 
-	chunk = malloc(class->size + sizeof(struct chunk));
+	if (size > palloc_greatest_size())
+		chunk_size = size;
+	else
+		chunk_size = class->size;
+
+	chunk = malloc(chunk_size + sizeof(struct chunk));
 
 	if (chunk == NULL)
 		return NULL;
 
+	class->chunks_count++;
 	chunk->magic = chunk_magic;
-	chunk->allocated = 0;
-	chunk->size = class->size;
+	chunk->size = chunk_size;
+	chunk->free = chunk_size;
+	chunk->brk = (void *)chunk + sizeof(struct chunk);
 	chunk->class = class;
       found:
 	assert(chunk != NULL && chunk->magic == chunk_magic);
@@ -174,24 +196,6 @@ next_chunk_for(struct palloc_pool * restrict pool, size_t size)
 	return chunk;
 }
 
-
-static void *
-alloc_from_chunk(struct chunk *chunk, size_t size)
-{
-	assert(chunk != NULL);
-	assert(chunk->magic == chunk_magic);
-	assert(chunk->size >= chunk->allocated);
-
-	if (likely(chunk->size - chunk->allocated >= size)) {
-		void *ptr = chunk->data + chunk->allocated;	// TODO: align ptr
-		chunk->allocated += size;
-		return ptr;
-	} else {
-		return NULL;
-	}
-}
-
-
 #ifndef NDEBUG
 static const char *
 poisoned(const char *b, size_t size)
@@ -207,68 +211,77 @@ poisoned(const char *b, size_t size)
 }
 #endif
 
-
-static void * __noinline__
-palloc_slow_path(struct palloc_pool * restrict pool, size_t size)
+static void *__noinline__
+palloc_slow_path(struct palloc_pool *restrict pool, size_t size)
 {
-	struct chunk * restrict chunk;
+	struct chunk *chunk;
 	chunk = next_chunk_for(pool, size);
-	assert(chunk != NULL);
-	return alloc_from_chunk(chunk, size);
+	if (chunk == NULL)
+		abort();
+
+	assert(chunk->free >= size);
+	void *ptr = chunk->brk;
+	chunk->brk += size;
+	chunk->free -= size;
+	return ptr;
 }
 
-void *
-palloc(struct palloc_pool *pool, size_t size)
+void *__regparm2__
+palloc(struct palloc_pool *restrict pool, size_t size)
 {
-	assert(size < palloc_greatest_size());
-	size_t rz_size = size + PALLOC_REDZONE * 2;
-	struct chunk * restrict chunk = SLIST_FIRST(&pool->chunks);
+	const size_t rz_size = size + PALLOC_REDZONE * 2;
+	struct chunk *restrict chunk = SLIST_FIRST(&pool->chunks);
 	void *ptr;
 
 	pool->allocated += rz_size;
-	ptr = alloc_from_chunk(chunk, rz_size);
-	if (unlikely(ptr == NULL))
+
+	if (likely(chunk->free >= rz_size)) {
+		ptr = chunk->brk;
+		chunk->brk += rz_size;
+		chunk->free -= rz_size;
+	} else
 		ptr = palloc_slow_path(pool, rz_size);
 
-	assert(ptr != NULL);
 	assert(poisoned(ptr + PALLOC_REDZONE, size) == NULL);
 	VALGRIND_MEMPOOL_ALLOC(pool, ptr + PALLOC_REDZONE, size);
+
 	return ptr + PALLOC_REDZONE;
 }
 
-void *
+void *__regparm2__
 p0alloc(struct palloc_pool *pool, size_t size)
 {
-	void * ptr;
+	void *ptr;
 
 	ptr = palloc(pool, size);
-	if (ptr)
-		memset(ptr, 0, size);
-
+	memset(ptr, 0, size);
 	return ptr;
 }
 
 void *
 palloca(struct palloc_pool *pool, size_t size, size_t align)
 {
-	void * ptr;
+	void *ptr;
 
 	ptr = palloc(pool, size + align);
-	return (void*)TYPEALIGN(align, (uintptr_t)ptr); 
+	return (void *)TYPEALIGN(align, (uintptr_t)ptr);
 }
 
 void
 prelease(struct palloc_pool *pool)
 {
-	struct chunk *chunk;
-
-	for (chunk = SLIST_FIRST(&pool->chunks);
-	     chunk != NULL;
-	     chunk = SLIST_NEXT(chunk, busy_link))
-	{
-		chunk->allocated = 0;
-		SLIST_INSERT_HEAD(&chunk->class->chunks, chunk, free_link);
-		poison_chunk(chunk);
+	struct chunk *chunk, *next_chunk;
+
+	for (chunk = SLIST_FIRST(&pool->chunks); chunk != NULL; chunk = next_chunk) {
+		next_chunk = SLIST_NEXT(chunk, busy_link);
+		if (chunk->size <= palloc_greatest_size()) {
+			chunk->free = chunk->size;
+			chunk->brk = (void *)chunk + sizeof(struct chunk);
+			SLIST_INSERT_HEAD(&chunk->class->chunks, chunk, free_link);
+			poison_chunk(chunk);
+		} else {
+			free(chunk);
+		}
 	}
 
 	SLIST_INIT(&pool->chunks);
@@ -277,6 +290,13 @@ prelease(struct palloc_pool *pool)
 	next_chunk_for(pool, 128);
 }
 
+void
+prelease_after(struct palloc_pool *pool, size_t after)
+{
+	if (pool->allocated > after)
+		prelease(pool);
+}
+
 struct palloc_pool *
 palloc_create_pool2(const char *name, size_t initial_size)
 {
@@ -319,14 +339,14 @@ palloc_stat(struct tbuf *buf)
 	tbuf_printf(buf, "palloc statistic:\n");
 	tbuf_printf(buf, "  classes:\n");
 	TAILQ_FOREACH(class, &classes, link) {
-		int free_chunks = 0, busy_chunks = 0;
+		int free_chunks = 0;
 		SLIST_FOREACH(chunk, &class->chunks, free_link)
-			free_chunks++;
-		SLIST_FOREACH(chunk, &class->chunks, busy_link)
-			busy_chunks++;
+		    free_chunks++;
 
-		tbuf_printf(buf, "    - { tsize: %- 8"PRIi64", free_chunks: %- 6i, busy_chunks: %- 6i }\n",
-			    class->size, free_chunks, busy_chunks);
+		tbuf_printf(buf,
+			    "    - { size: %"PRIu32
+			    ", free_chunks: %- 6i, busy_chunks: %- 6i }\n", class->size,
+			    free_chunks, class->chunks_count - free_chunks);
 	}
 	tbuf_printf(buf, "  pools:\n");
 
@@ -347,7 +367,7 @@ palloc_stat(struct tbuf *buf)
 			TAILQ_FOREACH(class, &classes, link) {
 				if (chunks[class->i] == 0)
 					continue;
-				tbuf_printf(buf, "        - { size: %- 7"PRIi64", used: %i }\n",
+				tbuf_printf(buf, "        - { size: %"PRIu32", used: %i }\n",
 					    class->size, chunks[class->i]);
 
 				if (indent == 0)
@@ -365,3 +385,9 @@ palloc_name(struct palloc_pool *pool, const char *new_name)
 		pool->name = new_name;
 	return old_name;
 }
+
+size_t
+palloc_allocated(struct palloc_pool *pool)
+{
+	return pool->allocated;
+}
diff --git a/core/pickle.c b/core/pickle.c
index a2971bfe2d837269e82b3083c4e68bfbdf2cca3c..5f385cf50455aa78693378e46d4fec9cde4e1964 100644
--- a/core/pickle.c
+++ b/core/pickle.c
@@ -30,7 +30,7 @@
 #include <tbuf.h>
 #include <palloc.h>
 #include <fiber.h>
-#include <iproto.h> /* for err codes */
+#include <iproto.h>		/* for err codes */
 #include <pickle.h>
 
 /* caller must ensure that there is space in target */
@@ -54,7 +54,6 @@ save_varint32(u8 *target, u32 value)
 	return target;
 }
 
-
 inline static void
 append_byte(struct tbuf *b, u8 byte)
 {
@@ -80,34 +79,22 @@ write_varint32(struct tbuf *b, u32 value)
 	append_byte(b, (u8)((value) & 0x7F));
 }
 
+#define read_u(bits)							\
+	u##bits read_u##bits(struct tbuf *b)				\
+	{								\
+		if (b->len < (bits)/8)					\
+			raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short"); \
+		u##bits r = *(u##bits *)b->data;			\
+		b->size -= (bits)/8;					\
+		b->len -= (bits)/8;					\
+		b->data += (bits)/8;					\
+		return r;						\
+	}
 
-u32
-read_u32(struct tbuf *b)
-{
-	if (b->len < 4)
-		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-
-	u32 r = *(u32 *)b->data; /* FIXME: endianess & aligment */
-	b->size -= 4;
-	b->len -= 4;
-	b->data += 4;
-
-	return r;
-}
-
-u8
-read_u8(struct tbuf *b)
-{
-	if (b->len < 1)
-		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-
-	u8 r = *(u8 *)b->data;
-	b->size -= 1;
-	b->len -= 1;
-	b->data += 1;
-
-	return r;
-}
+read_u(8)
+read_u(16)
+read_u(32)
+read_u(64)
 
 u32
 read_varint32(struct tbuf *buf)
@@ -117,7 +104,7 @@ read_varint32(struct tbuf *buf)
 
 	if (len < 1)
 		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-	if(!(b[0] & 0x80)) {
+	if (!(b[0] & 0x80)) {
 		buf->data += 1;
 		buf->size -= 1;
 		buf->len -= 1;
@@ -126,7 +113,7 @@ read_varint32(struct tbuf *buf)
 
 	if (len < 2)
 		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-	if(!(b[1] & 0x80)) {
+	if (!(b[1] & 0x80)) {
 		buf->data += 2;
 		buf->size -= 2;
 		buf->len -= 2;
@@ -134,7 +121,7 @@ read_varint32(struct tbuf *buf)
 	}
 	if (len < 3)
 		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-	if(!(b[2] & 0x80)) {
+	if (!(b[2] & 0x80)) {
 		buf->data += 3;
 		buf->size -= 3;
 		buf->len -= 3;
@@ -143,7 +130,7 @@ read_varint32(struct tbuf *buf)
 
 	if (len < 4)
 		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-	if(!(b[3] & 0x80)) {
+	if (!(b[3] & 0x80)) {
 		buf->data += 4;
 		buf->size -= 4;
 		buf->len -= 4;
@@ -153,7 +140,7 @@ read_varint32(struct tbuf *buf)
 
 	if (len < 5)
 		raise(ERR_CODE_UNKNOWN_ERROR, "buffer too short");
-	if(!(b[4] & 0x80)) {
+	if (!(b[4] & 0x80)) {
 		buf->data += 5;
 		buf->size -= 5;
 		buf->len -= 5;
@@ -165,7 +152,6 @@ read_varint32(struct tbuf *buf)
 	return 0;
 }
 
-
 u32
 pick_u32(void *data, void **rest)
 {
@@ -190,7 +176,6 @@ read_field(struct tbuf *buf)
 	return p;
 }
 
-
 u32
 valid_tuple(struct tbuf *buf, u32 cardinality)
 {
diff --git a/core/salloc.c b/core/salloc.c
index 398a1cff1179d374df836cf7e1425d0dc3829e13..7b845abcd752c10b02dac41a87421ad353aa4912 100644
--- a/core/salloc.c
+++ b/core/salloc.c
@@ -42,9 +42,9 @@
 
 #ifdef SLAB_DEBUG
 #undef NDEBUG
-u8 red_zone[4] = {0xfa, 0xfa, 0xfa, 0xfa};
+u8 red_zone[4] = { 0xfa, 0xfa, 0xfa, 0xfa };
 #else
-u8 red_zone[0] = {};
+u8 red_zone[0] = { };
 #endif
 
 const u32 SLAB_MAGIC = 0x51abface;
@@ -61,10 +61,11 @@ struct slab {
 	size_t items;
 	struct slab_item *free;
 	struct slab_class *class;
-	struct arena *arena;
-	 TAILQ_ENTRY(slab) link;
-	 TAILQ_ENTRY(slab) free_link;
-	 SLIST_ENTRY(slab) class_link;
+	void *brk;
+	 SLIST_ENTRY(slab) link;
+	 SLIST_ENTRY(slab) free_link;
+	 TAILQ_ENTRY(slab) class_free_link;
+	 TAILQ_ENTRY(slab) class_link;
 };
 
 SLIST_HEAD(slab_slist_head, slab);
@@ -72,8 +73,7 @@ TAILQ_HEAD(slab_tailq_head, slab);
 
 struct slab_class {
 	size_t item_size;
-	struct slab_tailq_head free_slabs;
-	struct slab_slist_head slabs;
+	struct slab_tailq_head slabs, free_slabs;
 };
 
 struct arena {
@@ -89,7 +89,7 @@ size_t slab_active_classes;
 struct slab_class slab_classes[256];
 struct arena arena;
 
-struct slab_tailq_head slabs;
+struct slab_slist_head slabs, free_slabs;
 
 static struct slab *
 slab_header(void *ptr)
@@ -109,11 +109,12 @@ slab_classes_init(size_t minimal, double factor)
 		slab_classes[i].item_size = size - sizeof(red_zone);
 		TAILQ_INIT(&slab_classes[i].free_slabs);
 
-		size = MAX((size_t) (size * factor) & ~(ptr_size - 1),
+		size = MAX((size_t)(size * factor) & ~(ptr_size - 1),
 			   (size + ptr_size) & ~(ptr_size - 1));
 	}
 
-	TAILQ_INIT(&slabs);
+	SLIST_INIT(&slabs);
+	SLIST_INIT(&free_slabs);
 	slab_active_classes = i;
 }
 
@@ -164,7 +165,7 @@ salloc_init(size_t size, size_t minimal, double factor)
 	if (!arena_init(&arena, size))
 		return false;
 
-	slab_classes_init(MAX(sizeof(void *),minimal), factor);
+	slab_classes_init(MAX(sizeof(void *), minimal), factor);
 	return true;
 }
 
@@ -178,35 +179,25 @@ salloc_destroy(void)
 }
 
 static void
-format_slab(struct arena *arena, struct slab_class *class, struct slab *slab)
+format_slab(struct slab_class *class, struct slab *slab)
 {
-	char *p;
-	struct slab_item *prev, *curr = NULL;
-
 	assert(class->item_size <= MAX_SLAB_ITEM);
 
 	slab->magic = SLAB_MAGIC;
-	slab->free = (void *)((char *)slab + sizeof(struct slab));
+	slab->free = NULL;
 	slab->class = class;
-	slab->arena = arena;
-
-	curr = NULL;
-	prev = slab->free;
-	for (p = (char *)slab + sizeof(struct slab) + class->item_size + sizeof(red_zone);
-	     p + class->item_size < (char *)slab + SLAB_SIZE;
-	     p += class->item_size + sizeof(red_zone))
-	{
-		curr = (struct slab_item *)p;
-		prev->next = curr;
-		memcpy((char *)prev + class->item_size, red_zone, sizeof(red_zone));
-		prev = curr;
-	}
-	curr->next = NULL;
-	memcpy((char *)curr + class->item_size, red_zone, sizeof(red_zone));
+	slab->items = 0;
+	slab->used = 0;
+	slab->brk = (void *)CACHEALIGN((void *)slab + sizeof(struct slab));
 
-	SLIST_INSERT_HEAD(&class->slabs, slab, class_link);
-	TAILQ_INSERT_HEAD(&class->free_slabs, slab, free_link);
-	TAILQ_INSERT_HEAD(&slabs, slab, link);
+	TAILQ_INSERT_HEAD(&class->slabs, slab, class_link);
+	TAILQ_INSERT_HEAD(&class->free_slabs, slab, class_free_link);
+}
+
+static bool
+full_formated(struct slab *slab)
+{
+	return slab->brk + slab->class->item_size >= (void *)slab + SLAB_SIZE;
 }
 
 void
@@ -214,7 +205,7 @@ slab_validate(void)
 {
 	struct slab *slab;
 
-	TAILQ_FOREACH(slab, &slabs, link) {
+	SLIST_FOREACH(slab, &slabs, link) {
 		for (char *p = (char *)slab + sizeof(struct slab);
 		     p + slab->class->item_size < (char *)slab + SLAB_SIZE;
 		     p += slab->class->item_size + sizeof(red_zone)) {
@@ -236,24 +227,37 @@ class_for(size_t size)
 static struct slab *
 slab_of(struct slab_class *class)
 {
-	struct slab *slab = TAILQ_FIRST(&class->free_slabs);
+	struct slab *slab;
 
-	if (slab != NULL) {
+	if (!TAILQ_EMPTY(&class->free_slabs)) {
+		slab = TAILQ_FIRST(&class->free_slabs);
 		assert(slab->magic == SLAB_MAGIC);
 		return slab;
 	}
-	if ((slab = arena_alloc(&arena)) == NULL)
-		return NULL;
-	format_slab(&arena, class, slab);
-	return slab;
+
+	if (!SLIST_EMPTY(&free_slabs)) {
+		slab = SLIST_FIRST(&free_slabs);
+		assert(slab->magic == SLAB_MAGIC);
+		SLIST_REMOVE_HEAD(&free_slabs, free_link);
+		format_slab(class, slab);
+		return slab;
+	}
+
+	if ((slab = arena_alloc(&arena)) != NULL) {
+		format_slab(class, slab);
+		SLIST_INSERT_HEAD(&slabs, slab, link);
+		return slab;
+	}
+
+	return NULL;
 }
 
 #ifndef NDEBUG
 static bool
 valid_item(struct slab *slab, void *item)
 {
-	return (u8*)item >= (u8 *)(slab) + sizeof(struct slab) &&
-		(u8*)item < (u8 *)(slab) + sizeof(struct slab) + SLAB_SIZE;
+	return (void *)item >= (void *)(slab) + sizeof(struct slab) &&
+	    (void *)item < (void *)(slab) + sizeof(struct slab) + SLAB_SIZE;
 }
 #endif
 
@@ -270,18 +274,26 @@ salloc(size_t size)
 	if ((slab = slab_of(class)) == NULL)
 		return NULL;
 
-	assert(valid_item(slab, slab->free));
+	if (slab->free == NULL) {
+		assert(valid_item(slab, slab->brk));
+		item = slab->brk;
+		memcpy((void *)item + class->item_size, red_zone, sizeof(red_zone));
+		slab->brk += class->item_size + sizeof(red_zone);
+	} else {
+		assert(valid_item(slab, slab->free));
+		item = slab->free;
+
+		VALGRIND_MAKE_MEM_DEFINED(item, sizeof(void *));
+		slab->free = item->next;
+		VALGRIND_MAKE_MEM_UNDEFINED(item, sizeof(void *));
+	}
+
+	if (full_formated(slab) && slab->free == NULL)
+		TAILQ_REMOVE(&class->free_slabs, slab, class_free_link);
 
-	item = slab->free;
-	VALGRIND_MAKE_MEM_DEFINED(item, sizeof(void *));
-	slab->free = item->next;
-	VALGRIND_MAKE_MEM_UNDEFINED(item, sizeof(void *));
 	slab->used += class->item_size + sizeof(red_zone);
 	slab->items += 1;
 
-	if (slab->free == NULL)
-		TAILQ_REMOVE(&class->free_slabs, slab, free_link);
-
 	VALGRIND_MALLOCLIKE_BLOCK(item, class->item_size, sizeof(red_zone), 0);
 	return (void *)item;
 }
@@ -294,8 +306,8 @@ sfree(void *ptr)
 	struct slab_class *class = slab->class;
 	struct slab_item *item = ptr;
 
-	if (slab->free == NULL)
-		TAILQ_INSERT_TAIL(&class->free_slabs, slab, free_link);
+	if (full_formated(slab) && slab->free == NULL)
+		TAILQ_INSERT_TAIL(&class->free_slabs, slab, class_free_link);
 
 	assert(valid_item(slab, item));
 	assert(slab->free == NULL || valid_item(slab, slab->free));
@@ -304,6 +316,13 @@ sfree(void *ptr)
 	slab->free = item;
 	slab->used -= class->item_size + sizeof(red_zone);
 	slab->items -= 1;
+
+	if (slab->items == 0) {
+		TAILQ_REMOVE(&class->free_slabs, slab, class_free_link);
+		TAILQ_REMOVE(&class->slabs, slab, class_link);
+		SLIST_INSERT_HEAD(&free_slabs, slab, free_link);
+	}
+
 	VALGRIND_FREELIKE_BLOCK(item, sizeof(red_zone));
 }
 
@@ -312,23 +331,29 @@ slab_stat(struct tbuf *t)
 {
 	struct slab *slab;
 	int slabs;
-	i64 items, used, free;
+	i64 items, used, free, total_used = 0;
 	tbuf_printf(t, "slab statistics:\n  classes:\n");
 	for (int i = 0; i < slab_active_classes; i++) {
 		slabs = items = used = free = 0;
-		SLIST_FOREACH(slab, &slab_classes[i].slabs, class_link) {
-			if (slab->free != NULL)
-				free += SLAB_SIZE - slab->used - sizeof(struct slab);
+		TAILQ_FOREACH(slab, &slab_classes[i].slabs, class_link) {
+			free += SLAB_SIZE - slab->used - sizeof(struct slab);
 			items += slab->items;
-			used += slab->used;
+			used += sizeof(struct slab) + slab->used;
+			total_used += sizeof(struct slab) + slab->used;
 			slabs++;
 		}
-		if (used == 0)
+
+		if (slabs == 0)
 			continue;
 
-		tbuf_printf(t, "     - { item_size: %- 5i, slabs: %- 3i, items: %- 11"PRIi64", bytes_used: %- 12"PRIi64", bytes_free: %- 12"PRIi64" }\n",
+		tbuf_printf(t,
+			    "     - { item_size: %- 5i, slabs: %- 3i, items: %- 11" PRIi64
+			    ", bytes_used: %- 12" PRIi64 ", bytes_free: %- 12" PRIi64 " }\n",
 			    (int)slab_classes[i].item_size, slabs, items, used, free);
+
 	}
+	tbuf_printf(t, "  items_used: %.2f\n", (double)total_used / arena.size * 100);
+	tbuf_printf(t, "  arena_used: %.2f\n", (double)arena.used / arena.size * 100);
 }
 
 void
@@ -337,7 +362,7 @@ slab_stat2(u64 *bytes_used, u64 *items)
 	struct slab *slab;
 
 	*bytes_used = *items = 0;
-	TAILQ_FOREACH(slab, &slabs, link) {
+	SLIST_FOREACH(slab, &slabs, link) {
 		*bytes_used += slab->used;
 		*items += slab->items;
 	}
diff --git a/core/say.c b/core/say.c
index c736aca64a59378904bcbf0e2fba74f715fb78b6..73d1266a5f3597eea1464ba55cdf1cd47663e47e 100644
--- a/core/say.c
+++ b/core/say.c
@@ -65,8 +65,8 @@ say_logger_init(int nonblock)
 {
 	int pipefd[2];
 	pid_t pid;
-	char *argv[] = {"/bin/sh", "-c", cfg.logger, NULL};
-	char *envp[] = {NULL};
+	char *argv[] = { "/bin/sh", "-c", cfg.logger, NULL };
+	char *envp[] = { NULL };
 
 	if (cfg.logger != NULL) {
 		if (pipe(pipefd) == -1) {
@@ -93,7 +93,7 @@ say_logger_init(int nonblock)
 	} else {
 		sayfd = STDERR_FILENO;
 	}
-out:
+      out:
 	if (nonblock)
 		set_nonblock(sayfd);
 }
@@ -105,6 +105,8 @@ vsay(int level, const char *error, const char *format, va_list ap)
 	size_t p = 0, len = PIPE_BUF;
 	static char buf[PIPE_BUF];
 
+	ev_now_update();
+
 	if (peer_name == NULL)
 		peer_name = "_";
 
diff --git a/core/stat.c b/core/stat.c
index 1dbe7642832547c59f414d898d77213963fed2d2..c515cd0d1be53bf3ece5061f33a6c4a82ff3f602 100644
--- a/core/stat.c
+++ b/core/stat.c
@@ -28,38 +28,53 @@
 #include <tarantool_ev.h>
 #include <tbuf.h>
 #include <say.h>
+#include <stat.h>
 
 #include <third_party/khash.h>
 
-KHASH_MAP_INIT_STR(char2int, i64, realloc);
-
 #define SECS 5
-static
-khash_t(char2int) *
-stats[SECS], *stats_all;
 static ev_timer timer;
 
-void
-stat_collect(const char *name, int value)
+struct {
+	char *name;
+	i64 value[SECS + 1];
+} *stats = NULL;
+static int stats_size = 0;
+static int stats_max = 0;
+static int base = 0;
+
+int
+stat_register(char **name, size_t count)
 {
-	int ret;
-	khiter_t k;
-
-	k = kh_put(char2int, stats[0], name, &ret);
-	if (ret == 0) {
-		kh_value(stats[0], k) += value;
-	} else {
-		kh_key(stats[0], k) = name;
-		kh_value(stats[0], k) = value;
-	}
+	int initial_base = base;
+
+	for (int i = 0; i < count; i++, name++, base++) {
+		if (stats_size <= base) {
+			stats_size += 1024;
+			stats = realloc(stats, sizeof(*stats) * stats_size);
+			if (stats == NULL)
+				abort();
+		}
 
-	k = kh_put(char2int, stats_all, name, &ret);
-	if (ret == 0) {
-		kh_value(stats_all, k) += value;
-	} else {
-		kh_key(stats_all, k) = name;
-		kh_value(stats_all, k) = value;
+		stats[base].name = *name;
+
+		if (*name == NULL)
+			continue;
+
+		for (int i = 0; i < SECS + 1; i++)
+			stats[base].value[i] = 0;
+
+		stats_max = base;
 	}
+
+	return initial_base;
+}
+
+void
+stat_collect(int base, int name, i64 value)
+{
+	stats[base + name].value[0] += value;
+	stats[base + name].value[SECS] += value;
 }
 
 void
@@ -68,50 +83,46 @@ stat_print(struct tbuf *buf)
 	int max_len = 0;
 	tbuf_printf(buf, "statistics:\n");
 
-	for (khiter_t k = kh_begin(stats_all); k != kh_end(stats_all); ++k) {
-		if (!kh_exist(stats_all, k))
+	for (int i = 0; i < stats_max; i++) {
+		if (stats[i].name == NULL)
 			continue;
-
-		const char *key = kh_key(stats_all, k);
-		max_len = MAX(max_len, strlen(key));
+		max_len = MAX(max_len, strlen(stats[i].name));
 	}
 
-	for (khiter_t k = kh_begin(stats_all); k != kh_end(stats_all); ++k) {
-		if (!kh_exist(stats_all, k))
+	for (int i = 0; i < stats_max; i++) {
+		if (stats[i].name == NULL)
 			continue;
 
-		const char *key = kh_key(stats_all, k);
-		int value = 0;
-		for (int i = 0; i < SECS; i++) {
-			khiter_t j = kh_get(char2int, stats[i], key);
-			if (j != kh_end(stats[i]))
-				value += kh_value(stats[i], j);
-		}
-		tbuf_printf(buf, "  %s:%*s{ rps:%- 6i, total:%- 12" PRIi64 " }\n",
-			    key, 1 + max_len - (int)strlen(key), " ",
-			    value / SECS, kh_value(stats_all, k));
+		int diff = 0;
+		for (int j = 0; j < SECS; j++)
+			diff += stats[i].value[j];
+
+		diff /= SECS;
+
+		tbuf_printf(buf, "  %s:%*s{ rps: %- 6i, total: %- 12" PRIi64 " }\n",
+			    stats[i].name, 1 + max_len - (int)strlen(stats[i].name), " ",
+			    diff, stats[i].value[SECS]);
 	}
 }
 
 void
-stat_age(ev_timer * timer, int events __unused__)
+stat_age(ev_timer *timer, int events __unused__)
 {
-	khash_t(char2int) * last = stats[SECS - 1];
-	for (int i = 0; i < SECS - 1; i++) {
-		stats[i + 1] = stats[i];
+	for (int i = 0; i < stats_max; i++) {
+		if (stats[i].name == NULL)
+			continue;
+
+		for (int j = SECS - 2; j >= 0;  j--)
+			stats[i].value[j + 1] = stats[i].value[j];
+		stats[i].value[0] = 0;
 	}
-	stats[0] = last;
-	kh_clear(char2int, last);
+
 	ev_timer_again(timer);
 }
 
 void
 stat_init(void)
 {
-	stats_all = kh_init(char2int, NULL);
-	for (int i = 0; i < SECS; i++)
-		stats[i] = kh_init(char2int, NULL);
-
 	ev_init(&timer, stat_age);
 	timer.repeat = 1.;
 	ev_timer_again(&timer);
diff --git a/core/tarantool.c b/core/tarantool.c
index 600d868772e5f31b75f7d0ce80c8ee486dd5ad00..8e89ba11990b9908e487a587d5019fdd419c8a96 100644
--- a/core/tarantool.c
+++ b/core/tarantool.c
@@ -62,8 +62,8 @@ enum tarantool_role role = def;
 
 extern int daemonize(int nochdir, int noclose);
 
-
-const char *tarantool_version(void)
+const char *
+tarantool_version(void)
 {
 	return tarantool_version_string;
 }
@@ -88,7 +88,7 @@ snapshot(void *ev __unused__, int events __unused__)
 		return;
 
 	fiber->name = "dumper";
-	set_proc_title("dumper (%"PRIu32")", getppid());
+	set_proc_title("dumper (%" PRIu32 ")", getppid());
 	close_all_xcpt(1, sayfd);
 	snapshot_save(recovery_state, mod_snapshot);
 #ifdef COVERAGE
@@ -103,7 +103,7 @@ sig_child(int signal __unused__)
 {
 	int child_status;
 	/* TODO: watch child status & destroy corresponding fibers */
-	while (waitpid(-1, &child_status, WNOHANG) > 0);
+	while (waitpid(-1, &child_status, WNOHANG) > 0) ;
 }
 
 static void
@@ -112,13 +112,12 @@ sig_int(int signal)
 	say_info("SIGINT or SIGTERM recieved, terminating");
 
 	if (recovery_state != NULL) {
-		struct child *writer = wal_writer(recovery_state);
+		struct child *writer = recovery_state->wal_writer;
 		if (writer && writer->out && writer->out->fd > 0) {
 			close(writer->out->fd);
 			usleep(1000);
 		}
 	}
-
 #ifdef COVERAGE
 	__gcov_flush();
 #endif
@@ -166,17 +165,16 @@ signal_init(void)
 		goto error;
 
 	return;
-error:
+      error:
 	say_syserror("sigaction");
 	exit(EX_OSERR);
 }
 
-
 static void
 create_pid(void)
 {
 	FILE *f;
-	char buf[16] = {0};
+	char buf[16] = { 0 };
 	pid_t pid;
 
 	if (cfg.pid_file == NULL)
@@ -207,8 +205,6 @@ remove_pid(void)
 	unlink(cfg.pid_file);
 }
 
-
-
 static void
 initialize(double slab_alloc_arena, int slab_alloc_minimal, double slab_alloc_factor)
 {
@@ -217,7 +213,6 @@ initialize(double slab_alloc_arena, int slab_alloc_minimal, double slab_alloc_fa
 		panic("can't initialize slab allocator");
 
 	fiber_init();
-	signal_init();
 }
 
 static void
@@ -244,44 +239,44 @@ main(int argc, char **argv)
 
 	const char *short_opt = "c:pvVD";
 	const struct option long_opt[] = {
-		{ .name = "config",
-		  .has_arg = 1,
-		  .flag = NULL,
-		  .val = 'c' },
+		{.name = "config",
+		 .has_arg = 1,
+		 .flag = NULL,
+		 .val = 'c'},
 #ifdef STORAGE
-		{ .name = "cat",
-		  .has_arg = 1,
-		  .flag = NULL,
-		  .val = 'C' },
-		{ .name = "init_storage",
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 'I'},
+		{.name = "cat",
+		 .has_arg = 1,
+		 .flag = NULL,
+		 .val = 'C'},
+		{.name = "init_storage",
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 'I'},
 #endif
-		{ .name = "create_pid",
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 'p'},
-		{ .name = "verbose",
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 'v'},
-		{ .name = "version",
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 'V'},
-		{ .name = "daemonize",
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 'D'},
-		{ .name = NULL,
-		  .has_arg = 0,
-		  .flag = NULL,
-		  .val = 0 }
+		{.name = "create_pid",
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 'p'},
+		{.name = "verbose",
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 'v'},
+		{.name = "version",
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 'V'},
+		{.name = "daemonize",
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 'D'},
+		{.name = NULL,
+		 .has_arg = 0,
+		 .flag = NULL,
+		 .val = 0}
 	};
 
 	while ((c = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
-                switch (c) {
+		switch (c) {
 		case 'V':
 			puts(tarantool_version());
 			return 0;
@@ -290,7 +285,7 @@ main(int argc, char **argv)
 				panic("no arg given");
 			cfg_filename = strdup(optarg);
 			break;
-                case 'C':
+		case 'C':
 			if (optarg == NULL)
 				panic("no arg given");
 			cat_filename = strdup(optarg);
@@ -346,12 +341,12 @@ main(int argc, char **argv)
 
 	fill_default_tarantool_cfg(&cfg);
 	parse_cfg_file_tarantool_cfg(&cfg, f, 0, &n_accepted, &n_skipped);
+	check_cfg_tarantool_cfg(&cfg);
 	fclose(f);
 
 #ifdef STORAGE
 	if (role == cat) {
 		initialize_minimal();
-		mod_init();
 		if (access(cat_filename, R_OK) == -1) {
 			say_syserror("access(\"%s\")", cat_filename);
 			exit(EX_OSFILE);
@@ -384,7 +379,7 @@ main(int argc, char **argv)
 	}
 
 	if (cfg.coredump) {
-		struct rlimit c = {0,0};
+		struct rlimit c = { 0, 0 };
 		if (getrlimit(RLIMIT_CORE, &c) < 0) {
 			say_syserror("getrlimit");
 			exit(EX_OSERR);
@@ -394,15 +389,13 @@ main(int argc, char **argv)
 			say_syserror("setrlimit");
 			exit(EX_OSERR);
 		}
-
 #ifdef Linux
-		if (prctl(PR_SET_DUMPABLE,1,0,0,0) < 0) {
+		if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
 			say_syserror("prctl");
 			exit(EX_OSERR);
 		}
 #endif
 	}
-
 #ifdef STORAGE
 	if (init_storage) {
 		initialize_minimal();
@@ -425,6 +418,7 @@ main(int argc, char **argv)
 
 #if defined(UTILITY)
 	initialize_minimal();
+	signal_init();
 	mod_init();
 #elif defined(STORAGE)
 	ev_signal *ev_sig;
@@ -433,10 +427,12 @@ main(int argc, char **argv)
 	ev_signal_start(ev_sig);
 
 	initialize(cfg.slab_alloc_arena, cfg.slab_alloc_minimal, cfg.slab_alloc_factor);
+	signal_init();
 	ev_default_loop(0);
 
 	mod_init();
 	admin_init();
+	prelease(fiber->pool);
 	say_crit("log level %i", cfg.log_level);
 	say_crit("entering event loop");
 	if (cfg.io_collect_interval > 0)
diff --git a/core/tbuf.c b/core/tbuf.c
index efca9e588a1efd9dca3abc01a3f971685fa7998a..37eb6ce5fd496f5969d24c16311e5a73c7e1e086 100644
--- a/core/tbuf.c
+++ b/core/tbuf.c
@@ -44,13 +44,11 @@
 #  define poison(ptr, len)
 #endif
 
-
 static void
-tbuf_assert(const struct tbuf * b)
+tbuf_assert(const struct tbuf *b)
 {
-	(void)b; /* arg used :-) */
+	(void)b;		/* arg used :-) */
 	assert(b->len <= b->size);
-	assert(b->size <= palloc_greatest_size());
 }
 
 struct tbuf *
@@ -58,10 +56,10 @@ tbuf_alloc(struct palloc_pool *pool)
 {
 	const size_t initial_size = 128 - sizeof(struct tbuf);
 	struct tbuf *e = palloc(pool, sizeof(*e) + initial_size);
-	e->pool = pool;
-	e->data = (char *)e + sizeof(*e);
-	e->size = initial_size;
 	e->len = 0;
+	e->size = initial_size;
+	e->data = (char *)e + sizeof(*e);
+	e->pool = pool;
 	poison(e->data, e->size);
 	tbuf_assert(e);
 	return e;
@@ -78,11 +76,6 @@ tbuf_ensure_resize(struct tbuf *e, size_t required)
 	while (new_size - e->len < required)
 		new_size *= 2;
 
-	if (unlikely(new_size > palloc_greatest_size())) {
-		new_size = palloc_greatest_size();
-		assert(new_size - e->len >= required);
-	}
-
 	void *p = palloc(e->pool, new_size);
 
 	poison(p, new_size);
@@ -132,7 +125,7 @@ tbuf_peek(struct tbuf *b, size_t count)
 }
 
 size_t
-tbuf_reserve(struct tbuf * b, size_t count)
+tbuf_reserve(struct tbuf *b, size_t count)
 {
 	tbuf_assert(b);
 	tbuf_ensure(b, count);
@@ -149,16 +142,6 @@ tbuf_reset(struct tbuf *b)
 	b->len = 0;
 }
 
-void
-tbuf_append(struct tbuf *b, const void *data, size_t len)
-{
-	tbuf_assert(b);
-	tbuf_ensure(b, len + 1);
-	memcpy(b->data + b->len, data, len);
-	b->len += len;
-	*(((char *)b->data) + b->len) = '\0';
-}
-
 void
 tbuf_append_field(struct tbuf *b, void *f)
 {
diff --git a/core/util.c b/core/util.c
index b86ee7a785c3afc0d766021e85a9ba769fededa0..0c7ba4eec72be7d723af399d7cf49a689262634d 100644
--- a/core/util.c
+++ b/core/util.c
@@ -125,15 +125,14 @@ print_trace(FILE *f)
 	}
 	fprintf(f, "backtrace:\n");
 	while (stack_bottom <= (void *)frame && (void *)frame < stack_top) {
-		fprintf(f, "  - { frame: %p, pc: %p }\n",
-			frame + 2 * sizeof(void *), frame->ret);
+		fprintf(f, "  - { frame: %p, pc: %p }\n", frame + 2 * sizeof(void *), frame->ret);
 		frame = frame->rbp;
 	}
 }
 #endif
 
-void __attribute__((noreturn))
-assert_fail(const char *assertion, const char *file, unsigned int line, const char *function)
+void __attribute__ ((noreturn))
+    assert_fail(const char *assertion, const char *file, unsigned int line, const char *function)
 {
 	fprintf(stderr, "%s:%i: %s: assertion %s failed.\n", file, line, function, assertion);
 #if CORO_ASM
diff --git a/include/bert.h b/include/bert.h
deleted file mode 100644
index fc9c7b6294bd44cf8d6ec67d991ee3328a43edf6..0000000000000000000000000000000000000000
--- a/include/bert.h
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef TARANTOOL_BERT_H
-#define TARANTOOL_BERT_H
-
-#include <arpa/inet.h>
-#include <string.h>
-#include <stdbool.h>
-
-#include <tbuf.h>
-#include <util.h>
-
-#define ERL_VERSION       131
-#define ERL_SMALL_INT     97
-#define ERL_INT           98
-#define ERL_SMALL_BIGNUM  110
-#define ERL_LARGE_BIGNUM  111
-#define ERL_FLOAT         99
-#define ERL_ATOM          100
-#define ERL_SMALL_TUPLE   104
-#define ERL_LARGE_TUPLE   105
-#define ERL_NIL           106
-#define ERL_STRING        107
-#define ERL_LIST          108
-#define ERL_BIN           109
-
-extern struct tbuf bert_saved_state, bert_last_packet;
-
-char *bert_sprint(struct tbuf *b);
-
-static inline void bert_save_state(struct tbuf *b)
-{
-	memcpy(&bert_saved_state, b, sizeof(*b));
-}
-
-static inline void bert_restore_state(struct tbuf *b)
-{
-	memcpy(b, &bert_saved_state, sizeof(*b));
-}
-
-static inline void bert_take_bytes(struct tbuf *b, size_t n)
-{
-	b->len -= n;
-	b->size -= n;
-	b->data += n;
-}
-
-#define bert_check_bytes(b, n) if (b->len < n) goto bert_match_failure;
-
-#define bert_peek(b, type, size)			\
-	({						\
-		bert_check_bytes(b, size);		\
-		type *p = b->data;			\
-		bert_take_bytes(b, size);		\
-		p;					\
-	})
-
-#define bert_match_prim(b, v, fail, type)				\
-	({								\
-		bool r = 0;						\
-		bert_check_bytes(b, sizeof(type));			\
-		type *p = b->data;					\
-		if (*p != v) {						\
-			fail;						\
-		} else {						\
-			r = 1;						\
-			bert_take_bytes(b, sizeof(type));		\
-		}							\
-		r;							\
-	})
-
-#define bert_peek_u8(b) *bert_peek(b, uint8_t, sizeof(uint8_t))
-#define bert_peek_n16(b) ntohs(*bert_peek(b, uint16_t, sizeof(uint16_t)))
-#define bert_peek_n32(b) ntohl(*bert_peek(b, uint32_t, sizeof(uint32_t)))
-#define bert_peek_bytes(b, n) bert_peek(b, uint8_t, n)
-
-#define bert_match_u8(b, v) bert_match_prim(b, v, goto bert_match_failure, uint8_t)
-#define bert_match_n16(b, v) bert_match_prim(b, htons(v), goto bert_match_failure, uint16_t)
-#define bert_match_n32(b, v) bert_match_prim(b, htonl(v), goto bert_match_failure, uint32_t)
-
-#define bert_cmp_u8(b, v) bert_match_prim(b, v, , uint8_t)
-#define bert_cmp_n16(b, v) bert_match_prim(b, htons(v), , uint16_t)
-#define bert_cmp_n32(b, v) bert_match_prim(b, htonl(v), , uint32_t)
-
-#define bert_match_header(b)					\
-	({							\
-		bert_match_u8(b, ERL_VERSION);			\
-		memcpy(&bert_last_packet, b, sizeof(*b));	\
-	})
-
-#define bert_cmp_atom(b, v)						\
-	({								\
-		bert_save_state(b);					\
-		bert_match_u8(b, ERL_ATOM);				\
-		size_t atom_len = bert_peek_n16(b);			\
-		uint8_t *atom = bert_peek_bytes(b, atom_len);		\
-		bool r = (strlen(v) == atom_len &&			\
-			  memcmp(atom, v, atom_len) == 0);		\
-		if (!r) bert_restore_state(b);				\
-		r;							\
-	})
-
-#define bert_match_atom(b, v)						\
-	({								\
-		bert_match_u8(b, ERL_ATOM);				\
-		size_t atom_len = bert_peek_n16(b);			\
-		uint8_t *atom = bert_peek_bytes(b, atom_len);		\
-		if (strlen(v) != atom_len &&				\
-		    memcmp(atom, v, atom_len) != 0)			\
-			goto bert_match_failure;			\
-	})
-
-#define bert_peek_int(b)					\
-	({							\
-		int64_t r = 0;					\
-		uint8_t tag;					\
-		tag = bert_peek_u8(b);				\
-		switch (tag) {					\
-		case ERL_SMALL_INT: r = bert_peek_u8(b); break;	\
-		case ERL_INT: r = bert_peek_n32(b); break;	\
-		case ERL_SMALL_BIGNUM: {			\
-			uint8_t n = bert_peek_u8(b);		\
-			uint8_t sign = bert_peek_u8(b);		\
-			uint8_t *bytes = bert_peek_bytes(b, n);	\
-			if (n > 8) goto bert_match_failure;	\
-			for(int i = 0; i < n; i++)		\
-				r += ((int64_t)bytes[i] << (i * 8));	\
-			if (sign) r = -r;			\
-			break;					\
-		}						\
-		default: goto bert_match_failure;		\
-		}						\
-		r;						\
-	})
-
-#define bert_peek_string(b)				\
-	({						\
-		match_u8(b, ERL_STRING);		\
-		int len = peek_n16(b);			\
-		tbuf_peek(p, len);			\
-	})
-
-#define bert_peek_bin(b)					\
-	({							\
-		bert_match_u8(b, ERL_BIN);				\
-		int len = bert_peek_n16(b);				\
-		struct tbuf *r = tbuf_alloc(b->pool, NULL, 0);		\
-		r->len = r->size = len;					\
-		r->data = bert_peek_bytes(b, len);			\
-		r;							\
-	})
-
-#define bert_peek_tuple(b)						\
-	({								\
-		uint32_t arity;						\
-		uint8_t tag;						\
-		tag = bert_peek_u8(b);					\
-		switch (tag) {						\
-		case ERL_SMALL_TUPLE: arity = bert_peek_u8(b);	break;	\
-		case ERL_LARGE_TUPLE: arity = bert_peek_n32(b);	break;	\
-		default: goto bert_match_failure;			\
-		}							\
-		arity;							\
-	})
-
-#define bert_match_tuple(b, a)					\
-	({							\
-		uint32_t arity = bert_peek_tuple(b);		\
-		if (arity != a) goto bert_match_failure;	\
-	})
-
-static inline void bert_pack_u8(struct tbuf *b, uint8_t v)
-{
-	size_t o = tbuf_reserve(b, sizeof(uint8_t));
-	*(uint8_t *) (b->data + o) = v;
-}
-
-static inline void bert_pack_n16(struct tbuf *b, uint16_t v)
-{
-	size_t o = tbuf_reserve(b, sizeof(uint16_t));
-	*(uint16_t *) (b->data + o) = htons(v);
-}
-
-static inline void bert_pack_n32(struct tbuf *b, uint32_t v)
-{
-	size_t o = tbuf_reserve(b, sizeof(uint32_t));
-	*(uint32_t *) (b->data + o) = htonl(v);
-}
-
-static inline void bert_pack_header(struct tbuf *b)
-{
-	bert_pack_u8(b, ERL_VERSION);
-}
-
-static inline void bert_pack_tuple(struct tbuf *b, uint8_t arity)
-{
-	bert_pack_u8(b, ERL_SMALL_TUPLE);
-	bert_pack_u8(b, arity);
-}
-
-#define bert_pack_atom(b, str) bert_pack_atom_((b), (str), strlen((str)))
-static inline void bert_pack_atom_(struct tbuf *b, const char *atom, size_t atom_len)
-{
-	bert_pack_u8(b, ERL_ATOM);
-	bert_pack_n16(b, atom_len);
-	size_t offt = tbuf_reserve(b, atom_len);
-	memcpy(b->data + offt, atom, atom_len);
-}
-
-static inline void bert_pack_int(struct tbuf *b, int64_t v)
-{
-	if (0 <= v && v <= 255) {
-		bert_pack_u8(b, ERL_SMALL_INT);
-		bert_pack_u8(b, (uint8_t) v);
-		return;
-	}
-
-	if (-(1 << 27) <= v && v <= (1 << 27) - 1) {
-		bert_pack_u8(b, ERL_INT);
-		bert_pack_n32(b, v);
-		return;
-	}
-
-	bert_pack_u8(b, ERL_SMALL_BIGNUM);
-	size_t n = tbuf_reserve(b, sizeof(uint8_t));
-	*(uint8_t *) (b->data + n) = 0;
-	bert_pack_u8(b, v < 0);
-	if (v < 0)
-		v = -v;
-	while (v) {
-		size_t o = tbuf_reserve(b, sizeof(uint8_t));
-		*(uint8_t *) (b->data + o) = v;
-		v >>= 8;
-		(*(uint8_t *) (b->data + n))++;
-	}
-}
-
-static inline void bert_pack_bin(struct tbuf *b, const struct tbuf *v)
-{
-	bert_pack_u8(b, ERL_BIN);
-	bert_pack_n16(b, v->len);
-	size_t o = tbuf_reserve(b, v->len);
-	memcpy(b->data + o, v->data, v->len);
-}
-
-#define bert_panic(msg) panic(msg "can't parse bert packet: %s", bert_sprint(&bert_last_packet))
-#endif
diff --git a/include/coro.h b/include/coro.h
index bcbca266165eacf9aae6f70882368a05d110270e..32e46a0052b2aae954bbe9d7860a31b918a48664 100644
--- a/include/coro.h
+++ b/include/coro.h
@@ -36,8 +36,8 @@ struct tarantool_coro {
 	size_t stack_size;
 };
 
-struct tarantool_coro *
-tarantool_coro_create(struct tarantool_coro *ctx, void (*f)(void *), void *data);
+struct tarantool_coro *tarantool_coro_create(struct tarantool_coro *ctx, void (*f) (void *),
+					     void *data);
 void tarantool_coro_destroy(struct tarantool_coro *ctx);
 
 #endif
diff --git a/include/fiber.h b/include/fiber.h
index 2e9bb53eb40613e7b9b0e32e86650c318be00850..63e21ad58a2a8441908b3048eecf392cc76cd206 100644
--- a/include/fiber.h
+++ b/include/fiber.h
@@ -34,7 +34,6 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
-
 #include <tarantool_ev.h>
 #include <palloc.h>
 #include <tbuf.h>
@@ -69,7 +68,7 @@ struct fiber {
 	struct tbuf *rbuf;
 	struct tbuf *cleanup;
 
-	SLIST_ENTRY(fiber) link, zombie_link;
+	 SLIST_ENTRY(fiber) link, zombie_link;
 
 	struct ring *inbox;
 
@@ -82,6 +81,7 @@ struct fiber {
 
 	void *data;
 
+	u64 cookie;
 	bool has_peer;
 	char peer_name[32];
 	bool reading_inbox;
@@ -121,9 +121,7 @@ void raise_(int);
 struct msg *read_inbox(void);
 int fiber_bread(struct tbuf *, size_t v);
 
-
-inline static void
-add_iov_unsafe(void *buf, size_t len)
+inline static void add_iov_unsafe(void *buf, size_t len)
 {
 	struct iovec *v;
 	assert(fiber->iov->size - fiber->iov->len >= sizeof(*v));
@@ -134,14 +132,12 @@ add_iov_unsafe(void *buf, size_t len)
 	fiber->iov_cnt++;
 }
 
-inline static void
-iov_ensure(size_t count)
+inline static void iov_ensure(size_t count)
 {
 	tbuf_ensure(fiber->iov, sizeof(struct iovec) * count);
 }
 
-inline static void
-add_iov(void *buf, size_t len)
+inline static void add_iov(void *buf, size_t len)
 {
 	iov_ensure(1);
 	add_iov_unsafe(buf, len);
@@ -149,8 +145,8 @@ add_iov(void *buf, size_t len)
 
 void add_iov_dup(void *buf, size_t len);
 bool write_inbox(struct fiber *recipient, struct tbuf *msg);
-int inbox_size(struct fiber * recipient);
-void wait_inbox(struct fiber * recipient);
+int inbox_size(struct fiber *recipient);
+void wait_inbox(struct fiber *recipient);
 
 char *fiber_peer_name(struct fiber *fiber);
 ssize_t fiber_read(void *buf, size_t count);
@@ -170,12 +166,11 @@ typedef enum fiber_server_type {
 	udp_server
 } fiber_server_type;
 
-struct fiber *fiber_server(fiber_server_type type, int port, void (*handler)(void *), void *,
-			   void (*on_bind)(void *));
+struct fiber *fiber_server(fiber_server_type type, int port, void (*handler) (void *), void *,
+			   void (*on_bind) (void *));
 
 struct child *spawn_child(const char *name,
 			  int inbox_size,
-			  struct tbuf *(*handler) (void *, struct tbuf *),
-			  void *state);
+			  struct tbuf *(*handler) (void *, struct tbuf *), void *state);
 
 #endif
diff --git a/include/iproto.h b/include/iproto.h
index 05c16a08f8a021c115a898b21236fa7bf2ffe467..1da28f79f8a6900edc182847e7b7e7a85cc0cff9 100644
--- a/include/iproto.h
+++ b/include/iproto.h
@@ -34,7 +34,7 @@ static inline struct iproto_header *iproto(const struct tbuf *t)
 struct iproto_interactor;
 
 struct iproto_interactor
-*iproto_interactor(uint32_t(*interact) (uint32_t msg, uint8_t * data, size_t len));
+*iproto_interactor(uint32_t (*interact) (uint32_t msg, uint8_t *data, size_t len));
 
 void iproto_interact(void *);
 
@@ -64,8 +64,12 @@ void iproto_interact(void *);
 	_(ERR_CODE_NOTHING,               0x00002400) /* nothing to do (not an error) */ \
 	_(ERR_CODE_UPDATE_ID,             0x00002502) /* id's update */ \
 	_(ERR_CODE_WRONG_VERSION,         0x00002602)	/* unsupported version of protocol */ \
-	_(ERR_CODE_UNKNOWN_ERROR,         0x00002702)
+	/* other generic error codes */					\
+	_(ERR_CODE_UNKNOWN_ERROR,         0x00002702) \
+        _(ERR_CODE_NODE_NOT_FOUND,	  0x00003102) \
+	_(ERR_CODE_NODE_FOUND,		  0x00003702) \
+	_(ERR_CODE_INDEX_VIOLATION,	  0x00003802)
 
 ENUM(error_codes, ERROR_CODES);
-extern const char *error_codes_strs[];
+extern char *error_codes_strs[];
 #endif
diff --git a/include/log_io.h b/include/log_io.h
index f643e33a86a59c28c3c9f2f46ceead2ae6c66342..58df0173457282de1e1eb06a826225f541a71da5 100644
--- a/include/log_io.h
+++ b/include/log_io.h
@@ -37,59 +37,110 @@
 
 #define RECOVER_READONLY 1
 
-struct log_io;
+extern const u16 wal_tag, snap_tag;
+extern const u64 default_cookie;
+extern const u32 default_version;
+
 struct recovery_state;
-typedef int (*row_handler) (struct recovery_state *, const struct tbuf *);
-typedef struct tbuf *(*row_reader) (FILE * f, struct palloc_pool * pool);
+typedef int (row_handler) (struct recovery_state *, struct tbuf *);
+typedef struct tbuf *(row_reader) (FILE *f, struct palloc_pool *pool);
+
+enum log_mode {
+	LOG_READ,
+	LOG_WRITE
+};
+
+struct log_io_class {
+	row_reader *reader;
+	u64 marker, eof_marker;
+	size_t marker_size, eof_marker_size;
+	size_t rows_per_file;
+	double fsync_delay;
+	bool panic_if_error;
+
+	const char *filetype;
+	const char *version;
+	const char *suffix;
+	const char *dirname;
+};
+
+struct log_io {
+	struct log_io_class *class;
+	FILE *f;
+
+	ev_stat stat;
+	enum log_mode mode;
+	size_t rows;
+	size_t retry;
+	char filename[PATH_MAX + 1];
+};
+
+struct recovery_state {
+	i64 lsn, confirmed_lsn;
+
+	struct log_io *current_wal;	/* the WAL we'r currently reading/writing from/to */
+	struct log_io_class **snap_class, **wal_class, *snap_prefered_class, *wal_prefered_class;
+	struct child *wal_writer;
+
+	/* row_handler will be presented by most recent format of data
+	   log_io_class->reader is responsible of converting data from old format */
+	row_handler *row_handler;
+
+	ev_timer wal_timer;
+	ev_tstamp recovery_lag, recovery_last_update_tstamp;
 
-struct row_v04 {
-	i64 lsn;		/* this used to be tid */
-	u16 type;
+	int snap_io_rate_limit;
+	u64 cookie;
+
+	/* pointer to user supplied custom data */
+	void *data;
+};
+
+struct wal_write_request {
+	i64 lsn;
 	u32 len;
 	u8 data[];
 } __packed__;
 
-struct row_v05 {
+struct row_v11 {
+	u32 header_crc32c;
 	i64 lsn;
 	double tm;
 	u32 len;
 	u32 data_crc32c;
-	u32 header_crc32c;
 	u8 data[];
 } __packed__;
 
-static inline struct row_v04 *row_v04(const struct tbuf *t)
+static inline struct row_v11 *row_v11(const struct tbuf *t)
 {
-	return (struct row_v04 *)t->data;
+	return (struct row_v11 *)t->data;
 }
 
-static inline struct row_v05 *row_v05(const struct tbuf *t)
-{
-	return (struct row_v05 *)t->data;
-}
+struct tbuf *convert_to_v11(struct tbuf *orig, u16 tag, u64 cookie, i64 lsn);
 
 struct recovery_state *recover_init(const char *snap_dirname, const char *xlog_dirname,
-				    row_reader snap_row_reader, row_handler snap_row_handler, row_handler xlog_row_handler,
-				    int rows_per_file, double fsync_delay, double snap_io_rate_limit,
-				    int inbox_size, int flags, void *data);
+				    row_reader snap_row_reader, row_handler row_handler,
+				    int rows_per_file, double fsync_delay, int inbox_size,
+				    int flags, void *data);
 int recover(struct recovery_state *, i64 lsn);
 void recover_follow(struct recovery_state *r, ev_tstamp wal_dir_rescan_delay);
 void recover_finalize(struct recovery_state *r);
-bool wal_write_v04(struct recovery_state *r, int op, const u8 * data, size_t len);
+bool wal_write(struct recovery_state *r, u16 tag, u64 cookie, i64 lsn, struct tbuf *data);
+
+void recovery_setup_panic(struct recovery_state *r, bool on_snap_error, bool on_wal_error);
 
-/* recovery accessors */
-struct palloc_pool *recovery_pool(struct recovery_state *r);
 int confirm_lsn(struct recovery_state *r, i64 lsn);
-int64_t confirmed_lsn(struct recovery_state *r);
 int64_t next_lsn(struct recovery_state *r, i64 new_lsn);
-struct child * wal_writer(struct recovery_state *r);
 
 int read_log(const char *filename, row_reader reader,
 	     row_handler xlog_handler, row_handler snap_handler, void *state);
 
-struct fiber *recover_follow_remote(struct recovery_state *r, char *ip_addr, int port);
+int default_remote_row_handler(struct recovery_state *r, struct tbuf *row);
+struct fiber *recover_follow_remote(struct recovery_state *r, char *ip_addr, int port,
+				    int (*handler) (struct recovery_state *r, struct tbuf *row));
 
 struct log_io_iter;
-void snapshot_write_row(struct log_io_iter *i, struct tbuf *row);
-void snapshot_save(struct recovery_state *r, void (*loop)(struct log_io_iter *));
+void snapshot_write_row(struct log_io_iter *i, u16 tag, u64 cookie, struct tbuf *row);
+void snapshot_save(struct recovery_state *r, void (*loop) (struct log_io_iter *));
+
 #endif
diff --git a/include/nbuf.h b/include/nbuf.h
deleted file mode 100644
index b41023ca7c765c058b4eb5e3a6106bd0b49e5fa1..0000000000000000000000000000000000000000
--- a/include/nbuf.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2010 Mail.RU
- * Copyright (C) 2010 Yuriy Vostrikov
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef TARANTOOL_NBUF_H
-#define TARANTOOL_NBUF_H
-
-#include <stdint.h>
-
-#include <palloc.h>
-#include <tbuf.h>
-
-struct nbuf;
-struct nbuf *nbuf_alloc(struct palloc_pool *pool, uint32_t owner, uint32_t initial_size);
-void nbuf_prepend_tbuf(struct nbuf *buf, struct tbuf *t);
-void *nbuf_promise(struct nbuf *buf, uint32_t size);
-void nbuf_confirm(struct nbuf *buf, uint32_t size);
-void *nbuf_request(struct nbuf *buf, uint32_t size);
-
-#endif
diff --git a/include/palloc.h b/include/palloc.h
index 10fcf049fbb3e69909e8c4e6a5815000155fcba9..b33a4724cc12fc3394b34e0529246fb7e77d0d95 100644
--- a/include/palloc.h
+++ b/include/palloc.h
@@ -32,20 +32,22 @@
 #include <stdint.h>
 
 #include <tbuf.h>
+#include <util.h>
 #include <third_party/queue.h>
 
 struct palloc_pool;
 extern struct palloc_pool *eter_pool;
 int palloc_init(void);
-void *palloc(struct palloc_pool *pool, size_t size);
-void *p0alloc(struct palloc_pool *pool, size_t size);
+void *palloc(struct palloc_pool *pool, size_t size) __regparm2__;
+void *p0alloc(struct palloc_pool *pool, size_t size) __regparm2__;
 void *palloca(struct palloc_pool *pool, size_t size, size_t align);
 void prelease(struct palloc_pool *pool);
+void prelease_after(struct palloc_pool *pool, size_t after);
 struct palloc_pool *palloc_create_pool2(const char *name, size_t initial_size);
 struct palloc_pool *palloc_create_pool(const char *name);
 void palloc_destroy(struct palloc_pool *);
 const char *palloc_name(struct palloc_pool *, const char *);
-size_t palloc_greatest_size(void);
+size_t palloc_allocated(struct palloc_pool *);
 
 void palloc_stat(struct tbuf *buf);
 
diff --git a/include/pickle.h b/include/pickle.h
index 8962b6c4a71ed550f69d2829fb09da9fa6c9b04e..fd98135bab5a4b23dd02a9d15bcc4ec2612a96c8 100644
--- a/include/pickle.h
+++ b/include/pickle.h
@@ -33,7 +33,10 @@ u8 *save_varint32(u8 *target, u32 value);
 void write_varint32(struct tbuf *b, u32 value);
 
 u8 read_u8(struct tbuf *b);
+u16 read_u16(struct tbuf *b);
 u32 read_u32(struct tbuf *b);
+u64 read_u64(struct tbuf *b);
+
 u32 read_varint32(struct tbuf *buf);
 void *read_field(struct tbuf *buf);
 
@@ -43,30 +46,28 @@ u32 valid_tuple(struct tbuf *buf, u32 cardinality);
 
 size_t varint32_sizeof(u32);
 
-
-inline static u32
-load_varint32(void **data)
+inline static u32 load_varint32(void **data)
 {
 	u8 *b = *data;
 
-	if(!(b[0] & 0x80)) {
+	if (!(b[0] & 0x80)) {
 		*data += 1;
 		return (b[0] & 0x7f);
 	}
-	if(!(b[1] & 0x80)) {
+	if (!(b[1] & 0x80)) {
 		*data += 2;
 		return (b[0] & 0x7f) << 7 | (b[1] & 0x7f);
 	}
-	if(!(b[2] & 0x80)) {
+	if (!(b[2] & 0x80)) {
 		*data += 3;
 		return (b[0] & 0x7f) << 14 | (b[1] & 0x7f) << 7 | (b[2] & 0x7f);
 	}
-	if(!(b[3] & 0x80)) {
+	if (!(b[3] & 0x80)) {
 		*data += 4;
 		return (b[0] & 0x7f) << 21 | (b[1] & 0x7f) << 14 |
 			(b[2] & 0x7f) << 7 | (b[3] & 0x7f);
 	}
-	if(!(b[4] & 0x80)) {
+	if (!(b[4] & 0x80)) {
 		*data += 5;
 		return (b[0] & 0x7f) << 28 | (b[1] & 0x7f) << 21 |
 			(b[2] & 0x7f) << 14 | (b[3] & 0x7f) << 7 | (b[4] & 0x7f);
@@ -76,5 +77,4 @@ load_varint32(void **data)
 	return 0;
 }
 
-
 #endif
diff --git a/include/say.h b/include/say.h
index f1c5672b9b3a639ab74a4953ca3e117b93a4747b..41f9ac7f2b8ad8e1683370a091cb6b5850ccfa7b 100644
--- a/include/say.h
+++ b/include/say.h
@@ -46,9 +46,9 @@ extern int sayfd;
 
 void say_logger_init(int nonblock);
 void vsay(int level, const char *error, const char *format, va_list ap)
-	__attribute__ ((format(FORMAT_PRINTF, 3, 0)));
+    __attribute__ ((format(FORMAT_PRINTF, 3, 0)));
 void _say(int level, const char *error, const char *format, ...)
-	__attribute__ ((format(FORMAT_PRINTF, 3, 4)));
+    __attribute__ ((format(FORMAT_PRINTF, 3, 4)));
 
 #define say(level, ...) ({ if(cfg.log_level >= level) _say(level, __VA_ARGS__); })
 
diff --git a/include/stat.h b/include/stat.h
index 902da056bf5f1b922578138ed0a14d0071e56046..85f93774f85e9da37850066c961a4bc8262eba09 100644
--- a/include/stat.h
+++ b/include/stat.h
@@ -30,7 +30,8 @@
 #include <tbuf.h>
 
 void stat_init(void);
-void stat_collect(const char *key, int value);
+int stat_register(char **name, size_t count);
+void stat_collect(int base, int name, i64 value);
 void stat_print(struct tbuf *buf);
 
 #endif
diff --git a/include/tarantool.h b/include/tarantool.h
index f8d4e70eebab84fc1f068874afd084e6c2a1e203..ff8600b240be793c9186822d11c136329e4c65be 100644
--- a/include/tarantool.h
+++ b/include/tarantool.h
@@ -32,12 +32,12 @@
 #include <log_io.h>
 #include TARANTOOL_CONFIG
 
-
 struct recovery_state *recovery_state;
-void mod_init (void);
+void mod_init(void);
 int mod_cat(const char *filename);
 void mod_snapshot(struct log_io_iter *);
 void mod_info(struct tbuf *out);
+void mod_exec(char *str, int len, struct tbuf *out);
 
 extern struct tarantool_module module;
 extern struct tarantool_cfg cfg;
diff --git a/include/tbuf.h b/include/tbuf.h
index dc2a69fc5b6eb236f9a71bc41cf19fe091c66022..729a8494dc59a550910a5b7460f50c4b937c2dd7 100644
--- a/include/tbuf.h
+++ b/include/tbuf.h
@@ -28,6 +28,7 @@
 #define TARANTOOL_TBUF_H
 
 #include <stddef.h>
+#include <string.h>
 
 #include <util.h>
 
@@ -48,6 +49,14 @@ static inline void tbuf_ensure(struct tbuf *e, size_t required)
 		tbuf_ensure_resize(e, required);
 }
 
+static inline void tbuf_append(struct tbuf *b, const void *data, size_t len)
+{
+	tbuf_ensure(b, len + 1);
+	memcpy(b->data + b->len, data, len);
+	b->len += len;
+	*(((char *)b->data) + b->len) = '\0';
+}
+
 struct tbuf *tbuf_clone(struct palloc_pool *pool, const struct tbuf *orig);
 struct tbuf *tbuf_split(struct tbuf *e, size_t at);
 size_t tbuf_reserve(struct tbuf *b, size_t count);
diff --git a/include/util.h b/include/util.h
index 938e7eb37a77370eb1d7db6ec21f29d2c9ca5a98..bc7e6f5370cf8a21793016ffcd03b56baf2096be 100644
--- a/include/util.h
+++ b/include/util.h
@@ -39,7 +39,7 @@
 #define ENUM_STRS_MEMBER(s, v) [s] = #s,
 #define ENUM(enum_name, enum_members) enum enum_name {enum_members(ENUM_MEMBER) enum_name##_MAX}
 #define STRS(enum_name, enum_members) \
-	const char *enum_name##_strs[enum_name##_MAX + 1] = {enum_members(ENUM_STRS_MEMBER) '\0'}
+	char *enum_name##_strs[enum_name##_MAX + 1] = {enum_members(ENUM_STRS_MEMBER) '\0'}
 
 // Macros for printf functions
 #include <inttypes.h>
@@ -47,10 +47,12 @@
 #define PRI_SZ  "lu"
 #define PRI_SSZ "ld"
 #define PRI_OFFT "lu"
+#define PRI_XFFT "lx"
 #else
 #define PRI_SZ  "u"
 #define PRI_SSZ "d"
 #define PRI_OFFT "llu"
+#define PRI_XFFT "llx"
 #endif
 
 #define nelem(x)     (sizeof((x))/sizeof((x)[0]))
@@ -72,19 +74,20 @@
 
 #ifndef TYPEALIGN
 #define TYPEALIGN(ALIGNVAL,LEN)  \
-        (((long) (LEN) + ((ALIGNVAL) - 1)) & ~((long) ((ALIGNVAL) - 1)))
+        (((uintptr_t) (LEN) + ((ALIGNVAL) - 1)) & ~((uintptr_t) ((ALIGNVAL) - 1)))
 
 #define SHORTALIGN(LEN)                 TYPEALIGN(sizeof(int16_t), (LEN))
 #define INTALIGN(LEN)                   TYPEALIGN(sizeof(int32_t), (LEN))
 #define MAXALIGN(LEN)                   TYPEALIGN(sizeof(int64_t), (LEN))
 #define PTRALIGN(LEN)                   TYPEALIGN(sizeof(void*), (LEN))
+#define CACHEALIGN(LEN)			TYPEALIGN(32, (LEN))
 #endif
 
-
 #define __packed__ __attribute__((packed))
 #define __noinline__ __attribute__((noinline))
 #define __unused__ __attribute__((unused))
 #define __cleanup__(f) __attribute__((cleanup (f)))
+#define __regparm2__  __attribute__((regparm(2)))
 
 typedef uint8_t u8;
 typedef uint16_t u16;
@@ -109,13 +112,11 @@ void *xrealloc(void *ptr, size_t size);
 
 void __gcov_flush();
 
-
 struct frame {
 	struct frame *rbp;
 	void *ret;
 };
 
-
 void save_rbp(void **rbp);
 extern void *main_stack_frame;
 
@@ -124,7 +125,7 @@ extern void *main_stack_frame;
 #else
 #  define assert(pred) ((pred) ? (void)(0) : assert_fail (#pred, __FILE__, __LINE__, __FUNCTION__))
 void assert_fail(const char *assertion, const char *file,
-		 unsigned int line, const char *function) __attribute__((noreturn));
+		 unsigned int line, const char *function) __attribute__ ((noreturn));
 #endif
 
 #endif
diff --git a/mod/feeder/feeder.c b/mod/feeder/feeder.c
index 9fd430938068187ede97a9b8d70c21e8ec18b3f0..16957dee7e488655dddc7d3b84364ef9821824e3 100644
--- a/mod/feeder/feeder.c
+++ b/mod/feeder/feeder.c
@@ -31,8 +31,10 @@
 #include <fiber.h>
 #include <util.h>
 
+static char *custom_proc_title;
+
 static int
-send_row(struct recovery_state *r __unused__, const struct tbuf *t)
+send_row(struct recovery_state *r __unused__, struct tbuf *t)
 {
 	u8 *data = t->data;
 	ssize_t bytes, len = t->len;
@@ -46,7 +48,7 @@ send_row(struct recovery_state *r __unused__, const struct tbuf *t)
 		data += bytes;
 	}
 
-	say_debug("send row: %"PRIu32" bytes %s", t->len, tbuf_to_hex(t));
+	say_debug("send row: %" PRIu32 " bytes %s", t->len, tbuf_to_hex(t));
 
 	return 0;
 }
@@ -55,13 +57,14 @@ static void
 recover_feed_slave(int sock)
 {
 	struct recovery_state *log_io;
+	struct tbuf *ver;
 	i64 lsn;
 	ssize_t r;
 
 	fiber->has_peer = true;
 	fiber->fd = sock;
 	fiber->name = "feeder";
-	set_proc_title("feeder:client_handler %s", fiber_peer_name(fiber));
+	set_proc_title("feeder:client_handler%s %s", custom_proc_title, fiber_peer_name(fiber));
 
 	ev_default_loop(0);
 
@@ -72,17 +75,18 @@ recover_feed_slave(int sock)
 		exit(EXIT_SUCCESS);
 	}
 
+	ver = tbuf_alloc(fiber->pool);
+	tbuf_append(ver, &default_version, sizeof(default_version));
+	send_row(NULL, ver);
+
 	log_io = recover_init(NULL, cfg.wal_feeder_dir,
-			      NULL, NULL, send_row,
-			      0, 0, 0, 
-			      64, RECOVER_READONLY, false);
+			      NULL, send_row, 0, 0, 64, RECOVER_READONLY, false);
 
 	recover(log_io, lsn);
 	recover_follow(log_io, 0.1);
 	ev_loop(0);
 }
 
-
 void
 mod_init(void)
 {
@@ -96,7 +100,16 @@ mod_init(void)
 	if (cfg.wal_feeder_dir == NULL)
 		panic("can't start feeder without wal_feeder_dir");
 
-	set_proc_title("feeder:acceptor %s:%i",
+	if (cfg.custom_proc_title == NULL)
+		custom_proc_title = "";
+	else {
+		custom_proc_title = palloc(eter_pool, strlen(cfg.custom_proc_title) + 2);
+		strcat(custom_proc_title, "@");
+		strcat(custom_proc_title, cfg.custom_proc_title);
+	}
+
+	set_proc_title("feeder:acceptor%s %s:%i",
+		       custom_proc_title,
 		       cfg.wal_feeder_bind_ipaddr == NULL ? "ANY" : cfg.wal_feeder_bind_ipaddr,
 		       cfg.wal_feeder_bind_port);
 
@@ -123,7 +136,7 @@ mod_init(void)
 		goto exit;
 	}
 
-	if (bind(server, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
+	if (bind(server, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
 		say_syserror("bind");
 		goto exit;
 	}
@@ -144,7 +157,15 @@ mod_init(void)
 		}
 		if (child == 0)
 			recover_feed_slave(client);
+		else
+			close(client);
 	}
-exit:
+      exit:
 	exit(EXIT_FAILURE);
 }
+
+void
+mod_exec(char *str __unused__, int len __unused__, struct tbuf *out)
+{
+	tbuf_printf(out, "Unimplemented");
+}
diff --git a/mod/feeder/feeder_cfg.cfg_tmpl b/mod/feeder/feeder_cfg.cfg_tmpl
index ce82d4905d66bfd6bc6d9419e34a46d680e3fcf6..41b2d40b31e9e519e0333a33b7e605b24cb2581b 100644
--- a/mod/feeder/feeder_cfg.cfg_tmpl
+++ b/mod/feeder/feeder_cfg.cfg_tmpl
@@ -6,3 +6,6 @@ wal_feeder_bind_port=0
 
 # Directory with WAL files to serve
 wal_feeder_dir=NULL
+
+# custom proc title is appended after normal
+custom_proc_title=NULL
diff --git a/mod/silverbox/Makefile b/mod/silverbox/Makefile
index e1ed00ba11410b5860b82a1397d737cb0e57c023..1a16feb4a728bf62e9de88785c2207bb8a0937e6 100644
--- a/mod/silverbox/Makefile
+++ b/mod/silverbox/Makefile
@@ -3,6 +3,6 @@ core/tarantool.o: CFLAGS += -DSTORAGE
 obj += core/admin.o
 obj += mod/silverbox/box.o
 obj += mod/silverbox/memcached.o
-# obj += third_party/murmur_hash2.o
+obj += third_party/qsort_arg.o
 
 cfg_tmpl += mod/silverbox/box_cfg.cfg_tmpl
diff --git a/mod/silverbox/assoc.h b/mod/silverbox/assoc.h
index 26680afc412875632f0e615b30cff49ac8d63310..69e0656a64373033db3be2d2526f3ff838ba8ac1 100644
--- a/mod/silverbox/assoc.h
+++ b/mod/silverbox/assoc.h
@@ -24,7 +24,9 @@ static inline khint_t __ac_X31_hash_lstr(void *s)
 	khint_t l;
 	l = load_varint32(&s);
 	khint_t h = 0;
-	if (l) for (; l-- ; s++) h = (h << 5) - h + *(u8 *)s;
+	if (l)
+		for (; l--; s++)
+			h = (h << 5) - h + *(u8 *)s;
 	return h;
 }
 
@@ -35,7 +37,7 @@ static inline int lstrcmp(void *a, void *b)
 	al = load_varint32(&a);
 	bl = load_varint32(&b);
 
-	if(al != bl)
+	if (al != bl)
 		return bl - al;
 	return memcmp(a, b, al);
 }
@@ -44,12 +46,11 @@ static inline int lstrcmp(void *a, void *b)
 #define kh_lstr_hash_func(key) ({ void *_k = key; unsigned int l = load_varint32(&_k); MurmurHash2(_k, l, 13); })
 #define kh_lstr_hash_equal(a, b) (lstrcmp(a, b) == 0)
 
-KHASH_INIT(lstr2ptr_map, void*, ptr_t, 1, kh_lstr_hash_func, kh_lstr_hash_equal, xrealloc)
-KHASH_INIT(ptr_set, uint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal, xrealloc);
+KHASH_INIT(lstr2ptr_map, void *, ptr_t, 1, kh_lstr_hash_func, kh_lstr_hash_equal, xrealloc)
+    KHASH_INIT(ptr_set, uint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal, xrealloc);
 
 void assoc_init(void);
 
-
 #define assoc_clear(hash_name, hash) kh_clear(hash_name, hash)
 
 #define assoc_exist(hash_name, hash, key) ({       \
@@ -90,7 +91,6 @@ void assoc_init(void);
         _k;                                         \
 })
 
-
 #define assoc_foreach(hash, kiter)                                    \
         for (kiter = kh_begin(hash); kiter != kh_end(hash); ++kiter)  \
                 if (kh_exist(hash, kiter))
diff --git a/mod/silverbox/box.c b/mod/silverbox/box.c
index d21c088c83477d6649a94af6d6434cde17a26b9c..47a209f0d2c1514a73758c78f6d07a1cd42c79c7 100644
--- a/mod/silverbox/box.c
+++ b/mod/silverbox/box.c
@@ -39,27 +39,35 @@
 #include <tarantool.h>
 #include <tbuf.h>
 #include <util.h>
+// #include <third_party/sptree.h>
 
 #include <mod/silverbox/box.h>
 
 bool box_updates_allowed = false;
 static char *status = "unknown";
 
+static int stat_base;
 STRS(messages, MESSAGES);
 
 const int MEMCACHED_NAMESPACE = 23;
 static char *custom_proc_title;
 
-/* hooks */
-typedef int (*box_hook_t)(struct box_txn *txn);
+const struct field ASTERISK = {
+	.len = UINT32_MAX,
+	{
+		.data_ptr = NULL,
+	}
+};
 
+/* hooks */
+typedef int (*box_hook_t) (struct box_txn * txn);
 
 struct namespace namespace[256];
 
 struct box_snap_row {
-        u32 namespace;
-        u32 tuple_size;
-        u32 data_size;
+	u32 namespace;
+	u32 tuple_size;
+	u32 data_size;
 	u8 data[];
 } __packed__;
 
@@ -69,10 +77,8 @@ box_snap_row(const struct tbuf *t)
 	return (struct box_snap_row *)t->data;
 }
 
-
 static void tuple_add_iov(struct box_txn *txn, struct box_tuple *tuple);
 
-
 box_hook_t *before_commit_update_hook;
 
 #define box_raise(n, err)						\
@@ -82,13 +88,12 @@ box_hook_t *before_commit_update_hook;
 		raise(n, err);						\
 	})
 
-
 static void
-run_hooks(struct box_txn *txn, box_hook_t *hook)
+run_hooks(struct box_txn *txn, box_hook_t * hook)
 {
 	if (hook != NULL) {
 		for (int i = 0; hook[i] != NULL; i++) {
-			int result = (*hook[i])(txn);
+			int result = (*hook[i]) (txn);
 			if (result != ERR_CODE_OK)
 				box_raise(result, "hook returned error");
 		}
@@ -98,149 +103,219 @@ run_hooks(struct box_txn *txn, box_hook_t *hook)
 void *
 next_field(void *f)
 {
-        u32 size = load_varint32(&f);
-        return (u8 *)f + size;
+	u32 size = load_varint32(&f);
+	return (u8 *)f + size;
 }
 
-
 void *
 tuple_field(struct box_tuple *tuple, size_t i)
 {
 	void *field = tuple->data;
 
-        if (i >= tuple->cardinality)
-                return NULL;
+	if (i >= tuple->cardinality)
+		return NULL;
 
-        while (i-- > 0)
-                field = next_field(field);
+	while (i-- > 0)
+		field = next_field(field);
 
-        return field;
+	return field;
 }
 
-#define foreach_index(n, index_var)					\
-	for (struct index *index_var = namespace[(n)].index;		\
-	     index_var->key_position >= 0;				\
-	     index_var++)
-
-struct box_tuple *
-index_find(struct index *index, int key_len, void *key)
+bool
+field_is_num(void *field)
 {
-	return index->find(index, key_len, key);
+	u32 len = load_varint32(&field);
+
+	if (len == sizeof(u32))
+		return true;
+
+	return false;
 }
 
-void
-index_remove(struct index *index, struct box_tuple *tuple)
+#define IS_ASTERISK(f) ((f)->len == ASTERISK.len && (f)->data_ptr == ASTERISK.data_ptr)
+static i8
+field_compare(struct field *f1, struct field *f2, enum field_data_type type)
 {
-	void *key = tuple_field(tuple, index->key_position);
-	index->remove(index, key);
+	i8 r;
+
+	if (IS_ASTERISK(f1) || IS_ASTERISK(f2))
+		r = 0;
+	else {
+		if (type == NUM) {
+			assert(f1->len == f2->len);
+			assert(f1->len == sizeof(f1->u32));
+
+			r = f1->u32 >f2->u32 ? 1 : f1->u32 == f2->u32 ? 0 : -1;
+		} else {
+			i32 cmp;
+			void *f1_data, *f2_data;
+
+			f1_data = f1->len <= sizeof(f1->data) ? f1->data : f1->data_ptr;
+			f2_data = f2->len <= sizeof(f2->data) ? f2->data : f2->data_ptr;
+
+			cmp = memcmp(f1_data, f2_data, MIN(f1->len, f2->len));
+
+			if (cmp > 0)
+				r = 1;
+			else if (cmp < 0)
+				r = -1;
+			else if (f1->len == f2->len)
+				r = 0;
+			else if (f1->len > f2->len)
+				r = 1;
+			else
+				r = -1;
+		}
+	}
+
+	return r;
 }
 
-void
-index_replace(struct index *index, struct box_tuple *tuple)
+/*
+ * Compare index_tree_members only by fields defined in index->field_cmp_order.
+ * Return:
+ *      Common meaning:
+ *              < 0  - a is smaler than b
+ *              == 0 - a is equal to b
+ *              > 0  - a is greater than b
+ *      Custom treatment (by absolute value):
+ *              1 - differ in some key field
+ *              2 - one tuple is a search pattern
+ *              3 - differ in pointers
+ */
+static int
+tree_index_member_compare(struct tree_index_member *member_a, struct tree_index_member *member_b,
+			  struct index *index)
 {
-	void *key = tuple_field(tuple, index->key_position);
-	index->replace(index, key, tuple);
+	i8 r = 0;
+
+	for (i32 i = 0, end = index->key_cardinality; i < end; ++i) {
+		r = field_compare(&member_a->key[i], &member_b->key[i], index->key_field[i].type);
+
+		if (r != 0)
+			break;
+	}
+
+	if (r != 0)
+		return r;
+
+	if (member_a->tuple == NULL)
+		return -2;
+
+	if (member_b->tuple == NULL)
+		return 2;
+
+	if (index->unique == false) {
+		if (member_a->tuple > member_b->tuple)
+			return 3;
+		else if (member_a->tuple < member_b->tuple)
+			return -3;
+	}
+
+	return 0;
 }
 
+#define foreach_index(n, index_var)					\
+	for (struct index *index_var = namespace[(n)].index;		\
+	     index_var->key_cardinality != 0;				\
+	     index_var++)						\
+		if (index_var->enabled)
 
 static void
 lock_tuple(struct box_txn *txn, struct box_tuple *tuple)
 {
-        if (tuple->flags & WAL_WAIT)
+	if (tuple->flags & WAL_WAIT)
 		box_raise(ERR_CODE_NODE_IS_RO, "tuple is locked");
 
 	say_debug("lock_tuple(%p)", tuple);
-        txn->lock_tuple = tuple;
-        tuple->flags |= WAL_WAIT;
+	txn->lock_tuple = tuple;
+	tuple->flags |= WAL_WAIT;
 }
 
-
 static void
 unlock_tuples(struct box_txn *txn)
 {
-        if (txn->lock_tuple) {
-                txn->lock_tuple->flags &= ~WAL_WAIT;
+	if (txn->lock_tuple) {
+		txn->lock_tuple->flags &= ~WAL_WAIT;
 		txn->lock_tuple = NULL;
 	}
 }
 
-
 static void
-field_print(struct tbuf *buf,  void *f)
+field_print(struct tbuf *buf, void *f)
 {
-        uint32_t size;
+	uint32_t size;
 
-        size = load_varint32(&f);
+	size = load_varint32(&f);
 
-        if (size == 2)
-                tbuf_printf(buf, "%i:", *(u16 *)f);
+	if (size == 2)
+		tbuf_printf(buf, "%i:", *(u16 *)f);
 
-        if (size == 4)
-                tbuf_printf(buf, "%i:", *(u32 *)f);
+	if (size == 4)
+		tbuf_printf(buf, "%i:", *(u32 *)f);
 
-        while (size-- > 0) {
-                if (0x20 <= *(u8 *)f && *(u8 *)f < 0x7f)
-                        tbuf_printf(buf, "%c", *(u8 *)f++);
-                else
-                        tbuf_printf(buf, "\\x%02X", *(u8 *)f++);
-        }
+	while (size-- > 0) {
+		if (0x20 <= *(u8 *)f && *(u8 *)f < 0x7f)
+			tbuf_printf(buf, "%c", *(u8 *)f++);
+		else
+			tbuf_printf(buf, "\\x%02X", *(u8 *)f++);
+	}
 
 }
 
 static void
 tuple_print(struct tbuf *buf, uint8_t cardinality, void *f)
 {
-        tbuf_printf(buf, "<");
+	tbuf_printf(buf, "<");
 
-        for(size_t i = 0; i < cardinality; i++, f = next_field(f)) {
-                tbuf_printf(buf, "\"");
-                field_print(buf, f);
-                tbuf_printf(buf, "\"");
+	for (size_t i = 0; i < cardinality; i++, f = next_field(f)) {
+		tbuf_printf(buf, "\"");
+		field_print(buf, f);
+		tbuf_printf(buf, "\"");
 
-                if (likely(i + 1 < cardinality))
-                        tbuf_printf(buf, ", ");
+		if (likely(i + 1 < cardinality))
+			tbuf_printf(buf, ", ");
 
-        }
+	}
 
-        tbuf_printf(buf, ">");
+	tbuf_printf(buf, ">");
 }
 
-
 static struct box_tuple *
 tuple_alloc(size_t size)
 {
-        size += sizeof(struct box_tuple);
-        struct box_tuple *tuple = salloc(size);
+	struct box_tuple *tuple = salloc(sizeof(struct box_tuple) + size);
 
-        if (tuple == NULL)
+	if (tuple == NULL)
 		box_raise(ERR_CODE_MEMORY_ISSUE, "can't allocate tuple");
 
-        tuple->flags = tuple->refs = 0;
+	tuple->flags = tuple->refs = 0;
 	tuple->flags |= NEW;
-        say_debug("tuple_alloc(%zu) = %p", size, tuple);
-        return tuple;
-}
+	tuple->bsize = size;
 
+	say_debug("tuple_alloc(%zu) = %p", size, tuple);
+	return tuple;
+}
 
 static void
 tuple_free(struct box_tuple *tuple)
 {
-        say_debug("tuple_free(%p)", tuple);
+	say_debug("tuple_free(%p)", tuple);
 	assert(tuple->refs == 0);
-        sfree(tuple);
+	sfree(tuple);
 }
 
 static void
 tuple_ref(struct box_tuple *tuple, int count)
 {
 	assert(tuple->refs + count >= 0);
-        tuple->refs += count;
+	tuple->refs += count;
 
 	if (tuple->refs > 0)
 		tuple->flags &= ~NEW;
 
 	if (tuple->refs == 0)
-                tuple_free(tuple);
+		tuple_free(tuple);
 }
 
 void
@@ -251,148 +326,304 @@ tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple)
 	tuple_ref(tuple, +1);
 }
 
+static struct box_tuple *
+index_find_hash_by_tuple(struct index *self, struct box_tuple *tuple)
+{
+	void *key = tuple_field(tuple, self->key_field->fieldno);
+	if (key == NULL)
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "invalid tuple, can't find key");
+	return self->find(self, key);
+}
 
 static struct box_tuple *
-index_find_hash_num(struct index *self, int key_len, void *key)
+index_find_hash_num(struct index *self, void *key)
 {
 	struct box_tuple *ret = NULL;
 	u32 key_size = load_varint32(&key);
 	u32 num = *(u32 *)key;
 
-	if (key_len != 1)
-		box_raise(ERR_CODE_ILLEGAL_PARAMS, "key must be single valued");
 	if (key_size != 4)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "key is not u32");
 
-	assoc_find(int2ptr_map, self->map.int_map, num, ret);
+	assoc_find(int2ptr_map, self->idx.int_hash, num, ret);
 #ifdef DEBUG
-	say_debug("index_find_hash_num(%i, key:%i, tuple:%p)", self->namespace->n, num, ret);
+	say_debug("index_find_hash_num(self:%p, key:%i) = %p", self, num, ret);
 #endif
 	return ret;
 }
 
 static struct box_tuple *
-index_find_hash_str(struct index *self, int key_len, void *key)
+index_find_hash_str(struct index *self, void *key)
 {
 	struct box_tuple *ret = NULL;
-	u32 size;
-
-	if (key_len != 1)
-		box_raise(ERR_CODE_ILLEGAL_PARAMS, "key must be single valued");
 
-	assoc_find(lstr2ptr_map, self->map.str_map, key, ret);
-	size = load_varint32(&key);
+	assoc_find(lstr2ptr_map, self->idx.str_hash, key, ret);
 #ifdef DEBUG
-	say_debug("index_find_hash_str(%i, key:(%i)'%.*s', tuple:%p)", self->namespace->n, size, size, (u8 *)key, ret);
+	u32 size = load_varint32(&key);
+	say_debug("index_find_hash_str(self:%p, key:(%i)'%.*s') = %p", self, size, size, (u8 *)key,
+		  ret);
 #endif
 	return ret;
 }
 
+static struct tree_index_member *
+tuple2tree_index_member(struct index *index,
+			struct box_tuple *tuple, struct tree_index_member **member_p)
+{
+	struct tree_index_member *member;
+	void *tuple_data = tuple->data;
+
+	if (member_p == NULL || *member_p == NULL)
+		member = palloc(fiber->pool, SIZEOF_TREE_INDEX_MEMBER(index));
+	else
+		member = *member_p;
+
+	for (i32 i = 0; i < index->field_cmp_order_cnt; ++i) {
+		struct field f;
+
+		if (i < tuple->cardinality) {
+			f.len = load_varint32(&tuple_data);
+			if (f.len <= sizeof(f.data)) {
+				memset(f.data, 0, sizeof(f.data));
+				memcpy(f.data, tuple_data, f.len);
+			} else
+				f.data_ptr = tuple_data;
+			tuple_data += f.len;
+		} else
+			f = ASTERISK;
+
+		if (index->field_cmp_order[i] == -1)
+			continue;
+
+		member->key[index->field_cmp_order[i]] = f;
+	}
+
+	member->tuple = tuple;
+
+	if (member_p)
+		*member_p = member;
+
+	return member;
+}
+
+static struct tree_index_member *
+alloc_search_pattern(struct index *index, int key_cardinality, void *key)
+{
+	struct tree_index_member *pattern = index->search_pattern;
+	void *key_field = key;
+
+	assert(key_cardinality <= index->key_cardinality);
+
+	for (i32 i = 0; i < index->key_cardinality; ++i)
+		pattern->key[i] = ASTERISK;
+	for (int i = 0; i < key_cardinality; i++) {
+		u32 len;
+
+		len = pattern->key[i].len = load_varint32(&key_field);
+		if (len <= sizeof(pattern->key[i].data)) {
+			memset(pattern->key[i].data, 0, sizeof(pattern->key[i].data));
+			memcpy(pattern->key[i].data, key_field, len);
+		} else
+			pattern->key[i].data_ptr = key_field;
+
+		key_field += len;
+	}
+
+	pattern->tuple = NULL;
+
+	return pattern;
+}
+
+static struct box_tuple *
+index_find_tree(struct index *self, void *key)
+{
+	struct tree_index_member *member = (struct tree_index_member *)key;
+
+	return sptree_str_t_find(self->idx.tree, member);
+}
+
+static struct box_tuple *
+index_find_tree_by_tuple(struct index *self, struct box_tuple *tuple)
+{
+	struct tree_index_member *member = tuple2tree_index_member(self, tuple, NULL);
+
+	return self->find(self, member);
+}
+
 static void
-index_remove_hash_num(struct index *self, void *key)
+index_remove_hash_num(struct index *self, struct box_tuple *tuple)
 {
+	void *key = tuple_field(tuple, self->key_field->fieldno);
 	unsigned int key_size = load_varint32(&key);
 	u32 num = *(u32 *)key;
 
-        if(key_size != 4)
+	if (key_size != 4)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "key is not u32");
-	assoc_delete(int2ptr_map, self->map.int_map, num);
+	assoc_delete(int2ptr_map, self->idx.int_hash, num);
 #ifdef DEBUG
-	say_debug("index_remove_hash_num(%i, key:%i)", self->namespace->n, num);
+	say_debug("index_remove_hash_num(self:%p, key:%i)", self, num);
 #endif
 }
 
 static void
-index_remove_hash_str(struct index *self, void *key)
+index_remove_hash_str(struct index *self, struct box_tuple *tuple)
 {
-	assoc_delete(lstr2ptr_map, self->map.str_map, key);
+	void *key = tuple_field(tuple, self->key_field->fieldno);
+	assoc_delete(lstr2ptr_map, self->idx.str_hash, key);
 #ifdef DEBUG
 	u32 size = load_varint32(&key);
-	say_debug("index_remove_hash_str(%i, key:'%.*s')", self->namespace->n, size, (u8 *)key);
+	say_debug("index_remove_hash_str(self:%p, key:'%.*s')", self, size, (u8 *)key);
 #endif
 }
 
 static void
-index_replace_hash_num(struct index *self, void *key, void *value)
+index_remove_tree_str(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);
+}
+
+static void
+index_replace_hash_num(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;
 
-        if(key_size != 4)
+	if (old_tuple != NULL) {
+		void *old_key = tuple_field(old_tuple, self->key_field->fieldno);
+		load_varint32(&old_key);
+		u32 old_num = *(u32 *)old_key;
+		assoc_delete(int2ptr_map, self->idx.int_hash, old_num);
+	}
+
+	if (key_size != 4)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "key is not u32");
-	assoc_replace(int2ptr_map, self->map.int_map, num, value);
+	assoc_replace(int2ptr_map, self->idx.int_hash, num, tuple);
 #ifdef DEBUG
-	say_debug("index_replace_hash_num(%i, key:%i, tuple:%p)", self->namespace->n, num, value);
+	say_debug("index_replace_hash_num(self:%p, old_tuple:%p, tuple:%p) key:%i", self, old_tuple,
+		  tuple, num);
 #endif
 }
 
 static void
-index_replace_hash_str(struct index *self, void *key, void *value)
+index_replace_hash_str(struct index *self, struct box_tuple *old_tuple, struct box_tuple *tuple)
 {
-	assoc_replace(lstr2ptr_map, self->map.str_map, key, value);
+	void *key = tuple_field(tuple, self->key_field->fieldno);
+
+	if (old_tuple != NULL) {
+		void *old_key = tuple_field(old_tuple, self->key_field->fieldno);
+		assoc_delete(lstr2ptr_map, self->idx.str_hash, old_key);
+	}
+
+	assoc_replace(lstr2ptr_map, self->idx.str_hash, key, tuple);
 #ifdef DEBUG
 	u32 size = load_varint32(&key);
-	say_debug("index_replace_hash_str(%i, key:'%.*s', tuple:%p)", self->namespace->n, size, (u8 *)key, value);
+	say_debug("index_replace_hash_str(self:%p, old_tuple:%p, tuple:%p) key:'%.*s'", self,
+		  old_tuple, tuple, size, (u8 *)key);
 #endif
 }
 
+static void
+index_replace_tree_str(struct index *self, struct box_tuple *old_tuple, struct box_tuple *tuple)
+{
+	struct tree_index_member *member = tuple2tree_index_member(self, tuple, NULL);
+
+	if (old_tuple)
+		index_remove_tree_str(self, old_tuple);
+	sptree_str_t_insert(self->idx.tree, member);
+}
+
+static void
+index_iterator_init_tree_str(struct index *self, struct tree_index_member *pattern)
+{
+	sptree_str_t_iterator_init_set(self->idx.tree,
+				       (struct sptree_str_t_iterator **)&self->iterator, pattern);
+}
+
+static struct box_tuple *
+index_iterator_next_tree_str(struct index *self, struct tree_index_member *pattern)
+{
+	struct tree_index_member *member =
+		sptree_str_t_iterator_next((struct sptree_str_t_iterator *)self->iterator);
+
+	if (member == NULL)
+		return NULL;
+
+	i32 r = tree_index_member_compare(pattern, member, self);
+	if (r == -2)
+		return member->tuple;
+
+	return NULL;
+}
+
+static void
+validate_indeces(struct box_txn *txn)
+{
+	if (namespace[txn->n].index[1].key_cardinality != 0) {	/* there is more then one index */
+		foreach_index(txn->n, index) {
+			for (u32 f = 0; f < index->key_cardinality; ++f) {
+				void *field;
+
+				if (index->key_field[f].type == STR)
+					continue;
+
+				field = tuple_field(txn->tuple, index->key_field[f].fieldno);
+				if (!field_is_num(field))
+					box_raise(ERR_CODE_ILLEGAL_PARAMS, "field must be NUM");
+			}
+			if (index->type == TREE && index->unique == false)
+				/* Don't check non unique indexes */
+				continue;
+
+			struct box_tuple *tuple = index->find_by_tuple(index, txn->tuple);
+
+			if (tuple != NULL && tuple != txn->old_tuple)
+				box_raise(ERR_CODE_INDEX_VIOLATION, "unique index violation");
+		}
+	}
+}
+
 static int __noinline__
 prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 {
-        uint8_t *key;
-
-        assert(data != NULL);
+	assert(data != NULL);
 	if (cardinality == 0)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "cardinality can't be equal to 0");
-	if(data->len == 0 || data->len != valid_tuple(data, cardinality))
+	if (data->len == 0 || data->len != valid_tuple(data, cardinality))
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "tuple encoding error");
 
-        txn->tuple = tuple_alloc(data->len);
+	txn->tuple = tuple_alloc(data->len);
 	tuple_txn_ref(txn, txn->tuple);
-        txn->tuple->cardinality = cardinality;
-        txn->tuple->bsize = data->len;
-        memcpy(txn->tuple->data, data->data, data->len);
-        key = tuple_field(txn->tuple, txn->index->key_position);
-        if(key == NULL)
-		box_raise(ERR_CODE_ILLEGAL_PARAMS, "invalid tuple: can't find key");
-
-	txn->old_tuple = index_find(txn->index, 1, key);
+	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)
 		tuple_txn_ref(txn, txn->old_tuple);
 
-	if (namespace[txn->n].index[1].key_position >= 0) { /* there is more then one index */
-		foreach_index(txn->n, index) {
-			void *key = tuple_field(txn->tuple, index->key_position);
-			if(key == NULL)
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "invalid tuple, can't find key");
-
-			struct box_tuple *tuple = index_find(index, 1, key);
-
-			/*
-			 * tuple referenced by secondary keys
-			 * must be same as tuple referenced by index[0]
-			 * if tuple nonexistent (NULL) - it must be nonexistent in all indeces
-			 */
-			if(tuple != txn->old_tuple) {
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "index violation");
-			}
-		}
-	}
+	if (txn->flags & BOX_ADD && txn->old_tuple != NULL)
+		box_raise(ERR_CODE_NODE_FOUND, "tuple found");
+
+	if (txn->flags & BOX_REPLACE && txn->old_tuple == NULL)
+		box_raise(ERR_CODE_NODE_NOT_FOUND, "tuple not found");
 
-        run_hooks(txn, before_commit_update_hook);
+	validate_indeces(txn);
+	run_hooks(txn, before_commit_update_hook);
 
-        if (txn->old_tuple != NULL) {
+	if (txn->old_tuple != NULL) {
 #ifndef NDEBUG
-                void *ka, *kb;
-		ka = tuple_field(txn->tuple, txn->index->key_position);
-                kb = tuple_field(txn->old_tuple, txn->index->key_position);
+		void *ka, *kb;
+		ka = tuple_field(txn->tuple, txn->index->key_field->fieldno);
+		kb = tuple_field(txn->old_tuple, txn->index->key_field->fieldno);
 		int kal, kab;
 		kal = load_varint32(&ka);
 		kab = load_varint32(&kb);
 		assert(kal == kab && memcmp(ka, kb, kal) == 0);
 #endif
 		lock_tuple(txn, txn->old_tuple);
-        } else {
+	} else {
 		/*
 		 * if tuple doesn't exist insert GHOST tuple in indeces
 		 * in order to avoid race condition
@@ -400,36 +631,36 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data)
 		 */
 
 		foreach_index(txn->n, index)
-			index_replace(index, txn->tuple);
+			index->replace(index, NULL, txn->tuple);
 
-                lock_tuple(txn, txn->tuple);
+		lock_tuple(txn, txn->tuple);
 		txn->tuple->flags |= GHOST;
-        }
+	}
 
-        return -1;
+	return -1;
 }
 
 static void
 commit_replace(struct box_txn *txn)
 {
-        int tuples_affected = 1;
+	int tuples_affected = 1;
 
-        if (txn->old_tuple != NULL) {
+	if (txn->old_tuple != NULL) {
 		foreach_index(txn->n, index)
-			index_replace(index, txn->tuple);
+			index->replace(index, txn->old_tuple, txn->tuple);
 
 		tuple_ref(txn->old_tuple, -1);
-        }
+	}
 
 	txn->tuple->flags &= ~GHOST;
 	tuple_ref(txn->tuple, +1);
 
-        if (!(txn->flags & BOX_QUIET) && !txn->in_recover) {
-                add_iov_dup(&tuples_affected, sizeof(uint32_t));
+	if (!(txn->flags & BOX_QUIET) && !txn->in_recover) {
+		add_iov_dup(&tuples_affected, sizeof(uint32_t));
 
-                if (txn->flags & BOX_RETURN_TUPLE)
-                        tuple_add_iov(txn, txn->tuple);
-        }
+		if (txn->flags & BOX_RETURN_TUPLE)
+			tuple_add_iov(txn, txn->tuple);
+	}
 }
 
 static void
@@ -437,19 +668,121 @@ rollback_replace(struct box_txn *txn)
 {
 	say_debug("rollback_replace: txn->tuple:%p", txn->tuple);
 
-        if (txn->tuple && txn->tuple->flags & GHOST) {
+	if (txn->tuple && txn->tuple->flags & GHOST) {
 		foreach_index(txn->n, index)
-			index_remove(index, txn->tuple);
-        }
+			index->remove(index, txn->tuple);
+	}
+}
+
+static void
+do_field_arith(u8 op, struct tbuf *field, void *arg, u32 arg_size)
+{
+	if (field->len != 4)
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "num op on field with length != 4");
+	if (arg_size != 4)
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "num op with arg not u32");
+
+	switch (op) {
+	case 1:
+		*(i32 *)field->data += *(i32 *)arg;
+		break;
+	case 2:
+		*(u32 *)field->data &= *(u32 *)arg;
+		break;
+	case 3:
+		*(u32 *)field->data ^= *(u32 *)arg;
+		break;
+	case 4:
+		*(u32 *)field->data |= *(u32 *)arg;
+		break;
+	}
 }
 
+static void
+do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size)
+{
+	struct tbuf args = {
+		.len = args_data_size,
+		.size = args_data_size,
+		.data = args_data,
+		.pool = NULL
+	};
+	struct tbuf *new_field = NULL;
+	void *offset_field, *length_field, *list_field;
+	u32 offset_size, length_size, list_size;
+	i32 offset, length;
+	u32 noffset, nlength;	/* normalized values */
+
+	new_field = tbuf_alloc(fiber->pool);
+
+	offset_field = read_field(&args);
+	length_field = read_field(&args);
+	list_field = read_field(&args);
+	if (args.len != 0)
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "do_field_splice: bad args");
+
+	offset_size = load_varint32(&offset_field);
+	if (offset_size == 0)
+		noffset = 0;
+	else if (offset_size == sizeof(offset)) {
+		offset = pick_u32(offset_field, &offset_field);
+		if (offset < 0) {
+			if (field->len < -offset)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS,
+					  "do_field_splice: noffset is negative");
+			noffset = offset + field->len;
+		} else
+			noffset = offset;
+	} else
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "do_field_splice: bad size of offset field");
+	if (noffset > field->len)
+		noffset = field->len;
+
+	length_size = load_varint32(&length_field);
+	if (length_size == 0)
+		nlength = field->len - noffset;
+	else if (length_size == sizeof(length)) {
+		if (offset_size == 0)
+			box_raise(ERR_CODE_ILLEGAL_PARAMS,
+				  "do_field_splice: offset field is empty but length is not");
+
+		length = pick_u32(length_field, &length_field);
+		if (length < 0) {
+			if ((field->len - noffset) < -length)
+				nlength = 0;
+			else
+				nlength = length + field->len - noffset;
+		} else
+			nlength = length;
+	} else
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "do_field_splice: bad size of length field");
+	if (nlength > (field->len - noffset))
+		nlength = field->len - noffset;
+
+	list_size = load_varint32(&list_field);
+	if (list_size > 0 && length_size == 0)
+		box_raise(ERR_CODE_ILLEGAL_PARAMS,
+			  "do_field_splice: length field is empty but list is not");
+	if (list_size > (UINT32_MAX - (field->len - nlength)))
+		box_raise(ERR_CODE_ILLEGAL_PARAMS, "do_field_splice: list_size is too long");
+
+	say_debug("do_field_splice: noffset = %i, nlength = %i, list_size = %u",
+		  noffset, nlength, list_size);
+
+	new_field->len = 0;
+	tbuf_append(new_field, field->data, noffset);
+	tbuf_append(new_field, list_field, list_size);
+	tbuf_append(new_field, field->data + noffset + nlength, field->len - (noffset + nlength));
+
+	*field = *new_field;
+}
 
 static int __noinline__
 prepare_update_fields(struct box_txn *txn, struct tbuf *data, bool old_format)
 {
-        struct tbuf **fields;
-        void *field;
-        int i;
+	struct tbuf **fields;
+	void *field;
+	int i;
 	void *key;
 	u32 op_cnt;
 
@@ -461,37 +794,34 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data, bool old_format)
 	key = read_field(data);
 	op_cnt = read_u32(data);
 
-	if(op_cnt > 128)
+	if (op_cnt > 128)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "too many ops");
-	if(op_cnt == 0)
+	if (op_cnt == 0)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "no ops");
-        if(key == NULL)
+	if (key == NULL)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "invalid key");
 
-        txn->old_tuple = index_find(txn->index, 1, key);
-        if (txn->old_tuple == NULL) {
-                if (!txn->in_recover) {
-                        int tuples_affected = 0;
-                        add_iov_dup(&tuples_affected, sizeof(uint32_t));
-                }
-                return ERR_CODE_OK;
-        }
+	txn->old_tuple = txn->index->find(txn->index, key);
+	if (txn->old_tuple == NULL) {
+		if (!txn->in_recover) {
+			int tuples_affected = 0;
+			add_iov_dup(&tuples_affected, sizeof(uint32_t));
+		}
+		return ERR_CODE_OK;
+	}
 
 	lock_tuple(txn, txn->old_tuple);
 
 	fields = palloc(fiber->pool, (txn->old_tuple->cardinality + 1) * sizeof(struct tbuf *));
 	memset(fields, 0, (txn->old_tuple->cardinality + 1) * sizeof(struct tbuf *));
 
-        for (i = 0, field = (uint8_t *)txn->old_tuple->data;
-	     i < txn->old_tuple->cardinality;
-	     i++)
-	{
+	for (i = 0, field = (uint8_t *)txn->old_tuple->data; i < txn->old_tuple->cardinality; i++) {
 		fields[i] = tbuf_alloc(fiber->pool);
 
 		u32 field_size = load_varint32(&field);
 		tbuf_append(fields[i], field, field_size);
 		field += field_size;
-        }
+	}
 
 	if (old_format) {
 		while (op_cnt-- > 0) {
@@ -507,28 +837,26 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data, bool old_format)
 			xor = read_u32(data);
 			add = read_u32(data);
 
-			foreach_index(txn->n, index) {
-				if(index->key_position == field_no)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "update of indexed field");
-			}
-
-			if(field_no >= txn->old_tuple->cardinality)
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "update of field beyond tuple cardinality");
+			if (field_no >= txn->old_tuple->cardinality)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS,
+					  "update of field beyond tuple cardinality");
 
 			struct tbuf *sptr_field = fields[field_no];
 
 			new_field_size = load_varint32(&new_field);
 			if (new_field_size) {
-				if(and != 0 || xor != 0 || add != 0)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "and or xor or add != 0");
+				if (and != 0 || xor != 0 || add != 0)
+					box_raise(ERR_CODE_ILLEGAL_PARAMS,
+						  "and or xor or add != 0");
 				tbuf_ensure(sptr_field, new_field_size);
 				sptr_field->len = new_field_size;
 				memcpy(sptr_field->data, new_field, new_field_size);
 			} else {
 				uint32_t *num;
-				if(sptr_field->len != 4)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "num op on field with length != 4");
-				num = (uint32_t *)sptr_field->data; /* FIXME: align && endianes */
+				if (sptr_field->len != 4)
+					box_raise(ERR_CODE_ILLEGAL_PARAMS,
+						  "num op on field with length != 4");
+				num = (uint32_t *)sptr_field->data;	/* FIXME: align && endianes */
 
 				*num &= and;
 				*num ^= xor;
@@ -543,19 +871,15 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data, bool old_format)
 
 			field_no = read_u32(data);
 
-			foreach_index(txn->n, index) {
-				if(index->key_position == field_no)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "update of indexed field");
-			}
-
-			if(field_no >= txn->old_tuple->cardinality)
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "update of field beyond tuple cardinality");
+			if (field_no >= txn->old_tuple->cardinality)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS,
+					  "update of field beyond tuple cardinality");
 
 			struct tbuf *sptr_field = fields[field_no];
 
 			op = read_u8(data);
-			if (op > 4)
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "op is not 0, 1, 2, 3 or 4");
+			if (op > 5)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS, "op is not 0, 1, 2, 3, 4 or 5");
 			arg = read_field(data);
 			arg_size = load_varint32(&arg);
 
@@ -564,158 +888,179 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data, bool old_format)
 				sptr_field->len = arg_size;
 				memcpy(sptr_field->data, arg, arg_size);
 			} else {
-				if (sptr_field->len != 4)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "num op on field with length != 4");
-				if (arg_size != 4)
-					box_raise(ERR_CODE_ILLEGAL_PARAMS, "num op with arg not u32");
-
-				switch(op) {
+				switch (op) {
 				case 1:
-					*(i32 *)sptr_field->data += *(i32 *)arg;
-					break;
 				case 2:
-					*(u32 *)sptr_field->data &= *(u32 *)arg;
-					break;
 				case 3:
-					*(u32 *)sptr_field->data ^= *(u32 *)arg;
-					break;
 				case 4:
-					*(u32 *)sptr_field->data |= *(u32 *)arg;
+					do_field_arith(op, sptr_field, arg, arg_size);
+					break;
+				case 5:
+					do_field_splice(sptr_field, arg, arg_size);
 					break;
 				}
 			}
 		}
 	}
 
-        if(data->len != 0)
+	if (data->len != 0)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "can't unpack request");
 
-        size_t bsize = 0;
-        for (int i = 0; i < txn->old_tuple->cardinality; i++)
+	size_t bsize = 0;
+	for (int i = 0; i < txn->old_tuple->cardinality; i++)
 		bsize += fields[i]->len + varint32_sizeof(fields[i]->len);
-        txn->tuple = tuple_alloc(bsize);
+	txn->tuple = tuple_alloc(bsize);
 	tuple_txn_ref(txn, txn->tuple);
-        txn->tuple->bsize = bsize;
-        txn->tuple->cardinality = txn->old_tuple->cardinality;
+	txn->tuple->cardinality = txn->old_tuple->cardinality;
 
-        uint8_t *p = txn->tuple->data;
-        for (int i = 0; i < txn->old_tuple->cardinality; i++) {
+	uint8_t *p = txn->tuple->data;
+	for (int i = 0; i < txn->old_tuple->cardinality; i++) {
 		p = save_varint32(p, fields[i]->len);
-                memcpy(p, fields[i]->data, fields[i]->len);
-                p += fields[i]->len;
-        }
+		memcpy(p, fields[i]->data, fields[i]->len);
+		p += fields[i]->len;
+	}
 
-        run_hooks(txn, before_commit_update_hook);
+	validate_indeces(txn);
+	run_hooks(txn, before_commit_update_hook);
 
-	if(data->len != 0)
+	if (data->len != 0)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "can't unpack request");
-        return -1;
+	return -1;
 }
 
 static void
 tuple_add_iov(struct box_txn *txn, struct box_tuple *tuple)
 {
-        tuple_txn_ref(txn, tuple);
+	tuple_txn_ref(txn, tuple);
 
 	if (txn->old_format) {
-                add_iov_dup(&tuple->bsize, sizeof(uint16_t));
-                add_iov_dup(&tuple->cardinality, sizeof(uint8_t));
-                add_iov(tuple->data, tuple->bsize);
-        } else
-                add_iov(&tuple->bsize,
+		add_iov_dup(&tuple->bsize, sizeof(uint16_t));
+		add_iov_dup(&tuple->cardinality, sizeof(uint8_t));
+		add_iov(tuple->data, tuple->bsize);
+	} else
+		add_iov(&tuple->bsize,
 			tuple->bsize +
 			field_sizeof(struct box_tuple, bsize) +
 			field_sizeof(struct box_tuple, cardinality));
 }
 
-
 static int __noinline__
 process_select(struct box_txn *txn, u32 limit, u32 offset, struct tbuf *data, bool old_format)
 {
-        struct box_tuple *tuple;
-        uint32_t *found;
+	struct box_tuple *tuple;
+	uint32_t *found;
 	u32 count = read_u32(data);
 
 	found = palloc(fiber->pool, sizeof(*found));
 	add_iov(found, sizeof(*found));
 	*found = 0;
 
-        for (u32 i = 0; i < count; i++) {
-		if (!old_format) {
+	if (txn->index->type == TREE) {
+		for (u32 i = 0; i < count; i++) {
 			u32 key_len = read_u32(data);
-			if (key_len != 1)
-				box_raise(ERR_CODE_ILLEGAL_PARAMS, "key must be single valued");
-		}
-		void *key = read_field(data);
-		tuple = index_find(txn->index, 1, key);
-                if (tuple == NULL || tuple->flags & GHOST)
-                        continue;
+			void *key = read_field(data);
 
-		if (offset > 0) {
-			offset--;
-			continue;
+			/* advance remaining fields of a key */
+			for (int i = 1; i < key_len; i++)
+				read_field(data);
+
+			struct tree_index_member *pattern =
+				alloc_search_pattern(txn->index, key_len, key);
+			txn->index->iterator_init(txn->index, pattern);
+
+			while ((tuple = txn->index->iterator_next(txn->index, pattern)) != NULL) {
+				if (tuple->flags & GHOST)
+					continue;
+
+				if (offset > 0) {
+					offset--;
+					continue;
+				}
+
+				(*found)++;
+				tuple_add_iov(txn, tuple);
+
+				if (--limit == 0)
+					break;
+			}
+			if (limit == 0)
+				break;
 		}
+	} else {
+		for (u32 i = 0; i < count; i++) {
+			if (!old_format) {
+				u32 key_len = read_u32(data);
+				if (key_len != 1)
+					box_raise(ERR_CODE_ILLEGAL_PARAMS,
+						  "key must be single valued");
+			}
+			void *key = read_field(data);
+			tuple = txn->index->find(txn->index, key);
+			if (tuple == NULL || tuple->flags & GHOST)
+				continue;
 
-                (*found)++;
-                tuple_add_iov(txn, tuple);
+			if (offset > 0) {
+				offset--;
+				continue;
+			}
 
-		if (--limit == 0)
-			break;
-        }
+			(*found)++;
+			tuple_add_iov(txn, tuple);
 
-	if(data->len != 0)
+			if (--limit == 0)
+				break;
+		}
+	}
+
+	if (data->len != 0)
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "can't unpack request");
 
-        return ERR_CODE_OK;
+	return ERR_CODE_OK;
 }
 
-
 static int __noinline__
 prepare_delete(struct box_txn *txn, void *key)
 {
-        txn->old_tuple = index_find(txn->index, 1, key);
+	txn->old_tuple = txn->index->find(txn->index, key);
 
-        if(txn->old_tuple == NULL) {
-                if (!txn->in_recover) {
-                        u32 tuples_affected = 0;
-                        add_iov_dup(&tuples_affected, sizeof(tuples_affected));
-                }
-                return ERR_CODE_OK;
-        } else {
+	if (txn->old_tuple == NULL) {
+		if (!txn->in_recover) {
+			u32 tuples_affected = 0;
+			add_iov_dup(&tuples_affected, sizeof(tuples_affected));
+		}
+		return ERR_CODE_OK;
+	} else {
 		tuple_txn_ref(txn, txn->old_tuple);
 	}
 
-        lock_tuple(txn, txn->old_tuple);
-        return -1;
+	lock_tuple(txn, txn->old_tuple);
+	return -1;
 }
 
-
 static void
 commit_delete(struct box_txn *txn)
 {
-        if (!(txn->flags & BOX_QUIET) && !txn->in_recover) {
-                int tuples_affected = 1;
-                add_iov_dup(&tuples_affected, sizeof(tuples_affected));
-        }
+	if (!(txn->flags & BOX_QUIET) && !txn->in_recover) {
+		int tuples_affected = 1;
+		add_iov_dup(&tuples_affected, sizeof(tuples_affected));
+	}
 
 	foreach_index(txn->n, index)
-		index_remove(index, txn->old_tuple);
-        tuple_ref(txn->old_tuple, -1);
+		index->remove(index, txn->old_tuple);
+	tuple_ref(txn->old_tuple, -1);
 
-        return;
+	return;
 }
 
 struct box_txn *
 txn_alloc(u32 flags)
 {
-	struct box_txn *txn = palloc(fiber->pool, sizeof(*txn));
-	memset(txn, 0, sizeof(*txn));
+	struct box_txn *txn = p0alloc(fiber->pool, sizeof(*txn));
 	txn->ref_tuples = tbuf_alloc(fiber->pool);
-	txn->flags |= flags; /* note - select will overwrite this flags */
+	txn->flags |= flags;	/* note - select will overwrite this flags */
 	return txn;
 }
 
-
 void
 txn_cleanup(struct box_txn *txn)
 {
@@ -746,48 +1091,47 @@ txn_cleanup(struct box_txn *txn)
 static void
 txn_commit(struct box_txn *txn)
 {
-        if (txn->op == 0)
-                return;
+	if (txn->op == 0)
+		return;
 
-        say_debug("box_commit(op:%s)", messages_strs[txn->op]);
+	say_debug("box_commit(op:%s)", messages_strs[txn->op]);
 
 	unlock_tuples(txn);
 
-        if (txn->op == DELETE)
-                commit_delete(txn);
-        else
-                commit_replace(txn);
+	if (txn->op == DELETE)
+		commit_delete(txn);
+	else
+		commit_replace(txn);
 }
 
 static void
 txn_abort(struct box_txn *txn)
 {
-        if (txn->op == 0)
+	if (txn->op == 0)
 		return;
-        say_debug("box_rollback(op:%s)", messages_strs[txn->op]);
+	say_debug("box_rollback(op:%s)", messages_strs[txn->op]);
 
 	unlock_tuples(txn);
 
 	if (txn->op == DELETE)
-                return;
+		return;
 
-        if (txn->op == INSERT)
-                rollback_replace(txn);
+	if (txn->op == INSERT)
+		rollback_replace(txn);
 }
 
 static bool
 op_is_select(u32 op)
 {
-	return  op == SELECT || op == SELECT_LIMIT;
+	return op == SELECT || op == SELECT_LIMIT;
 }
 
 u32
-box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
+box_dispach(struct box_txn *txn, enum box_mode mode, u16 op, struct tbuf *data)
 {
-        u32 cardinality;
-        int ret_code;
-	void *data__data = data->data;
-	u32 data__len = data->len;
+	u32 cardinality;
+	int ret_code;
+	struct tbuf req = { .data = data->data, .len = data->len };
 	int saved_iov_cnt = fiber->iov_cnt;
 	ev_tstamp start = ev_now(), stop;
 
@@ -797,10 +1141,10 @@ box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
 	say_debug("box_dispach(%i)", op);
 
 	if (!txn->in_recover) {
-                if (!op_is_select(op) && (mode == RO || !box_updates_allowed)) {
-                        say_error("can't process %i command on RO port", op);
-                        return ERR_CODE_NONMASTER;
-                }
+		if (!op_is_select(op) && (mode == RO || !box_updates_allowed)) {
+			say_error("can't process %i command on RO port", op);
+			return ERR_CODE_NONMASTER;
+		}
 
 		fiber_register_cleanup((void *)txn_cleanup, txn);
 	}
@@ -809,7 +1153,7 @@ box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
 	txn->n = read_u32(data);
 	txn->index = &namespace[txn->n].index[0];
 
-	if(!namespace[txn->n].enabled) {
+	if (!namespace[txn->n].enabled) {
 		say_warn("namespace %i is not enabled", txn->n);
 		box_raise(ERR_CODE_ILLEGAL_PARAMS, "namespace is not enabled");
 	}
@@ -819,17 +1163,17 @@ box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
 	void *key;
 	u32 key_len;
 
-        switch (op) {
-        case INSERT:
-                txn->flags = read_u32(data);
+	switch (op) {
+	case INSERT:
+		txn->flags = read_u32(data);
 		cardinality = read_u32(data);
-		if (namespace[txn->n].cardinality > 0 && namespace[txn->n].cardinality != cardinality)
-			box_raise(ERR_CODE_ILLEGAL_PARAMS, "tuple cardinality must match namespace cardinality");
-		if (!txn->in_recover && txn->n == 3 && cardinality != 14)
-			box_raise(ERR_CODE_ILLEGAL_PARAMS, "tuple cardinality must match namespace cardinality");
-                ret_code = prepare_replace(txn, cardinality, data);
-		stat_collect(messages_strs[op], 1);
-                break;
+		if (namespace[txn->n].cardinality > 0
+		    && namespace[txn->n].cardinality != cardinality)
+			box_raise(ERR_CODE_ILLEGAL_PARAMS,
+				  "tuple cardinality must match namespace cardinality");
+		ret_code = prepare_replace(txn, cardinality, data);
+		stat_collect(stat_base, op, 1);
+		break;
 
 	case DELETE:
 		key_len = read_u32(data);
@@ -837,49 +1181,52 @@ box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
 			box_raise(ERR_CODE_ILLEGAL_PARAMS, "key must be single valued");
 
 		key = read_field(data);
-		if(data->len != 0)
+		if (data->len != 0)
 			box_raise(ERR_CODE_ILLEGAL_PARAMS, "can't unpack request");
 
 		ret_code = prepare_delete(txn, key);
-		stat_collect(messages_strs[op], 1);
-                break;
-
-	case SELECT: {
-		u32 i = read_u32(data);
-		u32 offset = read_u32(data);
-		u32 limit = read_u32(data);
-
-		if (i > MAX_IDX)
-			box_raise(ERR_CODE_ILLEGAL_PARAMS, "index too big");
-		txn->index = &namespace[txn->n].index[i];
-		if (txn->index->key_position < 0)
-			box_raise(ERR_CODE_ILLEGAL_PARAMS, "index is invalid");
-
-		stat_collect(messages_strs[op], 1);
-                return process_select(txn, limit, offset, data, false);
-	}
+		stat_collect(stat_base, op, 1);
+		break;
+
+	case SELECT:{
+			u32 i = read_u32(data);
+			u32 offset = read_u32(data);
+			u32 limit = read_u32(data);
+
+			if (i > MAX_IDX)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS, "index too big");
+			txn->index = &namespace[txn->n].index[i];
+			if (txn->index->key_cardinality == 0)
+				box_raise(ERR_CODE_ILLEGAL_PARAMS, "index is invalid");
 
-        case UPDATE_FIELDS:
+			stat_collect(stat_base, op, 1);
+			return process_select(txn, limit, offset, data, false);
+		}
+
+	case UPDATE_FIELDS:
 		txn->flags = read_u32(data);
-		stat_collect(messages_strs[op], 1);
-                ret_code = prepare_update_fields(txn, data, false);
-                break;
+		stat_collect(stat_base, op, 1);
+		ret_code = prepare_update_fields(txn, data, false);
+		break;
 
 	default:
-                say_error("silverbox_dispach: unsupported command = %"PRIi32"", op);
-                return ERR_CODE_ILLEGAL_PARAMS;
-        }
+		say_error("silverbox_dispach: unsupported command = %" PRIi32 "", op);
+		return ERR_CODE_ILLEGAL_PARAMS;
+	}
 
 	if (ret_code == -1) {
-                if (!txn->in_recover) {
+		if (!txn->in_recover) {
+			fiber_peer_name(fiber); /* fill the cookie */
 			struct tbuf *t = tbuf_alloc(fiber->pool);
 			tbuf_append(t, &op, sizeof(op));
-			tbuf_append(t, data__data, data__len);
+			tbuf_append(t, req.data, req.len);
 
-			if (!wal_write_v04(recovery_state, op, data__data, data__len)) {
+			i64 lsn = next_lsn(recovery_state, 0);
+			if (!wal_write(recovery_state, wal_tag, fiber->cookie, lsn, t)) {
 				ret_code = ERR_CODE_UNKNOWN_ERROR;
 				goto abort;
 			}
+			confirm_lsn(recovery_state, lsn);
 		}
 		txn_commit(txn);
 
@@ -887,92 +1234,108 @@ box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data)
 		if (stop - start > cfg.too_long_threshold)
 			say_warn("too long %s: %.3f sec", messages_strs[op], stop - start);
 		return 0;
-        }
-
+	}
 
 	return ret_code;
 
-abort:
+      abort:
 	fiber->iov_cnt = saved_iov_cnt;
 	txn_abort(txn);
 	return ret_code;
 }
 
-
 static int
 box_xlog_sprint(struct tbuf *buf, const struct tbuf *t)
 {
-	struct row_v04 *row = row_v04(t);
+	struct row_v11 *row = row_v11(t);
 
 	struct tbuf *b = palloc(fiber->pool, sizeof(*b));
 	b->data = row->data;
 	b->len = row->len;
-	u32 op = row->type;
+	u16 tag, op;
+	u64 cookie;
+	struct sockaddr_in *peer = (void *)&cookie;
+
+	u32 n, key_len;
+	void *key;
+	u32 cardinality, field_no;
+	u32 flags;
+	u32 op_cnt;
 
-        u32 n, key_len;
-        void *key;
-        u32 cardinality, field_no;
-        u32 flags;
-        u32 op_cnt;
+	tbuf_printf(buf, "lsn:%" PRIi64 " ", row->lsn);
 
-	tbuf_printf(buf, "lsn:%"PRIi64" ", row->lsn);
+	say_debug("b->len:%" PRIu32, b->len);
 
-	say_debug("b->len:%"PRIu32, b->len);
-        n = read_u32(b);
+	tag = read_u16(b);
+	cookie = read_u64(b);
+	op = read_u16(b);
+	n = read_u32(b);
 
-        tbuf_printf(buf, "%s ", messages_strs[op]);
-        tbuf_printf(buf, "n:%i ", n);
+	tbuf_printf(buf, "tm:%.3f t:%"PRIu16 " %s:%d %s n:%i",
+		    row->tm, tag, inet_ntoa(peer->sin_addr), ntohs(peer->sin_port),
+		    messages_strs[op], n);
 
-        switch (op) {
-        case INSERT:
-                flags = read_u32(b);
+	switch (op) {
+	case INSERT:
+		flags = read_u32(b);
 		cardinality = read_u32(b);
-		if (b->len != valid_tuple(b, cardinality)) abort();
-                tuple_print(buf, cardinality, b->data);
-                break;
+		if (b->len != valid_tuple(b, cardinality))
+			abort();
+		tuple_print(buf, cardinality, b->data);
+		break;
 
 	case DELETE:
 		key_len = read_u32(b);
 		key = read_field(b);
-		if (b->len != 0) abort();
+		if (b->len != 0)
+			abort();
 		tuple_print(buf, key_len, key);
-                break;
+		break;
 
-        case UPDATE_FIELDS:
+	case UPDATE_FIELDS:
 		flags = read_u32(b);
 		key_len = read_u32(b);
-                key = read_field(b);
+		key = read_field(b);
 		op_cnt = read_u32(b);
 
 		tbuf_printf(buf, "flags:%08X ", flags);
-                tuple_print(buf, key_len, key);
+		tuple_print(buf, key_len, key);
 
 		while (op_cnt-- > 0) {
 			field_no = read_u32(b);
 			u8 op = read_u8(b);
 			void *arg = read_field(b);
 
-                        tbuf_printf(buf, " [field_no:%i op:", field_no);
-			switch(op) {
-			case 0: tbuf_printf(buf, "set "); break;
-			case 1: tbuf_printf(buf, "add "); break;
-			case 2: tbuf_printf(buf, "and "); break;
-			case 3: tbuf_printf(buf, "xor "); break;
-			case 4: tbuf_printf(buf, "or "); break;
+			tbuf_printf(buf, " [field_no:%i op:", field_no);
+			switch (op) {
+			case 0:
+				tbuf_printf(buf, "set ");
+				break;
+			case 1:
+				tbuf_printf(buf, "add ");
+				break;
+			case 2:
+				tbuf_printf(buf, "and ");
+				break;
+			case 3:
+				tbuf_printf(buf, "xor ");
+				break;
+			case 4:
+				tbuf_printf(buf, "or ");
+				break;
 			}
-                        tuple_print(buf, 1, arg);
+			tuple_print(buf, 1, arg);
 			tbuf_printf(buf, "] ");
 		}
 		break;
-        default:
-                tbuf_printf(buf, "unknown wal op %" PRIi32, op);
-        }
-        return 0;
+	default:
+		tbuf_printf(buf, "unknown wal op %" PRIi32, op);
+	}
+	return 0;
 }
 
-
 struct tbuf *
-box_snap_reader(FILE * f, struct palloc_pool *pool)
+box_snap_reader(FILE *f, struct palloc_pool *pool)
 {
 	struct tbuf *row = tbuf_alloc(pool);
 	const int header_size = sizeof(*box_snap_row(row));
@@ -985,76 +1348,102 @@ box_snap_reader(FILE * f, struct palloc_pool *pool)
 	if (fread(box_snap_row(row)->data, box_snap_row(row)->data_size, 1, f) != 1)
 		return NULL;
 
-	return row;
+	return convert_to_v11(row, snap_tag, default_cookie, 0);
 }
 
-
 static int
-snap_apply(struct recovery_state *r __unused__, const struct tbuf *t)
+snap_apply(struct box_txn *txn, struct tbuf *t)
 {
-	struct box_snap_row *row = box_snap_row(t);
-        struct box_txn *txn = txn_alloc(0);
-	txn->in_recover = true;
-        txn->n = row->namespace;
+	struct box_snap_row *row;
 
-	if (txn->n == 25)
-		return 0;
+	read_u64(t); /* drop cookie */
+
+	row = box_snap_row(t);
+	txn->n = row->namespace;
 
 	if (!namespace[txn->n].enabled) {
 		say_error("namespace %i is not configured", txn->n);
 		return -1;
 	}
 	txn->index = &namespace[txn->n].index[0];
-	assert(txn->index->key_position >= 0);
+	assert(txn->index->key_cardinality > 0);
 
 	struct tbuf *b = palloc(fiber->pool, sizeof(*b));
 	b->data = row->data;
 	b->len = row->data_size;
 
-        if (prepare_replace(txn, row->tuple_size, b) != -1) {
-                say_error("unable prepare");
-                return -1;
-        }
+	if (prepare_replace(txn, row->tuple_size, b) != -1) {
+		say_error("unable prepare");
+		return -1;
+	}
 
-        txn->op = INSERT;
+	txn->op = INSERT;
 	txn_commit(txn);
+	return 0;
+}
+
+static int
+wal_apply(struct box_txn *txn, struct tbuf *t)
+{
+	read_u64(t); /* drop cookie */
+
+	u16 type = read_u16(t);
+	if (box_dispach(txn, RW, type, t) != 0)
+		return -1;
+
 	txn_cleanup(txn);
 	return 0;
 }
 
 static int
-xlog_apply(struct recovery_state *r __unused__, const struct tbuf *t)
+recover_row(struct recovery_state *r __unused__, struct tbuf *t)
 {
-	struct row_v04 *row = row_v04(t);
-        struct box_txn *txn = txn_alloc(0);
+	struct box_txn *txn = txn_alloc(0);
+	int result = -1;
 	txn->in_recover = true;
 
-	assert(row->lsn > confirmed_lsn(r));
-
-	struct tbuf *b = palloc(fiber->pool, sizeof(*b));
-	b->data = row->data;
-	b->len = row->len;
+	/* drop wal header */
+	if (tbuf_peek(t, sizeof(struct row_v11)) == NULL)
+		return -1;
 
-	if (box_dispach(txn, RW, row->type, b) != 0)
+	u16 tag = read_u16(t);
+	if (tag == wal_tag) {
+		result = wal_apply(txn, t);
+	} else if (tag == snap_tag) {
+		result = snap_apply(txn, t);
+	} else {
+		say_error("unknown row tag: %i", (int)tag);
 		return -1;
+	}
 
 	txn_cleanup(txn);
-	return 0;
+	return result;
 }
 
+
 static int
-snap_print(struct recovery_state *r __unused__, const struct tbuf *t)
+snap_print(struct recovery_state *r __unused__, struct tbuf *t)
 {
 	struct tbuf *out = tbuf_alloc(t->pool);
-	struct box_snap_row *row = box_snap_row(t);
+	struct box_snap_row *row;
+	struct row_v11 *raw_row = row_v11(t);
+
+	struct tbuf *b = palloc(fiber->pool, sizeof(*b));
+	b->data = raw_row->data;
+	b->len = raw_row->len;
+
+	(void)read_u16(b); /* drop tag */
+	(void)read_u64(b); /* drop cookie */
 
-	tuple_print(out, row->tuple_size , row->data);
+	row = box_snap_row(b);
+
+	tuple_print(out, row->tuple_size, row->data);
 	printf("n:%i %*s\n", row->namespace, (int)out->len, (char *)out->data);
 	return 0;
 }
 
 static int
-xlog_print(struct recovery_state *r __unused__, const struct tbuf *t)
+xlog_print(struct recovery_state *r __unused__, struct tbuf *t)
 {
 	struct tbuf *out = tbuf_alloc(t->pool);
 	int res = box_xlog_sprint(out, t);
@@ -1063,7 +1452,6 @@ xlog_print(struct recovery_state *r __unused__, const struct tbuf *t)
 	return res;
 }
 
-
 static void
 custom_init(void)
 {
@@ -1084,45 +1472,145 @@ custom_init(void)
 		namespace[i].cardinality = cfg.namespace[i]->cardinality;
 		int estimated_rows = cfg.namespace[i]->estimated_rows;
 
+		if (cfg.namespace[i]->index == NULL)
+			panic("(namespace = %" PRIu32 ") at least one index must be defined", i);
+
 		for (int j = 0; j < nelem(namespace[i].index); j++) {
+			struct index *index = &namespace[i].index[j];
+			u32 max_key_fieldno = 0;
+
 			if (cfg.namespace[i]->index[j] == NULL)
 				break;
 
-			namespace[i].index[j].key_position = cfg.namespace[i]->index[j]->key_position;
-			if (namespace[i].index[j].key_position == -1)
-				continue;
+			if (cfg.namespace[i]->index[j]->key_field == NULL)
+				panic("(namespace = %" PRIu32 " index = %" PRIu32 ") "
+				      "at least one field must be defined", i, j);
 
+			for (int k = 0; cfg.namespace[i]->index[j]->key_field[k] != NULL; k++) {
+				if (cfg.namespace[i]->index[j]->key_field[k]->fieldno == -1)
+					break;
 
-			if (strcmp(cfg.namespace[i]->index[j]->type, "NUM") == 0) {
-				namespace[i].index[j].find = index_find_hash_num;
-				namespace[i].index[j].remove = index_remove_hash_num;
-				namespace[i].index[j].replace = index_replace_hash_num;
-				namespace[i].index[j].namespace = &namespace[i];
-				namespace[i].index[j].type = INDEX_NUM;
-				namespace[i].index[j].map.int_map = kh_init(int2ptr_map, NULL);
-				if (estimated_rows > 0)
-					kh_resize(int2ptr_map, namespace[i].index[j].map.int_map, estimated_rows);
-			} else if (strcmp(cfg.namespace[i]->index[j]->type, "STR") == 0) {
-				namespace[i].index[j].find = index_find_hash_str;
-				namespace[i].index[j].remove = index_remove_hash_str;
-				namespace[i].index[j].replace = index_replace_hash_str;
-				namespace[i].index[j].namespace = &namespace[i];
-				namespace[i].index[j].type = INDEX_STR;
-				namespace[i].index[j].map.str_map = kh_init(lstr2ptr_map, NULL);
-				if (estimated_rows > 0)
-					kh_resize(lstr2ptr_map, namespace[i].index[j].map.str_map, estimated_rows);
-			} else {
-				say_warn("unknown index type `%s'", cfg.namespace[i]->index[j]->type);
+				max_key_fieldno =
+					MAX(max_key_fieldno,
+					    cfg.namespace[i]->index[j]->key_field[k]->fieldno);
+
+				++index->key_cardinality;
+			}
+
+			if (index->key_cardinality == 0)
 				continue;
+
+			index->key_field = salloc(sizeof(index->key_field[0]) *
+						  index->key_cardinality);
+			if (index->key_field == NULL)
+				panic("can't allocate key_field for index");
+
+			index->field_cmp_order_cnt = max_key_fieldno + 1;
+			index->field_cmp_order =
+				salloc(sizeof(index->field_cmp_order[0]) *
+				       index->field_cmp_order_cnt);
+			if (index->field_cmp_order == NULL)
+				panic("can't allocate field_cmp_order for index");
+			memset(index->field_cmp_order, -1,
+			       sizeof(index->field_cmp_order[0]) * index->field_cmp_order_cnt);
+
+			for (int k = 0; cfg.namespace[i]->index[j]->key_field[k] != NULL; k++) {
+				if (cfg.namespace[i]->index[j]->key_field[k]->fieldno == -1)
+					break;
+
+				index->key_field[k].fieldno =
+					cfg.namespace[i]->index[j]->key_field[k]->fieldno;
+				if (strcmp(cfg.namespace[i]->index[j]->key_field[k]->type, "NUM") ==
+				    0)
+					index->key_field[k].type = NUM;
+				else if (strcmp(cfg.namespace[i]->index[j]->key_field[k]->type,
+						"STR") == 0)
+					index->key_field[k].type = STR;
+				else
+					panic("(namespace = %" PRIu32 " index = %" PRIu32 ") "
+					      "unknown field data type: `%s'",
+					      i, j, cfg.namespace[i]->index[j]->key_field[k]->type);
+
+				index->field_cmp_order[index->key_field[k].fieldno] = k;
 			}
+
+			index->search_pattern = palloc(eter_pool, SIZEOF_TREE_INDEX_MEMBER(index));
+
+			if (cfg.namespace[i]->index[j]->unique == 0)
+				index->unique = false;
+			else if (cfg.namespace[i]->index[j]->unique == 1)
+				index->unique = true;
+			else
+				panic("(namespace = %" PRIu32 " index = %" PRIu32 ") "
+				      "unique property is undefined", i, j);
+
+			if (strcmp(cfg.namespace[i]->index[j]->type, "HASH") == 0) {
+				if (index->key_cardinality != 1)
+					panic("(namespace = %" PRIu32 " index = %" PRIu32 ") "
+					      "hash index must have single-filed key", i, j);
+
+				index->enabled = true;
+				index->type = HASH;
+
+				if (index->unique == false)
+					panic("(namespace = %" PRIu32 " index = %" PRIu32 ") "
+					      "hash index must be unique", i, j);
+
+				if (index->key_field->type == NUM) {
+					index->find = index_find_hash_num;
+					index->find_by_tuple = index_find_hash_by_tuple;
+					index->remove = index_remove_hash_num;
+					index->replace = index_replace_hash_num;
+					index->namespace = &namespace[i];
+					index->idx.int_hash = kh_init(int2ptr_map, NULL);
+
+					if (estimated_rows > 0)
+						kh_resize(int2ptr_map, index->idx.int_hash,
+							  estimated_rows);
+				} else {
+					index->find = index_find_hash_str;
+					index->find_by_tuple = index_find_hash_by_tuple;
+					index->remove = index_remove_hash_str;
+					index->replace = index_replace_hash_str;
+					index->namespace = &namespace[i];
+					index->idx.str_hash = kh_init(lstr2ptr_map, NULL);
+
+					if (estimated_rows > 0)
+						kh_resize(lstr2ptr_map, index->idx.str_hash,
+							  estimated_rows);
+				}
+			} else if (strcmp(cfg.namespace[i]->index[j]->type, "TREE") == 0) {
+				index->enabled = false;
+				index->type = TREE;
+
+				index->find = index_find_tree;
+				index->find_by_tuple = index_find_tree_by_tuple;
+				index->remove = index_remove_tree_str;
+				index->replace = index_replace_tree_str;
+				index->iterator_init = index_iterator_init_tree_str;
+				index->iterator_next = index_iterator_next_tree_str;
+				index->namespace = &namespace[i];
+
+				index->idx.tree = palloc(eter_pool, sizeof(*index->idx.tree));
+			} else
+				panic("namespace = %" PRIu32 " index = %" PRIu32 ") "
+				      "unknown index type `%s'",
+				      i, j, cfg.namespace[i]->index[j]->type);
 		}
+
+		if (namespace[i].index[0].key_cardinality == 0)
+			panic("(namespace = %" PRIu32 ") namespace must have at least one index",
+			      i);
+		if (namespace[i].index[0].type != HASH)
+			panic("(namespace = %" PRIu32 ") namespace first index must be HASH", i);
+
 		namespace[i].enabled = true;
 		namespace[i].n = i;
+
 		say_info("namespace %i successfully configured", i);
 	}
 }
 
-
 static u32
 box_process_ro(u32 op, struct tbuf *request_data)
 {
@@ -1147,8 +1635,7 @@ title(const char *fmt, ...)
 
 	if (cfg.memcached)
 		set_proc_title("memcached:%s%s pri:%i adm:%i",
-			       buf, custom_proc_title,
-			       cfg.primary_port, cfg.admin_port);
+			       buf, custom_proc_title, cfg.primary_port, cfg.admin_port);
 	else
 		set_proc_title("box:%s%s pri:%i sec:%i adm:%i",
 			       buf, custom_proc_title,
@@ -1163,8 +1650,10 @@ box_bound_to_primary(void *data __unused__)
 	if (cfg.remote_hot_standby) {
 		say_info("starting remote hot standby");
 		status = palloc(eter_pool, 64);
-		snprintf(status, 64, "hot_standby/%s:%i%s", cfg.wal_feeder_ipaddr, cfg.wal_feeder_port, custom_proc_title);
-		recover_follow_remote(recovery_state, cfg.wal_feeder_ipaddr, cfg.wal_feeder_port);
+		snprintf(status, 64, "hot_standby/%s:%i%s", cfg.wal_feeder_ipaddr,
+			 cfg.wal_feeder_port, custom_proc_title);
+		recover_follow_remote(recovery_state, cfg.wal_feeder_ipaddr, cfg.wal_feeder_port,
+				      default_remote_row_handler);
 
 		title("hot_standby/%s:%i", cfg.wal_feeder_ipaddr, cfg.wal_feeder_port);
 	} else {
@@ -1180,22 +1669,103 @@ memcached_bound_to_primary(void *data __unused__)
 {
 	box_bound_to_primary(NULL);
 
-	if (!cfg.remote_hot_standby) {
-		struct fiber *expire = fiber_create("memecached_expire", -1, -1, memcached_expire, NULL);
+	if (0 && !cfg.remote_hot_standby) {
+		struct fiber *expire =
+			fiber_create("memecached_expire", -1, -1, memcached_expire, NULL);
 		if (expire == NULL)
 			panic("can't stared expire fiber");
 		fiber_call(expire);
 	}
 }
 
+static void
+build_indexes(void)
+{
+	for (u32 n = 0; n < nelem(namespace); ++n) {
+		u32 n_tuples, estimated_tuples;
+		struct tree_index_member *members[nelem(namespace[n].index)] = { NULL };
+
+		if (namespace[n].enabled == false)
+			continue;
+
+		n_tuples = kh_size(namespace[n].index[0].idx.hash);
+		estimated_tuples = n_tuples * 1.2;
+
+		say_info("build_indexes: n = %" PRIu32 ": build arrays", n);
+
+		khiter_t k;
+		u32 i = 0;
+		assoc_foreach(namespace[n].index[0].idx.hash, k) {
+			for (u32 idx = 0;; idx++) {
+				struct index *index = &namespace[n].index[idx];
+				struct tree_index_member *member;
+				struct tree_index_member *m;
+
+				if (index->key_cardinality == 0)
+					break;
+
+				if (index->type != TREE)
+					continue;
+
+				member = members[idx];
+				if (member == NULL) {
+					member = malloc(estimated_tuples *
+							SIZEOF_TREE_INDEX_MEMBER(index));
+					if (member == NULL)
+						panic("build_indexes: malloc failed: %m");
+
+					members[idx] = member;
+				}
+
+				m = (struct tree_index_member *)
+					((char *)member + i * SIZEOF_TREE_INDEX_MEMBER(index));
+
+				tuple2tree_index_member(index,
+							kh_value(namespace[n].index[0].idx.hash,
+								 k),
+							&m);
+			}
+
+			++i;
+		}
+
+		say_info("build_indexes: n = %" PRIu32 ": build trees", n);
+
+		for (u32 idx = 0;; idx++) {
+			struct index *index = &namespace[n].index[idx];
+			struct tree_index_member *member = members[idx];
+
+			if (index->key_cardinality == 0)
+				break;
+
+			if (index->type != TREE)
+				continue;
+
+			assert(index->enabled == false);
+
+			say_info("build_indexes: n = %" PRIu32 " idx = %" PRIu32 ": build tree", n,
+				 idx);
+
+			/* if n_tuples == 0 then estimated_tuples = 0, member == NULL, tree is empty */
+			sptree_str_t_init(index->idx.tree,
+					  SIZEOF_TREE_INDEX_MEMBER(index),
+					  member, n_tuples, estimated_tuples,
+					  (void *)tree_index_member_compare, index);
+			index->enabled = true;
+
+			say_info("build_indexes: n = %" PRIu32 " idx = %" PRIu32 ": end", n, idx);
+		}
+	}
+}
 
 void
 mod_init(void)
 {
+	stat_base = stat_register(messages_strs, messages_MAX);
 	for (int i = 0; i < nelem(namespace); i++) {
 		namespace[i].enabled = false;
 		for (int j = 0; j < MAX_IDX; j++)
-			namespace[i].index[j].key_position = -1;
+			namespace[i].index[j].key_cardinality = 0;
 	}
 
 	if (cfg.custom_proc_title == NULL)
@@ -1219,30 +1789,55 @@ mod_init(void)
 	}
 
 	recovery_state = recover_init(cfg.snap_dir, cfg.wal_dir,
-				      box_snap_reader, snap_apply, xlog_apply,
-				      cfg.rows_per_wal, cfg.wal_fsync_delay, cfg.snap_io_rate_limit,
-				      cfg.wal_writer_inbox_size, init_storage ? RECOVER_READONLY : 0, NULL);
+				      box_snap_reader, recover_row,
+				      cfg.rows_per_wal, cfg.wal_fsync_delay,
+				      cfg.wal_writer_inbox_size,
+				      init_storage ? RECOVER_READONLY : 0, NULL);
+
+	recovery_state->snap_io_rate_limit = cfg.snap_io_rate_limit * 1024 * 1024;
+	recovery_setup_panic(recovery_state, cfg.panic_on_snap_error, cfg.panic_on_wal_error);
 
 	/* initialize hashes _after_ starting wal writer */
 	if (cfg.memcached != 0) {
 		int n = cfg.memcached_namespace > 0 ? cfg.memcached_namespace : MEMCACHED_NAMESPACE;
-		namespace[n].enabled = true;
-		namespace[n].index[0].map.str_map = kh_init(lstr2ptr_map, NULL);
-		namespace[n].index[0].find = index_find_hash_str;
-		namespace[n].index[0].remove = index_remove_hash_str;
-		namespace[n].index[0].replace = index_replace_hash_str;
+
+		cfg.namespace = palloc(eter_pool, (n + 1) * sizeof(cfg.namespace[0]));
+		for (u32 i = 0; i <= n; ++i) {
+			cfg.namespace[i] = palloc(eter_pool, sizeof(cfg.namespace[0][0]));
+			cfg.namespace[i]->enabled = false;
+		}
+
+		cfg.namespace[n]->enabled = true;
+		cfg.namespace[n]->cardinality = 4;
+		cfg.namespace[n]->estimated_rows = 0;
+		cfg.namespace[n]->index = palloc(eter_pool, 2 * sizeof(cfg.namespace[n]->index[0]));
+		cfg.namespace[n]->index[0] =
+			palloc(eter_pool, sizeof(cfg.namespace[n]->index[0][0]));
+		cfg.namespace[n]->index[1] = NULL;
+		cfg.namespace[n]->index[0]->type = "HASH";
+		cfg.namespace[n]->index[0]->unique = 1;
+		cfg.namespace[n]->index[0]->key_field =
+			palloc(eter_pool, 2 * sizeof(cfg.namespace[n]->index[0]->key_field[0]));
+		cfg.namespace[n]->index[0]->key_field[0] =
+			palloc(eter_pool, sizeof(cfg.namespace[n]->index[0]->key_field[0][0]));
+		cfg.namespace[n]->index[0]->key_field[1] = NULL;
+		cfg.namespace[n]->index[0]->key_field[0]->fieldno = 0;
+		cfg.namespace[n]->index[0]->key_field[0]->type = "STR";
+
 		memcached_index = &namespace[n].index[0];
-		memcached_index->key_position = 0;
-		memcached_index->type = INDEX_STR;
-	} else {
-		custom_init();
 	}
 
+	custom_init();
+
 	if (init_storage)
 		return;
 
 	recover(recovery_state, 0);
 
+	title("build_indexes");
+
+	build_indexes();
+
 	title("orphan");
 
 	if (cfg.local_hot_standby) {
@@ -1253,13 +1848,16 @@ mod_init(void)
 	}
 
 	if (cfg.memcached != 0) {
-		fiber_server(tcp_server, cfg.primary_port, memcached_handler, NULL, memcached_bound_to_primary);
+		fiber_server(tcp_server, cfg.primary_port, memcached_handler, NULL,
+			     memcached_bound_to_primary);
 	} else {
 		if (cfg.secondary_port != 0)
-			fiber_server(tcp_server, cfg.secondary_port, iproto_interact, box_process_ro, NULL);
+			fiber_server(tcp_server, cfg.secondary_port, iproto_interact,
+				     box_process_ro, NULL);
 
 		if (cfg.primary_port != 0)
-			fiber_server(tcp_server, cfg.primary_port, iproto_interact, box_process, box_bound_to_primary);
+			fiber_server(tcp_server, cfg.primary_port, iproto_interact, box_process,
+				     box_bound_to_primary);
 	}
 
 	say_info("initialized");
@@ -1274,32 +1872,32 @@ mod_cat(const char *filename)
 void
 mod_snapshot(struct log_io_iter *i)
 {
-	struct tbuf *row = tbuf_alloc(fiber->pool);
+	struct tbuf *row;
 	struct box_snap_row header;
 	struct box_tuple *tuple;
-        khiter_t k;
+	khiter_t k;
 
-	for(uint32_t n = 0; n < nelem(namespace); ++n) {
-                if (!namespace[n].enabled)
+	for (uint32_t n = 0; n < nelem(namespace); ++n) {
+		if (!namespace[n].enabled)
 			continue;
 
-		assoc_foreach(namespace[n].index[0].map.int_map, k) {
-			tuple = kh_value(namespace[n].index[0].map.int_map, k);
+		assoc_foreach(namespace[n].index[0].idx.int_hash, k) {
+			tuple = kh_value(namespace[n].index[0].idx.int_hash, k);
 
-			if (tuple->flags & GHOST) // do not save fictive rows
+			if (tuple->flags & GHOST)	// do not save fictive rows
 				continue;
 
 			header.namespace = n;
 			header.tuple_size = tuple->cardinality;
 			header.data_size = tuple->bsize;
 
-			tbuf_reset(row);
+			row = tbuf_alloc(fiber->pool);
 			tbuf_append(row, &header, sizeof(header));
 			tbuf_append(row, tuple->data, tuple->bsize);
 
-			snapshot_write_row(i, row);
+			snapshot_write_row(i, snap_tag, default_cookie, row);
 		}
-        }
+	}
 }
 
 void
@@ -1309,7 +1907,15 @@ mod_info(struct tbuf *out)
 	tbuf_printf(out, "  version: \"%s\"\r\n", tarantool_version());
 	tbuf_printf(out, "  uptime: %i\r\n", (int)tarantool_uptime());
 	tbuf_printf(out, "  pid: %i\r\n", getpid());
-	tbuf_printf(out, "  wal_writer_pid: %"PRIi64"\r\n", (i64)wal_writer(recovery_state)->pid);
-	tbuf_printf(out, "  lsn: %"PRIi64"\r\n", confirmed_lsn(recovery_state));
+	tbuf_printf(out, "  wal_writer_pid: %" PRIi64 "\r\n", (i64)recovery_state->wal_writer->pid);
+	tbuf_printf(out, "  lsn: %" PRIi64 "\r\n", recovery_state->confirmed_lsn);
+	tbuf_printf(out, "  recovery_lag: %.3f\r\n", recovery_state->recovery_lag);
+	tbuf_printf(out, "  recovery_last_update: %.3f\r\n", recovery_state->recovery_last_update_tstamp);
 	tbuf_printf(out, "  status: %s\r\n", status);
 }
+
+void
+mod_exec(char *str __unused__, int len __unused__, struct tbuf *out)
+{
+	tbuf_printf(out, "unimplemented\r\n");
+}
diff --git a/mod/silverbox/box.h b/mod/silverbox/box.h
index ccc7fc188488de34c7251e996679f3b7977673c4..2c3a51ce4ca4dc9416cab649ee96c809b567fb48 100644
--- a/mod/silverbox/box.h
+++ b/mod/silverbox/box.h
@@ -33,18 +33,70 @@ extern bool box_updates_allowed;
 void memcached_handler(void *_data __unused__);
 
 struct namespace;
+struct box_tuple;
+
+struct field {
+	u32 len;
+	union {
+		u32 u32;
+
+		u8 data[sizeof(void *)];
+
+		void *data_ptr;
+	};
+};
+
+enum field_data_type { NUM, STR };
+
+struct tree_index_member {
+	struct box_tuple *tuple;
+	struct field key[];
+};
+
+#define SIZEOF_TREE_INDEX_MEMBER(index) \
+	(sizeof(struct tree_index_member) + sizeof(struct field) * (index)->key_cardinality)
+
+#include <third_party/sptree.h>
+SPTREE_DEF(str_t, realloc);
+
+// #include <mod/silverbox/tree.h>
 
 struct index {
-	struct box_tuple *(*find)(struct index *index, int key_len, void *key);
-	void (*remove)(struct index *index, void *key);
-	void (*replace)(struct index *index, void *key, void *value);
+	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);
 	union {
-                khash_t(lstr2ptr_map) *str_map;
-                khash_t(int2ptr_map) *int_map;
-        } map;
+		khash_t(lstr2ptr_map) * str_hash;
+		khash_t(int2ptr_map) * int_hash;
+		khash_t(int2ptr_map) * hash;
+		sptree_str_t *tree;
+	} idx;
+	void *iterator;
+	bool iterator_empty;
+
 	struct namespace *namespace;
-	int key_position;
-	enum { INDEX_NUM, INDEX_STR } type;
+
+	struct {
+		struct {
+			u32 fieldno;
+			enum field_data_type type;
+		} *key_field;
+		u32 key_cardinality;
+
+		u32 *field_cmp_order;
+		u32 field_cmp_order_cnt;
+	};
+
+	struct tree_index_member *search_pattern;
+
+	enum { HASH, TREE } type;
 };
 
 extern struct index *memcached_index;
@@ -58,35 +110,34 @@ struct namespace {
 };
 
 struct box_tuple {
-        u16 refs;
-        u16 flags;
-        u32 bsize;
-        u32 cardinality;
-        u8 data[0];
+	u16 refs;
+	u16 flags;
+	u32 bsize;
+	u32 cardinality;
+	u8 data[0];
 } __packed__;
 
-
 struct box_txn {
-        int op;
-        u32 flags;
+	int op;
+	u32 flags;
 
 	struct namespace *namespace;
-        struct index *index;
-        int n;
+	struct index *index;
+	int n;
 
 	struct tbuf *ref_tuples;
-        struct box_tuple *old_tuple;
-        struct box_tuple *tuple;
+	struct box_tuple *old_tuple;
+	struct box_tuple *tuple;
 	struct box_tuple *lock_tuple;
 
 	bool in_recover, old_format;
 };
 
-
 enum tuple_flags {
 	WAL_WAIT = 0x1,
-	GHOST    = 0x2,
-	NEW      = 0x4
+	GHOST = 0x2,
+	NEW = 0x4,
+	SEARCH = 0x8
 };
 
 enum box_mode {
@@ -95,9 +146,10 @@ enum box_mode {
 };
 
 #define BOX_RETURN_TUPLE 1
+#define BOX_ADD 2
+#define BOX_REPLACE 4
 #define BOX_QUIET 8
 
-
 /*
     deprecated commands:
         _(INSERT, 1)
@@ -126,10 +178,10 @@ enum box_mode {
 
 ENUM(messages, MESSAGES);
 
-struct box_tuple *index_find(struct index *index, int key_len, void *key);
+struct box_tuple *index_find(struct index *index, void *key);
 
 struct box_txn *txn_alloc(u32 flags);
-u32 box_dispach(struct box_txn *txn, enum box_mode mode, u32 op, struct tbuf *data);
+u32 box_dispach(struct box_txn *txn, enum box_mode mode, u16 op, struct tbuf *data);
 void tuple_txn_ref(struct box_txn *txn, struct box_tuple *tuple);
 void txn_cleanup(struct box_txn *txn);
 
diff --git a/mod/silverbox/box_cfg.cfg_tmpl b/mod/silverbox/box_cfg.cfg_tmpl
index d9b6cc60ab95fc7e80d0d32f88e227ac9aeae9de..1bc8a9bf9d4a9b1a7d8b035dd898384057fa5551 100644
--- a/mod/silverbox/box_cfg.cfg_tmpl
+++ b/mod/silverbox/box_cfg.cfg_tmpl
@@ -28,6 +28,10 @@ memcached_expire_per_loop=1024
 # tarantool will try iterate all rows within this time
 memcached_expire_full_sweep=3600
 
+
+# do not write snapshot faster then snap_io_rate_limit MBytes/sec
+snap_io_rate_limit=0.0
+
 # Write no more rows in WAL
 rows_per_wal=500000
 
@@ -44,6 +48,12 @@ local_hot_standby=0
 # delay in fractional seconds between successive re-readings of wal_dir
 wal_dir_rescan_delay=0.1
 
+
+# panic if where is error reading snap or wal
+# be default panic any snapshot reading error  and ignore errors then reading wals
+panic_on_snap_error=1
+panic_on_wal_error=0
+
 # Remote hot standby (if enabled server will run in hot standby mode
 # continuously fetching WAL records from wal_feeder_ipaddr:wal_feeder_port
 remote_hot_standby=0
@@ -56,7 +66,11 @@ namespace = [
   cardinality = -1
   estimated_rows = 0
   index = [
-    type = "NUM"
-    key_position = -1
+    type = ""
+    unique = -1
+    key_field = [
+      fieldno = -1
+      type = ""
+    ]
   ]
-]
\ No newline at end of file
+]
diff --git a/mod/silverbox/client/perl/MANIFEST b/mod/silverbox/client/perl/MANIFEST
new file mode 100755
index 0000000000000000000000000000000000000000..a6c5f9954c52a8f15db2066861100b238c79a9e3
--- /dev/null
+++ b/mod/silverbox/client/perl/MANIFEST
@@ -0,0 +1,5 @@
+Makefile.PL
+MANIFEST
+lib/MR/IProto.pm
+lib/MR/SilverBox.pm
+lib/MR/Storage/Const.pm
diff --git a/mod/silverbox/client/perl/Makefile.PL b/mod/silverbox/client/perl/Makefile.PL
new file mode 100755
index 0000000000000000000000000000000000000000..08d6dabb058446d6641780f0d15dba3c88ece0ae
--- /dev/null
+++ b/mod/silverbox/client/perl/Makefile.PL
@@ -0,0 +1,16 @@
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME          => "MR::SilverBox",
+    VERSION_FROM  => "lib/MR/SilverBox.pm",
+    MAKEFILE      => 'Makefile',
+    PREREQ_PM     => {
+        'Scalar::Util'      => 0,
+        'List::Util'        => 0,
+        'List::MoreUtils'   => 0,
+        'Time::HiRes'       => 0,
+        'String::CRC32'     => 0,
+        'Exporter'          => 0,
+        'Fcntl'             => 0,
+    },
+);
diff --git a/mod/silverbox/client/perl/MR/IProto.pm b/mod/silverbox/client/perl/lib/MR/IProto.pm
similarity index 77%
rename from mod/silverbox/client/perl/MR/IProto.pm
rename to mod/silverbox/client/perl/lib/MR/IProto.pm
index afc15b36d50aeeaf5484fc78409ca5016ad44ebe..f32ee14ac14824d4cc52e27b9e7599be19df10da 100644
--- a/mod/silverbox/client/perl/MR/IProto.pm
+++ b/mod/silverbox/client/perl/lib/MR/IProto.pm
@@ -1,21 +1,16 @@
 package MR::IProto;
 
-# $Id: IProto.pm,v 1.166 2010/04/20 15:02:49 nevinitsin Exp $
-
 use strict;
 
-use Exporter;
-use base qw/Exporter/;
-
-our @EXPORT_OK = qw/ipro/;
-
-use Socket qw(PF_INET SOCK_STREAM SOL_SOCKET SO_SNDTIMEO SO_RCVTIMEO TCP_NODELAY);
-use IO::Handle ();
-use Errno qw( EINPROGRESS EWOULDBLOCK EISCONN EAGAIN );
-use Carp qw(carp confess);
-use vars qw($PROTO_TCP %sockets);
+use Socket qw(PF_INET SOCK_STREAM SOL_SOCKET SO_SNDTIMEO SO_RCVTIMEO SO_KEEPALIVE TCP_NODELAY);
 use String::CRC32 qw(crc32);
 use Time::HiRes qw/time sleep/;
+use Fcntl;
+
+use vars qw($VERSION $PROTO_TCP %sockets);
+$VERSION = 0;
+
+use overload '""' => sub { "$_[0]->{name}\[$_[0]->{_last_server}]" };
 
 BEGIN {
     if (eval {require 5.8.3}) {
@@ -26,56 +21,49 @@ BEGIN {
     }
 }
 
-sub ipro ($) { # $ stands for LPS, % stands for LPS with 2-byte len.
-    my $s = $_[0];
-    $s =~ s{\$}{L/a*}g;
-    $s =~ s{\%}{S/a*}g;
-    $s =~ s{\s}{}gs;
-    return $s;
-}
-
-*mPOP::RemoteStorTimeOut = sub { 0 } unless defined &mPOP::RemoteStorTimeOut;
-
 $PROTO_TCP = 0;
 %sockets = ();
 
-sub DEFAULT_RETRY_DELAY() { 0 }
-sub DEFAULT_MAX_REQUEST_RETRIES() { 2 };
-sub DEFAULT_TIMEOUT () { 2 };
-sub RR () { 1 }
-sub HASH () { 2 }
-sub KETAMA () { 3 }
+sub DEFAULT_RETRY_DELAY         () { 0 }
+sub DEFAULT_MAX_REQUEST_RETRIES () { 2 }
+sub DEFAULT_TIMEOUT             () { 2 }
+
+sub RR                          () { 1 }
+sub HASH                        () { 2 }
+sub KETAMA                      () { 3 }
 
-sub DisconnectAll
-{
+sub confess { die @_ };
+
+sub DisconnectAll {
     close $_ foreach (values %sockets);
     %sockets = ();
 }
 
-sub new
-{
-    my $class = shift;
+sub new {
+    my ($class, $args) = @_;
     my $self = {};
     bless $self, $class;
 
-    my ($args) = @_;
-
-    $self->{debug} = defined($args->{debug}) ? $args->{debug} : mPOP::Config::GetValue('IProtoDebug');
-    $self->{balance} = $args->{balance} && $args->{balance} eq 'hash-crc32' ? HASH() : RR();
-    $self->{balance} = KETAMA() if ($args->{balance} && $args->{balance} eq 'ketama');
-    $self->{rotateservers} = 1 unless $args->{norotateservers};
-    $self->{max_request_retries} = $args->{max_request_retries} || DEFAULT_MAX_REQUEST_RETRIES();
-    $self->{retry_delay} = $args->{retry_delay} || DEFAULT_RETRY_DELAY();
-    $self->{dump_no_ints} = 1 if $args->{dump_no_ints};
-    $self->{tcp_nodelay} = 1 if $args->{tcp_nodelay};
-    $self->{param} = $args->{param};
-
-    my $servers = $args->{servers} || confess("MR::IProto->new: no servers given");
+    $self->{debug}                = $args->{debug} || 0;
+    $self->{balance}              = $args->{balance} && $args->{balance} eq 'hash-crc32' ? HASH() : RR();
+    $self->{balance}              = KETAMA() if $args->{balance} && $args->{balance} eq 'ketama';
+    $self->{rotateservers}        = 1 unless $args->{norotateservers};
+    $self->{max_request_retries}  = $args->{max_request_retries} || DEFAULT_MAX_REQUEST_RETRIES();
+    $self->{retry_delay}          = $args->{retry_delay} || DEFAULT_RETRY_DELAY();
+    $self->{dump_no_ints}         = 1 if $args->{dump_no_ints};
+    $self->{tcp_nodelay}          = 1 if $args->{tcp_nodelay};
+    $self->{tcp_keepalive}        = $args->{tcp_keepalive} || 0;
+    $self->{param}                = $args->{param};
+    $self->{_last_server}         = '';
+
+    $self->{name} = $args->{name} or ($self->{name}) = caller;
+
+    my $servers = $args->{servers} || confess("${class}->new: no servers given");
     _parse_servers $self 'servers', $servers;
 
     if ($self->{balance} == RR()) {
         $self->{aviable_servers} = [@{$self->{servers}}]; #make copy
-        $servers = $args->{broadcast_servers} || ''; # confess("MR::IProto->new: no broadcast servers given");
+        $servers = $args->{broadcast_servers} || ''; # confess("${class}->new: no broadcast servers given");
         _parse_servers $self 'broadcast_servers', $servers;
     } elsif ($self->{balance} == KETAMA()) {
         $self->{'ketama'} = [];
@@ -91,7 +79,7 @@ sub new
     my $timeout = exists $args->{timeout} ? $args->{timeout} : DEFAULT_TIMEOUT();
     SetTimeout $self $timeout;
 
-    confess "MR::Iproto: no servers given" unless @{$self->{servers}};
+    confess "${class}: no servers given" unless @{$self->{servers}};
     return $self;
 }
 
@@ -99,8 +87,7 @@ sub GetParam {
     return $_[0]->{param};
 }
 
-sub Chat
-{
+sub Chat {
     my ($self, %message) = @_;
 
     confess "wrong msg id" unless exists($message{msg});
@@ -111,7 +98,7 @@ sub Chat
 
     for (my $try = 1; $try <= $retries; $try++) {
         sleep $self->{retry_delay} if $try > 1 && $self->{retry_delay};
-        $self->{debug} >= 1 && _debug $self "chat msg=$message{msg} $try of $retries total";
+        $self->{debug} >= 2 && _debug $self "chat msg=$message{msg} $try of $retries total";
         my $ret = $self->Chat1(%message);
 
         if ($ret->{ok}) {
@@ -130,7 +117,7 @@ sub Chat1 {
 
     my $server = _select_server $self $message{key};
     unless($server) {
-        $self->{_error} .= 'All servers blocked by remote_stor_pinger';
+        $self->{_error} ||= 'Could not find a valid server';
         return;
     }
 
@@ -147,8 +134,7 @@ sub Chat1 {
 # private methods
 # order of method declaration is important, see perlobj
 
-sub _parse_servers
-{
+sub _parse_servers {
     my ($self, $key, $line) = @_;
     my @servers;
     my $weighted = $self->{balance} == HASH();
@@ -159,23 +145,12 @@ sub _parse_servers
             push @servers, { host => $host, port => $port, ok => 1, repr => "$host:$port" } for (1..$weight);
         } else {
             my ($host, $port) = $server =~ /(.+):(\d+)/;
-            push @servers, { host => $host, port => $port, repr => "$host:$port" }
-
+            push @servers, { host => $host, port => $port, repr => "$host:$port" };
         }
     }
     $self->{$key} = \@servers;
 }
 
-sub _stats {
-    my ($start_time, $server) = @_;
-    return unless $start_time;
-
-    my $repr = "$server->{host}:$server->{port}";
-    $repr =~ s/\./_/g;
-
-    $mPOP::Carbon::bulk{"wallclock_iproto.$repr"} += time() - $start_time;
-}
-
 sub _chat {
     my ($self, $server, $message) = @_;
     _a_init $self $server, $message
@@ -203,7 +178,7 @@ sub _a_init {
     $self->{_start_time} = time;
     $self->{_connecting} = 1;
     $self->{sock} = $sockets{$server->{repr}} || _connect $self $server, \$self->{_error}, $async;
-    IO::Handle::blocking($self->{sock},!$async) if $self->{sock};
+    _set_blocking($self->{sock},!$async) if $self->{sock};
     return $self->{sock};
 }
 
@@ -213,12 +188,15 @@ sub _a_send {
     $self->{_connecting} = 0;
     $self->{_timedout} = 0;
     while(length $self->{_write_buf}) {
+        local $! = 0;
         my $res = syswrite($self->{sock}, $self->{_write_buf});
         if(defined $res && $res == 0) {
+            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
             $self->{_error} .= "Server unexpectedly closed connection (${\length $self->{_write_buf}} bytes unwritten)";
             return;
         }
         if(!defined $res) {
+            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
             $self->{_timedout} = !!$!{EAGAIN};
             $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN};
             return;
@@ -240,6 +218,7 @@ sub _a_recv {
     $self->{_connecting} = 0;
     $self->{_timedout} = 0;
     while(1) {
+        local $! = 0;
         my $res;
         while($self->{_to_read} and $res = sysread($self->{sock}, my $buffer, $self->{_to_read} ) ) {
             $self->{_to_read} -= $res;
@@ -247,10 +226,12 @@ sub _a_recv {
             $self->{debug} >= 6 && _debug_dump $self '_a_recv: ', $buffer;
         }
         if(defined $res && $res == 0 && $self->{_to_read}) {
+            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
             $self->{_error} .= "Server unexpectedly closed connection ($self->{_to_read} bytes unread)";
             return;
         }
         if(!defined $res) {
+            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
             $self->{_timedout} = !!$!{EAGAIN};
             $self->{_error} .= $! unless $self->{_async} && $!{EAGAIN};
             return;
@@ -277,12 +258,11 @@ sub _a_close {
     $error = '' unless defined $error;
     $self->{_timedout} ||= $error eq 'timeout';
     $self->{_error} ||= $error;
-    _stats($self->{_start_time}, $self->{_server});
     my $ret;
     if ($self->{_error} || $self->{_timedout}) {
         _close_sock $self $self->{_server}; # something went wrong, close socket just in case
         $self->{_error} = "Timeout ($self->{_error})" if $self->{_timedout};
-        $self->{debug} >= 0 && _debug $self "failed with: $self->{_error}";
+        $self->{debug} >= 1 && _debug $self "failed with: $self->{_error}";
         $ret = { fail => $self->{_error}, timeout => $self->{_timedout} };
     } else {
         $self->{debug} >= 5 && _debug_dump $self '_unpack_body: ', $self->{_buf};
@@ -322,8 +302,11 @@ sub _a_clear {
     delete $self->{_write_buf};
 }
 
-sub _select_server
-{
+sub _n_servers {
+    return scalar @{$_[0]->{servers}};
+}
+
+sub _select_server {
     my ($self, $key) = @_;
     my $n = @{$self->{servers}};
     while($n--) {
@@ -335,23 +318,24 @@ sub _select_server
             $self->{current_server} = $self->_balance_hash($key);
         }
         last unless $self->{current_server};
-        $self->_mark_server_bad if mPOP::RemoteStorTimeOut("iproto:$self->{current_server}->{repr}");
         last if $self->{current_server};
     }
+    if($self->{current_server}) {
+        $self->{_last_server} = $self->{current_server}->{repr};
+    }
     return $self->{current_server};
 }
 
-sub _mark_server_bad
-{
-    my ($self) = @_;
+sub _mark_server_bad {
+    my ($self, $remove_last_server) = @_;
     if ($self->{balance} == HASH()) {
         delete($self->{current_server}->{ok});
     }
     delete($self->{current_server});
+    $self->{_last_server} = '' if $remove_last_server;
 }
 
-sub _balance_rr
-{
+sub _balance_rr {
     my ($self) = @_;
     if (scalar(@{$self->{servers}}) == 1) {
         return $self->{servers}->[0];
@@ -361,8 +345,7 @@ sub _balance_rr
     }
 }
 
-sub _balance_hash
-{
+sub _balance_hash {
     my ($self, $key) = @_;
     my ($hash_, $hash, $server);
 
@@ -376,8 +359,7 @@ sub _balance_hash
     return $self->{servers}->[rand @{$self->{servers}}]; #last resort
 }
 
-sub _balance_ketama
-{
+sub _balance_ketama {
     my ($self, $key) = @_;
 
     my $idx = crc32($key);
@@ -398,8 +380,19 @@ sub _set_sock_timeout ($$) { # not a class method!
     );
 }
 
-sub _connect
-{
+sub _set_blocking ($$) {
+    my ($sock, $blocking) = @_;
+    my $flags = 0;
+    fcntl($sock, F_GETFL, $flags) or die $!;
+    if($blocking) {
+        $flags &= ~O_NONBLOCK;
+    } else {
+        $flags |=  O_NONBLOCK;
+    }
+    fcntl($sock, F_SETFL, $flags) or die $!;
+}
+
+sub _connect {
     my ($self, $server, $err, $async) = @_;
 
     $self->{_timedout} = 0;
@@ -412,24 +405,36 @@ sub _connect
         socket($sock, PF_INET, SOCK_STREAM, $proto);
     };
 
-    _set_sock_timeout $sock, $self->{timeout_timeval} unless $async;
-    IO::Handle::blocking($sock,0) if $async;
+    if ($async) {
+        _set_blocking($sock,0);
+    } else {
+        _set_sock_timeout $sock, $self->{timeout_timeval};
+    }
 
     my $sin = Socket::sockaddr_in($server->{'port'}, Socket::inet_aton($server->{'host'}));
-    unless(connect($sock, $sin)) {
-        $self->{_timedout} = !!$!{EINPROGRESS};
-        if(!$async || !$!{EINPROGRESS}) {
-            $$err .= "cannot connect: $!";
-            close $sock;
-            return undef;
+    while(1) {
+        local $! = 0;
+        unless(connect($sock, $sin)) {
+            $self->{debug} <= 0 || _debug $self "$!" and next if $!{EINTR};
+            $self->{_timedout} = !!$!{EINPROGRESS};
+            if (!$async || !$!{EINPROGRESS}) {
+                $$err .= "cannot connect: $!";
+                close $sock;
+                return undef;
+            }
         }
+        last;
     }
 
     if($self->{tcp_nodelay}) {
         setsockopt($sock, $PROTO_TCP, TCP_NODELAY, 1);
     }
 
-    $self->{debug} >= 1 && _debug $self "connected";
+    if($self->{tcp_keepalive}) {
+        setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1);
+    }
+
+    $self->{debug} >= 2 && _debug $self "connected";
     return $sockets{$server->{repr}} = $sock;
 }
 
@@ -439,7 +444,7 @@ sub SetTimeout {
 
     my $sec  = int $timeout; # seconds
     my $usec = int( ($timeout - $sec) * 1_000_000 ); # micro-seconds
-    $self->{timeout_timeval} = pack "LL", $sec, $usec; # struct timeval;
+    $self->{timeout_timeval} = pack "L!L!", $sec, $usec; # struct timeval;
 
     _set_sock_timeout $sockets{$_}, $self->{timeout_timeval}
         for
@@ -448,29 +453,27 @@ sub SetTimeout {
                     @{ $self->{servers} };
 }
 
-sub _close_sock
-{
+sub _close_sock {
     my ($self, $server) = @_;
 
     if ($sockets{$server->{repr}}) {
-        $self->{debug} >= 1 && _debug $self 'closing socket';
+        $self->{debug} >= 2 && _debug $self 'closing socket';
         close $sockets{$server->{repr}};
         delete $sockets{$server->{repr}};
     }
 }
 
-sub _debug
-{
+sub _debug {
     my ($self, $msg)= @_;
-    my $server = $self->{current_server} && $self->{current_server}->{repr};
+    my $server = $self->{current_server} && $self->{current_server}->{repr} || $self->{_last_server} || '';
     my $sock = $self->{sock} || 'none';
     $server &&= "$server($sock) ";
 
-    warn("MR::IProto: $server$msg\n");
+    warn("$self->{name}: $server$msg\n");
+    1;
 }
 
-sub _debug_dump
-{
+sub _debug_dump {
     my ($self, $msg, $datum) = @_;
     my $server = $self->{current_server} && $self->{current_server}->{repr};
     my $sock = $self->{sock} || 'none';
@@ -481,35 +484,37 @@ sub _debug_dump
         $msg .= ' > ';
     }
     $msg .= join(' ', map { sprintf "%02x", $_ } unpack("C*", $datum));
-    warn("MR::IProto: $server$msg\n");
+    warn("$self->{name}: $server$msg\n");
 }
 
-1;
-
 package MR::IProto::Async;
-use Carp qw/confess/;
 use Time::HiRes qw/time/;
 use List::Util qw/min shuffle/;
-use Data::Dumper;
-use MR::IProto ();
 
 sub DEFAULT_TIMEOUT()           { 10 }
 sub DEFAULT_TIMEOUT_SINGLE()    {  4 }
 sub DEFAULT_TIMEOUT_CONNECT()   {  1 }
-
 sub DEFAULT_RETRY()             {  3 }
+sub IPROTO_CLASS()              { 'MR::IProto' }
+
+use overload '""' => sub { $_[0]->{name}.'[async]' };
+
+BEGIN { *confess = \&MR::IProto::confess }
 
 sub new {
     my ($class, %opt) = @_;
+    ($opt{name}) = caller unless $opt{name};
     my $self = bless {
-        timeout                     => $opt{timeout} || DEFAULT_TIMEOUT(),                  # over-all-requests timeout
-        timeout_single              => $opt{timeout_single} || DEFAULT_TIMEOUT_SINGLE(),    # per-request timeout
-        timeout_connect             => $opt{timeout_connect} || DEFAULT_TIMEOUT_CONNECT(),  # timeout to do connect()
-        max_request_retries         => $opt{max_request_retries} || DEFAULT_RETRY(),
-        debug                       => defined($opt{debug}) ? $opt{debug} : mPOP::Config::GetValue('IProtoDebug'),
+        name                        => $opt{name},
+        iproto_class                => $opt{iproto_class}         || $class->IPROTO_CLASS,
+        timeout                     => $opt{timeout}              || $class->DEFAULT_TIMEOUT(),          # over-all-requests timeout
+        timeout_single              => $opt{timeout_single}       || $class->DEFAULT_TIMEOUT_SINGLE(),   # per-request timeout
+        timeout_connect             => $opt{timeout_connect}      || $class->DEFAULT_TIMEOUT_CONNECT(),  # timeout to do connect()
+        max_request_retries         => $opt{max_request_retries}  || $class->DEFAULT_RETRY(),
+        debug                       => $opt{debug}                || 0,
         servers                     => [ ],
         requests                    => [ @{$opt{requests}||[]} ],
-        nreqs                       => 0,
+        nreqs                       => $opt{requests} ? scalar(@{$opt{requests}}) : 0,
         servers_used                => {},
         nserver                     => 0,
         working                     => {},
@@ -615,10 +620,10 @@ sub Results {
     }
 
     my $t9 = time;
-    warn sprintf "MR::IProto::Async: fetched %d requests for %.4f sec (%d/%d done)\n", $self->{nreqs}, $t9-$t0, scalar@done, $nreqs;
+    warn sprintf "$self->{name}: fetched %d requests for %.4f sec (%d/%d done)\n", $self->{nreqs}, $t9-$t0, scalar@done, $nreqs;
 
     if(%$working) {
-        warn "MR::IProto::Async: ${\scalar values %$working} requests not fetched\n";
+        warn "$self->{name}: ${\scalar values %$working} requests not fetched\n";
         $_->{_conn}->_a_close($err) for values %$working;
         # warn "return nothing\n";
         # return;
@@ -643,7 +648,7 @@ sub _init_req {
     $req->{_server} = undef;
 
     my $server = _select_server $self or return '0E0';
-    my $conn = MR::IProto->new({
+    my $conn = $self->{iproto_class}->new({
         servers => $server->{repr},
         debug   => $self->{debug},
     });
@@ -662,11 +667,12 @@ sub _init_req {
     ++$req->{_try};
     ++$self->{nreqs};
 
-    return $conn;
+    return $conn && 1;
 }
 
 sub _select_server {
-    my ($self) = @_;
+    my ($self, $disable_servers) = @_;
+    $disable_servers ||= {};
 
     my $servers = $self->{servers};
     my $servers_used = $self->{servers_used};
@@ -676,7 +682,7 @@ sub _select_server {
     while($i <= @$servers) {
         my $repr = $servers->[($i+$nserver)%@$servers]->{repr};
         next if $servers_used->{$repr};
-        next if mPOP::RemoteStorTimeOut("iproto:$repr");
+        next if $disable_servers->{$repr};
         last;
     } continue {
         ++$i;
@@ -714,10 +720,7 @@ sub _parse_servers {
 
 sub _die {
     my ($self, @e) = @_;
-    local $Data::Dumper::Terse = 1;
-    local $Data::Dumper::Indent = 2;
-    local $Data::Dumper::Maxdepth = 0;
-    confess join('; ', @e, Dumper($self));
+    die "$self->{name}: ".join('; ', @e);
 }
 
 1;
diff --git a/mod/silverbox/client/perl/MR/SilverBox.pm b/mod/silverbox/client/perl/lib/MR/SilverBox.pm
similarity index 65%
rename from mod/silverbox/client/perl/MR/SilverBox.pm
rename to mod/silverbox/client/perl/lib/MR/SilverBox.pm
index 8177b765f1d75877c35545bd38c382e4e3047223..76d350d7adc1888782bf2ac16981a87b8c680b5f 100644
--- a/mod/silverbox/client/perl/MR/SilverBox.pm
+++ b/mod/silverbox/client/perl/lib/MR/SilverBox.pm
@@ -1,38 +1,48 @@
-# $Id: SilverBox.pm,v 1.49 2010/06/18 08:52:42 nevinitsin Exp $
 package MR::SilverBox;
 
 use strict;
 use warnings;
-use MR::IProto ();
-use MR::Storage::Const ();
 use Scalar::Util qw/looks_like_number/;
 use List::MoreUtils qw/each_arrayref/;
-use Carp qw/confess/;
 
-use constant WANT_RESULT => 1;
-use constant UPDATE_BASE => 2;
-use constant UPDATE_FLAGS => 4;
+use MR::IProto ();
+use MR::Storage::Const ();
+
+use constant {
+    WANT_RESULT       => 1,
+    INSERT_ADD        => 2,
+    INSERT_REPLACE    => 4,
+};
+
 
+sub IPROTOCLASS () { 'MR::IProto' }
+sub ERRSTRCLASS () { 'MR::Storage::Const::Errors::SilverBox' }
 
-sub new
-{
+use vars qw/$VERSION/;
+$VERSION = 0;
+
+BEGIN { *confess = \&MR::IProto::confess }
+
+sub new {
     my ($class, $arg) = @_;
     my $self;
 
     $arg = { %$arg };
-    $self->{name}  = $arg->{name} || ref$class || $class;
-    $self->{timeout}   = $arg->{timeout}   || 23;
-    $self->{retry    } = $arg->{retry}     || 1;
-    $self->{softretry} = $arg->{softretry} || 3;
-    $self->{debug}     = $arg->{'debug'}   || 0;
-    $self->{ipdebug}   = $arg->{'ipdebug'} || 0;
-    $self->{raise}     = 1;
-    $self->{raise}     = $arg->{raise} if exists $arg->{raise};
-#    $self->{default_namespace} = exists $arg->{default_namespace} ? $arg->{default_namespace} : 0;
-    $self->{default_raw} = exists $arg->{default_raw} ? $arg->{default_raw} : 0;
-    $self->{hashify} = $arg->{'hashify'} if exists $arg->{'hashify'};
-#    $self->{string_key} = exists $arg->{string_key} ? $arg->{string_key} : { 1 => 'string' };
-    $self->{select_timeout} = $arg->{select_timeout} || $self->{timeout};
+    $self->{name}            = $arg->{name}      || ref$class || $class;
+    $self->{timeout}         = $arg->{timeout}   || 23;
+    $self->{retry    }       = $arg->{retry}     || 1;
+    $self->{select_retry}    = $arg->{select_retry} || 3;
+    $self->{softretry}       = $arg->{softretry} || 3;
+    $self->{debug}           = $arg->{'debug'}   || 0;
+    $self->{ipdebug}         = $arg->{'ipdebug'} || 0;
+    $self->{raise}           = 1;
+    $self->{raise}           = $arg->{raise} if exists $arg->{raise};
+    $self->{hashify}         = $arg->{'hashify'} if exists $arg->{'hashify'};
+    $self->{default_raw}     = exists $arg->{default_raw} ? $arg->{default_raw} : !$self->{hashify};
+    $self->{select_timeout}  = $arg->{select_timeout} || $self->{timeout};
+    $self->{iprotoclass}     = $arg->{iprotoclass} || $class->IPROTOCLASS;
+    $self->{errstrclass}     = $arg->{errstrclass} || $class->ERRSTRCLASS;
+    $self->{_last_error}     = 0;
 
     $arg->{namespaces} = [@{ $arg->{namespaces} }];
     my %namespaces;
@@ -47,8 +57,9 @@ sub new
         confess "ns[$namespace] bad format `$ns->{format}'" if $ns->{format} =~ m/[^&lLsScC ]/;
         $ns->{format} =~ s/\s+//g;
         my @f = split //, $ns->{format};
-        $ns->{unpack_format} = join('', map { /&/ ? 'w/a*' : "x$_" } @f );
-        $ns->{field_format}  = [        map { /&/ ? 'a*'   : $_    } @f ];
+        $ns->{byfield_unpack_format} = [ map { /&/ ? 'w/a*' : "x$_" } @f ];
+        $ns->{field_format}  = [         map { /&/ ? 'a*'   : $_    } @f ];
+        $ns->{unpack_format}  = join('', @{$ns->{byfield_unpack_format}});
         $ns->{append_for_unpack} = '' unless defined $ns->{append_for_unpack};
         $ns->{check_keys} = {};
         $ns->{string_keys} = { map { $_ => 1 } grep { $f[$_] eq '&' } 0..$#f };
@@ -96,25 +107,36 @@ sub _debug {
 
 sub _connect {
     my ($self, $servers) = @_;
+    $self->{server} = $self->{iprotoclass}->new({
+        servers       => $servers,
+        name          => $self->{name},
+        debug         => $self->{'ipdebug'},
+        dump_no_ints  => 1,
+    });
+}
 
-    $self->{'server'} = MR::IProto->new({ servers => $servers,
-                                          debug => $self->{'ipdebug'},
-                                          dump_no_ints => 1 });
+sub ErrorStr {
+    my ($self, $code) = @_;
+    return $self->{_last_error_msg} if $self->{_last_error} eq 'fail';
+    return $self->{errstrclass}->ErrorStr($code || $self->{_last_error});
+}
+
+sub Error {
+    return $_[0]->{_last_error};
 }
 
 sub _chat {
     my ($self, %param) = @_;
     my $orig_unpack = delete $param{unpack};
-    my $soft_error = {}; # exception, any ref will do
 
     $param{unpack} = sub {
         my $data = $_[0];
         confess __LINE__."$self->{name}: [common]: Bad response" if length $data < 4;
-        my @err_code = unpack('CCCC', substr($data, 0, 4, ''));
+        my ($full_code, @err_code) = unpack('LX[L]CCCC', substr($data, 0, 4, ''));
         # $err_code[0] = severity: 0 -> ok, 1 -> transient, 2 -> permanent;
         # $err_code[1] = description;
         # $err_code[3] = da box project;
-        return (\@err_code, \$data);
+        return (\@err_code, \$data, $full_code);
     };
 
     my $timeout = $param{timeout} || $self->{timeout};
@@ -125,31 +147,37 @@ sub _chat {
     while ($retry > 0) {
         $retry_count++;
 
+        $self->{_last_error} = 0x77777777;
         $self->{server}->SetTimeout($timeout);
         my $ret = $self->{server}->Chat1(%param);
         my $message;
 
         if (exists $ret->{ok}) {
-            my ($ret_code, $data) = @{$ret->{ok}};
+            my ($ret_code, $data, $full_code) = @{$ret->{ok}};
+            $self->{_last_error} = $full_code;
             if ($ret_code->[0] == 0) {
                 my $ret = $orig_unpack->($$data,$ret_code->[3]);
                 confess __LINE__."$self->{name}: [common]: Bad response (more data left)" if length $$data > 0;
                 return $ret;
             }
 
-            my $full_code = ($ret_code->[1] << 8) + $ret_code->[0];
-            $message = MR::Storage::Const::Errors::SilverBox->ErrorStr($full_code);
-            confess "$self->{name}: $message" if $ret_code->[0] == 2; #fatal error
-            $self->_debug("$self->{name}: $message");
+            $message = $self->{errstrclass}->ErrorStr($full_code);
+            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
+            if ($ret_code->[0] == 2) { #fatal error
+                $self->_raise($message) if $self->{raise};
+                return 0;
+            }
 
             # retry if error is soft even in case of update e.g. ROW_LOCK
             if ($ret_code->[0] == 1 and --$soft_retry > 0) {
                 --$retry if $retry > 1;
+                sleep 1;
                 next;
             }
-        } else {
-            $message ||= $ret->{fail} || $ret->{timeout};
-            $self->_debug("$self->{name}: $message");
+        } else { # timeout has caused the failure if $ret->{timeout}
+            $self->{_last_error} = 'fail';
+            $message ||= $self->{_last_error_msg} = $ret->{fail};
+            $self->_debug("$self->{name}: $message") if $self->{debug} >= 1;
         }
 
         last unless --$retry;
@@ -157,16 +185,20 @@ sub _chat {
         sleep 1;
     };
 
-    confess "$self->{name}: no success after $retry_count tries\n" if $self->{raise};
+    $self->_raise("no success after $retry_count tries\n") if $self->{raise};
+}
+
+sub _raise {
+    my ($self, $msg) = @_;
+    die "$self->{name}: $msg";
 }
 
-sub _validate_param
-{
+sub _validate_param {
     my ($self, $args, @pnames) = @_;
     my $param = ref $args->[-1] eq 'HASH' ? pop @$args: {};
 
     foreach my $pname (keys %$param) {
-        confess "$self->{name}: unknown param $pname\n" if grep { $_ eq $pname } @pnames == 0;
+        confess "$self->{name}: unknown param $pname\n" if 0 == grep { $_ eq $pname } @pnames;
     }
 
     $param->{namespace} = $self->{default_namespace} unless defined $param->{namespace};
@@ -178,17 +210,39 @@ sub _validate_param
     return ($param, map { /namespace/ ? $self->{namespaces}->{$param->{namespace}} : $param->{$_} } @pnames);
 }
 
+sub Add { # store tuple if tuple identified by primary key _does_not_ exist
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'add';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
+
+sub Set { # store tuple _anyway_
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'set';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
 
-sub Insert
-{
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace update_base/);
+sub Replace { # store tuple if tuple identified by primary key _does_ exist
+    my $param = @_ && ref $_[-1] eq 'HASH' ? pop : {};
+    $param->{action} = 'replace';
+    $_[0]->Insert(@_[1..$#_], $param);
+}
+
+sub Insert {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace _flags action/);
     my ($self, @tuple) = @_;
 
     $self->_debug("$self->{name}: INSERT(@{[map {qq{`$_'}} @tuple]})") if $self->{debug} >= 3;
 
-    my $flags = 0;
-    $flags |= UPDATE_BASE if $param->{update_base};
-
+    my $flags = $param->{_flags} || 0;
+    $param->{action} ||= 'set';
+    if ($param->{action} eq 'add') {
+        $flags |= INSERT_ADD;
+    } elsif ($param->{action} eq 'replace') {
+        $flags |= INSERT_REPLACE;
+    } elsif ($param->{action} ne 'set') {
+        confess "$self->{name}: Bad insert action `$param->{action}'";
+    }
     my $chkkey = $namespace->{check_keys};
     my $fmt = $namespace->{field_format};
     for (0..$#tuple) {
@@ -259,8 +313,7 @@ sub _unpack_affected {
 }
 
 sub NPRM () { 3 }
-sub _pack_keys
-{
+sub _pack_keys {
     my ($self, $ns, $idx) = @_;
 
     my $keys   = $idx->{keys};
@@ -296,37 +349,20 @@ sub _PackSelect {
     my ($self, $param, $namespace, @keys) = @_;
     return '' unless @keys;
     $self->_pack_keys($namespace, $param->{index}, @keys);
-    return pack("LLLLL a*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), scalar(@keys), join('',@keys));
-}
-
-sub SelectUnion {
-    my ($param) = $_[0]->_validate_param(\@_, qw/raw/);
-    my ($self, @reqs) = @_;
-    return [] unless @reqs;
-    confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs;
-    $param->{raw} ||= $self->{default_raw};
-    $param->{want} ||= 0;
-    for my $req (@reqs) {
-        my ($param, $namespace) = $self->_validate_param($req, qw/namespace use_index raw limit offset/);
-        $req = {
-            payload   => $self->_PackSelect($param, $namespace, $req),
-            param     => $param,
-            namespace => $namespace,
-        };
-    }
-    my $r = $self->_chat(
-        msg      => 18,
-        payload  => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs),
-        unpack   => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) },
-        retry    => 3,
-        timeout  => $param->{timeout} || $self->{select_timeout},
-    ) or return;
-    confess __LINE__."$self->{name}: something wrong" if @$r != @reqs;
-    my $ea = each_arrayref $r, \@reqs;
-    while(my ($res, $req) = $ea->()) {
-        $self->_PostSelect($res, { hashify => $req->{namespace}->{hashify}||$self->{hashify}, %$param, %{$req->{param}}, namespace => $req->{namespace} });
+    my $format = "";
+    if ($param->{format}) {
+        my $f = $namespace->{byfield_unpack_format};
+        $param->{unpack_format} = join '', map { $f->[$_->{field}] } @{$param->{format}};
+        $format = pack 'l*', scalar @{$param->{format}}, map {
+            if($_->{full}) {
+                $_->{offset} = 0;
+                $_->{length} = 'max';
+            }
+            $_->{length} = 0x7FFFFFFF if $_->{length} eq 'max';
+            @$_{qw/field offset length/}
+        } @{$param->{format}};
     }
-    return $r;
+    return pack("LLLL a* La*", $namespace->{namespace}, $param->{index}->{id}, $param->{offset} || 0, $param->{limit} || scalar(@keys), $format, scalar(@keys), join('',@keys));
 }
 
 sub _PostSelect {
@@ -336,14 +372,15 @@ sub _PostSelect {
     }
 }
 
-sub Select
-{
+my @select_param_ok = qw/namespace use_index raw want next_rows limit offset raise hashify timeout format hash_by/;
+sub Select {
     confess q/Select isnt callable in void context/ unless defined wantarray;
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace use_index raw want next_rows limit offset/);
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, @select_param_ok);
     my ($self, @keys) = @_;
-    @keys = @{$keys[0]} if ref $keys[0] eq 'ARRAY';
+    local $self->{raise} = $param->{raise} if defined $param->{raise};
+    @keys = @{$keys[0]} if ref $keys[0] eq 'ARRAY' and 1 == @{$param->{index}->{keys}} || ref $keys[0]->[0] eq 'ARRAY';
 
-    $self->_debug("$self->{name}: SELECT($namespace->{namespace})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3;
+    $self->_debug("$self->{name}: SELECT($namespace->{namespace}/$param->{use_index})[@{[map{ref$_?qq{[@$_]}:$_}@keys]}]") if $self->{debug} >= 3;
 
     my ($msg,$payload);
     if(exists $param->{next_rows}) {
@@ -352,25 +389,43 @@ sub Select
         $self->_pack_keys($namespace, $param->{index}, @keys);
         $payload = pack("LL a*", $namespace->{namespace}, $param->{next_rows}, join('',@keys)),
     } else {
-        $msg = 17;
-        $payload = $self->_PackSelect($param, $namespace, @keys) or return [];
+        $payload = $self->_PackSelect($param, $namespace, @keys);
+        $msg = $param->{format} ? 21 : 17;
     }
 
+    local $namespace->{unpack_format} = $param->{unpack_format} if $param->{unpack_format};
+
     my $r = [];
-    if (@keys) {
+    if (@keys && $payload) {
         $r = $self->_chat(
             msg      => $msg,
             payload  => $payload,
             unpack   => sub { $self->_unpack_select($namespace, "SELECT", @_) },
-            retry    => 3,
+            retry    => $self->{select_retry},
             timeout  => $param->{timeout} || $self->{select_timeout},
-        );
+        ) or return;
     }
 
-    $param->{raw} ||= $self->{default_raw};
+    $param->{raw} = $self->{default_raw} unless exists $param->{raw};
     $param->{want} ||= !1;
 
-    $self->_PostSelect($r, { hashify => $namespace->{hashify}||$self->{hashify}, %$param, namespace => $namespace });
+    $self->_PostSelect($r, { hashify => $param->{hashify}||$namespace->{hashify}||$self->{hashify}, %$param, namespace => $namespace });
+
+    if(defined(my $p = $param->{hash_by})) {
+        my %h;
+        if(@$r) {
+            if (ref $r->[0] eq 'HASH') {
+                confess "Bad hash_by `$p' for HASH" unless exists $r->[0]->{$p};
+                $h{$_->{$p}} = $_ for @$r;
+            } elsif(ref $r->[0] eq 'ARRAY') {
+                confess "Bad hash_by `$p' for ARRAY" unless $p =~ m/^\d+$/ && $p >= 0 && $p < @{$r->[0]};
+                $h{$_->[$p]} = $_ for @$r;
+            } else {
+                confess "i dont know how to hash_by ".ref($r->[0]);
+            }
+        }
+        return \%h;
+    }
 
     return $r if $param->{want} eq 'arrayref';
 
@@ -382,8 +437,39 @@ sub Select
     }
 }
 
-sub Delete
-{
+sub SelectUnion {
+    confess "not supported yet";
+    my ($param) = $_[0]->_validate_param(\@_, qw/raw raise/);
+    my ($self, @reqs) = @_;
+    return [] unless @reqs;
+    local $self->{raise} = $param->{raise} if defined $param->{raise};
+    confess "bad param" if grep { ref $_ ne 'ARRAY' } @reqs;
+    $param->{raw} = $self->{default_raw} unless exists $param->{raw};
+    $param->{want} ||= 0;
+    for my $req (@reqs) {
+        my ($param, $namespace) = $self->_validate_param($req, @select_param_ok);
+        $req = {
+            payload   => $self->_PackSelect($param, $namespace, $req),
+            param     => $param,
+            namespace => $namespace,
+        };
+    }
+    my $r = $self->_chat(
+        msg      => 18,
+        payload  => pack("L (a*)*", scalar(@reqs), map { $_->{payload} } @reqs),
+        unpack   => sub { $self->_unpack_select_multi([map { $_->{namespace} } @reqs], "SMULTI", @_) },
+        retry    => $self->{select_retry},
+        timeout  => $param->{timeout} || $self->{select_timeout},
+    ) or return;
+    confess __LINE__."$self->{name}: something wrong" if @$r != @reqs;
+    my $ea = each_arrayref $r, \@reqs;
+    while(my ($res, $req) = $ea->()) {
+        $self->_PostSelect($res, { hashify => $req->{namespace}->{hashify}||$self->{hashify}, %$param, %{$req->{param}}, namespace => $req->{namespace} });
+    }
+    return $r;
+}
+
+sub Delete {
     my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace/);
     my ($self, $key) = @_;
 
@@ -404,6 +490,7 @@ sub OP_ADD          () { 1 }
 sub OP_AND          () { 2 }
 sub OP_XOR          () { 3 }
 sub OP_OR           () { 4 }
+sub OP_SPLICE       () { 5 }
 
 my %update_ops = (
     set         => OP_SET,
@@ -411,16 +498,49 @@ my %update_ops = (
     and         => OP_AND,
     xor         => OP_XOR,
     or          => OP_OR,
+    splice      => sub {
+        confess "value for operation splice must be an ARRAYREF of <int[, int[, string]]>" if ref $_[0] ne 'ARRAY' || @{$_[0]} < 1;
+        $_[0]->[0] = 0x7FFFFFFF unless defined $_[0]->[0];
+        $_[0]->[0] = pack 'l', $_[0]->[0];
+        $_[0]->[1] = defined $_[0]->[1] ? pack 'l', $_[0]->[1] : '';
+        $_[0]->[2] = '' unless defined $_[0]->[2];
+        return (OP_SPLICE, [ pack '(w/a)*', @{$_[0]} ]);
+    },
+    append      => sub { splice => [undef,  0,     $_[0]] },
+    prepend     => sub { splice => [0,      0,     $_[0]] },
+    cutbeg      => sub { splice => [0,      $_[0], ''   ] },
+    cutend      => sub { splice => [-$_[0], $_[0], ''   ] },
+    substr      => 'splice',
 );
 
+!ref $_ && m/^\D/ and $_ = $update_ops{$_} || die "bad link" for values %update_ops;
+
 my %update_arg_fmt = (
     (map { $_ => 'l' } OP_ADD),
     (map { $_ => 'L' } OP_AND, OP_XOR, OP_OR),
 );
 
-sub UpdateMulti
-{
-    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace want_result update_base update_flags/);
+my %ops_type = (
+    (map { $_ => 'any'    } OP_SET),
+    (map { $_ => 'number' } OP_ADD, OP_AND, OP_XOR, OP_OR),
+    (map { $_ => 'string' } OP_SPLICE),
+);
+
+BEGIN {
+    for my $op (qw/Append Prepend Cutbeg Cutend Substr/) {
+        eval q/
+            sub /.$op.q/ {
+                my $param = ref $_[-1] eq 'HASH' ? pop : {};
+                my ($self, $key, $field_num, $val) = @_;
+                $self->UpdateMulti($key, [ $field_num => /.lc($op).q/ => $val ], $param);
+            }
+            1;
+        / or die $@;
+    }
+}
+
+sub UpdateMulti {
+    my ($param, $namespace) = $_[0]->_validate_param(\@_, qw/namespace want_result _flags raw/);
     my ($self, $key, @op) = @_;
 
     $self->_debug("$self->{name}: UPDATEMULTI($namespace->{namespace}=$key)[@{[map{qq{[@$_]}}@op]}]") if $self->{debug} >= 3;
@@ -428,17 +548,17 @@ sub UpdateMulti
     confess "$self->{name}\->UpdateMulti: for now key cardinality of 1 is only allowed" unless 1 == @{$param->{index}->{keys}};
     confess "$self->{name}: too many op" if scalar @op > 128;
 
-    my $flags = 0;
+    my $flags = $param->{_flags} || 0;
     $flags |= WANT_RESULT if $param->{want_result};
-    $flags |= UPDATE_BASE if $param->{update_base};
-    $flags |= UPDATE_FLAGS if $param->{update_flags};
 
     my $fmt = $namespace->{field_format};
 
     foreach (@op) {
         confess "$self->{name}: bad op <$_>" if ref ne 'ARRAY' or @$_ != 3;
         my ($field_num, $op, $value) = @$_;
+        my $field_type = $namespace->{string_keys}->{$field_num} ? 'string' : 'number';
 
+        my $is_array = 0;
         if ($op eq 'bit_set') {
             $op = OP_OR;
         } elsif ($op eq 'bit_clear') {
@@ -452,9 +572,18 @@ sub UpdateMulti
             $op = $update_ops{$op};
         }
 
-        confess "dunno what to do with ref `$value'" if ref $value;
+        while(ref $op eq 'CODE') {
+            ($op, $value) = &$op($value);
+            $op = $update_ops{$op} if exists $update_ops{$op};
+        }
+
+        confess "Are you sure you want to apply `$ops_type{$op}' operation to $field_type field?" if $ops_type{$op} ne $field_type && $ops_type{$op} ne 'any';
+
+        $value = [ $value ] unless ref $value;
+        confess "dunno what to do with ref `$value'" if ref $value ne 'ARRAY';
+
         confess "bad fieldnum: $field_num" if $field_num >= @$fmt;
-        $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], $value);
+        $value = pack($update_arg_fmt{$op} || $fmt->[$field_num], @$value);
         $_ = pack('LCw/a*', $field_num, $op, $value);
     }
 
@@ -467,15 +596,13 @@ sub UpdateMulti
     );
 }
 
-sub Update
-{
+sub Update {
     my $param = ref $_[-1] eq 'HASH' ? pop : {};
     my ($self, $key, $field_num, $value) = @_;
     $self->UpdateMulti($key, [$field_num => set => $value ], $param);
 }
 
-sub AndXorAdd
-{
+sub AndXorAdd {
     my $param = ref $_[-1] eq 'HASH' ? pop : {};
     my ($self, $key, $field_num, $and, $xor, $add) = @_;
     my @upd;
@@ -485,8 +612,7 @@ sub AndXorAdd
     $self->UpdateMulti($key, @upd, $param);
 }
 
-sub Bit
-{
+sub Bit {
     my $param = ref $_[-1] eq 'HASH' ? pop : {};
     my ($self, $key, $field_num, %arg) = @_;
     confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(bit_clear|bit_set|set)$/ } keys(%arg);
@@ -501,8 +627,7 @@ sub Bit
     $self->UpdateMulti($key, @op, $param);
 }
 
-sub Num
-{
+sub Num {
     my $param = ref $_[-1] eq 'HASH' ? pop : {};
     my ($self, $key, $field_num, %arg) = @_;
     confess "$self->{name}: unknown op '@{[keys %arg]}'"  if grep { not /^(num_add|num_sub|set)$/ } keys(%arg);
diff --git a/mod/silverbox/client/perl/MR/Storage/Const.pm b/mod/silverbox/client/perl/lib/MR/Storage/Const.pm
similarity index 96%
rename from mod/silverbox/client/perl/MR/Storage/Const.pm
rename to mod/silverbox/client/perl/lib/MR/Storage/Const.pm
index dae943a5ca66ae8f1ce5986daf489024672afdc8..64f1deee76fb7439be5de49f449bac4d0fd03df0 100644
--- a/mod/silverbox/client/perl/MR/Storage/Const.pm
+++ b/mod/silverbox/client/perl/lib/MR/Storage/Const.pm
@@ -1,12 +1,11 @@
-# $Id: Const.pm,v 1.12 2010/06/17 13:35:13 dubravsky Exp $
-
 use strict;
 use Exporter;
 
 package MR::Storage::Const;
 use base qw/Exporter/;
 
-use vars qw/@EXPORT_OK %EXPORT_TAGS %ERRORS/;
+use vars qw/$VERSION @EXPORT_OK %EXPORT_TAGS %ERRORS/;
+$VERSION = 0;
 
 my (@ERRORS);
 $EXPORT_TAGS{all}    = \@EXPORT_OK;
@@ -85,6 +84,8 @@ BEGIN {
     0x00003400  => q{Error during iconv},
     0x00003500  => q{Event isn't contained in this node},
     0x00003600  => q{Proxy reply: destination node timed out},
+    0x00003700  => q{Node found},
+    0x00003800  => q{Index violation},
 );
 
 sub ErrorText {
diff --git a/mod/silverbox/client/ruby/README b/mod/silverbox/client/ruby/README
new file mode 100644
index 0000000000000000000000000000000000000000..9863c682996aec42be34cdebe8e8c03aadfd81b7
--- /dev/null
+++ b/mod/silverbox/client/ruby/README
@@ -0,0 +1 @@
+A toy implementation of ruby client library for IProto/Silverbox binary protocol
\ No newline at end of file
diff --git a/mod/silverbox/client/ruby/iproto.rb b/mod/silverbox/client/ruby/iproto.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03362ce88e2ee5b8a51d217369c38ec63a9734ea
--- /dev/null
+++ b/mod/silverbox/client/ruby/iproto.rb
@@ -0,0 +1,133 @@
+#
+# Copyright (C) 2009, 2010 Mail.RU
+# Copyright (C) 2009, 2010 Yuriy Vostrikov
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+require 'socket'
+
+IPROTO_PING = 0xff00
+
+include Socket::Constants
+
+class IProtoError < RuntimeError
+end
+
+class IProto
+  @@sync = 0
+
+  def initialize(server, param = {})
+    host, port = server.split(/:/)
+    @end_point = [host, port.to_i]
+    [:logger, :reconnect].each do |p|
+      instance_variable_set "@#{p}", param[p] if param.has_key? p
+    end
+
+    reconnect
+  end
+
+  attr_reader :sock
+
+  def hexdump(string)
+    string.unpack('C*').map{ |c| "%02x" % c }.join(' ')
+  end
+
+  def next_sync
+    @@sync += 1
+    if @@sync > 0xffffffff
+      @@sync = 0
+    end
+    @@sync
+  end
+
+  def reconnect
+    @sock = TCPSocket.new(*@end_point)
+    @sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
+  end
+
+  def close
+    @sock.close unless @sock.closed
+  end
+
+  def send(message)
+    begin
+      reconnect if @sock.closed? and @reconnect
+
+      sync = self.next_sync
+      payload = message[:raw] || message[:data].pack(message[:pack] || 'L*')
+
+      buf = [message[:code], payload.bytesize, sync].pack('L3')
+      @logger.debug { "#{@end_point} => send hdr #{buf.unpack('L*').map{ |c| "%010i" % c }.join(' ')}" } if @logger
+
+      buf << payload
+      @logger.debug { "#{@end_point} => send bdy #{hexdump(payload)}" } if @logger
+
+      @sock.write(buf)
+
+      header = @sock.read(12)
+      raise IProtoError, "can't read header" unless header
+      header = header.unpack('L3')
+      @logger.debug { "#{@end_point} => recv hdr #{header.map{ |c| "%010i" % c }.join(' ')}" } if @logger
+
+      raise IProtoError, "response:#{header[0]} != message:#{message[:code]}" if header[0] != message[:code]
+      raise IProtoError, "response:#{header[2]} != message:#{sync}" if header[2] != sync
+
+      data = @sock.read(header[1])
+      @logger.debug { "#{@end_point} => recv bdy #{hexdump(data)}" } if @logger
+      data
+    rescue Exception => exc
+      @sock.close
+      raise exc
+    end
+  end
+
+  def msg(message)
+    reply = send message
+    result = pre_process_reply message, reply
+
+    return yield result if block_given?
+    result
+  end
+
+  def ping
+    send :code => IPROTO_PING, :raw => ''
+    :pong
+  end
+
+  def pre_process_reply(message, data)
+    if message[:unpack]
+      data.unpack(message[:unpack])
+    else
+      data
+    end
+  end
+end
+
+class IProtoRetCode < IProto
+  def pre_process_reply(message, data)
+    raise IProtoError, "too small response" if data.nil? or data.bytesize < 4
+    ret_code = data.slice!(0, 4).unpack('L')[0]
+    raise IProtoError, "remote: #{'0x%x' % ret_code}" if ret_code != 0
+    super message, data
+  end
+end
diff --git a/mod/silverbox/client/ruby/silverbox.rb b/mod/silverbox/client/ruby/silverbox.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc7ae99dfe6128da39a99e60ed48c45cdd58654c
--- /dev/null
+++ b/mod/silverbox/client/ruby/silverbox.rb
@@ -0,0 +1,171 @@
+#
+# Copyright (C) 2009, 2010 Mail.RU
+# Copyright (C) 2009, 2010 Yuriy Vostrikov
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+require 'iproto'
+
+class SilverBox < IProtoRetCode
+  BOX_RETURN_TUPLE = 0x01
+
+  def initialize(host, param = {})
+    @namespace = param[:namespace] || 1
+    super(host)
+  end
+
+  attr_accessor :namespace
+
+  def pack_field(value)
+    case value
+    when Integer
+      [4, value].pack('wL')
+    when String
+      fail "string too long" if value.bytesize > 1024 * 1024
+      [value.bytesize, value].pack('wa*')
+    else
+      fail "unsupported field class #{value.class}"
+    end
+  end
+
+  def pack_key(key)
+    [1].pack("L") + pack_field(key)
+  end
+
+  # poor man emulation of perl's pack("w/a*")
+  def pack(values, pattern)
+    raw = []
+    pattern.split(/\s+/).each do |fmt|
+      fail "not enough values given" unless values[0]
+
+      case fmt
+      when 'l', 'L', 'a*', 'C'
+        raw << [values.shift].pack(fmt)
+      when /^(L|w)\/$/
+        raw << [values[0].length].pack($1)
+      when 'field'
+        raw << pack_field(values.shift)
+      when 'field*'
+        values.shift.each {|x| raw << pack_field(x) }
+      when 'key'
+        raw << pack_key(values.shift)
+      when 'key*'
+        values.shift.each { |x| raw << pack_key(x) }
+      else
+        fail "unknown pack format: '#{fmt}'"
+      end
+    end
+    raw.join ''
+  end
+
+  def unpack_field!(data)
+    # is there an efficient way to simulate perl's unpack("w/a") ?
+    byte_size = data.unpack(?w)[0]
+    data.slice!(0 .. [byte_size].pack(?w).bytesize - 1)
+    data.slice!(0 .. byte_size - 1)
+  end
+
+  def unpack_tuple!(data)
+    tuple = []
+    byte_size, cardinality = data.slice!(0 .. 7).unpack("LL")
+    tuple_data = data.slice!(0 .. byte_size - 1)
+    cardinality.times do
+      tuple << unpack_field!(tuple_data)
+    end
+    tuple
+  end
+
+  def unpack_reply!(reply, param)
+    tuples_affected = reply.slice!(0 .. 3).unpack(?L)[0]
+    if param[:return_tuple]
+      tuples = []
+      tuples_affected.times do
+        tuples << unpack_tuple!(reply)
+      end
+      tuples
+    else
+      tuples_affected
+    end
+  end
+
+  private :pack_field, :pack_key, :pack
+  private :unpack_field!, :unpack_tuple!, :unpack_reply!
+
+  def insert(tuple, param = {})
+    namespace = param[:namespace] || @namespace
+    flags = 0
+    flags |= BOX_RETURN_TUPLE if param[:return_tuple]
+
+    reply = msg :code => 13, :raw => pack([namespace, flags, tuple], 'L L L/ field*')
+    unpack_reply!(reply, param)
+  end
+
+  def delete(key, param = {})
+    namespace = param[:namespace] || @namespace
+
+    tuples_affected, = msg(:code => 20, :raw => pack([namespace, key], 'L key')).unpack(?L)
+    tuples_affected
+  end
+
+  def select(*keys)
+    keys = keys[0] if keys[0].is_a? Array
+    return [] if keys.length == 0
+    param = keys[-1].is_a?(Hash) ? keys.pop : {}
+    namespace = param[:namespace] || @namespace
+    offset = param[:offset] || 0
+    limit = param[:limit] || -1 # UINT32_MAX actually
+    index = param[:index] || 0
+
+    reply = msg :code => 17, :raw => pack([namespace, index, offset, limit, keys], 'L L L L L/ key*')
+    unpack_reply!(reply, :return_tuple => true)
+  end
+
+  def update_fields(key, *ops)
+    return [] if ops.length == 0
+    param = ops[-1].is_a?(Hash) ? ops.pop : {}
+    namespace = param[:namespace] || @namespace
+    flags = 0
+    flags |= BOX_RETURN_TUPLE if param[:return_tuple]
+    ops.map! do |op|
+      fail "op must be Array" unless op.is_a? Array
+      case op[1]
+      when :set
+        op = [op[0], 0x00, pack_field(op[2])].pack("LCa*")
+      when :add
+        op = [op[0], 0x01, 4, op[2]].pack("LCwI")
+      when :and
+        op = [op[0], 0x02, 4, op[2]].pack("LCwL")
+      when :or
+        op = [op[0], 0x03, 4, op[2]].pack("LCwL")
+      when :xor
+        op = [op[0], 0x04, 4, op[2]].pack("LCwL")
+      else
+        fail "unsupported op: '#{op[1]}'"
+      end
+    end
+
+    reply = msg :code => 19, :raw => pack([namespace, flags, key, ops.length, ops.join('')], 'L L key L a*')
+    unpack_reply!(reply, param)
+  end
+end
+
diff --git a/mod/silverbox/memcached.c b/mod/silverbox/memcached.c
index a18a905d0893cf07bd2f92472550d5ac2e9bc4a2..f8a5ca1268f48e70cb8516204b92734b5c9c9e7b 100644
--- a/mod/silverbox/memcached.c
+++ b/mod/silverbox/memcached.c
@@ -42,6 +42,16 @@
 #include <mod/silverbox/box.h>
 #include <stat.h>
 
+
+#define STAT(_)					\
+        _(MEMC_GET, 1)				\
+        _(MEMC_GET_MISS, 2)			\
+	_(MEMC_GET_HIT, 3)
+
+ENUM(memcached_stat, STAT);
+STRS(memcached_stat, STAT);
+int stat_base;
+
 struct index *memcached_index;
 
 /* memcached tuple format:
@@ -54,7 +64,7 @@ struct meta {
 } __packed__;
 
 
-#line 58 "mod/silverbox/memcached.c"
+#line 68 "mod/silverbox/memcached.c"
 static const int memcached_start = 1;
 static const int memcached_first_final = 197;
 static const int memcached_error = 0;
@@ -62,7 +72,7 @@ static const int memcached_error = 0;
 static const int memcached_en_main = 1;
 
 
-#line 57 "mod/silverbox/memcached.rl"
+#line 67 "mod/silverbox/memcached.rl"
 
 
 
@@ -126,7 +136,7 @@ delete(struct box_txn *txn, void *key)
 static struct box_tuple *
 find(void *key)
 {
-	return index_find(memcached_index, 1, key);
+	return memcached_index->find(memcached_index, key);
 }
 
 static struct meta *
@@ -201,7 +211,7 @@ flush_all(void *data)
 {
 	uintptr_t delay = (uintptr_t)data;
 	fiber_sleep(delay - ev_now());
-	khash_t(lstr2ptr_map) *map = memcached_index->map.str_map;
+	khash_t(lstr2ptr_map) *map = memcached_index->idx.str_hash;
 	for (khiter_t i = kh_begin(map); i != kh_end(map); i++) {
 		if (kh_exist(map, i)) {
 			struct box_tuple *tuple = kh_value(map, i);
@@ -251,12 +261,12 @@ memcached_dispatch(struct box_txn *txn)
 })
 
 	
-#line 255 "mod/silverbox/memcached.c"
+#line 265 "mod/silverbox/memcached.c"
 	{
 	cs = memcached_start;
 	}
 
-#line 260 "mod/silverbox/memcached.c"
+#line 270 "mod/silverbox/memcached.c"
 	{
 	if ( p == pe )
 		goto _test_eof;
@@ -314,7 +324,7 @@ case 5:
 		goto st0;
 	goto tr15;
 tr15:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -331,7 +341,7 @@ case 5:
 	if ( ++p == pe )
 		goto _test_eof6;
 case 6:
-#line 335 "mod/silverbox/memcached.c"
+#line 345 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st7;
 	goto st0;
@@ -345,49 +355,49 @@ case 7:
 		goto tr17;
 	goto st0;
 tr17:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st8;
 st8:
 	if ( ++p == pe )
 		goto _test_eof8;
 case 8:
-#line 356 "mod/silverbox/memcached.c"
+#line 366 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr18;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st8;
 	goto st0;
 tr18:
-#line 489 "mod/silverbox/memcached.rl"
+#line 499 "mod/silverbox/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st9;
 st9:
 	if ( ++p == pe )
 		goto _test_eof9;
 case 9:
-#line 370 "mod/silverbox/memcached.c"
+#line 380 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st9;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr21;
 	goto st0;
 tr21:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st10;
 st10:
 	if ( ++p == pe )
 		goto _test_eof10;
 case 10:
-#line 384 "mod/silverbox/memcached.c"
+#line 394 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr22;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st10;
 	goto st0;
 tr22:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -398,21 +408,21 @@ case 10:
 	if ( ++p == pe )
 		goto _test_eof11;
 case 11:
-#line 402 "mod/silverbox/memcached.c"
+#line 412 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st11;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr25;
 	goto st0;
 tr25:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st12;
 st12:
 	if ( ++p == pe )
 		goto _test_eof12;
 case 12:
-#line 416 "mod/silverbox/memcached.c"
+#line 426 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr26;
 		case 13: goto tr27;
@@ -422,11 +432,11 @@ case 12:
 		goto st12;
 	goto st0;
 tr26:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -447,13 +457,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 250 "mod/silverbox/memcached.rl"
+#line 260 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -464,9 +474,9 @@ case 12:
 		}
 	goto st197;
 tr30:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -487,13 +497,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 250 "mod/silverbox/memcached.rl"
+#line 260 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -504,11 +514,11 @@ case 12:
 		}
 	goto st197;
 tr39:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -529,13 +539,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 250 "mod/silverbox/memcached.rl"
+#line 260 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -546,11 +556,11 @@ case 12:
 		}
 	goto st197;
 tr58:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -571,13 +581,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 279 "mod/silverbox/memcached.rl"
+#line 289 "mod/silverbox/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -606,9 +616,9 @@ case 12:
 		}
 	goto st197;
 tr62:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -629,13 +639,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 279 "mod/silverbox/memcached.rl"
+#line 289 "mod/silverbox/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -664,11 +674,11 @@ case 12:
 		}
 	goto st197;
 tr71:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -689,13 +699,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 279 "mod/silverbox/memcached.rl"
+#line 289 "mod/silverbox/memcached.rl"
 	{
 			struct tbuf *b;
 			void *value;
@@ -724,11 +734,11 @@ case 12:
 		}
 	goto st197;
 tr91:
-#line 491 "mod/silverbox/memcached.rl"
+#line 501 "mod/silverbox/memcached.rl"
 	{cas = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -749,13 +759,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/silverbox/memcached.rl"
+#line 278 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -768,9 +778,9 @@ case 12:
 		}
 	goto st197;
 tr95:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -791,13 +801,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/silverbox/memcached.rl"
+#line 278 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -810,11 +820,11 @@ case 12:
 		}
 	goto st197;
 tr105:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -835,13 +845,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 268 "mod/silverbox/memcached.rl"
+#line 278 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -854,17 +864,17 @@ case 12:
 		}
 	goto st197;
 tr118:
-#line 492 "mod/silverbox/memcached.rl"
+#line 502 "mod/silverbox/memcached.rl"
 	{incr = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 306 "mod/silverbox/memcached.rl"
+#line 316 "mod/silverbox/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -917,15 +927,15 @@ case 12:
 		}
 	goto st197;
 tr122:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 306 "mod/silverbox/memcached.rl"
+#line 316 "mod/silverbox/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -978,17 +988,17 @@ case 12:
 		}
 	goto st197;
 tr132:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 306 "mod/silverbox/memcached.rl"
+#line 316 "mod/silverbox/memcached.rl"
 	{
 			struct meta *m;
 			struct tbuf *b;
@@ -1041,15 +1051,15 @@ case 12:
 		}
 	goto st197;
 tr141:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 357 "mod/silverbox/memcached.rl"
+#line 367 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1064,21 +1074,21 @@ case 12:
 		}
 	goto st197;
 tr146:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
 				exptime = exptime + ev_now();
 		}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 357 "mod/silverbox/memcached.rl"
+#line 367 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1093,17 +1103,17 @@ case 12:
 		}
 	goto st197;
 tr157:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 357 "mod/silverbox/memcached.rl"
+#line 367 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1118,15 +1128,15 @@ case 12:
 		}
 	goto st197;
 tr169:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 447 "mod/silverbox/memcached.rl"
+#line 457 "mod/silverbox/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1138,17 +1148,17 @@ case 12:
 		}
 	goto st197;
 tr174:
-#line 493 "mod/silverbox/memcached.rl"
+#line 503 "mod/silverbox/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 447 "mod/silverbox/memcached.rl"
+#line 457 "mod/silverbox/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1160,17 +1170,17 @@ case 12:
 		}
 	goto st197;
 tr185:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 447 "mod/silverbox/memcached.rl"
+#line 457 "mod/silverbox/memcached.rl"
 	{
 			if (flush_delay > 0) {
 				struct fiber *f = fiber_create("flush_all", -1, -1, flush_all, (void *)flush_delay);
@@ -1182,21 +1192,21 @@ case 12:
 		}
 	goto st197;
 tr195:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 370 "mod/silverbox/memcached.rl"
+#line 380 "mod/silverbox/memcached.rl"
 	{
 			txn->op = SELECT;
 			fiber_register_cleanup((void *)txn_cleanup, txn);
-			stat_collect("MEMC_GET", 1);
+			stat_collect(stat_base, MEMC_GET, 1);
 			stats.cmd_get++;
-			say_debug("nesuring space for %i keys", keys->len);
+			say_debug("ensuring space for %"PRI_SZ" keys", keys_count);
 			iov_ensure(keys_count * 5 + 1);
 			while (keys_count-- > 0) {
 				struct box_tuple *tuple;
@@ -1214,7 +1224,7 @@ case 12:
 				key_len = load_varint32(&key);
 
 				if (tuple == NULL || tuple->flags & GHOST) {
-					stat_collect("MEMC_GET_MISS", 1);
+					stat_collect(stat_base, MEMC_GET_MISS, 1);
 					stats.get_misses++;
 					continue;
 				}
@@ -1241,11 +1251,11 @@ case 12:
 
 				if (m->exptime > 0 && m->exptime < ev_now()) {
 					stats.get_misses++;
-					stat_collect("MEMC_GET_MISS", 1);
+					stat_collect(stat_base, MEMC_GET_MISS, 1);
 					continue;
 				} else {
 					stats.get_hits++;
-					stat_collect("MEMC_GET_HIT", 1);
+					stat_collect(stat_base, MEMC_GET_HIT, 1);
 				}
 
 				tuple_txn_ref(txn, tuple);
@@ -1269,25 +1279,25 @@ case 12:
 		}
 	goto st197;
 tr213:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 461 "mod/silverbox/memcached.rl"
+#line 471 "mod/silverbox/memcached.rl"
 	{
 			return 0;
 		}
 	goto st197;
 tr233:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1308,13 +1318,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 259 "mod/silverbox/memcached.rl"
+#line 269 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1325,9 +1335,9 @@ case 12:
 		}
 	goto st197;
 tr237:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1348,13 +1358,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 259 "mod/silverbox/memcached.rl"
+#line 269 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1365,11 +1375,11 @@ case 12:
 		}
 	goto st197;
 tr246:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1390,13 +1400,13 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 259 "mod/silverbox/memcached.rl"
+#line 269 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			struct box_tuple *tuple = find(key);
@@ -1407,11 +1417,11 @@ case 12:
 		}
 	goto st197;
 tr263:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1432,22 +1442,22 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 245 "mod/silverbox/memcached.rl"
+#line 255 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr267:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1468,24 +1478,24 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 245 "mod/silverbox/memcached.rl"
+#line 255 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr276:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 495 "mod/silverbox/memcached.rl"
+#line 505 "mod/silverbox/memcached.rl"
 	{
 			size_t parsed = p - (u8 *)fiber->rbuf->data;
 			while (fiber->rbuf->len - parsed < bytes + 2) {
@@ -1506,28 +1516,28 @@ case 12:
 				goto exit;
 			}
 		}
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 245 "mod/silverbox/memcached.rl"
+#line 255 "mod/silverbox/memcached.rl"
 	{
 			key = read_field(keys);
 			STORE;
 		}
 	goto st197;
 tr281:
-#line 522 "mod/silverbox/memcached.rl"
+#line 532 "mod/silverbox/memcached.rl"
 	{ p++; }
-#line 516 "mod/silverbox/memcached.rl"
+#line 526 "mod/silverbox/memcached.rl"
 	{
 			done = true;
 			stats.bytes_read += p - (u8 *)fiber->rbuf->data;
 			tbuf_peek(fiber->rbuf, p - (u8 *)fiber->rbuf->data);
 		}
-#line 457 "mod/silverbox/memcached.rl"
+#line 467 "mod/silverbox/memcached.rl"
 	{
 			print_stats();
 		}
@@ -1536,33 +1546,33 @@ case 12:
 	if ( ++p == pe )
 		goto _test_eof197;
 case 197:
-#line 1540 "mod/silverbox/memcached.c"
+#line 1550 "mod/silverbox/memcached.c"
 	goto st0;
 tr27:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st13;
 tr40:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st13;
 st13:
 	if ( ++p == pe )
 		goto _test_eof13;
 case 13:
-#line 1554 "mod/silverbox/memcached.c"
+#line 1564 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr30;
 	goto st0;
 tr28:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st14;
 st14:
 	if ( ++p == pe )
 		goto _test_eof14;
 case 14:
-#line 1566 "mod/silverbox/memcached.c"
+#line 1576 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 32: goto st14;
 		case 110: goto st15;
@@ -1655,18 +1665,18 @@ case 26:
 		goto tr45;
 	goto st0;
 tr45:
-#line 530 "mod/silverbox/memcached.rl"
+#line 540 "mod/silverbox/memcached.rl"
 	{append = true; }
 	goto st27;
 tr209:
-#line 531 "mod/silverbox/memcached.rl"
+#line 541 "mod/silverbox/memcached.rl"
 	{append = false;}
 	goto st27;
 st27:
 	if ( ++p == pe )
 		goto _test_eof27;
 case 27:
-#line 1670 "mod/silverbox/memcached.c"
+#line 1680 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st27;
@@ -1675,7 +1685,7 @@ case 27:
 		goto st0;
 	goto tr46;
 tr46:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1692,7 +1702,7 @@ case 27:
 	if ( ++p == pe )
 		goto _test_eof28;
 case 28:
-#line 1696 "mod/silverbox/memcached.c"
+#line 1706 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st29;
 	goto st0;
@@ -1706,49 +1716,49 @@ case 29:
 		goto tr49;
 	goto st0;
 tr49:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st30;
 st30:
 	if ( ++p == pe )
 		goto _test_eof30;
 case 30:
-#line 1717 "mod/silverbox/memcached.c"
+#line 1727 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr50;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st30;
 	goto st0;
 tr50:
-#line 489 "mod/silverbox/memcached.rl"
+#line 499 "mod/silverbox/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st31;
 st31:
 	if ( ++p == pe )
 		goto _test_eof31;
 case 31:
-#line 1731 "mod/silverbox/memcached.c"
+#line 1741 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st31;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr53;
 	goto st0;
 tr53:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st32;
 st32:
 	if ( ++p == pe )
 		goto _test_eof32;
 case 32:
-#line 1745 "mod/silverbox/memcached.c"
+#line 1755 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr54;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st32;
 	goto st0;
 tr54:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -1759,21 +1769,21 @@ case 32:
 	if ( ++p == pe )
 		goto _test_eof33;
 case 33:
-#line 1763 "mod/silverbox/memcached.c"
+#line 1773 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st33;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr57;
 	goto st0;
 tr57:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st34;
 st34:
 	if ( ++p == pe )
 		goto _test_eof34;
 case 34:
-#line 1777 "mod/silverbox/memcached.c"
+#line 1787 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr58;
 		case 13: goto tr59;
@@ -1783,30 +1793,30 @@ case 34:
 		goto st34;
 	goto st0;
 tr59:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st35;
 tr72:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st35;
 st35:
 	if ( ++p == pe )
 		goto _test_eof35;
 case 35:
-#line 1798 "mod/silverbox/memcached.c"
+#line 1808 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr62;
 	goto st0;
 tr60:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st36;
 st36:
 	if ( ++p == pe )
 		goto _test_eof36;
 case 36:
-#line 1810 "mod/silverbox/memcached.c"
+#line 1820 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 32: goto st36;
 		case 110: goto st37;
@@ -1896,7 +1906,7 @@ case 47:
 		goto st0;
 	goto tr76;
 tr76:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -1913,7 +1923,7 @@ case 47:
 	if ( ++p == pe )
 		goto _test_eof48;
 case 48:
-#line 1917 "mod/silverbox/memcached.c"
+#line 1927 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st49;
 	goto st0;
@@ -1927,49 +1937,49 @@ case 49:
 		goto tr78;
 	goto st0;
 tr78:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st50;
 st50:
 	if ( ++p == pe )
 		goto _test_eof50;
 case 50:
-#line 1938 "mod/silverbox/memcached.c"
+#line 1948 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr79;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st50;
 	goto st0;
 tr79:
-#line 489 "mod/silverbox/memcached.rl"
+#line 499 "mod/silverbox/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st51;
 st51:
 	if ( ++p == pe )
 		goto _test_eof51;
 case 51:
-#line 1952 "mod/silverbox/memcached.c"
+#line 1962 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st51;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr82;
 	goto st0;
 tr82:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st52;
 st52:
 	if ( ++p == pe )
 		goto _test_eof52;
 case 52:
-#line 1966 "mod/silverbox/memcached.c"
+#line 1976 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr83;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st52;
 	goto st0;
 tr83:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -1980,49 +1990,49 @@ case 52:
 	if ( ++p == pe )
 		goto _test_eof53;
 case 53:
-#line 1984 "mod/silverbox/memcached.c"
+#line 1994 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st53;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr86;
 	goto st0;
 tr86:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st54;
 st54:
 	if ( ++p == pe )
 		goto _test_eof54;
 case 54:
-#line 1998 "mod/silverbox/memcached.c"
+#line 2008 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr87;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st54;
 	goto st0;
 tr87:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st55;
 st55:
 	if ( ++p == pe )
 		goto _test_eof55;
 case 55:
-#line 2012 "mod/silverbox/memcached.c"
+#line 2022 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st55;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr90;
 	goto st0;
 tr90:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st56;
 st56:
 	if ( ++p == pe )
 		goto _test_eof56;
 case 56:
-#line 2026 "mod/silverbox/memcached.c"
+#line 2036 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr91;
 		case 13: goto tr92;
@@ -2032,30 +2042,30 @@ case 56:
 		goto st56;
 	goto st0;
 tr106:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st57;
 tr92:
-#line 491 "mod/silverbox/memcached.rl"
+#line 501 "mod/silverbox/memcached.rl"
 	{cas = natoq(fstart, p);}
 	goto st57;
 st57:
 	if ( ++p == pe )
 		goto _test_eof57;
 case 57:
-#line 2047 "mod/silverbox/memcached.c"
+#line 2057 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr95;
 	goto st0;
 tr93:
-#line 491 "mod/silverbox/memcached.rl"
+#line 501 "mod/silverbox/memcached.rl"
 	{cas = natoq(fstart, p);}
 	goto st58;
 st58:
 	if ( ++p == pe )
 		goto _test_eof58;
 case 58:
-#line 2059 "mod/silverbox/memcached.c"
+#line 2069 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -2116,14 +2126,14 @@ case 65:
 	}
 	goto st0;
 tr107:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st66;
 st66:
 	if ( ++p == pe )
 		goto _test_eof66;
 case 66:
-#line 2127 "mod/silverbox/memcached.c"
+#line 2137 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr95;
 		case 13: goto st57;
@@ -2161,18 +2171,18 @@ case 70:
 		goto tr113;
 	goto st0;
 tr113:
-#line 539 "mod/silverbox/memcached.rl"
+#line 549 "mod/silverbox/memcached.rl"
 	{incr_sign = -1;}
 	goto st71;
 tr202:
-#line 538 "mod/silverbox/memcached.rl"
+#line 548 "mod/silverbox/memcached.rl"
 	{incr_sign = 1; }
 	goto st71;
 st71:
 	if ( ++p == pe )
 		goto _test_eof71;
 case 71:
-#line 2176 "mod/silverbox/memcached.c"
+#line 2186 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st71;
@@ -2181,7 +2191,7 @@ case 71:
 		goto st0;
 	goto tr114;
 tr114:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2198,7 +2208,7 @@ case 71:
 	if ( ++p == pe )
 		goto _test_eof72;
 case 72:
-#line 2202 "mod/silverbox/memcached.c"
+#line 2212 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st73;
 	goto st0;
@@ -2212,14 +2222,14 @@ case 73:
 		goto tr117;
 	goto st0;
 tr117:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st74;
 st74:
 	if ( ++p == pe )
 		goto _test_eof74;
 case 74:
-#line 2223 "mod/silverbox/memcached.c"
+#line 2233 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr118;
 		case 13: goto tr119;
@@ -2229,30 +2239,30 @@ case 74:
 		goto st74;
 	goto st0;
 tr133:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st75;
 tr119:
-#line 492 "mod/silverbox/memcached.rl"
+#line 502 "mod/silverbox/memcached.rl"
 	{incr = natoq(fstart, p);}
 	goto st75;
 st75:
 	if ( ++p == pe )
 		goto _test_eof75;
 case 75:
-#line 2244 "mod/silverbox/memcached.c"
+#line 2254 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr122;
 	goto st0;
 tr120:
-#line 492 "mod/silverbox/memcached.rl"
+#line 502 "mod/silverbox/memcached.rl"
 	{incr = natoq(fstart, p);}
 	goto st76;
 st76:
 	if ( ++p == pe )
 		goto _test_eof76;
 case 76:
-#line 2256 "mod/silverbox/memcached.c"
+#line 2266 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2313,14 +2323,14 @@ case 83:
 	}
 	goto st0;
 tr134:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st84;
 st84:
 	if ( ++p == pe )
 		goto _test_eof84;
 case 84:
-#line 2324 "mod/silverbox/memcached.c"
+#line 2334 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr122;
 		case 13: goto st75;
@@ -2367,7 +2377,7 @@ case 89:
 		goto st0;
 	goto tr140;
 tr140:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2384,7 +2394,7 @@ case 89:
 	if ( ++p == pe )
 		goto _test_eof90;
 case 90:
-#line 2388 "mod/silverbox/memcached.c"
+#line 2398 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2392,7 +2402,7 @@ case 90:
 	}
 	goto st0;
 tr147:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2400,14 +2410,14 @@ case 90:
 		}
 	goto st91;
 tr158:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st91;
 st91:
 	if ( ++p == pe )
 		goto _test_eof91;
 case 91:
-#line 2411 "mod/silverbox/memcached.c"
+#line 2421 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr141;
 	goto st0;
@@ -2425,14 +2435,14 @@ case 92:
 		goto tr144;
 	goto st0;
 tr144:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st93;
 st93:
 	if ( ++p == pe )
 		goto _test_eof93;
 case 93:
-#line 2436 "mod/silverbox/memcached.c"
+#line 2446 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr146;
 		case 13: goto tr147;
@@ -2442,7 +2452,7 @@ case 93:
 		goto st93;
 	goto st0;
 tr148:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -2453,7 +2463,7 @@ case 93:
 	if ( ++p == pe )
 		goto _test_eof94;
 case 94:
-#line 2457 "mod/silverbox/memcached.c"
+#line 2467 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2514,14 +2524,14 @@ case 101:
 	}
 	goto st0;
 tr159:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st102;
 st102:
 	if ( ++p == pe )
 		goto _test_eof102;
 case 102:
-#line 2525 "mod/silverbox/memcached.c"
+#line 2535 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr141;
 		case 13: goto st91;
@@ -2595,18 +2605,18 @@ case 111:
 	}
 	goto st0;
 tr186:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st112;
 tr175:
-#line 493 "mod/silverbox/memcached.rl"
+#line 503 "mod/silverbox/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st112;
 st112:
 	if ( ++p == pe )
 		goto _test_eof112;
 case 112:
-#line 2610 "mod/silverbox/memcached.c"
+#line 2620 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr169;
 	goto st0;
@@ -2624,14 +2634,14 @@ case 113:
 		goto tr172;
 	goto st0;
 tr172:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st114;
 st114:
 	if ( ++p == pe )
 		goto _test_eof114;
 case 114:
-#line 2635 "mod/silverbox/memcached.c"
+#line 2645 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr174;
 		case 13: goto tr175;
@@ -2641,14 +2651,14 @@ case 114:
 		goto st114;
 	goto st0;
 tr176:
-#line 493 "mod/silverbox/memcached.rl"
+#line 503 "mod/silverbox/memcached.rl"
 	{flush_delay = natoq(fstart, p);}
 	goto st115;
 st115:
 	if ( ++p == pe )
 		goto _test_eof115;
 case 115:
-#line 2652 "mod/silverbox/memcached.c"
+#line 2662 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2709,14 +2719,14 @@ case 122:
 	}
 	goto st0;
 tr187:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st123;
 st123:
 	if ( ++p == pe )
 		goto _test_eof123;
 case 123:
-#line 2720 "mod/silverbox/memcached.c"
+#line 2730 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr169;
 		case 13: goto st112;
@@ -2747,18 +2757,18 @@ case 126:
 	}
 	goto st0;
 tr191:
-#line 535 "mod/silverbox/memcached.rl"
+#line 545 "mod/silverbox/memcached.rl"
 	{show_cas = false;}
 	goto st127;
 tr198:
-#line 536 "mod/silverbox/memcached.rl"
+#line 546 "mod/silverbox/memcached.rl"
 	{show_cas = true;}
 	goto st127;
 st127:
 	if ( ++p == pe )
 		goto _test_eof127;
 case 127:
-#line 2762 "mod/silverbox/memcached.c"
+#line 2772 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 13: goto st0;
 		case 32: goto st127;
@@ -2767,7 +2777,7 @@ case 127:
 		goto st0;
 	goto tr193;
 tr193:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -2784,7 +2794,7 @@ case 127:
 	if ( ++p == pe )
 		goto _test_eof128;
 case 128:
-#line 2788 "mod/silverbox/memcached.c"
+#line 2798 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr195;
 		case 13: goto st129;
@@ -2991,7 +3001,7 @@ case 155:
 		goto st0;
 	goto tr222;
 tr222:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -3008,7 +3018,7 @@ case 155:
 	if ( ++p == pe )
 		goto _test_eof156;
 case 156:
-#line 3012 "mod/silverbox/memcached.c"
+#line 3022 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st157;
 	goto st0;
@@ -3022,49 +3032,49 @@ case 157:
 		goto tr224;
 	goto st0;
 tr224:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st158;
 st158:
 	if ( ++p == pe )
 		goto _test_eof158;
 case 158:
-#line 3033 "mod/silverbox/memcached.c"
+#line 3043 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr225;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st158;
 	goto st0;
 tr225:
-#line 489 "mod/silverbox/memcached.rl"
+#line 499 "mod/silverbox/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st159;
 st159:
 	if ( ++p == pe )
 		goto _test_eof159;
 case 159:
-#line 3047 "mod/silverbox/memcached.c"
+#line 3057 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st159;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr228;
 	goto st0;
 tr228:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st160;
 st160:
 	if ( ++p == pe )
 		goto _test_eof160;
 case 160:
-#line 3061 "mod/silverbox/memcached.c"
+#line 3071 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr229;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st160;
 	goto st0;
 tr229:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -3075,21 +3085,21 @@ case 160:
 	if ( ++p == pe )
 		goto _test_eof161;
 case 161:
-#line 3079 "mod/silverbox/memcached.c"
+#line 3089 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st161;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr232;
 	goto st0;
 tr232:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st162;
 st162:
 	if ( ++p == pe )
 		goto _test_eof162;
 case 162:
-#line 3093 "mod/silverbox/memcached.c"
+#line 3103 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr233;
 		case 13: goto tr234;
@@ -3099,30 +3109,30 @@ case 162:
 		goto st162;
 	goto st0;
 tr234:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st163;
 tr247:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st163;
 st163:
 	if ( ++p == pe )
 		goto _test_eof163;
 case 163:
-#line 3114 "mod/silverbox/memcached.c"
+#line 3124 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr237;
 	goto st0;
 tr235:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st164;
 st164:
 	if ( ++p == pe )
 		goto _test_eof164;
 case 164:
-#line 3126 "mod/silverbox/memcached.c"
+#line 3136 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 32: goto st164;
 		case 110: goto st165;
@@ -3214,7 +3224,7 @@ case 175:
 		goto st0;
 	goto tr252;
 tr252:
-#line 466 "mod/silverbox/memcached.rl"
+#line 476 "mod/silverbox/memcached.rl"
 	{
 			fstart = p;
 			for (; p < pe && *p != ' ' && *p != '\r' && *p != '\n'; p++);
@@ -3231,7 +3241,7 @@ case 175:
 	if ( ++p == pe )
 		goto _test_eof176;
 case 176:
-#line 3235 "mod/silverbox/memcached.c"
+#line 3245 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st177;
 	goto st0;
@@ -3245,49 +3255,49 @@ case 177:
 		goto tr254;
 	goto st0;
 tr254:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st178;
 st178:
 	if ( ++p == pe )
 		goto _test_eof178;
 case 178:
-#line 3256 "mod/silverbox/memcached.c"
+#line 3266 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr255;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st178;
 	goto st0;
 tr255:
-#line 489 "mod/silverbox/memcached.rl"
+#line 499 "mod/silverbox/memcached.rl"
 	{flags = natoq(fstart, p);}
 	goto st179;
 st179:
 	if ( ++p == pe )
 		goto _test_eof179;
 case 179:
-#line 3270 "mod/silverbox/memcached.c"
+#line 3280 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st179;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr258;
 	goto st0;
 tr258:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st180;
 st180:
 	if ( ++p == pe )
 		goto _test_eof180;
 case 180:
-#line 3284 "mod/silverbox/memcached.c"
+#line 3294 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto tr259;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto st180;
 	goto st0;
 tr259:
-#line 482 "mod/silverbox/memcached.rl"
+#line 492 "mod/silverbox/memcached.rl"
 	{
 			exptime = natoq(fstart, p);
 			if (exptime > 0 && exptime <= 60*60*24*30)
@@ -3298,21 +3308,21 @@ case 180:
 	if ( ++p == pe )
 		goto _test_eof181;
 case 181:
-#line 3302 "mod/silverbox/memcached.c"
+#line 3312 "mod/silverbox/memcached.c"
 	if ( (*p) == 32 )
 		goto st181;
 	if ( 48 <= (*p) && (*p) <= 57 )
 		goto tr262;
 	goto st0;
 tr262:
-#line 465 "mod/silverbox/memcached.rl"
+#line 475 "mod/silverbox/memcached.rl"
 	{ fstart = p; }
 	goto st182;
 st182:
 	if ( ++p == pe )
 		goto _test_eof182;
 case 182:
-#line 3316 "mod/silverbox/memcached.c"
+#line 3326 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 10: goto tr263;
 		case 13: goto tr264;
@@ -3322,30 +3332,30 @@ case 182:
 		goto st182;
 	goto st0;
 tr264:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st183;
 tr277:
-#line 524 "mod/silverbox/memcached.rl"
+#line 534 "mod/silverbox/memcached.rl"
 	{ noreply = true; }
 	goto st183;
 st183:
 	if ( ++p == pe )
 		goto _test_eof183;
 case 183:
-#line 3337 "mod/silverbox/memcached.c"
+#line 3347 "mod/silverbox/memcached.c"
 	if ( (*p) == 10 )
 		goto tr267;
 	goto st0;
 tr265:
-#line 490 "mod/silverbox/memcached.rl"
+#line 500 "mod/silverbox/memcached.rl"
 	{bytes = natoq(fstart, p);}
 	goto st184;
 st184:
 	if ( ++p == pe )
 		goto _test_eof184;
 case 184:
-#line 3349 "mod/silverbox/memcached.c"
+#line 3359 "mod/silverbox/memcached.c"
 	switch( (*p) ) {
 		case 32: goto st184;
 		case 110: goto st185;
@@ -3641,7 +3651,7 @@ case 196:
 	_out: {}
 	}
 
-#line 549 "mod/silverbox/memcached.rl"
+#line 559 "mod/silverbox/memcached.rl"
 
 
 	if (!done) {
@@ -3678,7 +3688,7 @@ memcached_handler(void *_data __unused__)
 	stats.curr_connections++;
 	int r, p;
 	int batch_count;
-	int i = 0;
+
 	for (;;) {
 		batch_count = 0;
 		if ((r = fiber_bread(fiber->rbuf, 1)) <= 0) {
@@ -3711,10 +3721,7 @@ memcached_handler(void *_data __unused__)
 		}
 
 		stats.bytes_written += r;
-		fiber_cleanup();
-
-		if (i++ % 20 == 0)
-			fiber_gc();
+		fiber_gc();
 
 		if (p == 1 && fiber->rbuf->len > 0) {
 			batch_count = 0;
@@ -3728,11 +3735,17 @@ memcached_handler(void *_data __unused__)
 	stats.curr_connections--; /* FIXME: nonlocal exit via exception will leak this counter */
 }
 
+void
+memcached_init(void)
+{
+	stat_base = stat_register(memcached_stat_strs, memcached_stat_MAX);
+}
+
 void
 memcached_expire(void *data __unused__)
 {
 	static khiter_t i;
-	khash_t(lstr2ptr_map) *map = memcached_index->map.str_map;
+	khash_t(lstr2ptr_map) *map = memcached_index->idx.str_hash;
 
 	say_info("memcached expire fiber started");
 	for (;;) {
@@ -3765,7 +3778,7 @@ memcached_expire(void *data __unused__)
 
 		fiber_gc();
 
-		double delay = cfg.memcached_expire_per_loop * cfg.memcached_expire_full_sweep / (map->size + 1);
+		double delay = (double)cfg.memcached_expire_per_loop * cfg.memcached_expire_full_sweep / (map->size + 1);
 		if (delay > 1)
 			delay = 1;
 		fiber_sleep(delay);
diff --git a/mod/silverbox/memcached.rl b/mod/silverbox/memcached.rl
index 9ffe4602dba972253d3ea635322c1d8684731f93..28c64356916a3da5cc1c312e216b0a60a477cbf9 100644
--- a/mod/silverbox/memcached.rl
+++ b/mod/silverbox/memcached.rl
@@ -40,6 +40,16 @@
 #include <mod/silverbox/box.h>
 #include <stat.h>
 
+
+#define STAT(_)					\
+        _(MEMC_GET, 1)				\
+        _(MEMC_GET_MISS, 2)			\
+	_(MEMC_GET_HIT, 3)
+
+ENUM(memcached_stat, STAT);
+STRS(memcached_stat, STAT);
+int stat_base;
+
 struct index *memcached_index;
 
 /* memcached tuple format:
@@ -117,7 +127,7 @@ delete(struct box_txn *txn, void *key)
 static struct box_tuple *
 find(void *key)
 {
-	return index_find(memcached_index, 1, key);
+	return memcached_index->find(memcached_index, key);
 }
 
 static struct meta *
@@ -192,7 +202,7 @@ flush_all(void *data)
 {
 	uintptr_t delay = (uintptr_t)data;
 	fiber_sleep(delay - ev_now());
-	khash_t(lstr2ptr_map) *map = memcached_index->map.str_map;
+	khash_t(lstr2ptr_map) *map = memcached_index->idx.str_hash;
 	for (khiter_t i = kh_begin(map); i != kh_end(map); i++) {
 		if (kh_exist(map, i)) {
 			struct box_tuple *tuple = kh_value(map, i);
@@ -370,9 +380,9 @@ memcached_dispatch(struct box_txn *txn)
 		action get {
 			txn->op = SELECT;
 			fiber_register_cleanup((void *)txn_cleanup, txn);
-			stat_collect("MEMC_GET", 1);
+			stat_collect(stat_base, MEMC_GET, 1);
 			stats.cmd_get++;
-			say_debug("nesuring space for %i keys", keys->len);
+			say_debug("ensuring space for %"PRI_SZ" keys", keys_count);
 			iov_ensure(keys_count * 5 + 1);
 			while (keys_count-- > 0) {
 				struct box_tuple *tuple;
@@ -390,7 +400,7 @@ memcached_dispatch(struct box_txn *txn)
 				key_len = load_varint32(&key);
 
 				if (tuple == NULL || tuple->flags & GHOST) {
-					stat_collect("MEMC_GET_MISS", 1);
+					stat_collect(stat_base, MEMC_GET_MISS, 1);
 					stats.get_misses++;
 					continue;
 				}
@@ -417,11 +427,11 @@ memcached_dispatch(struct box_txn *txn)
 
 				if (m->exptime > 0 && m->exptime < ev_now()) {
 					stats.get_misses++;
-					stat_collect("MEMC_GET_MISS", 1);
+					stat_collect(stat_base, MEMC_GET_MISS, 1);
 					continue;
 				} else {
 					stats.get_hits++;
-					stat_collect("MEMC_GET_HIT", 1);
+					stat_collect(stat_base, MEMC_GET_HIT, 1);
 				}
 
 				tuple_txn_ref(txn, tuple);
@@ -582,7 +592,7 @@ memcached_handler(void *_data __unused__)
 	stats.curr_connections++;
 	int r, p;
 	int batch_count;
-	int i = 0;
+
 	for (;;) {
 		batch_count = 0;
 		if ((r = fiber_bread(fiber->rbuf, 1)) <= 0) {
@@ -615,10 +625,7 @@ memcached_handler(void *_data __unused__)
 		}
 
 		stats.bytes_written += r;
-		fiber_cleanup();
-
-		if (i++ % 20 == 0)
-			fiber_gc();
+		fiber_gc();
 
 		if (p == 1 && fiber->rbuf->len > 0) {
 			batch_count = 0;
@@ -632,11 +639,17 @@ exit:
 	stats.curr_connections--; /* FIXME: nonlocal exit via exception will leak this counter */
 }
 
+void
+memcached_init(void)
+{
+	stat_base = stat_register(memcached_stat_strs, memcached_stat_MAX);
+}
+
 void
 memcached_expire(void *data __unused__)
 {
 	static khiter_t i;
-	khash_t(lstr2ptr_map) *map = memcached_index->map.str_map;
+	khash_t(lstr2ptr_map) *map = memcached_index->idx.str_hash;
 
 	say_info("memcached expire fiber started");
 	for (;;) {
@@ -669,7 +682,7 @@ memcached_expire(void *data __unused__)
 
 		fiber_gc();
 
-		double delay = cfg.memcached_expire_per_loop * cfg.memcached_expire_full_sweep / (map->size + 1);
+		double delay = (double)cfg.memcached_expire_per_loop * cfg.memcached_expire_full_sweep / (map->size + 1);
 		if (delay > 1)
 			delay = 1;
 		fiber_sleep(delay);
diff --git a/mod/silverbox/t/TBox.pm b/mod/silverbox/t/TBox.pm
index a9cf9d4e6d0743484ab9307d144d05f2d46f4131..6bd7cdfcd4953afc4fbec2b52e743216761a72f5 100644
--- a/mod/silverbox/t/TBox.pm
+++ b/mod/silverbox/t/TBox.pm
@@ -1,5 +1,5 @@
 use FindBin qw($Bin);
-use lib "$Bin/../client/perl";
+use lib "$Bin/../client/perl/lib";
 
 use MR::SilverBox;
 
@@ -7,12 +7,9 @@ $main::box = MR::SilverBox->new({ servers => $ARGV[1] || q/localhost:15013/,
                                   namespaces => [ {
                                       indexes => [ {
                                           index_name   => 'primary_id',
-                                          keys         => [TUPLE_ID],
-                                      }, {
-                                          index_name   => 'primary_email',
-                                          keys         => [TUPLE_Email],
-                                      }, ],
-                                      namespace     => 0,
+                                          keys         => [0],
+                                      } ],
+                                      namespace     => 1,
                                       format        => 'l& SSLL',
                                       default_index => 'primary_id',
                                   } ], });
diff --git a/mod/silverbox/t/box.pl b/mod/silverbox/t/box.pl
index 0e328fd810e9b1940e85fa1941b61f9056080ecd..29a16aad64841609eeeed5a7af525498fc481481 100644
--- a/mod/silverbox/t/box.pl
+++ b/mod/silverbox/t/box.pl
@@ -8,11 +8,21 @@ BEGIN {
 use FindBin qw($Bin);
 use lib "$Bin";
 use TBox ();
+use Carp qw/confess/;
 
-use Test::More qw/no_plan/;
+use Test::More tests => 201;
 use Test::Exception;
 
-use constant ILL_PARAM => qr/MR::SilverBox: Error 00000202/;
+local $SIG{__DIE__} = \&confess;
+
+our $CLASS;
+BEGIN { $CLASS = $ENV{BOXCLASS} || 'MR::SilverBox'; eval "require $CLASS" or die $@; }
+
+use constant ILL_PARAM         => qr/$CLASS: Error 00000202/;
+use constant TUPLE_NOT_EXISTS  => qr/$CLASS: Error 00003102/;
+use constant TUPLE_EXISTS      => qr/$CLASS: Error 00003702/;
+use constant INDEX_VIOLATION   => qr/$CLASS: Error 00003802/;
+
 use constant TOO_BIG_FIELD => qr/too big field/;
 
 my $box;
@@ -27,6 +37,7 @@ sub cleanup ($) {
 sub def_param  {
     my $format = shift || 'l& SSLL';
     return { servers => $server,
+             name    => $CLASS,
              namespaces => [ {
                  indexes => [ {
                      index_name   => 'primary_id',
@@ -40,13 +51,111 @@ sub def_param  {
                  default_index => 'primary_id',
              } ]}
 }
-$box = MR::SilverBox->new(def_param);
-ok $box->isa('MR::SilverBox'), 'connect';
+
+$box = $CLASS->new(def_param('l&SSLL&'));
+ok $box->isa($CLASS), 'connect';
+cleanup 13;
+
+ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '777'), 'insert';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '777'], 'select/insert';
+
+ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4, '666'), 'replace';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 2, 3, 4, '666'], 'select/replace';
+
+ok $box->Update(13, 3 => 1) == 1, 'update of some field';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, '666'], 'select/update';
+
+ok $box->Append( 13, 6 => 'APPEND') , 'append op';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, '666APPEND'], 'select/append';
+
+ok $box->Prepend(13, 6 => 'PREPEND'), 'prepend op';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, 'PREPEND666APPEND'], 'select/prepend';
+
+ok $box->Cutbeg(13, 6 => 2), 'cutbeg op';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, 'EPEND666APPEND'], 'select/cutbeg';
+
+ok $box->Cutend(13, 6 => 2), 'cutend op';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, 'EPEND666APPE'], 'select/cutend';
+
+ok $box->Substr(13, 6 => [3,4,'12345']), 'substr op';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, 'EPE123456APPE'], 'select/substr';
+
+ok $box->UpdateMulti(13, [6 => splice => [0]]), 'generic splice (offset = 0)';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4, ''], 'select/splice (offset = 0)';
+
+cleanup 13;
+
+ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'insert';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/insert';
+
+throws_ok sub { $box->UpdateMulti(13, [6 => splice => [-10]]) }, qr/Illegal parametrs/, "splice/bad_params_1";
+
+ok $box->UpdateMulti(13, [6 => splice => [100]]), "splice/big_offset";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [5]]), "splice/cut_tail_pos_offset";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '12345'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [-2]]), "splice/cut_tail_neg_offset";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123'], 'select';
+
+ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'replace';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [8, 1000]]), "splice/big_pos_length";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '12345678'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [1, -1000]]), "splice/big_neg_length";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '12345678'], 'select';
+
+ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'replace';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [0x7fffffff]]), "splice/max_offset";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select';
+
+ok $box->UpdateMulti(13, [6 => splice => [1, 2]], [6 => splice => [-2, -1, 'qwe']]), "splice/multi";
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '14567qwe9'], 'select';
+
+
 
 cleanup 13;
+cleanup 14;
+throws_ok sub { $box->Replace(13, q/some_email@test.mail.ru/, 5, 5, 5, 5, '555555555')}, TUPLE_NOT_EXISTS, 'replace';
+
+ok $box->Add(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'add';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/add';
+
+throws_ok sub { $box->Add(13, q/some_email@test.mail.ru/, 5, 5, 5, 5, '555555555')}, TUPLE_EXISTS, 'add2';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/add2';
+is_deeply scalar $box->Select(q/some_email@test.mail.ru/, {use_index => "primary_email"}), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/add2';
+
+throws_ok sub { $box->Add(14, q/some_email@test.mail.ru/, 5, 5, 5, 5, '555555555')}, INDEX_VIOLATION, 'add3';
+
+ok $box->Add(14, q/some1email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'add4';
+is_deeply scalar $box->Select(14), [14, 'some1email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/add4';
+is_deeply scalar $box->Select(q/some1email@test.mail.ru/, {use_index => "primary_email"}), [14, 'some1email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/add4';
+
+throws_ok sub { $box->Replace(13, q/some1email@test.mail.ru/, 6, 6, 6, 6, '666666666')}, INDEX_VIOLATION, 'replace';
+throws_ok sub { $box->Set(13, q/some1email@test.mail.ru/, 6, 6, 6, 6, '666666666')}, INDEX_VIOLATION, 'set';
+
+ok $box->Set(13, q/some_email@test.mail.ru/, 5, 5, 5, 5, '555555555'), 'set';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 5, 5, 5, 5, '555555555'], 'select/set';
+
+ok $box->Replace(13, q/some_email@test.mail.ru/, 1, 2, 3, 4, '123456789'), 'replace';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 1, 2, 3, 4, '123456789'], 'select/replace';
+
+
+
+$box = $CLASS->new(def_param);
+ok $box->isa($CLASS), 'connect';
+cleanup 13;
+
 ok $box->Insert(13, q/some_email@test.mail.ru/, 1, 2, 3, 4), 'insert';
 ok $box->Insert(13, q/some_email@test.mail.ru/, 2, 2, 3, 4), 'replace';
+
 ok $box->Update(13, 3 => 1) == 1, 'update of some field';
+is_deeply scalar $box->Select(13), [13, 'some_email@test.mail.ru', 2, 1, 3, 4], 'select/update';
 
 cleanup 14;
 ok $box->Insert(14, 'aaa@test.mail.ru', 0, 0, 1, 1), 'insert';
@@ -63,15 +172,15 @@ is_deeply scalar $box->Select('aaa@test.mail.ru', {use_index => 'primary_email'}
 
 
 
-$box = MR::SilverBox->new(def_param);
-ok $box->isa('MR::SilverBox'), 'connect';
+$box = $CLASS->new(def_param);
+ok $box->isa($CLASS), 'connect';
 
 
 
 for (1..3) {
     %MR::IProto::sockets = ();
-    $box = MR::SilverBox->new(def_param);
-    ok $box->isa('MR::SilverBox'), 'connect';
+    $box = $CLASS->new(def_param);
+    ok $box->isa($CLASS), 'connect';
 
     cleanup 14;
 
@@ -108,13 +217,13 @@ $tuple[5] |= (1 << 15);
 $tuple[5] &= ~(1 << 16);
 is_deeply scalar $box->Select($id), \@tuple, 'bit_set + bit_clear';
 
-$box->Bit($id, 5, set => 4095, bit_set => (1 << 5), bit_clear => (1 << 6), {update_flags => 1});
+$box->Bit($id, 5, set => 4095, bit_set => (1 << 5), bit_clear => (1 << 6));
 $tuple[5] = 4095;
 $tuple[5] |= (1 << 5);
 $tuple[5] &= ~(1 << 6);
 is_deeply scalar $box->Select($id), \@tuple, 'set + bit_set + bit_clear';
 
-$box->Bit($id, 5, set => 123, {update_flags => 1});
+$box->Bit($id, 5, set => 123);
 $tuple[5] = 123;
 is_deeply scalar $box->Select($id), \@tuple, 'set via Bit';
 
@@ -124,11 +233,11 @@ is_deeply scalar $box->Select($id), \@tuple, 'set via Bit';
 ## Num ops
 
 # zero namespace
-$box->Num($id, 5, num_add => 1, {update_flags => 1});
+$box->Num($id, 5, num_add => 1);
 $tuple[5] += 1;
 is_deeply scalar $box->Select($id), \@tuple, 'num_add';
 
-$box->Num($id, 5, num_sub => 1, {update_flags => 1});
+$box->Num($id, 5, num_sub => 1);
 $tuple[5] -= 1;
 is_deeply scalar $box->Select($id), \@tuple, 'num_sub';
 
@@ -152,7 +261,7 @@ throws_ok sub { $box->Num($id, 5, hxxxxx => 123) }, qr/unknown op 'hxxxxx'/, 'ba
 
 ### AndXorAdd
 
-$box->AndXorAdd($id, 5, 4095, 5, 10, {update_flags => 1});
+$box->AndXorAdd($id, 5, 4095, 5, 10);
 $tuple[5] &= 4095;
 $tuple[5] ^= 5;
 $tuple[5] += 10;
@@ -183,13 +292,13 @@ ok 0 == $box->Delete($id), 'delete by id';
 cleanup $id;
 ok $box->Insert(@tuple), 'insert';
 
-ok $box->UpdateMulti($id, ([5 => set => 1]) x 127, {update_flags => 1}), 'big update multi';
+ok $box->UpdateMulti($id, ([5 => set => 1]) x 127), 'big update multi';
 # CANT TEST throws_ok sub { $box->UpdateMulti($id, ([5 => set => 1]) x 128) }, ILL_PARAM, 'too big update multi';
 throws_ok sub { $box->UpdateMulti($id, ([5 => set => 1]) x 129) }, qr/too many op/, 'too big update multi';
 {
-    my $box = MR::SilverBox->new(def_param(q/l& SSL&/));
+    my $box = $CLASS->new(def_param(q/l& SSL&/));
     my @tuple = @tuple;
-    ok $box->isa('MR::SilverBox'), 'connect';
+    ok $box->isa($CLASS), 'connect';
 
     ok $box->UpdateMulti($id, map { [5 => set => 'x' x 127] } (1..127)), 'big update multi';
     $tuple[5] = 'x' x 127;
@@ -202,12 +311,12 @@ throws_ok sub { $box->UpdateMulti($id, ([5 => set => 1]) x 129) }, qr/too many o
 }
 
 {
-    my $box = MR::SilverBox->new(def_param(q/l& &&&&/));
+    my $box = $CLASS->new(def_param(q/l& &&&&/));
     my @tuple = @tuple;
     my $id = $tuple[0];
-    ok $box->isa('MR::SilverBox'), 'connect';
+    ok $box->isa($CLASS), 'connect';
 
-    ok $box->UpdateMulti($id, [2 => set => 'ab'], [5 => set => 'z' x 127], {update_flags => 1}), 'update multi no teplate';
+    ok $box->UpdateMulti($id, [2 => set => 'ab'], [5 => set => 'z' x 127]), 'update multi no teplate';
     $tuple[2] = 'ab';
     $tuple[5] = 'z' x 127;
     my @r = @{$box->Select($id)};
@@ -229,7 +338,7 @@ throws_ok sub { $box->UpdateMulti($id, '') }, qr/bad op/, 'bad op';
     ok $box->Insert(@tuple), 'insert';
 
     my @op = ([4 => num_add => 300], [5 => num_sub => 100], [5 => set => 1414], [5 => bit_set => 1 | 2], [5 => bit_clear => 2]);
-    ok $box->UpdateMulti($tuple[0], @op, {update_base => 1, update_flags => 1}), 'update multi';
+    ok $box->UpdateMulti($tuple[0], @op), 'update multi';
     my @tuple_new = @tuple;
     $tuple_new[4] += 300;
     $tuple_new[5] -= 100;
@@ -240,9 +349,9 @@ throws_ok sub { $box->UpdateMulti($id, '') }, qr/bad op/, 'bad op';
 
     cleanup 14;
     ok $box->Insert(@tuple), 'insert';
-    is_deeply scalar $box->UpdateMulti($tuple[0], @op, {update_base => 1, update_flags => 1, want_result => 1}), \@tuple_new, 'update multi, want_result';
+    is_deeply scalar $box->UpdateMulti($tuple[0], @op, {want_result => 1}), \@tuple_new, 'update multi, want_result';
 
-    ok 0 == $box->UpdateMulti(15, [4 => num_add => 300], [5 => num_sub => 100], [5 => set => 1414], {update_base => 1, update_flags => 1}), 'update multi of nonexist';
+    ok 0 == $box->UpdateMulti(15, [4 => num_add => 300], [5 => num_sub => 100], [5 => set => 1414]), 'update multi of nonexist';
 }
 
 
@@ -256,7 +365,7 @@ is_deeply [$box->Select($tuple[0], $tuple[0])], [\@tuple, \@tuple], 'select in s
 is_deeply scalar $box->Select($tuple[0], $tuple[0], {want => 'arrayref'}), [\@tuple, \@tuple], 'select want => arrrayref';
 
 {
-    my $box = MR::SilverBox->new(
+    my $box = $CLASS->new(
         { %{def_param()},
           hashify => sub {
               my ($namespace, $row) = @_;
@@ -278,3 +387,129 @@ is_deeply scalar $box->Select($tuple[0], $tuple[0], {want => 'arrayref'}), [\@tu
 
     is_deeply $box->Select($tuple[0]), $hash, 'select with hashify';
 }
+
+## Tree indexes
+sub def_param1 {
+    my $format = 'l&l';
+    return { servers => $server,
+             namespaces => [ {
+                 indexes => [ {
+                     index_name   => 'primary_num1',
+                     keys         => [0],
+                 }, {
+                     index_name   => 'secondary_str2',
+                     keys         => [1],
+                 }, {
+                     index_name   => 'secondary_complex',
+                     keys         => [1, 2],
+                 } ],
+                 namespace     => 26,
+                 format        => $format,
+                 default_index => 'primary_num1',
+             } ]}
+}
+
+$box = MR::SilverBox->new(def_param1);
+ok $box->isa('MR::SilverBox'), 'connect';
+
+my @tuple1 = (13, 'mail.ru', 123);
+cleanup $tuple1[0];
+
+my @tuple2 = (14, 'mail.ru', 456);
+cleanup $tuple2[0];
+
+ok $box->Insert(@tuple1);
+
+is_deeply [$box->Select([[$tuple1[0]]])], [\@tuple1], 'select by primary_num1 index';
+is_deeply [$box->Select([[$tuple1[1]]], { use_index => 'secondary_str2' })], [\@tuple1], 'select by secondary_str2 index';
+is_deeply [$box->Select([[$tuple1[1], $tuple1[2]]], { use_index => 'secondary_complex' })], [\@tuple1], 'select by secondary_complex index';
+is_deeply [$box->Select([[$tuple1[1]]], { use_index => 'secondary_complex' })], [\@tuple1], 'select by secondary_complex index, partial key';
+
+ok $box->Insert(@tuple2);
+
+is_deeply [$box->Select([[$tuple2[0]]])], [\@tuple2], 'select by primary_num1 index';
+is_deeply [$box->Select([[$tuple1[1]]], { use_index => 'secondary_str2', limit => 2, offset => 0 })], [\@tuple1, \@tuple2], 'select by secondary_str2 index';
+is_deeply [$box->Select([[$tuple2[1], $tuple2[2]]], { use_index => 'secondary_complex' })], [\@tuple2], 'select by secondary_complex index';
+
+## Check index constrains
+sub def_param_bad {
+    my $format = 'l&&';
+    return { servers => $server,
+             namespaces => [ {
+                 indexes => [ {
+                     index_name   => 'primary_num1',
+                     keys         => [0],
+                 }, {
+                     index_name   => 'secondary_str2',
+                     keys         => [1],
+                 }, {
+                     index_name   => 'secondary_complex',
+                     keys         => [1, 2],
+                 } ],
+                 namespace     => 26,
+                 format        => $format,
+                 default_index => 'primary_num1',
+             } ]}
+}
+
+$box = MR::SilverBox->new(def_param_bad);
+ok $box->isa('MR::SilverBox'), 'connect';
+
+my @tuple_bad = (13, 'mail.ru', '123');
+cleanup $tuple_bad[0];
+throws_ok sub { $box->Insert(@tuple_bad) }, qr/Illegal parametrs/, "index_constains/bad_field_type";
+
+
+## Check unique tree index
+sub def_param_unique {
+    my $format = 'l&&&';
+    return { servers => $server,
+             namespaces => [ {
+                 indexes => [ {
+		     index_name   => 'id',
+		     keys         => [0],
+		 }, {
+                     index_name   => 'email',
+                     keys         => [1],
+                 }, {
+                     index_name   => 'firstname',
+                     keys         => [2],
+                 }, {
+                     index_name   => 'lastname',
+                     keys         => [3],
+                 } , {
+		     index_name   => 'fullname',
+		     keys         => [2, 3]
+		 } ],
+                 namespace     => 27,
+                 format        => $format,
+                 default_index => 'id',
+             } ]}
+}
+
+$box = MR::SilverBox->new(def_param_unique);
+ok $box->isa('MR::SilverBox'), 'connect';
+
+my $tuples = [ [1, 'rtokarev@corp.mail.ru', 'Roman', 'Tokarev'],
+	       [2, 'vostrikov@corp.mail.ru', 'Yuri', 'Vostrikov'],
+	       [3, 'aleinikov@corp.mail.ru', 'Roman', 'Aleinikov'],
+	       [4, 'roman.s.tokarev@gmail.com', 'Roman', 'Tokarev'],
+	       [5, 'vostrikov@corp.mail.ru', 'delamon', 'delamon'] ];
+
+foreach my $tuple (@$tuples) {
+	cleanup $tuple->[0];
+}
+
+foreach my $tuple (@$tuples) {
+	if ($tuple == $tuples->[-1] || $tuple == $tuples->[-2]) {
+		throws_ok sub { $box->Insert(@$tuple) }, qr/Index violation/, "unique_tree_index/insert \'$tuple->[0]\'";
+	} else {
+		ok $box->Insert(@$tuple), "unique_tree_index/insert \'$tuple->[0]\'";
+	}
+}
+
+my @res = $box->Select([map $_->[0], @$tuples], { limit => 100 });
+foreach my $r (@res) {
+	ok sub { return $r != $tuples->[-1] && $r != $tuples->[-2] };
+}
+
diff --git a/scripts/config_def.mk b/scripts/config_def.mk
index 0d4585ed3afeedd116259e77f84f82b0de4b8488..5de0c0512ffea4b4e1ca76cafb586ad9a3fa78cf 100644
--- a/scripts/config_def.mk
+++ b/scripts/config_def.mk
@@ -26,7 +26,7 @@ endif
 
 ifeq ($(OS), Linux)
   CFLAGS += -DLinux -D_FILE_OFFSET_BITS=64  -DEV_USE_INOTIFY -D_GNU_SOURCE
-  ifeq ($(DEBUG), 2)
+  ifeq ($(DEBUG), 1)
     CFLAGS += -DHAVE_VALGRIND
   endif
 endif
@@ -35,14 +35,8 @@ ifeq (,$(filter -g%,$(CFLAGS)))
 CFLAGS += -g3 -ggdb
 endif
 
-ifdef DEBUG
-  CFLAGS += -DDEBUG -fno-omit-frame-pointer
-else
- CFLAGS += -DNDEBUG
-endif
-
 ifeq (,$(filter -O%,$(CFLAGS)))
-  ifeq ($(DEBUG), 2)
+  ifeq ($(DEBUG), 1)
     CFLAGS += -O0
   else
     CFLAGS += -O2
diff --git a/scripts/custom_types.hs b/scripts/custom_types.hs
new file mode 100644
index 0000000000000000000000000000000000000000..83b454d25490834940330ffb930e45d47d285443
--- /dev/null
+++ b/scripts/custom_types.hs
@@ -0,0 +1,34 @@
+module Main where
+
+import System
+import qualified Data.Map as M
+import Data.Maybe
+import Language.C
+import Language.C.System.GCC
+import Language.C.Analysis.AstAnalysis
+import Language.C.Analysis.SemRep
+import Language.C.Analysis.TravMonad
+
+main = do
+  args <- getArgs
+  parseFile (head args) (tail args) >>= printGlobalTypes
+
+parseFile :: FilePath -> [String] -> IO CTranslUnit
+parseFile input_file cOpts =
+  do parse_result <- parseCFile (newGCC "gcc") Nothing cOpts input_file
+     case parse_result of
+       Left parse_err -> error (show parse_err)
+       Right ast      -> return ast
+
+printGlobalTypes transUnit = 
+    case runTrav_ (analyseAST transUnit) of
+      Left err -> error (show err)
+      Right (globals, _) -> mapM_ print $ globalTypeNames globals
+
+globalTypeNames g =
+    map identToString $ typeNames ++ sueNames
+    where
+      typeNames = M.keys (gTypeDefs g)
+      sueNames = mapMaybe named (M.keys $ gTags g)
+      named (NamedRef x) = Just x
+      named _ = Nothing
diff --git a/scripts/indent b/scripts/indent
index 840d112fc7236db75e0804969f84ded15272c7e4..85548597d38246672b46973ee83449e36f90b1bf 100755
--- a/scripts/indent
+++ b/scripts/indent
@@ -1,5 +1,25 @@
 #!/bin/sh
 INDENT=$(which gindent 2>/dev/null || which indent 2>/dev/null)
 [ -z $INDENT ] && echo indent binary not found && exit 1
-PARAM="-npro -kr -i8 -ts8 -sob -l100 -ss -ncs -cp1"
-$INDENT $PARAM $@
+
+for f in $@
+do
+	PARAM="-npro -kr -i8 -ts8 -ci8 -sob -l100 -ss -ncs -cp1
+		-T bool"
+
+	case "$f" in
+		*.c)
+			PARAM="$PARAM -psl"
+			;;
+		*.h)
+			;;
+		*)
+			echo "Unknown file type: $f"
+			exit 1;
+			;;
+	esac
+
+	PARAM="$PARAM "$(scripts/custom_types $f -I. -Iinclude -DCORO_ASM -DSTORAGE -DTARANTOOL_CONFIG='<cfg/tarantool_silverbox_cfg.h>' | grep -v '^"_' | sed 's/"//g; s/^/-T /')
+
+	$INDENT $PARAM $f
+done
diff --git a/scripts/rules.mk b/scripts/rules.mk
new file mode 100644
index 0000000000000000000000000000000000000000..459c3d55ee83f23eb787ce64120c833541e0d89d
--- /dev/null
+++ b/scripts/rules.mk
@@ -0,0 +1,108 @@
+ifeq (,$(module))
+all:
+	@echo "Valid targets are:"
+	@echo "	_release_box/tarantool_silverbox"
+	@echo "	_release_feeder/tarantool_feeder"
+	@echo "	_debug_box/tarantool_silverbox"
+	@echo "	_debug_feeder/tarantool_feeder"
+	@echo "	clean"
+else
+all: tarantool_$(module)
+endif
+
+
+-include $(SRCDIR)/config.mk $(SRCDIR)/scripts/config.mk
+include $(SRCDIR)/scripts/config_def.mk
+
+ifneq (,$(findstring _release,$(OBJDIR)))
+  CFLAGS += -DNDEBUG
+else ifneq (,$(findstring _debug,$(OBJDIR)))
+  DEBUG=1
+  CFLAGS += -DDEBUG -fno-omit-frame-pointer
+else ifneq (,$(findstring _test,$(OBJDIR)))
+ CFLAGS += --coverage -DCOVERAGE -DNDEBUG
+else ifneq (,$(findstring _coverage,$(OBJDIR)))
+ CFLAGS += --coverage -DCOVERAGE
+endif
+
+# build dir includes going first
+ifneq (,$(OBJDIR))
+CFLAGS += -I$(SRCDIR)/$(OBJDIR) -I$(SRCDIR)/$(OBJDIR)/include
+endif
+CFLAGS += -I$(SRCDIR) -I$(SRCDIR)/include
+LIBS += -lm
+
+subdirs += third_party
+ifneq (,$(module))
+  subdirs += mod/$(module)
+endif
+subdirs += cfg core
+include $(foreach dir,$(subdirs),$(SRCDIR)/$(dir)/Makefile)
+
+tarantool_$(module): $(obj)
+	$(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) $^ -o $@
+
+ifdef I
+$(info * build with $(filter -D%,$(CFLAGS)))
+endif
+
+# makefile change will force rebuild
+$(obj): $(wildcard ../*.mk) $(wildcard ../scripts/*.mk)
+$(obj): $(foreach dir,$(subdirs),$(SRCDIR)/$(dir)/Makefile)
+$(obj): Makefile
+
+dep = $(patsubst %.o,%.d,$(obj)) $(patsubst %.o,%.pd,$(obj))
+-include $(dep)
+
+ifneq (,$(OBJDIR))
+%.o: %.c
+	@mkdir -p $(dir $@)
+	$(CC) $(CFLAGS) -MD -c $< -o $@
+	@sed -n -f $(SRCDIR)/scripts/slurp.sed \
+		-f $(SRCDIR)/scripts/fixdep.sed \
+		-e 's!$(SRCDIR)/!!; p' \
+		< $(@:.o=.d) > $(@:.o=.pd)
+else
+%.o: %.c
+	@mkdir -p $(dir $@)
+	$(CC) $(CFLAGS) -MD -c $< -o $@
+endif
+
+# code gen targets
+.PRECIOUS: %.h %.c %.dot
+ifeq ($(HAVE_RAGEL),1)
+%.c: %.rl
+	@mkdir -p $(dir $@)
+	$(RAGEL) -G2 $< -o $@
+
+%.dot: %.rl
+	@mkdir -p $(dir $@)
+	$(RAGEL) -p -V $< -o $(basename $@).dot
+
+%.png: %.dot
+	@mkdir -p $(dir $@)
+	$(DOT) -Tpng $< -o $(basename $@).png
+endif
+ifeq ($(HAVE_CONFETTI),1)
+%.cfg: %.cfg_tmpl
+	@mkdir -p $(dir $@)
+	$(CONFETTI) -i $< -n tarantool_cfg -f $@
+
+%.h: %.cfg_tmpl
+	@mkdir -p $(dir $@)
+	$(CONFETTI) -i $< -n tarantool_cfg -h $@
+
+%.c: %.cfg_tmpl
+	@mkdir -p $(dir $@)
+	$(CONFETTI) -i $< -n tarantool_cfg -c $@
+endif
+
+ifeq ($(HAVE_GIT),1)
+tarantool_version.h: FORCE
+	@echo -n "const char tarantool_version_string[] = " > $@_
+	@git show HEAD | sed 's/commit \(.*\)/\"\1/;q' | tr -d \\n >> $@_
+	@git diff --quiet || (echo -n ' AND'; git diff --shortstat) | tr -d \\n >> $@_
+	@echo '";' >> $@_
+	@diff -q $@ $@_ 2>/dev/null >/dev/null || ($(ECHO) "	GEN	" $(notdir $@); cp $@_ $@)
+FORCE:
+endif
diff --git a/scripts/run_test.sh b/scripts/run_test.sh
index 743b673218763bb3618dedd51fd518a21fab6ec0..ce6abef662c744e6f6d43c99f69b8f81009efa96 100755
--- a/scripts/run_test.sh
+++ b/scripts/run_test.sh
@@ -67,8 +67,8 @@ write_config() {
     local admin_port=${3:?}
 
     cat <<-EOF > $test_dir/$file_name.cfg
-	log_level = 2
-	slab_alloc_arena = 1
+	log_level = 4
+	slab_alloc_arena = 0.1
 	
 	work_dir = "$test_dir"
 	snap_dir = "storage/snap"
@@ -89,46 +89,141 @@ write_config() {
 	wal_feeder_port = 24444
 	
 	namespace[0].enabled = 1
-	namespace[0].index[0].type = "NUM"
-	namespace[0].index[0].key_position = 0
-	namespace[0].index[1].type = "STR"
-	namespace[0].index[1].key_position = 1
+	namespace[0].index[0].type = "HASH"
+	namespace[0].index[0].unique = 1
+	namespace[0].index[0].key_field[0].fieldno = 0
+	namespace[0].index[0].key_field[0].type = "NUM"
+	namespace[0].index[1].type = "HASH"
+	namespace[0].index[1].unique = 1
+	namespace[0].index[1].key_field[0].fieldno = 1
+	namespace[0].index[1].key_field[0].type = "STR"
 	
 	namespace[3].enabled = 1
-	namespace[3].index[0].type = "NUM"
-	namespace[3].index[0].key_position = 0
+	namespace[3].index[0].type = "HASH"
+	namespace[3].index[0].unique = 1
+	namespace[3].index[0].key_field[0].fieldno = 0
+	namespace[3].index[0].key_field[0].type = "NUM"
 	
 	namespace[5].enabled = 1
-	namespace[5].index[0].type = "NUM"
-	namespace[5].index[0].key_position = 0
+	namespace[5].index[0].type = "HASH"
+	namespace[5].index[0].unique = 1
+	namespace[5].index[0].key_field[0].fieldno = 0
+	namespace[5].index[0].key_field[0].type = "NUM"
 	
 	namespace[11].enabled = 1
-	namespace[11].index[0].type = "NUM"
-	namespace[11].index[0].key_position = 0
+	namespace[11].index[0].type = "HASH"
+	namespace[11].index[0].unique = 1
+	namespace[11].index[0].key_field[0].fieldno = 0
+	namespace[11].index[0].key_field[0].type = "NUM"
 	
 	namespace[19].enabled = 1
-	namespace[19].index[0].type = "STR"
-	namespace[19].index[0].key_position = 0
+	namespace[19].index[0].type = "HASH"
+	namespace[19].index[0].unique = 1
+	namespace[19].index[0].key_field[0].fieldno = 0
+	namespace[19].index[0].key_field[0].type = "STR"
 	
 	namespace[22].enabled = 1
-	namespace[22].index[0].type = NUM
-	namespace[22].index[0].key_position = 0
+	namespace[22].index[0].type = "HASH"
+	namespace[22].index[0].unique = 1
+	namespace[22].index[0].key_field[0].fieldno = 0
+	namespace[22].index[0].key_field[0].type = "NUM"
 	
 	namespace[23].enabled = 1
-	namespace[23].index[0].type = NUM
-	namespace[23].index[0].key_position = 0
+	namespace[23].index[0].type = "HASH"
+	namespace[23].index[0].unique = 1
+	namespace[23].index[0].key_field[0].fieldno = 0
+	namespace[23].index[0].key_field[0].type = "NUM"
 	
 	namespace[24].enabled = 1
-	# namespace[24].dual_to = 25
-	namespace[24].index[0].type = "NUM"
-	namespace[24].index[0].key_position = 0
-	namespace[24].index[1].type = "STR"
-	namespace[24].index[1].key_position = 1
+	namespace[24].index[0].type = "HASH"
+	namespace[24].index[0].unique = 1
+	namespace[24].index[0].key_field[0].fieldno = 0
+	namespace[24].index[0].key_field[0].type = "NUM"
+	namespace[24].index[1].type = "HASH"
+	namespace[24].index[1].unique = 1
+	namespace[24].index[1].key_field[0].fieldno = 1
+	namespace[24].index[1].key_field[0].type = "STR"
 	
 	#namespace[25].enabled = 0
 	#namespace[25].dual_to = 24
-	#namespace[25].index[0].type = "STR"
-	#namespace[25].index[0].key_position = 1
+	#namespace[25].index[0].type = "HASH"
+	#namespace[25].index[0].key_field[0].fieldno = 1
+	#namespace[25].index[0].key_field[0].type = "STR"
+
+	namespace[26].enabled = 1
+	namespace[26].index[0].type = "HASH"
+	namespace[26].index[0].unique = 1
+	namespace[26].index[0].key_field[0].fieldno = 0
+	namespace[26].index[0].key_field[0].type = "NUM"
+	namespace[26].index[1].type = "TREE"
+	namespace[26].index[1].unique = 0
+	namespace[26].index[1].key_field[0].fieldno = 1
+	namespace[26].index[1].key_field[0].type = "STR"
+	namespace[26].index[2].type = "TREE"
+	namespace[26].index[2].unique = 1
+	namespace[26].index[2].key_field[0].fieldno = 1
+	namespace[26].index[2].key_field[0].type = "STR"
+	namespace[26].index[2].key_field[1].fieldno = 2
+	namespace[26].index[2].key_field[1].type = "NUM"
+
+	namespace[27] = {
+		enabled = 1
+
+		# Email
+		index[0] = {
+			type = "HASH"
+			unique = 1
+
+			key_field[0] = {
+				type = "NUM"
+				fieldno = 0
+			}
+		}
+		index[1] = {
+			type = "TREE"
+			unique = 1
+
+			key_field[0] = {
+				type = "STR"
+				fieldno = 1
+			}
+		}
+		# FirstName
+		index[2] = {
+			type = "TREE"
+			unique = 0
+
+			key_field[0] = {
+				type = "STR"
+				fieldno = 2
+			}
+		}
+		# LastName
+		index[3] = {
+			type = "TREE"
+			unique = 0
+
+			key_field[0] = {
+				type = "STR"
+				fieldno = 3
+			}
+		}
+		# FullName
+		index[4] = {
+			type = "TREE"
+			unique = 1
+
+			key_field[0] = {
+				type = "STR"
+				fieldno = 2
+			}
+			key_field[1] = {
+				type = "STR"
+				fieldno = 3
+			}
+		}
+	}
+
 EOF
 }
 
diff --git a/third_party/confetti/prscfg.c b/third_party/confetti/prscfg.c
index 43cefce17bc2ede47d8e7280ea909f000d1d1672..98358885e3d38a5cc88ad13da4c0b5e204034cc5 100644
--- a/third_party/confetti/prscfg.c
+++ b/third_party/confetti/prscfg.c
@@ -62,7 +62,8 @@ static int prscfgGetLineNo(prscfg_yyscan_t yyscanner);
      KEY_P = 258,
      NULL_P = 259,
      STRING_P = 260,
-     NUMBER_P = 261
+     NUMBER_P = 261,
+     PATH_P = 262
    };
 #endif
 /* Tokens.  */
@@ -70,6 +71,7 @@ static int prscfgGetLineNo(prscfg_yyscan_t yyscanner);
 #define NULL_P 259
 #define STRING_P 260
 #define NUMBER_P 261
+#define PATH_P 262
 
 
 
@@ -89,7 +91,7 @@ typedef union YYSTYPE
 
 
 /* Line 1676 of yacc.c  */
-#line 73 "y.tab.h"
+#line 75 "y.tab.h"
 } YYSTYPE;
 # define YYSTYPE_IS_TRIVIAL 1
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
@@ -275,7 +277,8 @@ static OptDef	*output;
      KEY_P = 258,
      NULL_P = 259,
      STRING_P = 260,
-     NUMBER_P = 261
+     NUMBER_P = 261,
+     PATH_P = 262
    };
 #endif
 /* Tokens.  */
@@ -283,6 +286,7 @@ static OptDef	*output;
 #define NULL_P 259
 #define STRING_P 260
 #define NUMBER_P 261
+#define PATH_P 262
 
 
 
@@ -302,7 +306,7 @@ typedef union YYSTYPE
 
 
 /* Line 214 of yacc.c  */
-#line 205 "y.tab.c"
+#line 207 "y.tab.c"
 } YYSTYPE;
 # define YYSTYPE_IS_TRIVIAL 1
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
@@ -314,7 +318,7 @@ typedef union YYSTYPE
 
 
 /* Line 264 of yacc.c  */
-#line 217 "y.tab.c"
+#line 219 "y.tab.c"
 
 #ifdef short
 # undef short
@@ -527,22 +531,22 @@ union yyalloc
 #endif
 
 /* YYFINAL -- State number of the termination state.  */
-#define YYFINAL  9
+#define YYFINAL  10
 /* YYLAST -- Last index in YYTABLE.  */
-#define YYLAST   36
+#define YYLAST   54
 
 /* YYNTOKENS -- Number of terminals.  */
-#define YYNTOKENS  14
+#define YYNTOKENS  15
 /* YYNNTS -- Number of nonterminals.  */
-#define YYNNTS  8
+#define YYNNTS  10
 /* YYNRULES -- Number of rules.  */
-#define YYNRULES  18
+#define YYNRULES  24
 /* YYNRULES -- Number of states.  */
-#define YYNSTATES  34
+#define YYNSTATES  47
 
 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
 #define YYUNDEFTOK  2
-#define YYMAXUTOK   261
+#define YYMAXUTOK   262
 
 #define YYTRANSLATE(YYX)						\
   ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
@@ -554,15 +558,15 @@ static const yytype_uint8 yytranslate[] =
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,     2,    13,     2,     9,     2,     2,     2,
+       2,     2,     2,     2,    14,     2,    10,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,    10,     2,     2,     2,     2,     2,     2,     2,     2,
+       2,    11,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     7,     2,     8,     2,     2,     2,     2,     2,     2,
+       2,     8,     2,     9,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
-       2,     2,     2,    11,     2,    12,     2,     2,     2,     2,
+       2,     2,     2,    12,     2,    13,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
@@ -576,7 +580,7 @@ static const yytype_uint8 yytranslate[] =
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
        2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
-       5,     6
+       5,     6,     7
 };
 
 #if YYDEBUG
@@ -585,26 +589,31 @@ static const yytype_uint8 yytranslate[] =
 static const yytype_uint8 yyprhs[] =
 {
        0,     0,     3,     5,     7,     9,    11,    16,    18,    22,
-      24,    27,    31,    35,    39,    43,    49,    55,    59
+      27,    31,    33,    37,    41,    45,    49,    53,    57,    64,
+      71,    78,    80,    81,    86
 };
 
 /* YYRHS -- A `-1'-separated list of the rules' RHS.  */
 static const yytype_int8 yyrhs[] =
 {
-      15,     0,    -1,    19,    -1,     3,    -1,     4,    -1,    16,
-      -1,    16,     7,     6,     8,    -1,    16,    -1,    17,     9,
-      18,    -1,    20,    -1,    19,    20,    -1,    18,    10,     6,
-      -1,    18,    10,     5,    -1,    18,    10,     3,    -1,    18,
-      10,     4,    -1,    18,    10,    11,    19,    12,    -1,    18,
-      10,     7,    21,     8,    -1,    11,    19,    12,    -1,    21,
-      13,    11,    19,    12,    -1
+      16,     0,    -1,    21,    -1,     3,    -1,     4,    -1,    17,
+      -1,    17,     8,     6,     9,    -1,    17,    -1,    18,    10,
+      19,    -1,    17,     8,     6,     9,    -1,    18,    10,    20,
+      -1,    22,    -1,    21,    23,    22,    -1,    19,    11,     6,
+      -1,    19,    11,     5,    -1,    19,    11,     7,    -1,    19,
+      11,     3,    -1,    19,    11,     4,    -1,    19,    11,    12,
+      21,    23,    13,    -1,    19,    11,     8,    24,    23,     9,
+      -1,    20,    11,    12,    21,    23,    13,    -1,    14,    -1,
+      -1,    12,    21,    23,    13,    -1,    24,    23,    12,    21,
+      23,    13,    -1
 };
 
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
 static const yytype_uint8 yyrline[] =
 {
-       0,    91,    91,    95,    96,   100,   101,   110,   111,   116,
-     117,   121,   122,   123,   124,   125,   126,   130,   141
+       0,    92,    92,    96,    97,   101,   102,   111,   112,   116,
+     122,   126,   127,   131,   132,   133,   134,   135,   136,   137,
+     138,   142,   143,   147,   158
 };
 #endif
 
@@ -614,9 +623,9 @@ static const yytype_uint8 yyrline[] =
 static const char *const yytname[] =
 {
   "$end", "error", "$undefined", "KEY_P", "NULL_P", "STRING_P",
-  "NUMBER_P", "'['", "']'", "'.'", "'='", "'{'", "'}'", "','", "$accept",
-  "cfg", "identifier", "elem_identifier", "keyname", "param_list", "param",
-  "struct_list", 0
+  "NUMBER_P", "PATH_P", "'['", "']'", "'.'", "'='", "'{'", "'}'", "','",
+  "$accept", "cfg", "identifier", "elem_identifier", "keyname",
+  "array_keyname", "param_list", "param", "comma_opt", "struct_list", 0
 };
 #endif
 
@@ -625,23 +634,25 @@ static const char *const yytname[] =
    token YYLEX-NUM.  */
 static const yytype_uint16 yytoknum[] =
 {
-       0,   256,   257,   258,   259,   260,   261,    91,    93,    46,
-      61,   123,   125,    44
+       0,   256,   257,   258,   259,   260,   261,   262,    91,    93,
+      46,    61,   123,   125,    44
 };
 # endif
 
 /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
 static const yytype_uint8 yyr1[] =
 {
-       0,    14,    15,    16,    16,    17,    17,    18,    18,    19,
-      19,    20,    20,    20,    20,    20,    20,    21,    21
+       0,    15,    16,    17,    17,    18,    18,    19,    19,    20,
+      20,    21,    21,    22,    22,    22,    22,    22,    22,    22,
+      22,    23,    23,    24,    24
 };
 
 /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
 static const yytype_uint8 yyr2[] =
 {
-       0,     2,     1,     1,     1,     1,     4,     1,     3,     1,
-       2,     3,     3,     3,     3,     5,     5,     3,     5
+       0,     2,     1,     1,     1,     1,     4,     1,     3,     4,
+       3,     1,     3,     3,     3,     3,     3,     3,     6,     6,
+       6,     1,     0,     4,     6
 };
 
 /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
@@ -649,64 +660,71 @@ static const yytype_uint8 yyr2[] =
    means the default is an error.  */
 static const yytype_uint8 yydefact[] =
 {
-       0,     3,     4,     0,     5,     0,     0,     2,     9,     1,
-       0,     0,     0,    10,     0,     8,    13,    14,    12,    11,
-       0,     0,     6,     0,     0,     0,     0,    16,     0,    15,
-      17,     0,     0,    18
+       0,     3,     4,     0,     5,     0,     0,     0,    22,    11,
+       1,     0,     0,     0,     0,    21,     0,     0,     8,    10,
+      16,    17,    14,    13,    15,     0,     0,     0,    12,     6,
+       0,    22,    22,    22,    22,     0,     0,     0,     0,    19,
+       0,    18,    20,    23,    22,     0,    24
 };
 
 /* YYDEFGOTO[NTERM-NUM].  */
 static const yytype_int8 yydefgoto[] =
 {
-      -1,     3,     4,     5,     6,     7,     8,    24
+      -1,     3,     4,     5,     6,     7,     8,     9,    16,    31
 };
 
 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
    STATE-NUM.  */
-#define YYPACT_NINF -21
+#define YYPACT_NINF -26
 static const yytype_int8 yypact[] =
 {
-      29,   -21,   -21,     2,    20,     7,     0,    29,   -21,   -21,
-       6,    29,    17,   -21,    21,   -21,   -21,   -21,   -21,   -21,
-      23,    29,   -21,    29,    18,     1,     3,   -21,    24,   -21,
-     -21,    29,     5,   -21
+      29,   -26,   -26,     6,    -4,     4,    17,    24,     3,   -26,
+     -26,    31,    29,    38,    26,   -26,    29,    30,   -26,   -26,
+     -26,   -26,   -26,   -26,   -26,    35,    29,    29,   -26,    37,
+      29,    39,    39,    39,    39,     7,     5,    21,    23,   -26,
+      29,   -26,   -26,   -26,    39,    27,   -26
 };
 
 /* YYPGOTO[NTERM-NUM].  */
 static const yytype_int8 yypgoto[] =
 {
-     -21,   -21,   -21,   -21,    25,   -20,    -7,   -21
+     -26,   -26,   -26,   -26,    40,    42,   -25,   -16,   -21,   -26
 };
 
 /* YYTABLE[YYPACT[STATE-NUM]].  What to do in state STATE-NUM.  If
    positive, shift that token.  If negative, reduce the rule which
    number is the opposite.  If zero, do what YYDEFACT says.
    If YYTABLE_NINF, syntax error.  */
-#define YYTABLE_NINF -8
+#define YYTABLE_NINF -10
 static const yytype_int8 yytable[] =
 {
-      13,    25,     9,    26,     1,     2,     1,     2,     1,     2,
-      12,    32,    14,    29,     0,    30,    11,    33,    13,    13,
-      16,    17,    18,    19,    20,    13,    27,    10,    21,    22,
-      -7,    28,     1,     2,    23,    31,    15
+      28,    32,    33,    -2,    11,    34,    10,    -7,     1,     2,
+      35,    36,    37,    38,    12,    44,    39,    15,    41,    40,
+      28,    28,    28,    45,     1,     2,     1,     2,    13,    28,
+       1,     2,     1,     2,    42,    14,    43,    17,    27,    29,
+      46,    20,    21,    22,    23,    24,    25,    30,    -9,     0,
+      26,     0,    18,    15,    19
 };
 
 static const yytype_int8 yycheck[] =
 {
-       7,    21,     0,    23,     3,     4,     3,     4,     3,     4,
-      10,    31,     6,    12,    -1,    12,     9,    12,    25,    26,
-       3,     4,     5,     6,     7,    32,     8,     7,    11,     8,
-      10,    13,     3,     4,    11,    11,    11
+      16,    26,    27,     0,     8,    30,     0,    11,     3,     4,
+      31,    32,    33,    34,    10,    40,     9,    14,    13,    12,
+      36,    37,    38,    44,     3,     4,     3,     4,    11,    45,
+       3,     4,     3,     4,    13,    11,    13,     6,    12,     9,
+      13,     3,     4,     5,     6,     7,     8,    12,    11,    -1,
+      12,    -1,    12,    14,    12
 };
 
 /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
    symbol of state STATE-NUM.  */
 static const yytype_uint8 yystos[] =
 {
-       0,     3,     4,    15,    16,    17,    18,    19,    20,     0,
-       7,     9,    10,    20,     6,    18,     3,     4,     5,     6,
-       7,    11,     8,    11,    21,    19,    19,     8,    13,    12,
-      12,    11,    19,    12
+       0,     3,     4,    16,    17,    18,    19,    20,    21,    22,
+       0,     8,    10,    11,    11,    14,    23,     6,    19,    20,
+       3,     4,     5,     6,     7,     8,    12,    12,    22,     9,
+      12,    24,    21,    21,    21,    23,    23,    23,    23,     9,
+      12,    13,    13,    13,    21,    23,    13
 };
 
 #define yyerrok		(yyerrstatus = 0)
@@ -1525,35 +1543,35 @@ YYSTYPE yylval;
         case 2:
 
 /* Line 1455 of yacc.c  */
-#line 91 "prscfg.y"
+#line 92 "prscfg.y"
     { output = (yyval.node) = (yyvsp[(1) - (1)].node); }
     break;
 
   case 3:
 
 /* Line 1455 of yacc.c  */
-#line 95 "prscfg.y"
+#line 96 "prscfg.y"
     { MakeAtom((yyval.atom), (yyvsp[(1) - (1)].str)); }
     break;
 
   case 4:
 
 /* Line 1455 of yacc.c  */
-#line 96 "prscfg.y"
+#line 97 "prscfg.y"
     { MakeAtom((yyval.atom), (yyvsp[(1) - (1)].str)); }
     break;
 
   case 5:
 
 /* Line 1455 of yacc.c  */
-#line 100 "prscfg.y"
+#line 101 "prscfg.y"
     { (yyval.atom) = (yyvsp[(1) - (1)].atom); }
     break;
 
   case 6:
 
 /* Line 1455 of yacc.c  */
-#line 101 "prscfg.y"
+#line 102 "prscfg.y"
     { 
 			(yyval.atom) = (yyvsp[(1) - (4)].atom); 
 			(yyval.atom)->index = atoi((yyvsp[(3) - (4)].str));
@@ -1565,14 +1583,14 @@ YYSTYPE yylval;
   case 7:
 
 /* Line 1455 of yacc.c  */
-#line 110 "prscfg.y"
+#line 111 "prscfg.y"
     { (yyval.atom) = (yyvsp[(1) - (1)].atom); }
     break;
 
   case 8:
 
 /* Line 1455 of yacc.c  */
-#line 111 "prscfg.y"
+#line 112 "prscfg.y"
     { MakeList((yyval.atom), (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].atom)); }
     break;
 
@@ -1580,97 +1598,144 @@ YYSTYPE yylval;
 
 /* Line 1455 of yacc.c  */
 #line 116 "prscfg.y"
-    { (yyval.node) = (yyvsp[(1) - (1)].node); }
+    { 
+			(yyval.atom) = (yyvsp[(1) - (4)].atom);
+			(yyval.atom)->index = atoi((yyvsp[(3) - (4)].str));
+			/* XXX check !*/
+			free((yyvsp[(3) - (4)].str));
+		}
     break;
 
   case 10:
 
 /* Line 1455 of yacc.c  */
-#line 117 "prscfg.y"
-    { MakeList((yyval.node), (yyvsp[(2) - (2)].node), (yyvsp[(1) - (2)].node)); /* plainOptDef will revert the list */ }
+#line 122 "prscfg.y"
+    { MakeList((yyval.atom), (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].atom)); }
     break;
 
   case 11:
 
 /* Line 1455 of yacc.c  */
-#line 121 "prscfg.y"
-    { MakeScalarParam((yyval.node), number, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
+#line 126 "prscfg.y"
+    { (yyval.node) = (yyvsp[(1) - (1)].node); }
     break;
 
   case 12:
 
 /* Line 1455 of yacc.c  */
-#line 122 "prscfg.y"
-    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
+#line 127 "prscfg.y"
+    { MakeList((yyval.node), (yyvsp[(3) - (3)].node), (yyvsp[(1) - (3)].node)); /* plainOptDef will revert the list */ }
     break;
 
   case 13:
 
 /* Line 1455 of yacc.c  */
-#line 123 "prscfg.y"
-    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
+#line 131 "prscfg.y"
+    { MakeScalarParam((yyval.node), number, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
     break;
 
   case 14:
 
 /* Line 1455 of yacc.c  */
-#line 124 "prscfg.y"
-    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), NULL); free((yyvsp[(3) - (3)].str)); }
+#line 132 "prscfg.y"
+    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
     break;
 
   case 15:
 
 /* Line 1455 of yacc.c  */
-#line 125 "prscfg.y"
-    { MakeScalarParam((yyval.node), struct, (yyvsp[(1) - (5)].atom), (yyvsp[(4) - (5)].node)); SetParent( (yyval.node), (yyvsp[(4) - (5)].node) ); }
+#line 133 "prscfg.y"
+    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
     break;
 
   case 16:
 
 /* Line 1455 of yacc.c  */
-#line 126 "prscfg.y"
-    { (yyvsp[(4) - (5)].node)->name = (yyvsp[(1) - (5)].atom); (yyval.node) = (yyvsp[(4) - (5)].node); }
+#line 134 "prscfg.y"
+    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), (yyvsp[(3) - (3)].str)); }
     break;
 
   case 17:
 
 /* Line 1455 of yacc.c  */
-#line 130 "prscfg.y"
+#line 135 "prscfg.y"
+    { MakeScalarParam((yyval.node), string, (yyvsp[(1) - (3)].atom), NULL); free((yyvsp[(3) - (3)].str)); }
+    break;
+
+  case 18:
+
+/* Line 1455 of yacc.c  */
+#line 136 "prscfg.y"
+    { MakeScalarParam((yyval.node), struct, (yyvsp[(1) - (6)].atom), (yyvsp[(4) - (6)].node)); SetParent( (yyval.node), (yyvsp[(4) - (6)].node) ); }
+    break;
+
+  case 19:
+
+/* Line 1455 of yacc.c  */
+#line 137 "prscfg.y"
+    { (yyvsp[(4) - (6)].node)->name = (yyvsp[(1) - (6)].atom); (yyval.node) = (yyvsp[(4) - (6)].node); }
+    break;
+
+  case 20:
+
+/* Line 1455 of yacc.c  */
+#line 138 "prscfg.y"
+    { MakeScalarParam((yyval.node), struct, (yyvsp[(1) - (6)].atom), (yyvsp[(4) - (6)].node)); SetParent( (yyval.node), (yyvsp[(4) - (6)].node) ); }
+    break;
+
+  case 21:
+
+/* Line 1455 of yacc.c  */
+#line 142 "prscfg.y"
+    { (yyval.str)=NULL; }
+    break;
+
+  case 22:
+
+/* Line 1455 of yacc.c  */
+#line 143 "prscfg.y"
+    { (yyval.str)=NULL; }
+    break;
+
+  case 23:
+
+/* Line 1455 of yacc.c  */
+#line 147 "prscfg.y"
     {
 			OptDef	*str;
 			NameAtom	*idx;
 
 			MakeAtom(idx, NULL);
-			MakeScalarParam(str, struct, idx, (yyvsp[(2) - (3)].node)); 
-			SetParent( str, (yyvsp[(2) - (3)].node) );
+			MakeScalarParam(str, struct, idx, (yyvsp[(2) - (4)].node)); 
+			SetParent( str, (yyvsp[(2) - (4)].node) );
 			SetIndex( str, 0 );
 			MakeScalarParam((yyval.node), array, NULL, str);
 			SetParent( (yyval.node), str );
 		}
     break;
 
-  case 18:
+  case 24:
 
 /* Line 1455 of yacc.c  */
-#line 141 "prscfg.y"
+#line 158 "prscfg.y"
     {
 			OptDef	*str;
 			NameAtom	*idx;
 
 			MakeAtom(idx, NULL);
-			MakeScalarParam(str, struct, idx, (yyvsp[(4) - (5)].node)); 
-			SetParent(str, (yyvsp[(4) - (5)].node));
-			SetIndex(str, (yyvsp[(1) - (5)].node)->paramValue.arrayval->name->index + 1);
-			MakeList((yyvsp[(1) - (5)].node)->paramValue.arrayval, str, (yyvsp[(1) - (5)].node)->paramValue.arrayval); 
-			SetParent((yyvsp[(1) - (5)].node), str);
-			(yyval.node) = (yyvsp[(1) - (5)].node);
+			MakeScalarParam(str, struct, idx, (yyvsp[(4) - (6)].node)); 
+			SetParent(str, (yyvsp[(4) - (6)].node));
+			SetIndex(str, (yyvsp[(1) - (6)].node)->paramValue.arrayval->name->index + 1);
+			MakeList((yyvsp[(1) - (6)].node)->paramValue.arrayval, str, (yyvsp[(1) - (6)].node)->paramValue.arrayval); 
+			SetParent((yyvsp[(1) - (6)].node), str);
+			(yyval.node) = (yyvsp[(1) - (6)].node);
 		}
     break;
 
 
 
 /* Line 1455 of yacc.c  */
-#line 1573 "y.tab.c"
+#line 1636 "y.tab.c"
       default: break;
     }
   YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
@@ -1882,7 +1947,7 @@ YYSTYPE yylval;
 
 
 /* Line 1675 of yacc.c  */
-#line 155 "prscfg.y"
+#line 172 "prscfg.y"
 
 
 static int
@@ -2440,8 +2505,8 @@ static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner );
 	*yy_cp = '\0'; \
 	yyg->yy_c_buf_p = yy_cp;
 
-#define YY_NUM_RULES 26
-#define YY_END_OF_BUFFER 27
+#define YY_NUM_RULES 27
+#define YY_END_OF_BUFFER 28
 /* This struct is not used in this scanner,
    but its presence is necessary. */
 struct yy_trans_info
@@ -2449,14 +2514,14 @@ struct yy_trans_info
 	flex_int32_t yy_verify;
 	flex_int32_t yy_nxt;
 	};
-static yyconst flex_int16_t yy_accept[55] =
+static yyconst flex_int16_t yy_accept[56] =
     {   0,
-        0,    0,    0,    0,    0,    0,    0,    0,   27,   25,
-       23,   24,    6,    4,   25,    5,   25,    7,    3,    3,
-       15,   16,   13,   14,   21,   22,   19,   20,   19,   19,
-       23,    4,    7,    2,    0,    0,    3,    3,   15,   11,
-       12,   21,   19,   18,   17,    8,    0,    9,    3,    0,
-        1,    0,   10,    0
+        0,    0,    0,    0,    0,    0,    0,    0,   28,   26,
+       24,   25,    7,    4,   26,    6,   26,    8,    3,    3,
+       16,   17,   14,   15,   22,   23,   20,   21,   20,   20,
+       24,    4,    8,    5,    2,    0,    0,    3,    3,   16,
+       12,   13,   22,   20,   19,   18,    9,    0,   10,    3,
+        0,    1,    0,   11,    0
     } ;
 
 static yyconst flex_int32_t yy_ec[256] =
@@ -2464,17 +2529,17 @@ static yyconst flex_int32_t yy_ec[256] =
         1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
         1,    4,    4,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    2,    1,    5,    6,    1,    1,    1,    1,    1,
-        1,    7,    8,    9,    8,   10,   11,   12,   12,   12,
-       12,   12,   12,   12,   12,   12,   12,    1,    1,    1,
-        9,    1,    1,    1,   13,   13,   13,   13,   14,   13,
-       13,   13,   13,   13,   13,   15,   13,   16,   13,   13,
-       13,   13,   13,   13,   17,   13,   13,   13,   13,   13,
-        9,   18,    9,    1,   13,    1,   13,   13,   13,   13,
-
-       14,   13,   13,   13,   13,   13,   13,   15,   13,   16,
-       13,   13,   13,   13,   13,   13,   17,   13,   13,   13,
-       13,   13,    9,    1,    9,    1,    1,    1,    1,    1,
+        1,    2,    1,    5,    6,    1,    7,    1,    1,    1,
+        1,    8,    9,   10,   11,   12,   13,   14,   14,   14,
+       14,   14,   14,   14,   14,   14,   14,    1,    1,    1,
+       10,    1,    1,    1,   15,   15,   15,   15,   16,   15,
+       15,   15,   15,   15,   15,   17,   15,   18,   15,   15,
+       15,   15,   15,   15,   19,   15,   15,   15,   15,   15,
+       10,   20,   10,    1,   15,    1,   15,   15,   15,   15,
+
+       16,   15,   15,   15,   15,   15,   15,   17,   15,   18,
+       15,   15,   15,   15,   15,   15,   19,   15,   15,   15,
+       15,   15,   10,    1,   10,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
@@ -2491,66 +2556,74 @@ static yyconst flex_int32_t yy_ec[256] =
         1,    1,    1,    1,    1
     } ;
 
-static yyconst flex_int32_t yy_meta[19] =
+static yyconst flex_int32_t yy_meta[21] =
     {   0,
-        1,    1,    2,    1,    3,    1,    1,    1,    1,    1,
-        1,    4,    4,    4,    4,    4,    4,    3
+        1,    1,    2,    1,    3,    1,    4,    4,    5,    1,
+        6,    4,    4,    7,    8,    8,    8,    8,    8,    9
     } ;
 
-static yyconst flex_int16_t yy_base[63] =
+static yyconst flex_int16_t yy_base[67] =
     {   0,
-        0,    0,   16,   32,   51,   50,   48,   57,   52,  102,
-       18,  102,  102,   47,   36,  102,   40,   59,    0,   29,
-        0,  102,  102,   42,    0,  102,    0,  102,   33,   36,
-       21,   40,    0,  102,   29,   16,    0,   25,    0,  102,
-      102,    0,    0,    0,    0,   15,   27,   26,   21,   18,
-        0,   21,   20,  102,   73,   77,   81,   27,   85,   89,
-       93,   97
+        0,    0,   18,   19,  130,  129,   22,   23,  131,  134,
+       25,  134,  134,  128,  115,  134,  120,   28,    0,  108,
+        0,  134,  134,  123,    0,  134,    0,  134,  112,  116,
+       30,  121,    0,  114,  134,  107,   93,    0,   81,    0,
+      134,  134,    0,    0,    0,    0,   27,   68,   67,   56,
+       23,    0,   19,   14,  134,   44,   53,   62,   68,   71,
+       79,   87,   96,  105,  110,  113
     } ;
 
-static yyconst flex_int16_t yy_def[63] =
+static yyconst flex_int16_t yy_def[67] =
     {   0,
-       54,    1,   55,   55,   56,   56,   57,   57,   54,   54,
-       54,   54,   54,   54,   54,   54,   54,   54,   58,   58,
-       59,   54,   54,   60,   61,   54,   62,   54,   62,   62,
-       54,   54,   18,   54,   54,   54,   58,   58,   59,   54,
-       54,   61,   62,   62,   62,   54,   54,   54,   58,   54,
-       58,   54,   54,    0,   54,   54,   54,   54,   54,   54,
-       54,   54
+       55,    1,   56,   56,   57,   57,   58,   58,   55,   55,
+       55,   55,   55,   55,   55,   55,   59,   55,   60,   60,
+       61,   55,   55,   62,   63,   55,   64,   55,   64,   64,
+       55,   55,   18,   59,   55,   55,   65,   60,   60,   61,
+       55,   55,   63,   64,   64,   64,   55,   55,   55,   60,
+       66,   60,   55,   55,    0,   55,   55,   55,   55,   55,
+       55,   55,   55,   55,   55,   55
     } ;
 
-static yyconst flex_int16_t yy_nxt[121] =
+static yyconst flex_int16_t yy_nxt[155] =
     {   0,
-       10,   11,   12,   11,   13,   14,   10,   15,   16,   16,
-       17,   18,   19,   19,   19,   20,   19,   10,   22,   31,
-       23,   31,   31,   47,   31,   52,   46,   48,   50,   53,
-       37,   53,   53,   24,   22,   51,   23,   48,   48,   49,
-       46,   32,   45,   44,   41,   38,   34,   33,   32,   24,
-       28,   54,   26,   26,   29,   54,   54,   54,   30,   28,
-       54,   54,   54,   29,   54,   54,   54,   30,   35,   54,
-       33,   54,   36,   21,   21,   21,   21,   25,   25,   25,
-       25,   27,   27,   27,   27,   39,   54,   54,   39,   40,
-       40,   40,   40,   42,   54,   42,   42,   43,   54,   43,
-
-       43,    9,   54,   54,   54,   54,   54,   54,   54,   54,
-       54,   54,   54,   54,   54,   54,   54,   54,   54,   54
+       10,   11,   12,   11,   13,   14,   10,   10,   15,   16,
+       15,   16,   17,   18,   19,   19,   19,   20,   19,   10,
+       22,   22,   23,   23,   28,   28,   31,   54,   31,   29,
+       29,   31,   54,   31,   30,   30,   54,   24,   24,   36,
+       47,   33,   51,   37,   21,   21,   21,   21,   21,   21,
+       21,   21,   21,   25,   25,   25,   25,   25,   25,   25,
+       25,   25,   27,   27,   27,   27,   27,   27,   27,   27,
+       27,   34,   52,   34,   34,   34,   34,   38,   38,   40,
+       49,   49,   40,   40,   40,   40,   40,   41,   41,   41,
+       41,   41,   41,   41,   41,   41,   43,   50,   43,   43,
+
+       43,   43,   43,   43,   43,   44,   49,   44,   44,   44,
+       44,   44,   44,   44,   48,   48,   48,   53,   53,   53,
+       47,   55,   32,   46,   45,   42,   39,   35,   33,   32,
+       55,   26,   26,    9,   55,   55,   55,   55,   55,   55,
+       55,   55,   55,   55,   55,   55,   55,   55,   55,   55,
+       55,   55,   55,   55
     } ;
 
-static yyconst flex_int16_t yy_chk[121] =
+static yyconst flex_int16_t yy_chk[155] =
     {   0,
         1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
-        1,    1,    1,    1,    1,    1,    1,    1,    3,   11,
-        3,   11,   31,   36,   31,   50,   46,   36,   46,   50,
-       58,   53,   52,    3,    4,   49,    4,   48,   47,   38,
-       35,   32,   30,   29,   24,   20,   17,   15,   14,    4,
-        7,    9,    6,    5,    7,    0,    0,    0,    7,    8,
-        0,    0,    0,    8,    0,    0,    0,    8,   18,    0,
-       18,    0,   18,   55,   55,   55,   55,   56,   56,   56,
-       56,   57,   57,   57,   57,   59,    0,    0,   59,   60,
-       60,   60,   60,   61,    0,   61,   61,   62,    0,   62,
-
-       62,   54,   54,   54,   54,   54,   54,   54,   54,   54,
-       54,   54,   54,   54,   54,   54,   54,   54,   54,   54
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        3,    4,    3,    4,    7,    8,   11,   54,   11,    7,
+        8,   31,   53,   31,    7,    8,   51,    3,    4,   18,
+       47,   18,   47,   18,   56,   56,   56,   56,   56,   56,
+       56,   56,   56,   57,   57,   57,   57,   57,   57,   57,
+       57,   57,   58,   58,   58,   58,   58,   58,   58,   58,
+       58,   59,   50,   59,   59,   59,   59,   60,   60,   61,
+       49,   48,   61,   61,   61,   61,   61,   62,   62,   62,
+       62,   62,   62,   62,   62,   62,   63,   39,   63,   63,
+
+       63,   63,   63,   63,   63,   64,   37,   64,   64,   64,
+       64,   64,   64,   64,   65,   65,   65,   66,   66,   66,
+       36,   34,   32,   30,   29,   24,   20,   17,   15,   14,
+        9,    6,    5,   55,   55,   55,   55,   55,   55,   55,
+       55,   55,   55,   55,   55,   55,   55,   55,   55,   55,
+       55,   55,   55,   55
     } ;
 
 /* The intent behind this definition is that it'll catch
@@ -2582,7 +2655,7 @@ static YY_BUFFER_STATE buf = NULL;
 
 
 
-#line 504 "prscfg_scan.c"
+#line 512 "prscfg_scan.c"
 
 #define INITIAL 0
 #define xQUOTED 1
@@ -2824,7 +2897,7 @@ YY_DECL
 #line 40 "prscfg.l"
 
 
-#line 746 "prscfg_scan.c"
+#line 754 "prscfg_scan.c"
 
     yylval = yylval_param;
 
@@ -2879,13 +2952,13 @@ YY_DECL
 			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 				{
 				yy_current_state = (int) yy_def[yy_current_state];
-				if ( yy_current_state >= 55 )
+				if ( yy_current_state >= 56 )
 					yy_c = yy_meta[(unsigned int) yy_c];
 				}
 			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
 			++yy_cp;
 			}
-		while ( yy_current_state != 54 );
+		while ( yy_current_state != 55 );
 		yy_cp = yyg->yy_last_accepting_cpos;
 		yy_current_state = yyg->yy_last_accepting_state;
 
@@ -2943,11 +3016,21 @@ YY_RULE_SETUP
 case 5:
 YY_RULE_SETUP
 #line 65 "prscfg.l"
-{ return *yytext; }
+{
+			yylval->str = strdupn(yytext, yyleng);
+			if (!yylval->str)
+				scan_yyerror("No memory", yyextra->lineno);
+			return PATH_P;
+		}
 	YY_BREAK
 case 6:
 YY_RULE_SETUP
-#line 67 "prscfg.l"
+#line 72 "prscfg.l"
+{ return *yytext; }
+	YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 74 "prscfg.l"
 {
 			yyextra->total = 256;
 			yyextra->strbuf = malloc(yyextra->total);
@@ -2957,16 +3040,6 @@ YY_RULE_SETUP
 			BEGIN xQUOTED;
 	}
 	YY_BREAK
-case 7:
-YY_RULE_SETUP
-#line 76 "prscfg.l"
-{
-			yylval->str = strdupn(yytext, yyleng);
-			if (!yylval->str)
-				scan_yyerror("No memory", yyextra->lineno);	
-			return NUMBER_P;
-		}
-	YY_BREAK
 case 8:
 YY_RULE_SETUP
 #line 83 "prscfg.l"
@@ -3000,22 +3073,32 @@ YY_RULE_SETUP
 case 11:
 YY_RULE_SETUP
 #line 104 "prscfg.l"
+{
+			yylval->str = strdupn(yytext, yyleng);
+			if (!yylval->str)
+				scan_yyerror("No memory", yyextra->lineno);	
+			return NUMBER_P;
+		}
+	YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 111 "prscfg.l"
 {
 			if (addchar(yyscanner, yytext[1]))
 				scan_yyerror("No memory", yyextra->lineno);
 		}
 	YY_BREAK
-case 12:
-/* rule 12 can match eol */
+case 13:
+/* rule 13 can match eol */
 YY_RULE_SETUP
-#line 109 "prscfg.l"
+#line 116 "prscfg.l"
 {
 			yyextra->lineno++;
 		}
 	YY_BREAK
-case 13:
+case 14:
 YY_RULE_SETUP
-#line 113 "prscfg.l"
+#line 120 "prscfg.l"
 {
 			yyextra->strbuf[yyextra->length] = '\0';
 			yylval->str = yyextra->strbuf;
@@ -3024,47 +3107,47 @@ YY_RULE_SETUP
 			return STRING_P;
 		}
 	YY_BREAK
-case 14:
+case 15:
 YY_RULE_SETUP
-#line 121 "prscfg.l"
+#line 128 "prscfg.l"
 {
 			/* This is only needed for \ just before EOF */
 		}
 	YY_BREAK
 case YY_STATE_EOF(xQUOTED):
-#line 125 "prscfg.l"
+#line 132 "prscfg.l"
 {
 			return scan_yyerror("Unexpected end of string", yyextra->lineno);
 		}
 	YY_BREAK
-case 15:
+case 16:
 YY_RULE_SETUP
-#line 129 "prscfg.l"
+#line 136 "prscfg.l"
 {
 			if (addstring(yyscanner, yytext, yyleng))
 				scan_yyerror("No memory", yyextra->lineno);
 		}
 	YY_BREAK
-case 16:
-/* rule 16 can match eol */
+case 17:
+/* rule 17 can match eol */
 YY_RULE_SETUP
-#line 134 "prscfg.l"
+#line 141 "prscfg.l"
 {
 			if (addchar(yyscanner, yytext[0]))
 				scan_yyerror("No memory", yyextra->lineno);
 			yyextra->lineno++;
 		}
 	YY_BREAK
-case 17:
+case 18:
 YY_RULE_SETUP
-#line 140 "prscfg.l"
+#line 147 "prscfg.l"
 {
 			yyextra->commentCounter++;
 		}
 	YY_BREAK
-case 18:
+case 19:
 YY_RULE_SETUP
-#line 144 "prscfg.l"
+#line 151 "prscfg.l"
 {
 			yyextra->commentCounter--;
 			if (yyextra->commentCounter == 0)
@@ -3072,31 +3155,31 @@ YY_RULE_SETUP
 		}
 	YY_BREAK
 case YY_STATE_EOF(CCOMMENT):
-#line 150 "prscfg.l"
+#line 157 "prscfg.l"
 {
 			return scan_yyerror("Unexpected end of string (inside comment)", yyextra->lineno);
 		}
 	YY_BREAK
-case 19:
+case 20:
 YY_RULE_SETUP
-#line 154 "prscfg.l"
+#line 161 "prscfg.l"
 { /* ignore */ }
 	YY_BREAK
-case 20:
-/* rule 20 can match eol */
+case 21:
+/* rule 21 can match eol */
 YY_RULE_SETUP
-#line 156 "prscfg.l"
+#line 163 "prscfg.l"
 { yyextra->lineno++; }
 	YY_BREAK
-case 21:
+case 22:
 YY_RULE_SETUP
-#line 158 "prscfg.l"
+#line 165 "prscfg.l"
 { /* ignore */ }
 	YY_BREAK
-case 22:
-/* rule 22 can match eol */
+case 23:
+/* rule 23 can match eol */
 YY_RULE_SETUP
-#line 160 "prscfg.l"
+#line 167 "prscfg.l"
 { 
 			BEGIN INITIAL; 
 			yyextra->lineno++;
@@ -3104,33 +3187,33 @@ YY_RULE_SETUP
 	YY_BREAK
 case YY_STATE_EOF(INITIAL):
 case YY_STATE_EOF(SCOMMENT):
-#line 165 "prscfg.l"
+#line 172 "prscfg.l"
 {
 			yyterminate();
 		}
 	YY_BREAK
-case 23:
+case 24:
 YY_RULE_SETUP
-#line 169 "prscfg.l"
+#line 176 "prscfg.l"
 { /* ignore */ }
 	YY_BREAK
-case 24:
-/* rule 24 can match eol */
+case 25:
+/* rule 25 can match eol */
 YY_RULE_SETUP
-#line 171 "prscfg.l"
+#line 178 "prscfg.l"
 { yyextra->lineno++; }
 	YY_BREAK
-case 25:
+case 26:
 YY_RULE_SETUP
-#line 173 "prscfg.l"
+#line 180 "prscfg.l"
 { return scan_yyerror("syntax error: Unknown character", yyextra->lineno); }
 	YY_BREAK
-case 26:
+case 27:
 YY_RULE_SETUP
-#line 175 "prscfg.l"
+#line 182 "prscfg.l"
 YY_FATAL_ERROR( "flex scanner jammed" );
 	YY_BREAK
-#line 1052 "prscfg_scan.c"
+#line 1070 "prscfg_scan.c"
 
 	case YY_END_OF_BUFFER:
 		{
@@ -3423,7 +3506,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
 		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 			{
 			yy_current_state = (int) yy_def[yy_current_state];
-			if ( yy_current_state >= 55 )
+			if ( yy_current_state >= 56 )
 				yy_c = yy_meta[(unsigned int) yy_c];
 			}
 		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
@@ -3452,11 +3535,11 @@ static int yy_get_next_buffer (yyscan_t yyscanner)
 	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
 		{
 		yy_current_state = (int) yy_def[yy_current_state];
-		if ( yy_current_state >= 55 )
+		if ( yy_current_state >= 56 )
 			yy_c = yy_meta[(unsigned int) yy_c];
 		}
 	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
-	yy_is_jam = (yy_current_state == 54);
+	yy_is_jam = (yy_current_state == 55);
 
 	return yy_is_jam ? 0 : yy_current_state;
 }
@@ -4254,7 +4337,7 @@ void prscfg_yyfree (void * ptr , yyscan_t yyscanner)
 
 #define YYTABLES_NAME "yytables"
 
-#line 175 "prscfg.l"
+#line 182 "prscfg.l"
 
 
 
diff --git a/third_party/confetti/prscfg.h b/third_party/confetti/prscfg.h
index 7fbc20bdaff8fdb20e04cbd35b7a353397124cfb..ceeeab4e29ef39c42b1620fc38014a62d1adc38f 100644
--- a/third_party/confetti/prscfg.h
+++ b/third_party/confetti/prscfg.h
@@ -45,6 +45,7 @@ typedef	enum ConfettyError {
 	CNF_WRONGRANGE,
 	CNF_NOMEMORY,
 	CNF_SYNTAXERROR,
+	CNF_NOTSET,
 	CNF_INTERNALERROR
 } ConfettyError;
 
diff --git a/third_party/qsort_arg.c b/third_party/qsort_arg.c
new file mode 100644
index 0000000000000000000000000000000000000000..e627af9c8e5c48599ea6fbec6bccd13c53d48e12
--- /dev/null
+++ b/third_party/qsort_arg.c
@@ -0,0 +1,200 @@
+/*
+ * Imported from PostgreSQL sources by Teodor Sigaev <teodor@sigaev.ru>, <sigaev@corp.mail.ru>
+ */
+
+/*
+ *	qsort_arg.c: qsort with a passthrough "void *" argument
+ *
+ *	Modifications from vanilla NetBSD source:
+ *	  Add do ... while() macro fix
+ *	  Remove __inline, _DIAGASSERTs, __P
+ *	  Remove ill-considered "swap_cnt" switch to insertion sort,
+ *	  in favor of a simple check for presorted input.
+ *
+ *	CAUTION: if you change this file, see also qsort.c
+ *
+ *	$PostgreSQL: pgsql/src/port/qsort_arg.c,v 1.4 2007/03/18 05:36:50 neilc Exp $
+ */
+
+/*	$NetBSD: qsort.c,v 1.13 2003/08/07 16:43:42 agc Exp $	*/
+
+/*-
+ * Copyright (c) 1992, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *	  may be used to endorse or promote products derived from this software
+ *	  without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.	IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <third_party/qsort_arg.h>
+
+#define min(a, b)   (a) < (b) ? a : b
+
+static char *med3(char *a, char *b, char *c,
+	 int (*cmp)(const void *a, const void *b, void *arg), void *arg);
+static void swapfunc(char *, char *, size_t, int);
+
+/*
+ * Qsort routine based on J. L. Bentley and M. D. McIlroy,
+ * "Engineering a sort function",
+ * Software--Practice and Experience 23 (1993) 1249-1265.
+ * We have modified their original by adding a check for already-sorted input,
+ * which seems to be a win per discussions on pgsql-hackers around 2006-03-21.
+ */
+#define swapcode(TYPE, parmi, parmj, n) \
+do {		\
+	size_t i = (n) / sizeof (TYPE);			\
+	TYPE *pi = (TYPE *)(void *)(parmi);			\
+	TYPE *pj = (TYPE *)(void *)(parmj);			\
+	do {						\
+		TYPE	t = *pi;			\
+		*pi++ = *pj;				\
+		*pj++ = t;				\
+		} while (--i > 0);				\
+} while (0)
+
+#define SWAPINIT(a, es) swaptype = ((char *)(a) - (char *)0) % sizeof(long) || \
+	(es) % sizeof(long) ? 2 : (es) == sizeof(long)? 0 : 1;
+
+static void
+swapfunc(char *a, char *b, size_t n, int swaptype)
+{
+	if (swaptype <= 1)
+		swapcode(long, a, b, n);
+	else
+		swapcode(char, a, b, n);
+}
+
+#define swap(a, b)						\
+	if (swaptype == 0) {					\
+		long t = *(long *)(void *)(a);			\
+		*(long *)(void *)(a) = *(long *)(void *)(b);	\
+		*(long *)(void *)(b) = t;			\
+	} else							\
+		swapfunc(a, b, es, swaptype)
+
+#define vecswap(a, b, n) if ((n) > 0) swapfunc((a), (b), (size_t)(n), swaptype)
+
+static char *
+med3(char *a, char *b, char *c, int (*cmp)(const void *a, const void *b, void *arg), void *arg)
+{
+	return cmp(a, b, arg) < 0 ?
+		(cmp(b, c, arg) < 0 ? b : (cmp(a, c, arg) < 0 ? c : a))
+		: (cmp(b, c, arg) > 0 ? b : (cmp(a, c, arg) < 0 ? a : c));
+}
+
+void
+qsort_arg(void *a, size_t n, size_t es, int (*cmp)(const void *a, const void *b, void *arg), void *arg)
+{
+	char	   *pa,
+			   *pb,
+			   *pc,
+			   *pd,
+			   *pl,
+			   *pm,
+			   *pn;
+	int			d,
+				r,
+				swaptype,
+				presorted;
+
+loop:SWAPINIT(a, es);
+	if (n < 7)
+	{
+		for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
+			for (pl = pm; pl > (char *) a && cmp(pl - es, pl, arg) > 0;
+				 pl -= es)
+				swap(pl, pl - es);
+		return;
+	}
+	presorted = 1;
+	for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
+	{
+		if (cmp(pm - es, pm, arg) > 0)
+		{
+			presorted = 0;
+			break;
+		}
+	}
+	if (presorted)
+		return;
+	pm = (char *) a + (n / 2) * es;
+	if (n > 7)
+	{
+		pl = (char *) a;
+		pn = (char *) a + (n - 1) * es;
+		if (n > 40)
+		{
+			d = (n / 8) * es;
+			pl = med3(pl, pl + d, pl + 2 * d, cmp, arg);
+			pm = med3(pm - d, pm, pm + d, cmp, arg);
+			pn = med3(pn - 2 * d, pn - d, pn, cmp, arg);
+		}
+		pm = med3(pl, pm, pn, cmp, arg);
+	}
+	swap(a, pm);
+	pa = pb = (char *) a + es;
+	pc = pd = (char *) a + (n - 1) * es;
+	for (;;)
+	{
+		while (pb <= pc && (r = cmp(pb, a, arg)) <= 0)
+		{
+			if (r == 0)
+			{
+				swap(pa, pb);
+				pa += es;
+			}
+			pb += es;
+		}
+		while (pb <= pc && (r = cmp(pc, a, arg)) >= 0)
+		{
+			if (r == 0)
+			{
+				swap(pc, pd);
+				pd -= es;
+			}
+			pc -= es;
+		}
+		if (pb > pc)
+			break;
+		swap(pb, pc);
+		pb += es;
+		pc -= es;
+	}
+	pn = (char *) a + n * es;
+	r = min(pa - (char *) a, pb - pa);
+	vecswap(a, pb - r, r);
+	r = min(pd - pc, pn - pd - es);
+	vecswap(pb, pn - r, r);
+	if ((r = pb - pa) > es)
+		qsort_arg(a, r / es, es, cmp, arg);
+	if ((r = pd - pc) > es)
+	{
+		/* Iterate rather than recurse to save stack space */
+		a = pn - r;
+		n = r / es;
+		goto loop;
+	}
+/*		qsort_arg(pn - r, r / es, es, cmp, arg);*/
+}
diff --git a/third_party/qsort_arg.h b/third_party/qsort_arg.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a94e1e3b4889257d845d365cd5b9677581e268a
--- /dev/null
+++ b/third_party/qsort_arg.h
@@ -0,0 +1,8 @@
+#ifndef QSORT_ARG_H
+#define QSORT_ARG_H
+
+#include <sys/types.h>
+
+void qsort_arg(void *a, size_t n, size_t es, int (*cmp)(const void *a, const void *b, void *arg), void *arg);
+
+#endif
diff --git a/third_party/sptree.h b/third_party/sptree.h
new file mode 100644
index 0000000000000000000000000000000000000000..865b70a5367883ba6aa9d611e95f555fc559a366
--- /dev/null
+++ b/third_party/sptree.h
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2010 Mail.RU
+ * Copyright (C) 2010 Teodor Sigaev
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _SPTREE_H_
+#define _SPTREE_H_
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <third_party/qsort_arg.h>
+
+#ifndef SPTREE_NODE_SELF
+/*
+ * user could suggest pointer's storage himself
+ */
+typedef    u_int32_t spnode_t;
+#define    SPNIL (0xffffffff)
+
+typedef struct sptree_node_pointers {
+    u_int32_t    left;   /* sizeof(spnode_t) >= sizeof(sptree_node_pointers.left) !!! */
+    u_int32_t    right;
+} sptree_node_pointers;
+
+#define GET_SPNODE_LEFT(snp)        ( (snp)->left ) 
+#define SET_SPNODE_LEFT(snp, v)        (snp)->left = (v) 
+#define GET_SPNODE_RIGHT(snp)        ( (snp)->right )
+#define SET_SPNODE_RIGHT(snp, v)    (snp)->right = (v)
+
+#endif /* SPTREE_NODE_SELF */
+
+#ifndef alpha
+#define    alpha    ((double)0.75)
+#endif
+#define    COUNTALPHA(size)     floor(log((double)(size))/log((double)1.0/alpha))
+
+#define    _GET_SPNODE_LEFT(n)         GET_SPNODE_LEFT( t->lrpointers + (n) )
+#define    _SET_SPNODE_LEFT(n, v)      SET_SPNODE_LEFT( t->lrpointers + (n), (v) )
+#define    _GET_SPNODE_RIGHT(n)        GET_SPNODE_RIGHT( t->lrpointers + (n) )
+#define    _SET_SPNODE_RIGHT(n, v)     SET_SPNODE_RIGHT( t->lrpointers + (n), (v) )
+
+#define    ITHELEM(t, i)               ( (t)->members + (t)->elemsize * (i) )
+
+/*
+ * makes definition of tree with methods, name should
+ * be unique across all definitions.
+ *
+ * Methods:
+ *   void sptree_NAME_init(sptree_NAME *tree, size_t elemsize, void *array, 
+ *                         spnode_t array_len, spnode_t array_size, 
+ *                         int (*compar)(const void *, const void *, void *), void *arg)
+ *   void* sptree_NAME_find(sptree_NAME *tree, void *key)
+ *   void sptree_NAME_insert(sptree_NAME *tree, void *value)
+ *   void sptree_NAME_delete(sptree_NAME *tree, void *value)
+ *   spnode_t sptree_NAME_walk(sptree_NAME *t, void* array, spnode_t limit, spnode_t offset)
+ *   sptree_NAME_walk_cb(sptree_NAME *t, int (*cb)(void* cb_arg, void* elem), void *cb_arg )
+ *   sptree_NAME_iterator* sptree_NAME_iterator_init(sptree_NAME *t) 
+ *   void sptree_NAME_iterator_init_set(sptree_NAME *t, sptree_NAME_iterator **iterator, void *start)
+ *   void* sptree_NAME_iterator_next(sptree_NAME_iterator *i)
+ *   void sptree_NAME_iterator_free(sptree_NAME_iterator *i)
+ */
+
+#define SPTREE_DEF(name, realloc)                                                         \
+typedef struct sptree_##name {                                                            \
+    void                    *members;                                                     \
+    sptree_node_pointers    *lrpointers;                                                  \
+                                                                                          \
+    spnode_t                nmember;                                                      \
+    spnode_t                ntotal;                                                       \
+                                                                                          \
+    int                     (*compare)(const void *, const void *, void *);               \
+    void*                   arg;                                                          \
+    size_t                  elemsize;                                                     \
+                                                                                          \
+    spnode_t                root;                                                         \
+    spnode_t                garbage_head;                                                 \
+    spnode_t                size;                                                         \
+    spnode_t                max_size;                                                     \
+    spnode_t                max_depth;                                                    \
+} sptree_##name;                                                                          \
+                                                                                          \
+static spnode_t                                                                           \
+sptree_##name##_mktree(sptree_##name *t, spnode_t depth,                                  \
+                                    spnode_t start, spnode_t end) {                       \
+    spnode_t    half = ( (end + start) >> 1 ), tmp;                                       \
+                                                                                          \
+    if (depth > t->max_depth) t->max_depth = depth;                                       \
+                                                                                          \
+    if ( half == start ||                                                                 \
+            ( tmp = sptree_##name##_mktree(t, depth+1, start, half)) == half )            \
+        _SET_SPNODE_LEFT(half, SPNIL);                                                    \
+    else                                                                                  \
+        _SET_SPNODE_LEFT(half, tmp);                                                      \
+    if ( half+1 >= end ||                                                                 \
+            ( tmp = sptree_##name##_mktree(t, depth+1, half+1, end)) == half )            \
+        _SET_SPNODE_RIGHT(half, SPNIL);                                                   \
+    else                                                                                  \
+        _SET_SPNODE_RIGHT(half, tmp);                                                     \
+                                                                                          \
+    return half;                                                                          \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_init(sptree_##name *t, size_t elemsize, void *m,                          \
+                    spnode_t nm, spnode_t nt,                                             \
+                    int (*compare)(const void *, const void *, void *), void *arg) {      \
+    memset(t, 0, sizeof(*t));                                                             \
+    t->members = m;                                                                       \
+    t->max_size = t->size = t->nmember = nm;                                              \
+    t->ntotal = (nt==0) ? nm : nt;                                                        \
+    t->compare = compare;                                                                 \
+    t->arg = arg;                                                                         \
+    t->elemsize = elemsize;                                                               \
+    t->garbage_head = t->root = SPNIL;                                                    \
+                                                                                          \
+    if (t->ntotal == 0 || t->members == NULL) { /* from scratch */                        \
+        if (t->ntotal == 0) {                                                             \
+            t->members = NULL;                                                            \
+            t->ntotal = 64;                                                               \
+        }                                                                                 \
+                                                                                          \
+        if (t->members == NULL)                                                           \
+            t->members = realloc(NULL, elemsize * t->ntotal);                             \
+    }                                                                                     \
+    t->lrpointers = realloc(NULL, sizeof(sptree_node_pointers) * t->ntotal);              \
+                                                                                          \
+    if (t->nmember == 1) {                                                                \
+        t->root = 0;                                                                      \
+        _SET_SPNODE_RIGHT(0, SPNIL);                                                      \
+        _SET_SPNODE_LEFT(0, SPNIL);                                                       \
+    } else if (t->nmember > 1)    {                                                       \
+        qsort_arg(t->members, t->nmember, elemsize, t->compare, t->arg);                  \
+        /* create tree */                                                                 \
+        t->root = sptree_##name##_mktree(t, 1, 0, t->nmember);                            \
+    }                                                                                     \
+}                                                                                         \
+                                                                                          \
+static inline void*                                                                       \
+sptree_##name##_find(sptree_##name *t, void *k)    {                                      \
+    spnode_t    node = t->root;                                                           \
+    while(node != SPNIL) {                                                                \
+        int r = t->compare(k, ITHELEM(t, node), t->arg);                                  \
+        if (r > 0) {                                                                      \
+            node = _GET_SPNODE_RIGHT(node);                                               \
+        } else if (r < 0) {                                                               \
+            node = _GET_SPNODE_LEFT(node);                                                \
+        } else {                                                                          \
+            return ITHELEM(t, node);                                                      \
+        }                                                                                 \
+    }                                                                                     \
+    return NULL;                                                                          \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_size_of_subtree(sptree_##name *t, spnode_t node) {                        \
+    if (node == SPNIL)                                                                    \
+        return 0;                                                                         \
+    return 1 +                                                                            \
+        sptree_##name##_size_of_subtree(t, _GET_SPNODE_LEFT(node)) +                      \
+        sptree_##name##_size_of_subtree(t, _GET_SPNODE_RIGHT(node));                      \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_get_place(sptree_##name *t) {                                             \
+    spnode_t    node;                                                                     \
+    if (t->garbage_head != SPNIL) {                                                       \
+        node = t->garbage_head;                                                           \
+        t->garbage_head = _GET_SPNODE_LEFT(t->garbage_head);                              \
+    } else {                                                                              \
+        if (t->nmember >= t->ntotal) {                                                    \
+            t->ntotal *= 2;                                                               \
+            t->members = realloc(t->members, t->ntotal * t->elemsize);                    \
+            t->lrpointers = realloc(t->lrpointers,                                        \
+                                    t->ntotal * sizeof(sptree_node_pointers));            \
+        }                                                                                 \
+                                                                                          \
+        node = t->nmember;                                                                \
+        t->nmember++;                                                                     \
+    }                                                                                     \
+    _SET_SPNODE_LEFT(node, SPNIL);                                                        \
+    _SET_SPNODE_RIGHT(node, SPNIL);                                                       \
+    return node;                                                                          \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_flatten_tree(sptree_##name *t, spnode_t root, spnode_t head){             \
+    spnode_t    node;                                                                     \
+    if (root == SPNIL)                                                                    \
+        return head;                                                                      \
+    node = sptree_##name##_flatten_tree(t, _GET_SPNODE_RIGHT(root), head);                \
+    _SET_SPNODE_RIGHT(root, node);                                                        \
+    return sptree_##name##_flatten_tree(t, _GET_SPNODE_LEFT(root), root);                 \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_build_tree(sptree_##name *t, spnode_t node, spnode_t size) {              \
+    spnode_t    tmp;                                                                      \
+    if (size == 0) {                                                                      \
+        _SET_SPNODE_LEFT(node, SPNIL);                                                    \
+        return node;                                                                      \
+    }                                                                                     \
+    spnode_t root = sptree_##name##_build_tree(t,                                         \
+                node, ceil(((double)size-1.0)/2.0));                                      \
+    spnode_t list = sptree_##name##_build_tree(t,                                         \
+                _GET_SPNODE_RIGHT(root), floor(((double)size-1.0)/2.0));                  \
+    tmp = _GET_SPNODE_LEFT(list);                                                         \
+    _SET_SPNODE_RIGHT(root, tmp);                                                         \
+    _SET_SPNODE_LEFT(list, root);                                                         \
+                                                                                          \
+    return list;                                                                          \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_balance(sptree_##name *t, spnode_t node, spnode_t size) {                 \
+    spnode_t fake = sptree_##name##_get_place(t);                                         \
+    spnode_t z;                                                                           \
+                                                                                          \
+    z = sptree_##name##_flatten_tree(t, node, fake);                                      \
+    sptree_##name##_build_tree(t, z, size);                                               \
+                                                                                          \
+    z = _GET_SPNODE_LEFT(fake);                                                           \
+    _SET_SPNODE_LEFT(fake, t->garbage_head);                                              \
+    t->garbage_head = fake;                                                               \
+    return z;                                                                             \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_insert(sptree_##name *t, void *v) {                                       \
+    spnode_t    node, depth = 0;                                                          \
+    spnode_t    path[ t->max_depth + 2];                                                  \
+                                                                                          \
+    if (t->root == SPNIL) {                                                               \
+        _SET_SPNODE_LEFT(0, SPNIL);                                                       \
+        _SET_SPNODE_RIGHT(0, SPNIL);                                                      \
+        memcpy(t->members, v, t->elemsize);                                               \
+        t->root = 0;                                                                      \
+        t->garbage_head = SPNIL;                                                          \
+        t->nmember = 1;                                                                   \
+        t->size=1;                                                                        \
+        return;                                                                           \
+    } else {                                                                              \
+        spnode_t    parent = t->root;                                                     \
+                                                                                          \
+        for(;;)    {                                                                      \
+            int r = t->compare(v, ITHELEM(t, parent), t->arg);                            \
+            if (r==0) {                                                                   \
+                memcpy(ITHELEM(t, parent), v, t->elemsize);                               \
+                return;                                                                   \
+            }                                                                             \
+            path[depth] = parent;                                                         \
+            depth++;                                                                      \
+            if (r>0) {                                                                    \
+                if (_GET_SPNODE_RIGHT(parent) == SPNIL) {                                 \
+                    node = sptree_##name##_get_place(t);                                  \
+                    memcpy(ITHELEM(t, node), v, t->elemsize);                             \
+                    _SET_SPNODE_RIGHT(parent, node);                                      \
+                    break;                                                                \
+                } else {                                                                  \
+                    parent = _GET_SPNODE_RIGHT(parent);                                   \
+                }                                                                         \
+            } else {                                                                      \
+                if (_GET_SPNODE_LEFT(parent) == SPNIL) {                                  \
+                    node = sptree_##name##_get_place(t);                                  \
+                    memcpy(ITHELEM(t, node), v, t->elemsize);                             \
+                    _SET_SPNODE_LEFT(parent, node);                                       \
+                    break;                                                                \
+                } else {                                                                  \
+                    parent = _GET_SPNODE_LEFT(parent);                                    \
+                }                                                                         \
+            }                                                                             \
+        }                                                                                 \
+    }                                                                                     \
+                                                                                          \
+    t->size++;                                                                            \
+    if ( t->size > t->max_size )                                                          \
+        t->max_size = t->size;                                                            \
+    if ( depth > t->max_depth )                                                           \
+        t->max_depth = depth;                                                             \
+                                                                                          \
+    if ( (double)depth > COUNTALPHA(t->size)) {                                           \
+        spnode_t    parent;                                                               \
+        spnode_t    i, size = 1 ;                                                         \
+                                                                                          \
+        path[depth] = node;                                                               \
+                                                                                          \
+        for (i = 1; ; i++) {                                                              \
+            if (i < depth) {                                                              \
+                parent = path[ depth - i ];                                               \
+                size += 1 + sptree_##name##_size_of_subtree( t,                           \
+                    _GET_SPNODE_RIGHT(parent) == path[depth - i + 1] ?                    \
+                        _GET_SPNODE_LEFT(parent) :  _GET_SPNODE_RIGHT(parent));           \
+                if ((double)i > COUNTALPHA(size)) {                                       \
+                    spnode_t n = sptree_##name##_balance(t, parent, size);                \
+                    spnode_t pp = path[  depth - i - 1 ];                                 \
+                    if (_GET_SPNODE_LEFT(pp) == parent)                                   \
+                        _SET_SPNODE_LEFT(pp, n);                                          \
+                    else                                                                  \
+                        _SET_SPNODE_RIGHT(pp, n);                                         \
+                    break;                                                                \
+                }                                                                         \
+            } else {                                                                      \
+                t->root = sptree_##name##_balance(t, t->root, t->size);                   \
+                t->max_size = t->size;                                                    \
+                break;                                                                    \
+            }                                                                             \
+        }                                                                                 \
+    }                                                                                     \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_delete(sptree_##name *t, void *k) {                                       \
+    spnode_t    node = t->root;                                                           \
+    spnode_t    parent = SPNIL;                                                           \
+    int            lr = 0;                                                                \
+    while(node != SPNIL) {                                                                \
+        int r = t->compare(k, ITHELEM(t, node), t->arg);                                  \
+        if (r > 0) {                                                                      \
+            parent = node;                                                                \
+            node = _GET_SPNODE_RIGHT(node);                                               \
+            lr = +1;                                                                      \
+        } else if (r < 0) {                                                               \
+            parent = node;                                                                \
+            node = _GET_SPNODE_LEFT(node);                                                \
+            lr = -1;                                                                      \
+        } else {/* found */                                                               \
+            if (_GET_SPNODE_LEFT(node) == SPNIL && _GET_SPNODE_RIGHT(node) == SPNIL) {    \
+                if ( parent == SPNIL )                                                    \
+                    t->root = SPNIL;                                                      \
+                else if (lr <0)                                                           \
+                    _SET_SPNODE_LEFT(parent, SPNIL);                                      \
+                else                                                                      \
+                    _SET_SPNODE_RIGHT(parent, SPNIL);                                     \
+            } else if (_GET_SPNODE_LEFT(node) == SPNIL) {                                 \
+                spnode_t    child = _GET_SPNODE_RIGHT(node);                              \
+                if (parent == SPNIL) t->root = child;                                     \
+                else if (lr <0) _SET_SPNODE_LEFT(parent, child);                          \
+                else _SET_SPNODE_RIGHT(parent, child);                                    \
+            } else if (_GET_SPNODE_RIGHT(node) == SPNIL) {                                \
+                spnode_t    child = _GET_SPNODE_LEFT(node);                               \
+                if (parent == SPNIL) t->root = child;                                     \
+                else if (lr <0) _SET_SPNODE_LEFT(parent, child);                          \
+                else _SET_SPNODE_RIGHT(parent, child);                                    \
+            } else {                                                                      \
+                spnode_t    todel = _GET_SPNODE_LEFT(node);                               \
+                                                                                          \
+                parent = SPNIL;                                                           \
+                for(;;) {                                                                 \
+                    if ( _GET_SPNODE_RIGHT(todel) != SPNIL ) {                            \
+                        parent = todel;                                                   \
+                        todel = _GET_SPNODE_RIGHT(todel);                                 \
+                    } else                                                                \
+                        break;                                                            \
+                }                                                                         \
+                memcpy(ITHELEM(t, node), ITHELEM(t, todel), t->elemsize);                 \
+                if (parent != SPNIL)                                                      \
+                    _SET_SPNODE_RIGHT(parent, _GET_SPNODE_LEFT(todel));                   \
+                else                                                                      \
+                    _SET_SPNODE_LEFT(node, _GET_SPNODE_LEFT(todel));                      \
+                node = todel; /* node to delete */                                        \
+            }                                                                             \
+                                                                                          \
+            _SET_SPNODE_LEFT(node, t->garbage_head);                                      \
+            t->garbage_head = node;                                                       \
+                                                                                          \
+            break;                                                                        \
+        }                                                                                 \
+    }                                                                                     \
+                                                                                          \
+    if (node == SPNIL) /* not found */                                                    \
+        return;                                                                           \
+                                                                                          \
+    t->size --;                                                                           \
+    if ( t->size > 0 && (double)t->size < alpha * t->max_size ) {                         \
+        t->root = sptree_##name##_balance(t, t->root, t->size);                           \
+        t->max_size = t->size;                                                            \
+    }                                                                                     \
+}                                                                                         \
+                                                                                          \
+static inline spnode_t                                                                    \
+sptree_##name##_walk(sptree_##name *t, void* array, spnode_t limit, spnode_t offset) {    \
+    int         level = 0;                                                                \
+    spnode_t    count= 0,                                                                 \
+                node,                                                                     \
+                stack[ t->max_depth + 1 ];                                                \
+                                                                                          \
+    if (t->root == SPNIL) return 0;                                                       \
+    stack[0] = t->root;                                                                   \
+                                                                                          \
+    while( (node = _GET_SPNODE_LEFT( stack[level] )) != SPNIL ) {                         \
+        level++;                                                                          \
+        stack[level] = node;                                                              \
+    }                                                                                     \
+                                                                                          \
+    while( count < offset + limit && level >= 0 ) {                                       \
+                                                                                          \
+        if (count >= offset)                                                              \
+             memcpy(array + (count-offset) * t->elemsize,                                 \
+                    ITHELEM(t, stack[level]), t->elemsize);                               \
+        count++;                                                                          \
+                                                                                          \
+        node = _GET_SPNODE_RIGHT( stack[level] );                                         \
+        level--;                                                                          \
+        while( node != SPNIL ) {                                                          \
+            level++;                                                                      \
+            stack[level] = node;                                                          \
+            node = _GET_SPNODE_LEFT( stack[level] );                                      \
+        }                                                                                 \
+    }                                                                                     \
+                                                                                          \
+    return (count > offset) ? count - offset : 0;                                         \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_walk_cb(sptree_##name *t, int (*cb)(void*, void*), void *cb_arg ) {       \
+    int         level = 0;                                                                \
+    spnode_t    node,                                                                     \
+                stack[ t->max_depth + 1 ];                                                \
+                                                                                          \
+    if (t->root == SPNIL) return;                                                         \
+    stack[0] = t->root;                                                                   \
+                                                                                          \
+    while( (node = _GET_SPNODE_LEFT( stack[level] )) != SPNIL ) {                         \
+        level++;                                                                          \
+        stack[level] = node;                                                              \
+    }                                                                                     \
+                                                                                          \
+    while( level >= 0 ) {                                                                 \
+        if ( cb(cb_arg, ITHELEM(t, stack[level])) == 0 )                                  \
+             return;                                                                      \
+                                                                                          \
+        node = _GET_SPNODE_RIGHT( stack[level] );                                         \
+        level--;                                                                          \
+        while( node != SPNIL ) {                                                          \
+            level++;                                                                      \
+            stack[level] = node;                                                          \
+            node = _GET_SPNODE_LEFT( stack[level] );                                      \
+        }                                                                                 \
+    }                                                                                     \
+}                                                                                         \
+                                                                                          \
+typedef struct sptree_##name##_iterator {                                                 \
+    sptree_##name        *t;                                                              \
+    int                  level;                                                           \
+    int                  max_depth;                                                       \
+    spnode_t             stack[0];                                                        \
+} sptree_##name##_iterator;                                                               \
+                                                                                          \
+static inline sptree_##name##_iterator *                                                  \
+sptree_##name##_iterator_init(sptree_##name *t)    {                                      \
+    sptree_##name##_iterator    *i;                                                       \
+    spnode_t node;                                                                        \
+                                                                                          \
+    if (t->root == SPNIL) return NULL;                                                    \
+                                                                                          \
+    i = realloc(NULL, sizeof(*i) + sizeof(spnode_t) * (t->max_depth + 1));                \
+    i->t = t;                                                                             \
+    i->level = 0;                                                                         \
+    i->stack[0] = t->root;                                                                \
+                                                                                          \
+    while( (node = _GET_SPNODE_LEFT( i->stack[i->level] )) != SPNIL ) {                   \
+        i->level++;                                                                       \
+        i->stack[i->level] = node;                                                        \
+    }                                                                                     \
+                                                                                          \
+    return i;                                                                             \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_iterator_init_set(sptree_##name *t, sptree_##name##_iterator **i, void *k) { \
+    spnode_t node;                                                                        \
+    int      lastLevelEq = -1, cmp;                                                       \
+                                                                                          \
+    if ((*i) == NULL || t->max_depth > (*i)->max_depth)					  \
+        *i = realloc(*i, sizeof(**i) + sizeof(spnode_t) * (t->max_depth + 1));            \
+                                                                                          \
+    (*i)->t = t;                                                                          \
+    if (t->root == SPNIL) return;                                                         \
+                                                                                          \
+    (*i)->level = -1;                                                                     \
+    (*i)->max_depth = t->max_depth;                                                       \
+    (*i)->stack[0] = t->root;                                                             \
+                                                                                          \
+    node = t->root;                                                                       \
+    while(node != SPNIL) {                                                                \
+        cmp = t->compare(k, ITHELEM(t, node), t->arg);                                    \
+                                                                                          \
+        (*i)->level++;                                                                    \
+        (*i)->stack[(*i)->level] = node;                                                  \
+                                                                                          \
+        if (cmp > 0) {                                                                    \
+            (*i)->level--; /* exclude current node from path, ie "mark as visited" */     \
+            node = _GET_SPNODE_RIGHT(node);                                               \
+        } else if (cmp < 0) {                                                             \
+            node = _GET_SPNODE_LEFT(node);                                                \
+        } else {                                                                          \
+            lastLevelEq = (*i)->level;                                                    \
+            node = _GET_SPNODE_LEFT(node); /* one way iterator: from left to right */     \
+        }                                                                                 \
+    }                                                                                     \
+                                                                                          \
+    if (lastLevelEq >= 0)                                                                 \
+        (*i)->level = lastLevelEq;                                                        \
+}                                                                                         \
+                                                                                          \
+static inline void                                                                        \
+sptree_##name##_iterator_free(sptree_##name##_iterator *i)    {                           \
+    if (i == NULL)    return;                                                             \
+    free(i);                                                                              \
+}                                                                                         \
+                                                                                          \
+static inline void*                                                                       \
+sptree_##name##_iterator_next(sptree_##name##_iterator *i)    {                           \
+    sptree_##name *t;                                                                     \
+    spnode_t node, returnNode = SPNIL;                                                    \
+                                                                                          \
+    if (i == NULL)  return NULL;                                                          \
+                                                                                          \
+    t = i->t;                                                                             \
+    if ( i->level >= 0 ) {                                                                \
+        returnNode = i->stack[i->level];                                                  \
+                                                                                          \
+        node = _GET_SPNODE_RIGHT( i->stack[i->level] );                                   \
+        i->level--;                                                                       \
+        while( node != SPNIL ) {                                                          \
+            i->level++;                                                                   \
+            i->stack[i->level] = node;                                                    \
+            node = _GET_SPNODE_LEFT( i->stack[i->level] );                                \
+        }                                                                                 \
+    }                                                                                     \
+                                                                                          \
+    return (returnNode == SPNIL) ? NULL : ITHELEM(t, returnNode);                         \
+}
+
+#endif