[Tarantool-patches] [PATCH] memtx: allow quota overuse for truncation

Ilya Kosarev i.kosarev at tarantool.org
Fri Dec 11 18:37:24 MSK 2020


Trying to perform space:truncate() while reaching memtx_memory limit
we could experience slab allocator failure. This behavior seems to be
quite surprising for users. Now we are allowing to overuse memtx quota
for tuples in space _truncate using flag in struct quota.
Truncate tuples are only being allocated with large slabs using malloc
so that the quota can shrink back when they are freed.

Closes #3807
---
Branch: https://github.com/tarantool/tarantool/tree/i.kosarev/gh-3807-safe-alloc-on-truncation
Issue: https://github.com/tarantool/tarantool/issues/3807

 src/box/memtx_engine.c                  |  25 ++++++
 src/box/memtx_engine.h                  |   2 +
 src/box/memtx_space.c                   |  36 ++++----
 test/box/gh-3807-truncate-fail.result   | 104 ++++++++++++++++++++++++
 test/box/gh-3807-truncate-fail.test.lua |  55 +++++++++++++
 test/box/low_memory.lua                 |   8 ++
 6 files changed, 215 insertions(+), 15 deletions(-)
 create mode 100644 test/box/gh-3807-truncate-fail.result
 create mode 100644 test/box/gh-3807-truncate-fail.test.lua
 create mode 100644 test/box/low_memory.lua

diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
index db2bb23333..2f061d9f9c 100644
--- a/src/box/memtx_engine.c
+++ b/src/box/memtx_engine.c
@@ -1306,6 +1306,31 @@ struct tuple_format_vtab memtx_tuple_format_vtab = {
 	memtx_tuple_chunk_new,
 };
 
+struct tuple *
+memtx_truncate_tuple_new(struct tuple_format *format,
+                         const char *data, const char *end)
+{
+	quota_disable(&((struct memtx_engine *)format->engine)->quota);
+	struct tuple *tuple = memtx_tuple_new(format, data, end);
+	quota_enable(&((struct memtx_engine *)format->engine)->quota);
+	return tuple;
+}
+
+void
+memtx_truncate_tuple_delete(struct tuple_format *format, struct tuple *tuple)
+{
+	quota_disable(&((struct memtx_engine *)format->engine)->quota);
+	memtx_tuple_delete(format, tuple);
+	quota_enable(&((struct memtx_engine *)format->engine)->quota);
+}
+
+struct tuple_format_vtab memtx_truncate_tuple_format_vtab = {
+	memtx_truncate_tuple_delete,
+	memtx_truncate_tuple_new,
+	metmx_tuple_chunk_delete,
+	memtx_tuple_chunk_new,
+};
+
 /**
  * Allocate a block of size MEMTX_EXTENT_SIZE for memtx index
  */
diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index 8b380bf3cc..ddd33f8942 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -255,6 +255,8 @@ memtx_tuple_delete(struct tuple_format *format, struct tuple *tuple);
 
 /** Tuple format vtab for memtx engine. */
 extern struct tuple_format_vtab memtx_tuple_format_vtab;
