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 extern bool dict_stats_start_shutdown;
48 
49 /*****************************************************************//**
50 Initialize the defrag pool, called once during thread initialization. */
51 void
dict_defrag_pool_init(void)52 dict_defrag_pool_init(void)
53 /*=======================*/
54 {
55 	ut_ad(!srv_read_only_mode);
56 
57 	/* We choose SYNC_STATS_DEFRAG to be below SYNC_FSP_PAGE. */
58 	mutex_create(LATCH_ID_DEFRAGMENT_MUTEX, &defrag_pool_mutex);
59 }
60 
61 /*****************************************************************//**
62 Free the resources occupied by the defrag pool, called once during
63 thread de-initialization. */
64 void
dict_defrag_pool_deinit(void)65 dict_defrag_pool_deinit(void)
66 /*=========================*/
67 {
68 	ut_ad(!srv_read_only_mode);
69 
70 	mutex_free(&defrag_pool_mutex);
71 }
72 
73 /*****************************************************************//**
74 Get an index from the auto defrag pool. The returned index id is removed
75 from the pool.
76 @return true if the pool was non-empty and "id" was set, false otherwise */
77 static
78 bool
dict_stats_defrag_pool_get(table_id_t * table_id,index_id_t * index_id)79 dict_stats_defrag_pool_get(
80 /*=======================*/
81 	table_id_t*	table_id,	/*!< out: table id, or unmodified if
82 					list is empty */
83 	index_id_t*	index_id)	/*!< out: index id, or unmodified if
84 					list is empty */
85 {
86 	ut_ad(!srv_read_only_mode);
87 
88 	mutex_enter(&defrag_pool_mutex);
89 
90 	if (defrag_pool.empty()) {
91 		mutex_exit(&defrag_pool_mutex);
92 		return(false);
93 	}
94 
95 	defrag_pool_item_t& item = defrag_pool.back();
96 	*table_id = item.table_id;
97 	*index_id = item.index_id;
98 
99 	defrag_pool.pop_back();
100 
101 	mutex_exit(&defrag_pool_mutex);
102 
103 	return(true);
104 }
105 
106 /*****************************************************************//**
107 Add an index in a table to the defrag pool, which is processed by the
108 background stats gathering thread. Only the table id and index id are
109 added to the list, so the table can be closed after being enqueued and
110 it will be opened when needed. If the table or index does not exist later
111 (has been DROPped), then it will be removed from the pool and skipped. */
112 void
dict_stats_defrag_pool_add(const dict_index_t * index)113 dict_stats_defrag_pool_add(
114 /*=======================*/
115 	const dict_index_t*	index)	/*!< in: table to add */
116 {
117 	defrag_pool_item_t item;
118 
119 	ut_ad(!srv_read_only_mode);
120 
121 	mutex_enter(&defrag_pool_mutex);
122 
123 	/* quit if already in the list */
124 	for (defrag_pool_iterator_t iter = defrag_pool.begin();
125 	     iter != defrag_pool.end();
126 	     ++iter) {
127 		if ((*iter).table_id == index->table->id
128 		    && (*iter).index_id == index->id) {
129 			mutex_exit(&defrag_pool_mutex);
130 			return;
131 		}
132 	}
133 
134 	item.table_id = index->table->id;
135 	item.index_id = index->id;
136 	defrag_pool.push_back(item);
137 
138 	mutex_exit(&defrag_pool_mutex);
139 
140 	os_event_set(dict_stats_event);
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() && !dict_stats_start_shutdown) {
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