[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