]> git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/tools/advisor/advisor/db_config_optimizer.py
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / rocksdb / tools / advisor / advisor / db_config_optimizer.py
1 # Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 # This source code is licensed under both the GPLv2 (found in the
3 # COPYING file in the root directory) and Apache 2.0 License
4 # (found in the LICENSE.Apache file in the root directory).
5
6 import copy
7 import random
8
9 from advisor.db_log_parser import NO_COL_FAMILY
10 from advisor.db_options_parser import DatabaseOptions
11 from advisor.rule_parser import Suggestion
12
13
14 class ConfigOptimizer:
15 SCOPE = "scope"
16 SUGG_VAL = "suggested values"
17
18 @staticmethod
19 def apply_action_on_value(old_value, action, suggested_values):
20 chosen_sugg_val = None
21 if suggested_values:
22 chosen_sugg_val = random.choice(list(suggested_values))
23 new_value = None
24 if action is Suggestion.Action.set or not old_value:
25 assert chosen_sugg_val
26 new_value = chosen_sugg_val
27 else:
28 # For increase/decrease actions, currently the code tries to make
29 # a 30% change in the option's value per iteration. An addend is
30 # also present (+1 or -1) to handle the cases when the option's
31 # old value was 0 or the final int() conversion suppressed the 30%
32 # change made to the option
33 old_value = float(old_value)
34 mul = 0
35 add = 0
36 if action is Suggestion.Action.increase:
37 if old_value < 0:
38 mul = 0.7
39 add = 2
40 else:
41 mul = 1.3
42 add = 2
43 elif action is Suggestion.Action.decrease:
44 if old_value < 0:
45 mul = 1.3
46 add = -2
47 else:
48 mul = 0.7
49 add = -2
50 new_value = int(old_value * mul + add)
51 return new_value
52
53 @staticmethod
54 def improve_db_config(options, rule, suggestions_dict):
55 # this method takes ONE 'rule' and applies all its suggestions on the
56 # appropriate options
57 required_options = []
58 rule_suggestions = []
59 for sugg_name in rule.get_suggestions():
60 option = suggestions_dict[sugg_name].option
61 action = suggestions_dict[sugg_name].action
62 # A Suggestion in the rules spec must have the 'option' and
63 # 'action' fields defined, always call perform_checks() method
64 # after parsing the rules file using RulesSpec
65 assert option
66 assert action
67 required_options.append(option)
68 rule_suggestions.append(suggestions_dict[sugg_name])
69 current_config = options.get_options(required_options)
70 # Create the updated configuration from the rule's suggestions
71 updated_config = {}
72 for sugg in rule_suggestions:
73 # case: when the option is not present in the current configuration
74 if sugg.option not in current_config:
75 try:
76 new_value = ConfigOptimizer.apply_action_on_value(
77 None, sugg.action, sugg.suggested_values
78 )
79 if sugg.option not in updated_config:
80 updated_config[sugg.option] = {}
81 if DatabaseOptions.is_misc_option(sugg.option):
82 # this suggestion is on an option that is not yet
83 # supported by the Rocksdb OPTIONS file and so it is
84 # not prefixed by a section type.
85 updated_config[sugg.option][NO_COL_FAMILY] = new_value
86 else:
87 for col_fam in rule.get_trigger_column_families():
88 updated_config[sugg.option][col_fam] = new_value
89 except AssertionError:
90 print(
91 "WARNING(ConfigOptimizer): provide suggested_values "
92 + "for "
93 + sugg.option
94 )
95 continue
96 # case: when the option is present in the current configuration
97 if NO_COL_FAMILY in current_config[sugg.option]:
98 old_value = current_config[sugg.option][NO_COL_FAMILY]
99 try:
100 new_value = ConfigOptimizer.apply_action_on_value(
101 old_value, sugg.action, sugg.suggested_values
102 )
103 if sugg.option not in updated_config:
104 updated_config[sugg.option] = {}
105 updated_config[sugg.option][NO_COL_FAMILY] = new_value
106 except AssertionError:
107 print(
108 "WARNING(ConfigOptimizer): provide suggested_values "
109 + "for "
110 + sugg.option
111 )
112 else:
113 for col_fam in rule.get_trigger_column_families():
114 old_value = None
115 if col_fam in current_config[sugg.option]:
116 old_value = current_config[sugg.option][col_fam]
117 try:
118 new_value = ConfigOptimizer.apply_action_on_value(
119 old_value, sugg.action, sugg.suggested_values
120 )
121 if sugg.option not in updated_config:
122 updated_config[sugg.option] = {}
123 updated_config[sugg.option][col_fam] = new_value
124 except AssertionError:
125 print(
126 "WARNING(ConfigOptimizer): provide "
127 + "suggested_values for "
128 + sugg.option
129 )
130 return current_config, updated_config
131
132 @staticmethod
133 def pick_rule_to_apply(rules, last_rule_name, rules_tried, backtrack):
134 if not rules:
135 print("\nNo more rules triggered!")
136 return None
137 # if the last rule provided an improvement in the database performance,
138 # and it was triggered again (i.e. it is present in 'rules'), then pick
139 # the same rule for this iteration too.
140 if last_rule_name and not backtrack:
141 for rule in rules:
142 if rule.name == last_rule_name:
143 return rule
144 # there was no previous rule OR the previous rule did not improve db
145 # performance OR it was not triggered for this iteration,
146 # then pick another rule that has not been tried yet
147 for rule in rules:
148 if rule.name not in rules_tried:
149 return rule
150 print("\nAll rules have been exhausted")
151 return None
152
153 @staticmethod
154 def apply_suggestions(
155 triggered_rules,
156 current_rule_name,
157 rules_tried,
158 backtrack,
159 curr_options,
160 suggestions_dict,
161 ):
162 curr_rule = ConfigOptimizer.pick_rule_to_apply(
163 triggered_rules, current_rule_name, rules_tried, backtrack
164 )
165 if not curr_rule:
166 return tuple([None] * 4)
167 # if a rule has been picked for improving db_config, update rules_tried
168 rules_tried.add(curr_rule.name)
169 # get updated config based on the picked rule
170 curr_conf, updated_conf = ConfigOptimizer.improve_db_config(
171 curr_options, curr_rule, suggestions_dict
172 )
173 conf_diff = DatabaseOptions.get_options_diff(curr_conf, updated_conf)
174 if not conf_diff: # the current and updated configs are the same
175 (
176 curr_rule,
177 rules_tried,
178 curr_conf,
179 updated_conf,
180 ) = ConfigOptimizer.apply_suggestions(
181 triggered_rules,
182 None,
183 rules_tried,
184 backtrack,
185 curr_options,
186 suggestions_dict,
187 )
188 print("returning from apply_suggestions")
189 return (curr_rule, rules_tried, curr_conf, updated_conf)
190
191 # TODO(poojam23): check if this method is required or can we directly set
192 # the config equal to the curr_config
193 @staticmethod
194 def get_backtrack_config(curr_config, updated_config):
195 diff = DatabaseOptions.get_options_diff(curr_config, updated_config)
196 bt_config = {}
197 for option in diff:
198 bt_config[option] = {}
199 for col_fam in diff[option]:
200 bt_config[option][col_fam] = diff[option][col_fam][0]
201 print(bt_config)
202 return bt_config
203
204 def __init__(self, bench_runner, db_options, rule_parser, base_db):
205 self.bench_runner = bench_runner
206 self.db_options = db_options
207 self.rule_parser = rule_parser
208 self.base_db_path = base_db
209
210 def run(self):
211 # In every iteration of this method's optimization loop we pick ONE
212 # RULE from all the triggered rules and apply all its suggestions to
213 # the appropriate options.
214 # bootstrapping the optimizer
215 print("Bootstrapping optimizer:")
216 options = copy.deepcopy(self.db_options)
217 old_data_sources, old_metric = self.bench_runner.run_experiment(
218 options, self.base_db_path
219 )
220 print("Initial metric: " + str(old_metric))
221 self.rule_parser.load_rules_from_spec()
222 self.rule_parser.perform_section_checks()
223 triggered_rules = self.rule_parser.get_triggered_rules(
224 old_data_sources, options.get_column_families()
225 )
226 print("\nTriggered:")
227 self.rule_parser.print_rules(triggered_rules)
228 backtrack = False
229 rules_tried = set()
230 (
231 curr_rule,
232 rules_tried,
233 curr_conf,
234 updated_conf,
235 ) = ConfigOptimizer.apply_suggestions(
236 triggered_rules,
237 None,
238 rules_tried,
239 backtrack,
240 options,
241 self.rule_parser.get_suggestions_dict(),
242 )
243 # the optimizer loop
244 while curr_rule:
245 print("\nRule picked for next iteration:")
246 print(curr_rule.name)
247 print("\ncurrent config:")
248 print(curr_conf)
249 print("updated config:")
250 print(updated_conf)
251 options.update_options(updated_conf)
252 # run bench_runner with updated config
253 new_data_sources, new_metric = self.bench_runner.run_experiment(
254 options, self.base_db_path
255 )
256 print("\nnew metric: " + str(new_metric))
257 backtrack = not self.bench_runner.is_metric_better(new_metric, old_metric)
258 # update triggered_rules, metric, data_sources, if required
259 if backtrack:
260 # revert changes to options config
261 print("\nBacktracking to previous configuration")
262 backtrack_conf = ConfigOptimizer.get_backtrack_config(
263 curr_conf, updated_conf
264 )
265 options.update_options(backtrack_conf)
266 else:
267 # run advisor on new data sources
268 self.rule_parser.load_rules_from_spec() # reboot the advisor
269 self.rule_parser.perform_section_checks()
270 triggered_rules = self.rule_parser.get_triggered_rules(
271 new_data_sources, options.get_column_families()
272 )
273 print("\nTriggered:")
274 self.rule_parser.print_rules(triggered_rules)
275 old_metric = new_metric
276 old_data_sources = new_data_sources
277 rules_tried = set()
278 # pick rule to work on and set curr_rule to that
279 (
280 curr_rule,
281 rules_tried,
282 curr_conf,
283 updated_conf,
284 ) = ConfigOptimizer.apply_suggestions(
285 triggered_rules,
286 curr_rule.name,
287 rules_tried,
288 backtrack,
289 options,
290 self.rule_parser.get_suggestions_dict(),
291 )
292 # return the final database options configuration
293 return options