From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp53.i.mail.ru (smtp53.i.mail.ru [94.100.177.113]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 990AC45C304 for ; Fri, 11 Dec 2020 18:37:28 +0300 (MSK) From: Ilya Kosarev Date: Fri, 11 Dec 2020 18:37:24 +0300 Message-Id: <20201211153724.7575-1-i.kosarev@tarantool.org> Subject: [Tarantool-patches] [PATCH] memtx: allow quota overuse for truncation List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: v.shpilevoy@tarantool.org, alyapunov@tarantool.org Cc: tarantool-patches@dev.tarantool.org 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