1/*****************************************************************************
2
3Copyright (c) 2016, 2018, MariaDB Corporation.
4
5This program is free software; you can redistribute it and/or modify it under
6the terms of the GNU General Public License as published by the Free Software
7Foundation; version 2 of the License.
8
9This program is distributed in the hope that it will be useful, but WITHOUT
10ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
13You should have received a copy of the GNU General Public License along with
14this program; if not, write to the Free Software Foundation, Inc.,
1551 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16
17*****************************************************************************/
18
19/**************************************************//**
20@file dict/dict0defrag_bg.cc
21Defragmentation routines.
22
23Created 25/08/2016 Jan Lindström
24*******************************************************/
25
26#include "dict0dict.h"
27#include "dict0stats.h"
28#include "dict0stats_bg.h"
29#include "dict0defrag_bg.h"
30#include "row0mysql.h"
31#include "srv0start.h"
32#include "ut0new.h"
33
34#include <vector>
35
36static ib_mutex_t defrag_pool_mutex;
37
38#ifdef MYSQL_PFS
39static mysql_pfs_key_t defrag_pool_mutex_key;
40#endif
41
42/** Indices whose defrag stats need to be saved to persistent storage.*/
43struct defrag_pool_item_t {
44 table_id_t table_id;
45 index_id_t index_id;
46};
47
48/** Allocator type, used by std::vector */
49typedef ut_allocator<defrag_pool_item_t>
50 defrag_pool_allocator_t;
51
52/** The multitude of tables to be defragmented- an STL vector */
53typedef std::vector<defrag_pool_item_t, defrag_pool_allocator_t>
54 defrag_pool_t;
55
56/** Iterator type for iterating over the elements of objects of type
57defrag_pool_t. */
58typedef defrag_pool_t::iterator defrag_pool_iterator_t;
59
60/** Pool where we store information on which tables are to be processed
61by background defragmentation. */
62static defrag_pool_t* defrag_pool;
63
64extern bool dict_stats_start_shutdown;
65
66/*****************************************************************//**
67Initialize the defrag pool, called once during thread initialization. */
68void
69dict_defrag_pool_init(void)
70/*=======================*/
71{
72 ut_ad(!srv_read_only_mode);
73 /* JAN: TODO: MySQL 5.7 PSI
74 const PSI_memory_key key2 = mem_key_dict_defrag_pool_t;
75
76 defrag_pool = UT_NEW(defrag_pool_t(defrag_pool_allocator_t(key2)), key2);
77
78 recalc_pool->reserve(RECALC_POOL_INITIAL_SLOTS);
79 */
80 defrag_pool = new std::vector<defrag_pool_item_t, defrag_pool_allocator_t>();
81
82 /* We choose SYNC_STATS_DEFRAG to be below SYNC_FSP_PAGE. */
83 mutex_create(LATCH_ID_DEFRAGMENT_MUTEX, &defrag_pool_mutex);
84}
85
86/*****************************************************************//**
87Free the resources occupied by the defrag pool, called once during
88thread de-initialization. */
89void
90dict_defrag_pool_deinit(void)
91/*=========================*/
92{
93 ut_ad(!srv_read_only_mode);
94
95 defrag_pool->clear();
96 mutex_free(&defrag_pool_mutex);
97
98 UT_DELETE(defrag_pool);
99}
100
101/*****************************************************************//**
102Get an index from the auto defrag pool. The returned index id is removed
103from the pool.
104@return true if the pool was non-empty and "id" was set, false otherwise */
105static
106bool
107dict_stats_defrag_pool_get(
108/*=======================*/
109 table_id_t* table_id, /*!< out: table id, or unmodified if
110 list is empty */
111 index_id_t* index_id) /*!< out: index id, or unmodified if
112 list is empty */
113{
114 ut_ad(!srv_read_only_mode);
115
116 mutex_enter(&defrag_pool_mutex);
117
118 if (defrag_pool->empty()) {
119 mutex_exit(&defrag_pool_mutex);
120 return(false);
121 }
122
123 defrag_pool_item_t& item = defrag_pool->back();
124 *table_id = item.table_id;
125 *index_id = item.index_id;
126
127 defrag_pool->pop_back();
128
129 mutex_exit(&defrag_pool_mutex);
130
131 return(true);
132}
133
134/*****************************************************************//**
135Add an index in a table to the defrag pool, which is processed by the
136background stats gathering thread. Only the table id and index id are
137added to the list, so the table can be closed after being enqueued and
138it will be opened when needed. If the table or index does not exist later
139(has been DROPped), then it will be removed from the pool and skipped. */
140void
141dict_stats_defrag_pool_add(
142/*=======================*/
143 const dict_index_t* index) /*!< in: table to add */
144{
145 defrag_pool_item_t item;
146
147 ut_ad(!srv_read_only_mode);
148
149 mutex_enter(&defrag_pool_mutex);
150
151 /* quit if already in the list */
152 for (defrag_pool_iterator_t iter = defrag_pool->begin();
153 iter != defrag_pool->end();
154 ++iter) {
155 if ((*iter).table_id == index->table->id
156 && (*iter).index_id == index->id) {
157 mutex_exit(&defrag_pool_mutex);
158 return;
159 }
160 }
161
162 item.table_id = index->table->id;
163 item.index_id = index->id;
164 defrag_pool->push_back(item);
165
166 mutex_exit(&defrag_pool_mutex);
167
168 os_event_set(dict_stats_event);
169}
170
171/*****************************************************************//**
172Delete a given index from the auto defrag pool. */
173void
174dict_stats_defrag_pool_del(
175/*=======================*/
176 const dict_table_t* table, /*!<in: if given, remove
177 all entries for the table */
178 const dict_index_t* index) /*!< in: if given, remove this index */
179{
180 ut_a((table && !index) || (!table && index));
181 ut_ad(!srv_read_only_mode);
182 ut_ad(mutex_own(&dict_sys->mutex));
183
184 mutex_enter(&defrag_pool_mutex);
185
186 defrag_pool_iterator_t iter = defrag_pool->begin();
187 while (iter != defrag_pool->end()) {
188 if ((table && (*iter).table_id == table->id)
189 || (index
190 && (*iter).table_id == index->table->id
191 && (*iter).index_id == index->id)) {
192 /* erase() invalidates the iterator */
193 iter = defrag_pool->erase(iter);
194 if (index)
195 break;
196 } else {
197 iter++;
198 }
199 }
200
201 mutex_exit(&defrag_pool_mutex);
202}
203
204/*****************************************************************//**
205Get the first index that has been added for updating persistent defrag
206stats and eventually save its stats. */
207static
208void
209dict_stats_process_entry_from_defrag_pool()
210{
211 table_id_t table_id;
212 index_id_t index_id;
213
214 ut_ad(!srv_read_only_mode);
215
216 /* pop the first index from the auto defrag pool */
217 if (!dict_stats_defrag_pool_get(&table_id, &index_id)) {
218 /* no index in defrag pool */
219 return;
220 }
221
222 dict_table_t* table;
223
224 mutex_enter(&dict_sys->mutex);
225
226 /* If the table is no longer cached, we've already lost the in
227 memory stats so there's nothing really to write to disk. */
228 table = dict_table_open_on_id(table_id, TRUE,
229 DICT_TABLE_OP_OPEN_ONLY_IF_CACHED);
230
231 dict_index_t* index = table && !table->corrupted
232 ? dict_table_find_index_on_id(table, index_id)
233 : NULL;
234
235 if (!index || index->is_corrupted()) {
236 if (table) {
237 dict_table_close(table, TRUE, FALSE);
238 }
239 mutex_exit(&dict_sys->mutex);
240 return;
241 }
242
243 mutex_exit(&dict_sys->mutex);
244 dict_stats_save_defrag_stats(index);
245 dict_table_close(table, FALSE, FALSE);
246}
247
248/*****************************************************************//**
249Get the first index that has been added for updating persistent defrag
250stats and eventually save its stats. */
251void
252dict_defrag_process_entries_from_defrag_pool()
253/*==========================================*/
254{
255 while (defrag_pool->size() && !dict_stats_start_shutdown) {
256 dict_stats_process_entry_from_defrag_pool();
257 }
258}
259
260/*********************************************************************//**
261Save defragmentation result.
262@return DB_SUCCESS or error code */
263dberr_t
264dict_stats_save_defrag_summary(
265/*============================*/
266 dict_index_t* index) /*!< in: index */
267{
268 dberr_t ret=DB_SUCCESS;
269 lint now = (lint) ut_time();
270
271 if (dict_index_is_ibuf(index)) {
272 return DB_SUCCESS;
273 }
274
275 rw_lock_x_lock(dict_operation_lock);
276 mutex_enter(&dict_sys->mutex);
277
278 ret = dict_stats_save_index_stat(index, now, "n_pages_freed",
279 index->stat_defrag_n_pages_freed,
280 NULL,
281 "Number of pages freed during"
282 " last defragmentation run.",
283 NULL);
284
285 mutex_exit(&dict_sys->mutex);
286 rw_lock_x_unlock(dict_operation_lock);
287
288 return (ret);
289}
290
291/*********************************************************************//**
292Save defragmentation stats for a given index.
293@return DB_SUCCESS or error code */
294dberr_t
295dict_stats_save_defrag_stats(
296/*============================*/
297 dict_index_t* index) /*!< in: index */
298{
299 dberr_t ret;
300
301 if (dict_index_is_ibuf(index)) {
302 return DB_SUCCESS;
303 }
304
305 if (!index->is_readable()) {
306 return dict_stats_report_error(index->table, true);
307 }
308
309 lint now = (lint) ut_time();
310 mtr_t mtr;
311 ulint n_leaf_pages;
312 ulint n_leaf_reserved;
313 mtr_start(&mtr);
314 mtr_s_lock(dict_index_get_lock(index), &mtr);
315 n_leaf_reserved = btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES,
316 &n_leaf_pages, &mtr);
317 mtr_commit(&mtr);
318
319 if (n_leaf_reserved == ULINT_UNDEFINED) {
320 // The index name is different during fast index creation,
321 // so the stats won't be associated with the right index
322 // for later use. We just return without saving.
323 return DB_SUCCESS;
324 }
325
326 rw_lock_x_lock(dict_operation_lock);
327
328 mutex_enter(&dict_sys->mutex);
329 ret = dict_stats_save_index_stat(index, now, "n_page_split",
330 index->stat_defrag_n_page_split,
331 NULL,
332 "Number of new page splits on leaves"
333 " since last defragmentation.",
334 NULL);
335 if (ret != DB_SUCCESS) {
336 goto end;
337 }
338
339 ret = dict_stats_save_index_stat(
340 index, now, "n_leaf_pages_defrag",
341 n_leaf_pages,
342 NULL,
343 "Number of leaf pages when this stat is saved to disk",
344 NULL);
345 if (ret != DB_SUCCESS) {
346 goto end;
347 }
348
349 ret = dict_stats_save_index_stat(
350 index, now, "n_leaf_pages_reserved",
351 n_leaf_reserved,
352 NULL,
353 "Number of pages reserved for this index leaves when this stat "
354 "is saved to disk",
355 NULL);
356
357end:
358 mutex_exit(&dict_sys->mutex);
359 rw_lock_x_unlock(dict_operation_lock);
360
361 return (ret);
362}
363