+/** Tuple format vtab for space _truncate. */
+extern struct tuple_format_vtab memtx_truncate_tuple_format_vtab;
 
 enum {
 	MEMTX_EXTENT_SIZE = 16 * 1024,
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index 73b4c450eb..cc431ea816 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -327,8 +327,9 @@ memtx_space_execute_replace(struct space *space, struct txn *txn,
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
 	struct txn_stmt *stmt = txn_current_stmt(txn);
 	enum dup_replace_mode mode = dup_replace_mode(request->type);
-	stmt->new_tuple = memtx_tuple_new(space->format, request->tuple,
-					  request->tuple_end);
+	stmt->new_tuple = space->format->vtab.tuple_new(space->format,
+							request->tuple,
+							request->tuple_end);
 	if (stmt->new_tuple == NULL)
 		return -1;
 	tuple_ref(stmt->new_tuple);
@@ -412,8 +413,8 @@ memtx_space_execute_update(struct space *space, struct txn *txn,
 	if (new_data == NULL)
 		return -1;
 
-	stmt->new_tuple = memtx_tuple_new(format, new_data,
-					  new_data + new_size);
+	stmt->new_tuple = format->vtab.tuple_new(format, new_data,
+						 new_data + new_size);
 	if (stmt->new_tuple == NULL)
 		return -1;
 	tuple_ref(stmt->new_tuple);
@@ -483,8 +484,9 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 					  format, request->index_base) != 0) {
 			return -1;
 		}
-		stmt->new_tuple = memtx_tuple_new(format, request->tuple,
-						  request->tuple_end);
+		stmt->new_tuple = format->vtab.tuple_new(format,
+							 request->tuple,
+							 request->tuple_end);
 		if (stmt->new_tuple == NULL)
 			return -1;
 		tuple_ref(stmt->new_tuple);
@@ -507,8 +509,8 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 		if (new_data == NULL)
 			return -1;
 
-		stmt->new_tuple = memtx_tuple_new(format, new_data,
-						  new_data + new_size);
+		stmt->new_tuple = format->vtab.tuple_new(format, new_data,
+							 new_data + new_size);
 		if (stmt->new_tuple == NULL)
 			return -1;
 		tuple_ref(stmt->new_tuple);
@@ -559,14 +561,15 @@ memtx_space_ephemeral_replace(struct space *space, const char *tuple,
 				      const char *tuple_end)
 {
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
-	struct tuple *new_tuple = memtx_tuple_new(space->format, tuple,
-						  tuple_end);
+	struct tuple *new_tuple = space->format->vtab.tuple_new(space->format,
+								tuple,
+								tuple_end);
 	if (new_tuple == NULL)
 		return -1;
 	struct tuple *old_tuple;
 	if (memtx_space->replace(space, NULL, new_tuple,
 				 DUP_REPLACE_OR_INSERT, &old_tuple) != 0) {
-		memtx_tuple_delete(space->format, new_tuple);
+		space->format->vtab.tuple_delete(space->format, new_tuple);
 		return -1;
 	}
 	if (old_tuple != NULL)
@@ -1207,11 +1210,14 @@ memtx_space_new(struct memtx_engine *memtx,
 		free(memtx_space);
 		return NULL;
 	}
+	struct tuple_format_vtab *vtab = &memtx_tuple_format_vtab;
+	if (def->id == BOX_TRUNCATE_ID)
+		vtab = &memtx_truncate_tuple_format_vtab;
 	struct tuple_format *format =
-		tuple_format_new(&memtx_tuple_format_vtab, memtx, keys, key_count,
-				 def->fields, def->field_count,
-				 def->exact_field_count, def->dict,
-				 def->opts.is_temporary, def->opts.is_ephemeral);
+		tuple_format_new(vtab, memtx, keys, key_count, def->fields,
+				 def->field_count, def->exact_field_count,
+				 def->dict, def->opts.is_temporary,
+				 def->opts.is_ephemeral);
 	if (format == NULL) {
 		free(memtx_space);
 		return NULL;
diff --git a/test/box/gh-3807-truncate-fail.result b/test/box/gh-3807-truncate-fail.result
new file mode 100644
index 0000000000..1a8f961aef
--- /dev/null
+++ b/test/box/gh-3807-truncate-fail.result
@@ -0,0 +1,104 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+
+-- Creating tarantool with 32 megabytes memory to make truncate fail easier.
+test_run:cmd("create server master with script='box/low_memory.lua'")
+ | ---
+ | - true
+ | ...
+test_run:cmd('start server master')
+ | ---
+ | - true
+ | ...
+test_run:cmd("switch master")
+ | ---
+ | - true
+ | ...
+
+
+test_run:cmd("setopt delimiter ';'")
+ | ---
+ | - true
+ | ...
+function create_space(name)
+    local space = box.schema.create_space(name)
+    space:format({
+        { name = "id",  type = "unsigned" },
+        { name = "val", type = "str" }
+    })
+    space:create_index('primary', { parts = { 'id' } })
+    return space
+end;
+ | ---
+ | ...
+
+function insert(space, i)
+    space:insert{ i, string.rep('-', 256) }
+end;
+ | ---
+ | ...
+
+function fill_space(space, start)
+    local err = nil
+    local i = start
+    while err == nil do _, err = pcall(insert, space, i) i = i + 1 end
+end;
+ | ---
+ | ...
+
+-- Creating space if possible. If the space creation fails, stacking
+-- some more tuples into the test space to exhaust slabs.
+-- Then trying to truncate all spaces except the filled one.
+-- Truncate shouldn't fail.
+function stress_truncation(i)
+    local res, space = pcall(create_space, 'test' .. tostring(i))
+    if res then spaces[i] = space return end
+    fill_space(box.space.test, box.space.test:len())
+    for _, s in pairs(spaces) do s:truncate() end
+end;
+ | ---
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
+ | ...
+
+
+_ = create_space('test')
+ | ---
+ | ...
+fill_space(box.space.test, 0)
+ | ---
+ | ...
+
+spaces = {}
+ | ---
+ | ...
+counter = 0
+ | ---
+ | ...
+status, res = true, nil
+ | ---
+ | ...
+while status and counter < 42 do status, res = pcall(stress_truncation, counter) counter = counter + 1 end
+ | ---
+ | ...
+status
+ | ---
+ | - true
+ | ...
+res
+ | ---
+ | - null
+ | ...
+
+-- Cleanup.
+test_run:cmd('switch default')
+ | ---
+ | - true
+ | ...
+test_run:drop_cluster({'master'})
+ | ---
+ | ...
diff --git a/test/box/gh-3807-truncate-fail.test.lua b/test/box/gh-3807-truncate-fail.test.lua
new file mode 100644
index 0000000000..1e2dd5d750
--- /dev/null
+++ b/test/box/gh-3807-truncate-fail.test.lua
@@ -0,0 +1,55 @@
+test_run = require('test_run').new()
+
+-- Creating tarantool with 32 megabytes memory to make truncate fail easier.
+test_run:cmd("create server master with script='box/low_memory.lua'")
+test_run:cmd('start server master')
+test_run:cmd("switch master")
+
+
+test_run:cmd("setopt delimiter ';'")
+function create_space(name)
+    local space = box.schema.create_space(name)
+    space:format({
+        { name = "id",  type = "unsigned" },
+        { name = "val", type = "str" }
+    })
+    space:create_index('primary', { parts = { 'id' } })
+    return space
+end;
+
+function insert(space, i)
+    space:insert{ i, string.rep('-', 256) }
+end;
+
+function fill_space(space, start)
+    local err = nil
+    local i = start
+    while err == nil do _, err = pcall(insert, space, i) i = i + 1 end
+end;
+
+-- Creating space if possible. If the space creation fails, stacking
+-- some more tuples into the test space to exhaust slabs.
+-- Then trying to truncate all spaces except the filled one.
+-- Truncate shouldn't fail.
+function stress_truncation(i)
+    local res, space = pcall(create_space, 'test' .. tostring(i))
+    if res then spaces[i] = space return end
+    fill_space(box.space.test, box.space.test:len())
+    for _, s in pairs(spaces) do s:truncate() end
+end;
+test_run:cmd("setopt delimiter ''");
+
+
+_ = create_space('test')
+fill_space(box.space.test, 0)
+
+spaces = {}
+counter = 0
+status, res = true, nil
+while status and counter < 42 do status, res = pcall(stress_truncation, counter) counter = counter + 1 end
+status
+res
+
+-- Cleanup.
+test_run:cmd('switch default')
+test_run:drop_cluster({'master'})
diff --git a/test/box/low_memory.lua b/test/box/low_memory.lua
new file mode 100644
index 0000000000..46fd26d1b6
--- /dev/null
+++ b/test/box/low_memory.lua
@@ -0,0 +1,8 @@
+#!/usr/bin/env tarantool
+os = require('os')
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    memtx_memory        = 32 * 1024 * 1024,
+})
+
+require('console').listen(os.getenv('ADMIN'))
-- 
2.17.1



More information about the Tarantool-patches mailing list