[go: up one dir, main page]

File: vy_quota.h

package info (click to toggle)
tarantool 2.6.0-1.4
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 85,412 kB
  • sloc: ansic: 513,775; cpp: 69,493; sh: 25,650; python: 19,190; perl: 14,973; makefile: 4,178; yacc: 1,329; sql: 1,074; pascal: 620; ruby: 190; awk: 18; lisp: 7
file content (342 lines) | stat: -rw-r--r-- 9,548 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#ifndef INCLUDES_TARANTOOL_BOX_VY_QUOTA_H
#define INCLUDES_TARANTOOL_BOX_VY_QUOTA_H
/*
 * Copyright 2010-2017, Tarantool AUTHORS, please see AUTHORS file.
 *
 * 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 AUTHORS ``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
 * AUTHORS 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 <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <small/rlist.h>
#include <tarantool_ev.h>

#include "trivia/util.h"

#if defined(__cplusplus)
extern "C" {
#endif /* defined(__cplusplus) */

struct fiber;
struct vy_quota;

/** Rate limit state. */
struct vy_rate_limit {
	/** Max allowed rate, per second. */
	size_t rate;
	/** Current quota. */
	ssize_t value;
};

/** Initialize a rate limit state. */
static inline void
vy_rate_limit_create(struct vy_rate_limit *rl)
{
	rl->rate = SIZE_MAX;
	rl->value = SSIZE_MAX;
}

/** Set rate limit. */
static inline void
vy_rate_limit_set(struct vy_rate_limit *rl, size_t rate)
{
	rl->rate = rate;
}

/**
 * Return true if quota may be consumed without exceeding
 * the configured rate limit.
 */
static inline bool
vy_rate_limit_may_use(struct vy_rate_limit *rl)
{
	return rl->value > 0;
}

/** Consume the given amount of quota. */
static inline void
vy_rate_limit_use(struct vy_rate_limit *rl, size_t size)
{
	rl->value -= size;
}

/** Release the given amount of quota. */
static inline void
vy_rate_limit_unuse(struct vy_rate_limit *rl, size_t size)
{
	rl->value += size;
}

/**
 * Replenish quota by the amount accumulated for the given
 * time interval.
 */
static inline void
vy_rate_limit_refill(struct vy_rate_limit *rl, double time)
{
	double size = rl->rate * time;
	double value = rl->value + size;
	/* Allow bursts up to 2x rate. */
	value = MIN(value, size * 2);
	rl->value = MIN(value, SSIZE_MAX);
}

typedef void
(*vy_quota_exceeded_f)(struct vy_quota *quota);

/**
 * Apart from memory usage accounting and limiting, vy_quota is
 * responsible for consumption rate limiting (aka throttling).
 * There are multiple rate limits, each of which is associated
 * with a particular resource type. Different kinds of consumers
 * respect different limits. The following enumeration defines
 * the resource types for which vy_quota enables throttling.
 *
 * See also vy_quota_consumer_resource_map.
 */
enum vy_quota_resource_type {
	/**
	 * The goal of disk-based throttling is to keep LSM trees
	 * in a good shape so that read and space amplification
	 * stay within bounds. It is enabled when compaction does
	 * not keep up with dumps.
	 */
	VY_QUOTA_RESOURCE_DISK = 0,
	/**
	 * Memory-based throttling is needed to avoid long stalls
	 * caused by hitting the hard memory limit. It is set so
	 * that by the time the hard limit is hit, the last memory
	 * dump will have completed.
	 */
	VY_QUOTA_RESOURCE_MEMORY = 1,

	vy_quota_resource_type_MAX,
};

/**
 * Quota consumer type determines how a quota consumer will be
 * rate limited.
 *
 * See also vy_quota_consumer_resource_map.
 */
enum vy_quota_consumer_type {
	/** Transaction processor. */
	VY_QUOTA_CONSUMER_TX = 0,
	/** Compaction job. */
	VY_QUOTA_CONSUMER_COMPACTION = 1,
	/** Request to build a new index. */
	VY_QUOTA_CONSUMER_DDL = 2,

	vy_quota_consumer_type_MAX,
};

