Skip to content

Commit 4276d30

Browse files
author
Dmitry Lenev
committed
Bug#29634540 "DROP DATABASE OF 1 MILLION TABLES RESULTED CRASH OF MYSQLD" [noclose]
Attempt to drop database with 1 million tables results in out-of-memory condition. With transition to New Data-Dictionary and atomic DDL, DROP DATABASE started to allocate much more memory which is released at the end of statement. This memory allocated for various DD API objects, new MDL locks, temporary objects used during updates to DD tables and so on. Part of the problem was that DROP DATABASE for each table, tablespace, routine or event it has dropped allocated full-blown copy of respective DD object and stored it in thread's Dictionary_client registry of dropped objects. These objects were kept in this registry until statement's end. Since each individual object contained copy of all sub-objects of the original object (even though this information as irrelevant for tracking dropped objects) they hogged quite some memory in total (in my model test that has dropped database with 5K of tables it was responsible for around 25% of memory consumed by server). This patch addresses the issue by replacing use of full-blown copies of DD objects in thread's dropped objects registry with special placeholders. These placeholders are almost-empty versions of original objects with only their key members set, which is the only info relevant for dropped objects registry. This change doesn't solve the original problem to the full extent but is a big step in this direction. Reviewed-by: Dyre Tjeldvoll <Dyre.Tjeldvoll@oracle.com>
1 parent be4ea6d commit 4276d30

29 files changed

+301
-38
lines changed

mysql-test/r/dd_pfs.result

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ include/assert.inc ['SELECT * FROM s.t_innodb_1' will allocate 0 objects.]
3535
include/assert.inc ['SELECT * FROM s.t_innodb_1' will free 0 objects.]
3636
include/assert.inc ['SELECT * FROM s.t_innodb_1' will leave 0 objects.]
3737
###############################################################################
38-
include/assert.inc ['DROP TABLE s.t_innodb_1' will allocate 16 objects.]
39-
include/assert.inc ['DROP TABLE s.t_innodb_1' will free 26 objects.]
38+
include/assert.inc ['DROP TABLE s.t_innodb_1' will allocate 7 objects.]
39+
include/assert.inc ['DROP TABLE s.t_innodb_1' will free 17 objects.]
4040
include/assert.inc ['DROP TABLE s.t_innodb_1' will leave -10 objects.]
4141
###############################################################################
42-
include/assert.inc ['DROP TABLE s.t_innodb_2' will allocate 16 objects.]
43-
include/assert.inc ['DROP TABLE s.t_innodb_2' will free 26 objects.]
42+
include/assert.inc ['DROP TABLE s.t_innodb_2' will allocate 7 objects.]
43+
include/assert.inc ['DROP TABLE s.t_innodb_2' will free 17 objects.]
4444
include/assert.inc ['DROP TABLE s.t_innodb_2' will leave -10 objects.]
4545
###############################################################################
46-
include/assert.inc ['DROP SCHEMA s' will allocate 3 objects.]
47-
include/assert.inc ['DROP SCHEMA s' will free 6 objects.]
46+
include/assert.inc ['DROP SCHEMA s' will allocate 2 objects.]
47+
include/assert.inc ['DROP SCHEMA s' will free 5 objects.]
4848
include/assert.inc ['DROP SCHEMA s' will leave -3 objects.]
4949
###############################################################################
5050
include/assert.inc ['SELECT * FROM test.tables' will allocate 168 objects.]
@@ -55,5 +55,6 @@ We will see that the total size allocated is about 40K.
5555
include/assert.inc ['' will allocate 151 + 151 objects.]
5656
include/assert.inc ['' will free 151 + 151 objects.]
5757
###############################################################################
58-
include/assert.inc ['DROP SCHEMA s' will allocate 3801 objects.]
59-
include/assert.inc ['DROP SCHEMA s' will free 3802 objects.]
58+
include/assert.inc ['DROP SCHEMA s' will allocate 601 objects.]
59+
include/assert.inc ['DROP SCHEMA s' will free 2202 objects.]
60+
include/assert.inc ['DROP SCHEMA s' will leave -1601 objects.]

