From 706a44d4d44003cbd75f57016edfbae35e7a0cd6 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Wed, 6 May 2020 13:21:18 +0300 Subject: [PATCH] Fix optimize_initial in both perl and js versions --- lp/LPOptimizer.pm | 22 +++--- lp/lp-optimizer.js | 102 ++++++++++++++------------- lp/test-optimize.js | 39 ++++++++++ lp/{optimize.pl => test-optimize.pl} | 7 +- 4 files changed, 108 insertions(+), 62 deletions(-) create mode 100644 lp/test-optimize.js rename lp/{optimize.pl => test-optimize.pl} (78%) diff --git a/lp/LPOptimizer.pm b/lp/LPOptimizer.pm index 5df3a779..4e181ec5 100644 --- a/lp/LPOptimizer.pm +++ b/lp/LPOptimizer.pm @@ -25,6 +25,8 @@ sub optimize_initial { my ($osd_tree, $pg_count) = @_; my $all_weights = { map { %$_ } values %$osd_tree }; + my $total_weight = 0; + $total_weight += $all_weights->{$_} for keys %$all_weights; my $pgs = all_combinations($osd_tree); my $pg_per_osd = {}; for my $pg (@$pgs) @@ -35,7 +37,7 @@ sub optimize_initial $lp .= "max: ".join(" + ", map { "pg_".join("_", @$_) } @$pgs).";\n"; for my $osd (keys %$pg_per_osd) { - $lp .= join(" + ", @{$pg_per_osd->{$osd}})." <= ".$all_weights->{$osd}.";\n"; + $lp .= join(" + ", @{$pg_per_osd->{$osd}})." <= ".int($all_weights->{$osd}/$total_weight*$pg_count + 0.5).";\n"; } for my $pg (@$pgs) { @@ -45,9 +47,7 @@ sub optimize_initial my ($score, $weights) = lp_solve($lp); my $int_pgs = make_int_pgs($weights, $pg_count); my $eff = pg_list_space_efficiency($int_pgs, $osd_tree); - my $total_weight = 0; - $total_weight += $all_weights->{$_} for keys %$all_weights; - return { score => $score, weights => $weights, int_pgs => $int_pgs, total_space => $eff * 3, space_eff => $eff * 3 / $total_weight }; + return { score => $score, weights => $weights, int_pgs => $int_pgs, space => $eff, total_space => $total_weight }; } sub make_int_pgs @@ -224,7 +224,6 @@ sub optimize_change for my $k (keys %$weights) { delete $weights->{$k} if !$weights->{$k}; - print "$k: ".$weights->{$k}."\n" if $weights->{$k}; } my $int_pgs = make_int_pgs($weights, $pg_count); # Align them with most similar previous PGs @@ -261,15 +260,18 @@ sub print_change_stats { my ($retval) = @_; my $new_pgs = $retval->{int_pgs}; - my $prev_int_pgs = $retval->{prev_pgs}; - for my $i (0..$#$new_pgs) + if ($retval->{prev_pgs}) { - if (join('_', @{$new_pgs->[$i]}) ne join('_', @{$prev_int_pgs->[$i]})) + my $prev_int_pgs = $retval->{prev_pgs}; + for my $i (0..$#$new_pgs) { - print "pg $i: ".join(' ', @{$prev_int_pgs->[$i]})." -> ".join(' ', @{$new_pgs->[$i]})."\n"; + if (join('_', @{$new_pgs->[$i]}) ne join('_', @{$prev_int_pgs->[$i]})) + { + print "pg $i: ".join(' ', @{$prev_int_pgs->[$i]})." -> ".join(' ', @{$new_pgs->[$i]})."\n"; + } } + printf("Data movement: ".$retval->{differs}." pgs, ".$retval->{osd_differs}." pg-osds = %.2f %%\n", $retval->{osd_differs} / @$prev_int_pgs / 3 * 100); } - printf("Data movement: ".$retval->{differs}." pgs, ".$retval->{osd_differs}." pg-osds = %.2f %%\n", $retval->{osd_differs} / @$prev_int_pgs / 3 * 100); printf("Total space: %.2f TB, space efficiency: %.2f %%\n", $retval->{space} * 3, $retval->{space} * 3 / $retval->{total_space} * 100); } diff --git a/lp/lp-optimizer.js b/lp/lp-optimizer.js index e7451ae6..69d35ab6 100644 --- a/lp/lp-optimizer.js +++ b/lp/lp-optimizer.js @@ -44,7 +44,7 @@ async function lp_solve(text) function make_single(osd_tree) { const initial = all_combinations(osd_tree, 1)[0]; - const all_weights = Object.assign.apply({}, Object.values(osd_tree)); + const all_weights = Object.assign({}, ...Object.values(osd_tree)); let weight; for (const osd of initial) { @@ -60,10 +60,11 @@ function make_single(osd_tree) async function optimize_initial(osd_tree, pg_count) { - const all_weights = Object.assign.apply({}, Object.values(osd_tree)); - const all_pgs = all_combinations($osd_tree); + const all_weights = Object.assign({}, ...Object.values(osd_tree)); + const total_weight = Object.values(all_weights).reduce((a, c) => Number(a) + Number(c)); + const all_pgs = all_combinations(osd_tree, null, true); const pg_per_osd = {}; - for (const pg in all_pgs) + for (const pg of all_pgs) { for (const osd of pg) { @@ -75,18 +76,18 @@ async function optimize_initial(osd_tree, pg_count) lp += "max: "+all_pgs.map(pg => 'pg_'+pg.join('_')).join(' + ')+";\n"; for (const osd in pg_per_osd) { - lp += pg_per_osd[osd].join(' + ')+' <= '+all_weights[osd]+';\n'; + const osd_pg_count = all_weights[osd]*3/total_weight*pg_count; + lp += pg_per_osd[osd].join(' + ')+' <= '+Math.round(osd_pg_count)+';\n'; } for (const pg of all_pgs) { lp += 'pg_'+pg.join('_')+" >= 0;\n"; } lp += "int "+all_pgs.map(pg => 'pg_'+pg.join('_')).join(', ')+";\n"; - const [ score, weights ] = await lp_solve(lp); - const int_pgs = make_int_pgs(weights, pg_count); - const eff = pg_list_space_efficiency(int_pgs, osd_tree); - const total_weight = Object.values(all_weights).reduce((a, c) => Number(a) + Number(c)); - return { score, weights, int_pgs, total_space: eff*3, space_eff: eff*3/total_weight }; + const lp_result = await lp_solve(lp); + const int_pgs = make_int_pgs(lp_result.vars, pg_count); + const eff = pg_list_space_efficiency(int_pgs, all_weights); + return { score: lp_result.score, weights: lp_result.vars, int_pgs, space: eff, total_space: total_weight }; } function make_int_pgs(weights, pg_count) @@ -110,7 +111,8 @@ function make_int_pgs(weights, pg_count) function get_int_pg_weights(prev_int_pgs, osd_tree) { - const space = pg_list_space_efficiency(prev_int_pgs, osd_tree); + const all_weights = Object.assign({}, ...Object.values(osd_tree)); + const space = pg_list_space_efficiency(prev_int_pgs, all_weights); const prev_weights = {}; let count = 0; for (const pg of prev_int_pgs) @@ -172,7 +174,7 @@ async function optimize_change(prev_int_pgs, osd_tree) } // Calculate total weight - old PG weights const all_pg_names = all_pgs.map(pg => 'pg_'+pg.join('_')); - const all_weights = Object.assign.apply({}, Object.values(osd_tree)); + const all_weights = Object.assign({}, ...Object.values(osd_tree)); const total_weight = Object.values(all_weights).reduce((a, c) => Number(a) + Number(c)); // Generate the LP problem let lp = ''; @@ -181,8 +183,8 @@ async function optimize_change(prev_int_pgs, osd_tree) for (const osd in pg_per_osd) { const osd_sum = (pg_per_osd[osd]||[]).map(pg_name => prev_weights[pg_name] ? `add_${pg_name} - del_${pg_name}` : pg_name).join(' + '); - const osd_weight = all_weights[osd]*3/total_weight*pg_count - (prev_pg_per_osd[osd]||[]).length; - lp += osd_sum + ' <= ' + Math.round(osd_weight) + ';\n'; + const osd_pg_count = all_weights[osd]*3/total_weight*pg_count - (prev_pg_per_osd[osd]||[]).length; + lp += osd_sum + ' <= ' + Math.round(osd_pg_count) + ';\n'; } let pg_vars = []; for (const pg_name of all_pg_names) @@ -259,7 +261,7 @@ async function optimize_change(prev_int_pgs, osd_tree) int_pgs: new_pgs, differs, osd_differs, - space: pg_list_space_efficiency(new_pgs, osd_tree), + space: pg_list_space_efficiency(new_pgs, all_weights), total_space: total_weight, }; } @@ -268,17 +270,20 @@ function print_change_stats(retval) { const new_pgs = retval.int_pgs; const prev_int_pgs = retval.prev_pgs; - for (let i = 0; i < new_pgs.length; i++) + if (prev_int_pgs) { - if (new_pgs[i].join('_') != prev_int_pgs[i].join('_')) + for (let i = 0; i < new_pgs.length; i++) { - console.log("pg "+i+": "+prev_int_pgs[i].join(' ')+" -> "+new_pgs[i].join(' ')); + if (new_pgs[i].join('_') != prev_int_pgs[i].join('_')) + { + console.log("pg "+i+": "+prev_int_pgs[i].join(' ')+" -> "+new_pgs[i].join(' ')); + } } + console.log( + "Data movement: "+retval.differs+" pgs, "+ + retval.osd_differs+" pg*osds = "+Math.round(retval.osd_differs / prev_int_pgs.length / 3 * 10000)/100+" %" + ); } - console.log( - "Data movement: "+retval.differs+" pgs, "+ - retval.osd_differs+" pg*osds = "+Math.round(retval.osd_differs / prev_int_pgs.length / 3 * 10000)/100+" %" - ); console.log( "Total space: "+Math.round(retval.space*3*100)/100+" TB, space efficiency: "+ Math.round(retval.space*3/retval.total_space*10000)/100+" %" @@ -338,22 +343,22 @@ function all_combinations(osd_tree, count, ordered) { const hosts = Object.keys(osd_tree).sort(); const osds = Object.keys(osd_tree).reduce((a, c) => { a[c] = Object.keys(osd_tree[c]).sort(); return a; }, {}); - let h = [ 0, 1, 2 ]; - let o = [ 0, 0, 0 ]; + let host_idx = [ 0, 1, 2 ]; + let osd_idx = [ 0, 0, 0 ]; const r = []; while (!count || count < 0 || r.length < count) { let inc; - if (h[2] != h[1] && h[2] != h[0] && h[1] != h[0]) + if (host_idx[2] != host_idx[1] && host_idx[2] != host_idx[0] && host_idx[1] != host_idx[0]) { - r.push(h.map((host_idx, i) => osds[hosts[host_idx]][o[i]])); + r.push(host_idx.map((hi, i) => osds[hosts[hi]][osd_idx[i]])); inc = 2; while (inc >= 0) { - o[inc]++; - if (o[inc] >= osds[hosts[h[inc]]].length) + osd_idx[inc]++; + if (osd_idx[inc] >= osds[hosts[host_idx[inc]]].length) { - o[inc] = 0; + osd_idx[inc] = 0; inc--; } else @@ -369,18 +374,18 @@ function all_combinations(osd_tree, count, ordered) if (inc < 0) { // no osds left in current host combination, select the next one - o = [ 0, 0, 0 ]; - h[2]++; - if (h[2] >= hosts.length) + osd_idx = [ 0, 0, 0 ]; + host_idx[2]++; + if (host_idx[2] >= hosts.length) { - h[1]++; - h[2] = ordered ? h[1]+1 : 0; - if ((ordered ? h[2] : h[1]) >= hosts.length) + host_idx[1]++; + host_idx[2] = ordered ? host_idx[1]+1 : 0; + if ((ordered ? host_idx[2] : host_idx[1]) >= hosts.length) { - h[0]++; - h[1] = ordered ? h[0]+1 : 0; - h[2] = ordered ? h[1]+1 : 0; - if ((ordered ? h[2] : h[0]) >= hosts.length) + host_idx[0]++; + host_idx[1] = ordered ? host_idx[0]+1 : 0; + host_idx[2] = ordered ? host_idx[1]+1 : 0; + if ((ordered ? host_idx[2] : host_idx[0]) >= hosts.length) { break; } @@ -391,7 +396,7 @@ function all_combinations(osd_tree, count, ordered) return r; } -function pg_weights_space_efficiency(weights, pg_count, osd_tree) +function pg_weights_space_efficiency(weights, pg_count, osd_sizes) { const per_osd = {}; for (const pg_name in weights) @@ -401,10 +406,10 @@ function pg_weights_space_efficiency(weights, pg_count, osd_tree) per_osd[osd] = (per_osd[osd]||0) + weights[pg_name]; } } - return pg_per_osd_space_efficiency(per_osd, pg_count, osd_tree); + return pg_per_osd_space_efficiency(per_osd, pg_count, osd_sizes); } -function pg_list_space_efficiency(pgs, osd_tree) +function pg_list_space_efficiency(pgs, osd_sizes) { const per_osd = {}; for (const pg of pgs) @@ -414,21 +419,20 @@ function pg_list_space_efficiency(pgs, osd_tree) per_osd[osd] = (per_osd[osd]||0) + 1; } } - return pg_per_osd_space_efficiency(per_osd, pgs.length, osd_tree); + return pg_per_osd_space_efficiency(per_osd, pgs.length, osd_sizes); } -function pg_per_osd_space_efficiency(per_osd, pg_count, osd_tree) +function pg_per_osd_space_efficiency(per_osd, pg_count, osd_sizes) { - const all_weights = Object.assign.apply({}, Object.values(osd_tree)); // each PG gets randomly selected in 1/N cases - // => there are x PGs per OSD + // & there are x PGs per OSD // => an OSD is selected in x/N cases - // => total space * x/N <= OSD weight - // => total space <= OSD weight * N/x + // => total space * x/N <= OSD size + // => total space <= OSD size * N/x let space; for (let osd in per_osd) { - const space_estimate = all_weights[osd] * pg_count / per_osd[osd]; + const space_estimate = osd_sizes[osd] * pg_count / per_osd[osd]; if (space == null || space > space_estimate) { space = space_estimate; diff --git a/lp/test-optimize.js b/lp/test-optimize.js new file mode 100644 index 00000000..523b2bf9 --- /dev/null +++ b/lp/test-optimize.js @@ -0,0 +1,39 @@ +const LPOptimizer = require('./lp-optimizer.js'); + +const osd_tree = { + 100: { + 7: 3.63869, + }, + 300: { + 10: 3.46089, + 11: 3.46089, + 12: 3.46089, + }, + 400: { + 1: 3.49309, + 2: 3.49309, + 3: 3.49309, + }, + 500: { + 4: 3.58498, +// 8: 3.58589, + 9: 3.63869, + }, + 600: { + 5: 3.63869, + 6: 3.63869, + }, +}; + +async function run() +{ + // Test: add 1 OSD of almost the same size. Ideal data movement could be 1/12 = 8.33%. Actual is ~11.72% + // Space efficiency is ~99.5% in both cases. + let prev = await LPOptimizer.optimize_initial(osd_tree, 256); + LPOptimizer.print_change_stats(prev); + osd_tree[500][8] = 3.58589; + let next = await LPOptimizer.optimize_change(prev.int_pgs, osd_tree); + LPOptimizer.print_change_stats(next); +} + +run().catch(console.error); diff --git a/lp/optimize.pl b/lp/test-optimize.pl similarity index 78% rename from lp/optimize.pl rename to lp/test-optimize.pl index 9b9dcefe..d4dcdb29 100644 --- a/lp/optimize.pl +++ b/lp/test-optimize.pl @@ -24,12 +24,13 @@ my $osd_tree = { }, 500 => { 4 => 3.58498, - 9 => 3.63869, # 8 => 3.58589, + 9 => 3.63869, }, }; my $prev = LPOptimizer::optimize_initial($osd_tree, 256); -my $int = LPOptimizer::get_int_pg_weights($prev->{int_pgs}, $osd_tree); +LPOptimizer::print_change_stats($prev); $osd_tree->{500}->{8} = 3.58589; -LPOptimizer::optimize_change($prev->{int_pgs}, $osd_tree); +my $next = LPOptimizer::optimize_change($prev->{int_pgs}, $osd_tree); +LPOptimizer::print_change_stats($next);