struct vy_quota_wait_node {
	/** Link in vy_quota::wait_queue. */
	struct rlist in_wait_queue;
	/** Fiber waiting for quota. */
	struct fiber *fiber;
	/** Amount of requested memory. */
	size_t size;
	/**
	 * Ticket assigned to this fiber when it was put to
	 * sleep, see vy_quota::wait_ticket for more details.
	 */
	int64_t ticket;
};

/**
 * Quota used for accounting and limiting memory consumption
 * in the vinyl engine. It is NOT multi-threading safe.
 */
struct vy_quota {
	/** Set if the quota was enabled. */
	bool is_enabled;
	/**
	 * Memory limit. Once hit, new transactions are
	 * throttled until memory is reclaimed.
	 */
	size_t limit;
	/** Current memory consumption. */
	size_t used;
	/**
	 * If vy_quota_use() takes longer than the given
	 * value, warn about it in the log.
	 */
	double too_long_threshold;
	/**
	 * Called if the limit is hit when quota is consumed.
	 * It is supposed to trigger memory reclaim.
	 */
	vy_quota_exceeded_f quota_exceeded_cb;
	/**
	 * Monotonically growing counter assigned to consumers
	 * waiting for quota. It is used for balancing wakeups
	 * among wait queues: if two fibers from different wait
	 * queues may proceed, the one with the lowest ticket
	 * will be picked.
	 *
	 * See also vy_quota_wait_node::ticket.
	 */
	int64_t wait_ticket;
	/**
	 * Queue of consumers waiting for quota, one per each
	 * consumer type, linked by vy_quota_wait_node::state.
	 * Newcomers are added to the tail.
	 */
	struct rlist wait_queue[vy_quota_consumer_type_MAX];
	/** Rate limit state, one per each resource type. */
	struct vy_rate_limit rate_limit[vy_quota_resource_type_MAX];
	/**
	 * Periodic timer that is used for refilling the rate
	 * limit value.
	 */
	ev_timer timer;
};

/**
 * Initialize a quota object.
 *
 * Note, the limit won't be imposed until vy_quota_enable()
 * is called.
 */
void
vy_quota_create(struct vy_quota *q, size_t limit,
		vy_quota_exceeded_f quota_exceeded_cb);

/**
 * Enable the configured limit for a quota object.
 */
void
vy_quota_enable(struct vy_quota *q);

/**
 * Destroy a quota object.
 */
void
vy_quota_destroy(struct vy_quota *q);

/**
 * Set memory limit. If current memory usage exceeds
 * the new limit, invoke the callback.
 */
void
vy_quota_set_limit(struct vy_quota *q, size_t limit);

/**
 * Set the rate limit corresponding to the resource of the given
 * type. The rate limit is given in bytes per second.
 */
void
vy_quota_set_rate_limit(struct vy_quota *q, enum vy_quota_resource_type type,
			size_t rate);

/**
 * Return the rate limit applied to a consumer of the given type.
 */
size_t
vy_quota_get_rate_limit(struct vy_quota *q, enum vy_quota_consumer_type type);

/**
 * Consume @size bytes of memory. In contrast to vy_quota_use()
 * this function does not throttle the caller.
 */
void
vy_quota_force_use(struct vy_quota *q, enum vy_quota_consumer_type type,
		   size_t size);

/**
 * Release @size bytes of memory.
 */
void
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 = <estimate>;
 *   if (vy_quota_use(q, reserved, timeout) != 0)
 *           return -1;
 *   <allocate memory>
 *   size_t used = <actually allocated>;
 *   vy_quota_adjust(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.
 */
int
vy_quota_use(struct vy_quota *q, enum vy_quota_consumer_type type,
	     size_t size, double timeout);

/**
 * Adjust quota after allocating memory.
 *
 * @reserved: size of quota reserved by vy_quota_use().
 * @used: size of memory actually allocated.
 *
 * See also vy_quota_use().
 */
void
vy_quota_adjust(struct vy_quota *q, enum vy_quota_consumer_type type,
		size_t reserved, size_t used);

/**
 * Block the caller until the quota is not exceeded.
 */
static inline void
vy_quota_wait(struct vy_quota *q, enum vy_quota_consumer_type type)
{
	vy_quota_use(q, type, 0, TIMEOUT_INFINITY);
}

#if defined(__cplusplus)
} /* extern "C" */
#endif /* defined(__cplusplus) */

#endif /* INCLUDES_TARANTOOL_BOX_VY_QUOTA_H */