mysql-test/t/dd_pfs.test

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,14 @@ let $operation= SELECT * FROM s.t_innodb_1;
147147
# We first fill in tablespace names referred by the table and index, allocating
148148
# tablespace and file objects uncached twice (+4). These are then freed (-4).
149149
# While updating the SDI, the tablespace and file objects are allocated uncached
150-
# again (+2). Then, the table object(s) are cloned (+10) and kept as uncommitted
151-
# objects, and the cached objects are freed (-10). Finally, the uncommitted
152-
# objects are freed (-10), and the remaining uncached ones are deleted (-2). So
153-
# this results in 16 allocations and 26 frees, and a net change of -10.
154-
155-
let $object_alloc= 16;
156-
let $object_free= 26;
150+
# again (+2). Then, the placeholder object is cloned from the table object (+1)
151+
# and kept as uncommitted object, and the cached table object and its subobjects
152+
# are freed (-10). Finally, the uncommitted object is freed (-1), and the
153+
# remaining uncached ones are deleted (-2). So this results in 7 allocations and
154+
# 17 frees, and a net change of -10.
155+
156+
let $object_alloc= 7;
157+
let $object_free= 17;
157158
let $object_count= -10;
158159
let $operation= DROP TABLE s.t_innodb_1;
159160
--source include/dd_pfs_report_state.inc
@@ -163,22 +164,22 @@ let $operation= DROP TABLE s.t_innodb_1;
163164
#
164165
# We get the same scenario as above.
165166

166-
let $object_alloc= 16;
167-
let $object_free= 26;
167+
let $object_alloc= 7;
168+
let $object_free= 17;
168169
let $object_count= -10;
169170
let $operation= DROP TABLE s.t_innodb_2;
170171
--source include/dd_pfs_report_state.inc
171172

172173
###############################################################################
173174
# Drop the schema.
174175
#
175-
# We will clone the table object (+2) and the schema object (+1) and store them
176-
# as uncommitted objects. Then, the objects will be freed from the DD cache (-3).
177-
# Finally, the uncommitted objects are freed (-3). This gives us 3 allocations,
178-
# 6 frees and a net change of -3 objects.
176+
# We will create placeholders for the table object (+1) and the schema object (+1)
177+
# and store them as uncommitted objects. Then, the objects will be freed from the DD
178+
# cache (-3). Finally, the uncommitted objects are freed (-2). This gives us 2
179+
# allocations, 5 frees and a net change of -3 objects.
179180

180-
let $object_alloc= 3;
181-
let $object_free= 6;
181+
let $object_alloc= 2;
182+
let $object_free= 5;
182183
let $object_count= -3;
183184
let $operation= DROP SCHEMA s;
184185
--source include/dd_pfs_report_state.inc
@@ -335,11 +336,13 @@ while ($i > 0)
335336
--enable_query_log
336337

337338
###############################################################################
338-
# Mem usage by drop schema
339+
# Mem usage by drop schema / test for partial fix for bug#29634540
340+
# "DROP DATABASE OF 1 MILLION TABLES RESULTED CRASH OF MYSQLD".
339341
#
340-
# Dropping a schema will hog memory because the dictionary client will store
341-
# all DD objects for the tables in the schema in memory while the schema is
342-
# being deleted. The DROP SCHEMA below will allocate about 2MB of memory.
342+
# Dropping a schema was hogging memory because the dictionary client was
343+
# storingfull DD objects for all tables in the schema in memory while the
344+
# schema was deleted. After partial fix for the problem we now store slimmed
345+
# down placeholders instead of full-blown DD objects for all tables instead.
343346

