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