From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladimir Davydov Subject: [PATCH 01/18] vinyl: rework internal quota API Date: Thu, 16 Aug 2018 19:11:55 +0300 Message-Id: In-Reply-To: References: In-Reply-To: References: To: kostja@tarantool.org Cc: tarantool-patches@freelists.org List-ID: The API is too generic now. It would be rather difficult to introduce throttling on top of it. Let's rework it to reflect vinyl algorithms. --- src/box/vinyl.c | 28 +++++-------------------- src/box/vy_quota.h | 61 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/box/vinyl.c b/src/box/vinyl.c index 7f779634..d0e822bf 100644 --- a/src/box/vinyl.c +++ b/src/box/vinyl.c @@ -2329,7 +2329,7 @@ vinyl_engine_prepare(struct engine *engine, struct txn *txn) * the transaction to be sent to read view or aborted, we call * it before checking for conflicts. */ - if (vy_quota_use(&env->quota, tx->write_size, timeout) != 0) { + if (vy_quota_try_use(&env->quota, tx->write_size, timeout) != 0) { diag_set(ClientError, ER_VY_QUOTA_TIMEOUT); return -1; } @@ -2341,21 +2341,7 @@ vinyl_engine_prepare(struct engine *engine, struct txn *txn) size_t mem_used_after = lsregion_used(&env->mem_env.allocator); assert(mem_used_after >= mem_used_before); size_t write_size = mem_used_after - mem_used_before; - /* - * Insertion of a statement into an in-memory tree can trigger - * an allocation of a new tree block. This should not normally - * result in a noticeable excess of the memory limit, because - * most memory is occupied by statements anyway, but we need to - * adjust the quota accordingly in this case. - * - * The actual allocation size can also be less than reservation - * if a statement is allocated from an lsregion slab allocated - * by a previous transaction. Take this into account, too. - */ - if (write_size >= tx->write_size) - vy_quota_force_use(&env->quota, write_size - tx->write_size); - else - vy_quota_release(&env->quota, tx->write_size - write_size); + vy_quota_commit_use(&env->quota, tx->write_size, write_size); if (rc != 0) return -1; @@ -2512,7 +2498,7 @@ vy_env_dump_complete_cb(struct vy_scheduler *scheduler, size_t mem_used_after = lsregion_used(allocator); assert(mem_used_after <= mem_used_before); size_t mem_dumped = mem_used_before - mem_used_after; - vy_quota_release(quota, mem_dumped); + vy_quota_dump(quota, mem_dumped); say_info("dumped %zu bytes in %.1f sec", mem_dumped, dump_duration); @@ -3214,7 +3200,7 @@ vinyl_space_apply_initial_join_row(struct space *space, struct request *request) * quota accounting. */ size_t reserved = tx->write_size; - if (vy_quota_use(&env->quota, reserved, TIMEOUT_INFINITY) != 0) + if (vy_quota_try_use(&env->quota, reserved, TIMEOUT_INFINITY) != 0) unreachable(); size_t mem_used_before = lsregion_used(&env->mem_env.allocator); @@ -3233,11 +3219,7 @@ vinyl_space_apply_initial_join_row(struct space *space, struct request *request) size_t mem_used_after = lsregion_used(&env->mem_env.allocator); assert(mem_used_after >= mem_used_before); size_t used = mem_used_after - mem_used_before; - if (used >= reserved) - vy_quota_force_use(&env->quota, used - reserved); - else - vy_quota_release(&env->quota, reserved - used); - + vy_quota_commit_use(&env->quota, reserved, used); return rc; } diff --git a/src/box/vy_quota.h b/src/box/vy_quota.h index d741c34a..fd1004da 100644 --- a/src/box/vy_quota.h +++ b/src/box/vy_quota.h @@ -67,7 +67,7 @@ struct vy_quota { /** Current memory consumption. */ size_t used; /** - * If vy_quota_use() takes longer than the given + * If vy_quota_try_use() takes longer than the given * value, warn about it in the log. */ double too_long_threshold; @@ -127,7 +127,7 @@ vy_quota_set_watermark(struct vy_quota *q, size_t watermark) } /** - * Consume @size bytes of memory. In contrast to vy_quota_use() + * Consume @size bytes of memory. In contrast to vy_quota_try_use() * this function does not throttle the caller. */ static inline void @@ -139,10 +139,11 @@ vy_quota_force_use(struct vy_quota *q, size_t size) } /** - * Release @size bytes of memory. + * Function called on dump completion to release quota after + * freeing memory. */ static inline void -vy_quota_release(struct vy_quota *q, size_t size) +vy_quota_dump(struct vy_quota *q, size_t size) { assert(q->used >= size); q->used -= size; @@ -153,9 +154,38 @@ vy_quota_release(struct vy_quota *q, size_t size) * Try to consume @size bytes of memory, throttle the caller * if the limit is exceeded. @timeout specifies the maximal * time to wait. Return 0 on success, -1 on timeout. + * + * Usage pattern: + * + * size_t reserved = ; + * if (vy_quota_try_use(q, reserved, timeout) != 0) + * return -1; + * + * size_t used = ; + * vy_quota_commit_use(q, reserved, used); + * + * We use two-step quota allocation strategy (reserve-consume), + * because we may not yield after we start inserting statements + * into a space so we estimate the allocation size and wait for + * quota before committing statements. At the same time, we + * cannot precisely estimate the size of memory we are going to + * consume so we adjust the quota after the allocation. + * + * The size of memory allocated while committing a transaction + * may be greater than an estimate, because insertion of a + * statement into an in-memory index can trigger allocation + * of a new index extent. This should not normally result in a + * noticeable breach in the memory limit, because most memory + * is occupied by statements, but we need to adjust the quota + * accordingly after the allocation in this case. + * + * The actual memory allocation size may also be less than an + * estimate if the space has multiple indexes, because statements + * are stored in the common memory level, which isn't taken into + * account while estimating the size of a memory allocation. */ static inline int -vy_quota_use(struct vy_quota *q, size_t size, double timeout) +vy_quota_try_use(struct vy_quota *q, size_t size, double timeout) { double start_time = ev_monotonic_now(loop()); double deadline = start_time + timeout; @@ -178,6 +208,27 @@ vy_quota_use(struct vy_quota *q, size_t size, double timeout) } /** + * Adjust quota after allocating memory. + * + * @reserved: size of quota reserved by vy_quota_try_use(). + * @used: size of memory actually allocated. + * + * See also vy_quota_try_use(). + */ +static inline void +vy_quota_commit_use(struct vy_quota *q, size_t reserved, size_t used) +{ + if (reserved > used) { + size_t excess = reserved - used; + assert(q->used >= excess); + q->used -= excess; + fiber_cond_broadcast(&q->cond); + } + if (reserved < used) + vy_quota_force_use(q, used - reserved); +} + +/** * Block the caller until the quota is not exceeded. */ static inline void -- 2.11.0