344347
--disable_query_log
345348
--disable_result_log
@@ -349,20 +352,43 @@ while ($i > 0)
349352
{
350353
dec $i;
351354
eval CREATE TABLE s.t_$i (i int, j int, k int, l int);
355+
# Pull in DD objects for tables in cache.
356+
eval SELECT * FROM s.t_$i;
352357
}
353358
--enable_result_log
354359
--enable_query_log
355360

361+
# For each table:
362+
#
363+
# We first get tablespace name referred by the index in order to acquire
364+
# MDL on it, allocating tablespace and file objects uncached (+2) and
365+
# then freeing them immediately (-2).
366+
#
367+
# Then during call to drop table in SE, InnoDB will drop file-per-table
368+
# tablespace. To do this it will get get uncached tablespace and file
369+
# objects once again (+2). The placeholder for tablespace object will
370+
# be created and added to uncommited registry (+1), Original uncached
371+
# tablespace and file objects will be deleted (-2).
372+
#
373+
# After that we will delete table from DD, and in the process we will
374+
# create placeholder for table object (+1) and keep it as uncommitted,
375+
# free cached table object and its subobjects (-16).
376+
#
377+
# Then once for the statement, we will create placeholder for schema
378+
# object to keep as uncommited (+1) and free cached schema object (-1).
379+
#
380+
# Finally, we will free uncommitted placeholders for each table, tablespace
381+
# (-2 per table) and for schema (-1).
382+
#
383+
# So in total this results in (2 + 2 + 1 + 1) * 100 + 1 = 601 allocations,
384+
# (2 + 2 + 16) * 100 + 1 + 2 * 100 + 1 = 2202 frees, and a net change of -1601.
356385
--source include/dd_pfs_save_state.inc
357-
let $object_alloc= 3801;
358-
let $object_free= 3802;
359-
let $object_left= -1;
386+
let $object_alloc= 601;
387+
let $object_free= 2202;
388+
let $object_count= -1601;
360389
let $operation= DROP SCHEMA s;
361390
--source include/dd_pfs_report_state.inc
362391

363-
# Using the DD P_S instrumentation, it will at least be possible to monitor
364-
# the memory usage.
365-
366392
###############################################################################
367393
# Cleanup.
368394

sql/dd/impl/cache/dictionary_client.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2468,11 +2468,14 @@ bool Dictionary_client::drop(const T *object) {
24682468
return true;
24692469
}
24702470

2471-
// Prepare an instance to be added to the dropped registry. This must be done
2472-
// prior to cleaning up the committed registry since the instance we drop
2473-
// might be present there (since we are allowed to drop const object coming
2474-
// from acquire()).
2475-
T *dropped_object = object->clone();
2471+
// Prepare an object placeholder to be added to the dropped registry.
2472+
// This must be done prior to cleaning up the committed registry since
2473+
// the instance we drop might be present there (since we are allowed to
2474+
// drop const object coming from acquire()).
2475+
// We use placeholder instead of simple clone of the original object in
2476+
// order to avoid consuming too much memory in cases when we need to drop
2477+
// many (thousands or more) objects within the single atomic operation.
2478+
T *dropped_object = object->clone_dropped_object_placeholder();
24762479

24772480
// Invalidate the entry in the shared cache (if present).
24782481
invalidate(object);

sql/dd/impl/types/charset_impl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,17 @@ class Charset_impl : public Entity_object_impl, public Charset {
127127
Object_id m_default_collation_id;
128128

129129
Charset *clone() const override { return new Charset_impl(*this); }
130+
131+
Charset *clone_dropped_object_placeholder() const override {
132+
/*
133+
Even though we don't drop charsets en masse we still create slimmed
134+
down version for consistency sake.
135+
*/
136+
Charset_impl *placeholder = new Charset_impl();
137+
placeholder->set_id(id());
138+
placeholder->set_name(name());
139+
return placeholder;
140+
}
130141
};
131142

132143
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/collation_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ class Collation_impl : public Entity_object_impl, public Collation {
137137
Object_id m_charset_id;
138138

139139
Collation *clone() const override { return new Collation_impl(*this); }
140+
141+
Collation *clone_dropped_object_placeholder() const override {
142+
// Play simple. Proper placeholder will take the same memory as clone.
143+
return clone();
144+
}
140145
};
141146

142147
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/column_statistics_impl.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,21 @@ class Column_statistics_impl final : public Entity_object_impl,
160160
Column_statistics *clone() const override {
161161
return new Column_statistics_impl(*this);
162162
}
163+
164+
Column_statistics *clone_dropped_object_placeholder() const override {
165+
Column_statistics_impl *placeholder = new Column_statistics_impl();
166+
placeholder->set_id(id());
167+
placeholder->set_name(name());
168+
/*
169+
Even though schema, table and column name members are not used in keys
170+
directly, they are still used to check that correct metadata locks are
171+
held, so for safety we copy them as well.
172+
*/
173+
placeholder->set_schema_name(schema_name());
174+
placeholder->set_table_name(table_name());
175+
placeholder->set_column_name(column_name());
176+
return placeholder;
177+
}
163178
};
164179

165180
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/event_impl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,14 @@ class Event_impl : public Entity_object_impl, public Event {
370370
Object_id m_schema_collation_id;
371371

372372
Event *clone() const override { return new Event_impl(*this); }
373+
374+
Event *clone_dropped_object_placeholder() const override {
375+
Event_impl *placeholder = new Event_impl();
376+
placeholder->set_id(id());
377+
placeholder->set_schema_id(schema_id());
378+
placeholder->set_name(name());
379+
return placeholder;
380+
}
373381
};
374382

375383
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/function_impl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,16 @@ class Function_impl : public Routine_impl, public Function {
340340

341341
Function_impl(const Function_impl &src);
342342
Function_impl *clone() const override { return new Function_impl(*this); }
343+
344+
// N.B.: returning dd::Function from this function confuses MSVC compiler
345+
// thanks to diamond inheritance.
346+
Function_impl *clone_dropped_object_placeholder() const override {
347+
Function_impl *placeholder = new Function_impl();
348+
placeholder->set_id(id());
349+
placeholder->set_schema_id(schema_id());
350+
placeholder->set_name(name());
351+
return placeholder;
352+
}
343353
};
344354

345355
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/procedure_impl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ class Procedure_impl : public Routine_impl, public Procedure {
174174
private:
175175
Procedure_impl(const Procedure_impl &src);
176176
Procedure_impl *clone() const override { return new Procedure_impl(*this); }
177+
178+
// N.B.: returning dd::Procedure from this function might confuse MSVC
179+
// compiler thanks to diamond inheritance.
180+
Procedure_impl *clone_dropped_object_placeholder() const override {
181+
Procedure_impl *placeholder = new Procedure_impl();
182+
placeholder->set_id(id());
183+
placeholder->set_schema_id(schema_id());
184+
placeholder->set_name(name());
185+
return placeholder;
186+
}
177187
};
178188

179189
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/resource_group_impl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ class Resource_group_impl : public Entity_object_impl, public Resource_group {
113113
Resource_group *clone() const override {
114114
return new Resource_group_impl(*this);
115115
}
116+
117+
Resource_group *clone_dropped_object_placeholder() const override {
118+
// Play simple. Proper placeholder will take the same memory as clone.
119+
return clone();
120+
}
116121
};
117122

118123
} // namespace dd

sql/dd/impl/types/schema_impl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@ class Schema_impl : public Entity_object_impl, public Schema {
231231
Object_id m_default_collation_id;
232232

233233
Schema *clone() const override { return new Schema_impl(*this); }
234+
235+
Schema *clone_dropped_object_placeholder() const override {
236+
/*
237+
Even though we don't drop databases en masse we still create slimmed
238+
down version for consistency sake.
239+
*/
240+
Schema_impl *placeholder = new Schema_impl();
241+
placeholder->set_id(id());
242+
placeholder->set_name(name());
243+
return placeholder;
244+
}
234245
};
235246

236247
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/spatial_reference_system_impl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,18 @@ class Spatial_reference_system_impl : public Entity_object_impl,
366366
Spatial_reference_system *clone() const override {
367367
return new Spatial_reference_system_impl(*this);
368368
}
369+
370+
Spatial_reference_system *clone_dropped_object_placeholder() const override {
371+
/*
372+
Even though we don't drop SRSes en masse we still create slimmed
373+
down version for consistency sake.
374+
*/
375+
Spatial_reference_system_impl *placeholder =
376+
new Spatial_reference_system_impl();
377+
placeholder->set_id(id());
378+
placeholder->set_name(name());
379+
return placeholder;
380+
}
369381
};
370382

371383
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/table_impl.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,25 @@ class Table_impl : public Abstract_table_impl, virtual public Table {
591591

592592
Table_impl(const Table_impl &src);
593593
Table_impl *clone() const override { return new Table_impl(*this); }
594+
595+
// N.B.: returning dd::Table from this function might confuse MSVC
596+
// compiler thanks to diamond inheritance.
597+
Table_impl *clone_dropped_object_placeholder() const override {
598+
/*
599+
TODO: In future we might want to save even more memory and use separate
600+
placeholder class implementing dd::Table interface instead of
601+
Table_impl. Instances of such class can be several times smaller
602+
than an empty Table_impl. It might make sense to do the same for
603+
for some of other types as well.
604+
*/
605+
Table_impl *placeholder = new Table_impl();
606+
placeholder->set_id(id());
607+
placeholder->set_schema_id(schema_id());
608+
placeholder->set_name(name());
609+
placeholder->set_engine(engine());
610+
placeholder->set_se_private_id(se_private_id());
611+
return placeholder;
612+
}
594613
};
595614

596615
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/tablespace_impl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ class Tablespace_impl : public Entity_object_impl, public Tablespace {
176176
Tablespace_impl(const Tablespace_impl &src);
177177

178178
Tablespace *clone() const override { return new Tablespace_impl(*this); }
179+
180+
Tablespace *clone_dropped_object_placeholder() const override {
181+
Tablespace_impl *placeholder = new Tablespace_impl();
182+
placeholder->set_id(id());
183+
placeholder->set_name(name());
184+
return placeholder;
185+
}
179186
};
180187

181188
///////////////////////////////////////////////////////////////////////////

sql/dd/impl/types/view_impl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,16 @@ class View_impl : public Abstract_table_impl, public View {
300300

301301
View_impl(const View_impl &src);
302302
View_impl *clone() const override { return new View_impl(*this); }
303+
304+
// N.B.: returning dd::View from this function confuses MSVC compiler
305+
// thanks to diamond inheritance.
306+
View_impl *clone_dropped_object_placeholder() const override {
307+
View_impl *placeholder = new View_impl();
308+
placeholder->set_id(id());
309+
placeholder->set_schema_id(schema_id());
310+
placeholder->set_name(name());
311+
return placeholder;
312+
}
303313
};
304314

305315
///////////////////////////////////////////////////////////////////////////

sql/dd/types/abstract_table.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ class Abstract_table : virtual public Entity_object {
180180
@return pointer to dynamically allocated copy
181181
*/
182182
virtual Abstract_table *clone() const = 0;
183+
184+
/**
185+
Allocate a new object which can serve as a placeholder for the original
186+
object in the Dictionary_client's dropped registry. Such object has the
187+
same keys as the original but has no other info and as result occupies
188+
less memory.
189+
*/
190+
virtual Abstract_table *clone_dropped_object_placeholder() const = 0;
183191
};
184192

185193
///////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)