forked from vitalif/vitastor
Fix optimize_initial in both perl and js versions
parent
842f88f94f
commit
706a44d4d4
|
@ -25,6 +25,8 @@ sub optimize_initial
|
||||||
{
|
{
|
||||||
my ($osd_tree, $pg_count) = @_;
|
my ($osd_tree, $pg_count) = @_;
|
||||||
my $all_weights = { map { %$_ } values %$osd_tree };
|
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 $pgs = all_combinations($osd_tree);
|
||||||
my $pg_per_osd = {};
|
my $pg_per_osd = {};
|
||||||
for my $pg (@$pgs)
|
for my $pg (@$pgs)
|
||||||
|
@ -35,7 +37,7 @@ sub optimize_initial
|
||||||
$lp .= "max: ".join(" + ", map { "pg_".join("_", @$_) } @$pgs).";\n";
|
$lp .= "max: ".join(" + ", map { "pg_".join("_", @$_) } @$pgs).";\n";
|
||||||
for my $osd (keys %$pg_per_osd)
|
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)
|
for my $pg (@$pgs)
|
||||||
{
|
{
|
||||||
|
@ -45,9 +47,7 @@ sub optimize_initial
|
||||||
my ($score, $weights) = lp_solve($lp);
|
my ($score, $weights) = lp_solve($lp);
|
||||||
my $int_pgs = make_int_pgs($weights, $pg_count);
|
my $int_pgs = make_int_pgs($weights, $pg_count);
|
||||||
my $eff = pg_list_space_efficiency($int_pgs, $osd_tree);
|
my $eff = pg_list_space_efficiency($int_pgs, $osd_tree);
|
||||||
my $total_weight = 0;
|
return { score => $score, weights => $weights, int_pgs => $int_pgs, space => $eff, total_space => $total_weight };
|
||||||
$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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub make_int_pgs
|
sub make_int_pgs
|
||||||
|
@ -224,7 +224,6 @@ sub optimize_change
|
||||||
for my $k (keys %$weights)
|
for my $k (keys %$weights)
|
||||||
{
|
{
|
||||||
delete $weights->{$k} if !$weights->{$k};
|
delete $weights->{$k} if !$weights->{$k};
|
||||||
print "$k: ".$weights->{$k}."\n" if $weights->{$k};
|
|
||||||
}
|
}
|
||||||
my $int_pgs = make_int_pgs($weights, $pg_count);
|
my $int_pgs = make_int_pgs($weights, $pg_count);
|
||||||
# Align them with most similar previous PGs
|
# Align them with most similar previous PGs
|
||||||
|
@ -261,15 +260,18 @@ sub print_change_stats
|
||||||
{
|
{
|
||||||
my ($retval) = @_;
|
my ($retval) = @_;
|
||||||
my $new_pgs = $retval->{int_pgs};
|
my $new_pgs = $retval->{int_pgs};
|
||||||
my $prev_int_pgs = $retval->{prev_pgs};
|
if ($retval->{prev_pgs})
|
||||||
for my $i (0..$#$new_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);
|
printf("Total space: %.2f TB, space efficiency: %.2f %%\n", $retval->{space} * 3, $retval->{space} * 3 / $retval->{total_space} * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ async function lp_solve(text)
|
||||||
function make_single(osd_tree)
|
function make_single(osd_tree)
|
||||||
{
|
{
|
||||||
const initial = all_combinations(osd_tree, 1)[0];
|
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;
|
let weight;
|
||||||
for (const osd of initial)
|
for (const osd of initial)
|
||||||
{
|
{
|
||||||
|
@ -60,10 +60,11 @@ function make_single(osd_tree)
|
||||||
|
|
||||||
async function optimize_initial(osd_tree, pg_count)
|
async function optimize_initial(osd_tree, pg_count)
|
||||||
{
|
{
|
||||||
const all_weights = Object.assign.apply({}, Object.values(osd_tree));
|
const all_weights = Object.assign({}, ...Object.values(osd_tree));
|
||||||
const all_pgs = all_combinations($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 = {};
|
const pg_per_osd = {};
|
||||||
for (const pg in all_pgs)
|
for (const pg of all_pgs)
|
||||||
{
|
{
|
||||||
for (const osd of pg)
|
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";
|
lp += "max: "+all_pgs.map(pg => 'pg_'+pg.join('_')).join(' + ')+";\n";
|
||||||
for (const osd in pg_per_osd)
|
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)
|
for (const pg of all_pgs)
|
||||||
{
|
{
|
||||||
lp += 'pg_'+pg.join('_')+" >= 0;\n";
|
lp += 'pg_'+pg.join('_')+" >= 0;\n";
|
||||||
}
|
}
|
||||||
lp += "int "+all_pgs.map(pg => 'pg_'+pg.join('_')).join(', ')+";\n";
|
lp += "int "+all_pgs.map(pg => 'pg_'+pg.join('_')).join(', ')+";\n";
|
||||||
const [ score, weights ] = await lp_solve(lp);
|
const lp_result = await lp_solve(lp);
|
||||||
const int_pgs = make_int_pgs(weights, pg_count);
|
const int_pgs = make_int_pgs(lp_result.vars, pg_count);
|
||||||
const eff = pg_list_space_efficiency(int_pgs, osd_tree);
|
const eff = pg_list_space_efficiency(int_pgs, all_weights);
|
||||||
const total_weight = Object.values(all_weights).reduce((a, c) => Number(a) + Number(c));
|
return { score: lp_result.score, weights: lp_result.vars, int_pgs, space: eff, total_space: total_weight };
|
||||||
return { score, weights, int_pgs, total_space: eff*3, space_eff: eff*3/total_weight };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function make_int_pgs(weights, pg_count)
|
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)
|
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 = {};
|
const prev_weights = {};
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const pg of prev_int_pgs)
|
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
|
// Calculate total weight - old PG weights
|
||||||
const all_pg_names = all_pgs.map(pg => 'pg_'+pg.join('_'));
|
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));
|
const total_weight = Object.values(all_weights).reduce((a, c) => Number(a) + Number(c));
|
||||||
// Generate the LP problem
|
// Generate the LP problem
|
||||||
let lp = '';
|
let lp = '';
|
||||||
|
@ -181,8 +183,8 @@ async function optimize_change(prev_int_pgs, osd_tree)
|
||||||
for (const osd in pg_per_osd)
|
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_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;
|
const osd_pg_count = all_weights[osd]*3/total_weight*pg_count - (prev_pg_per_osd[osd]||[]).length;
|
||||||
lp += osd_sum + ' <= ' + Math.round(osd_weight) + ';\n';
|
lp += osd_sum + ' <= ' + Math.round(osd_pg_count) + ';\n';
|
||||||
}
|
}
|
||||||
let pg_vars = [];
|
let pg_vars = [];
|
||||||
for (const pg_name of all_pg_names)
|
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,
|
int_pgs: new_pgs,
|
||||||
differs,
|
differs,
|
||||||
osd_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,
|
total_space: total_weight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -268,17 +270,20 @@ function print_change_stats(retval)
|
||||||
{
|
{
|
||||||
const new_pgs = retval.int_pgs;
|
const new_pgs = retval.int_pgs;
|
||||||
const prev_int_pgs = retval.prev_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(
|
console.log(
|
||||||
"Total space: "+Math.round(retval.space*3*100)/100+" TB, space efficiency: "+
|
"Total space: "+Math.round(retval.space*3*100)/100+" TB, space efficiency: "+
|
||||||
Math.round(retval.space*3/retval.total_space*10000)/100+" %"
|
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 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; }, {});
|
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 host_idx = [ 0, 1, 2 ];
|
||||||
let o = [ 0, 0, 0 ];
|
let osd_idx = [ 0, 0, 0 ];
|
||||||
const r = [];
|
const r = [];
|
||||||
while (!count || count < 0 || r.length < count)
|
while (!count || count < 0 || r.length < count)
|
||||||
{
|
{
|
||||||
let inc;
|
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;
|
inc = 2;
|
||||||
while (inc >= 0)
|
while (inc >= 0)
|
||||||
{
|
{
|
||||||
o[inc]++;
|
osd_idx[inc]++;
|
||||||
if (o[inc] >= osds[hosts[h[inc]]].length)
|
if (osd_idx[inc] >= osds[hosts[host_idx[inc]]].length)
|
||||||
{
|
{
|
||||||
o[inc] = 0;
|
osd_idx[inc] = 0;
|
||||||
inc--;
|
inc--;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -369,18 +374,18 @@ function all_combinations(osd_tree, count, ordered)
|
||||||
if (inc < 0)
|
if (inc < 0)
|
||||||
{
|
{
|
||||||
// no osds left in current host combination, select the next one
|
// no osds left in current host combination, select the next one
|
||||||
o = [ 0, 0, 0 ];
|
osd_idx = [ 0, 0, 0 ];
|
||||||
h[2]++;
|
host_idx[2]++;
|
||||||
if (h[2] >= hosts.length)
|
if (host_idx[2] >= hosts.length)
|
||||||
{
|
{
|
||||||
h[1]++;
|
host_idx[1]++;
|
||||||
h[2] = ordered ? h[1]+1 : 0;
|
host_idx[2] = ordered ? host_idx[1]+1 : 0;
|
||||||
if ((ordered ? h[2] : h[1]) >= hosts.length)
|
if ((ordered ? host_idx[2] : host_idx[1]) >= hosts.length)
|
||||||
{
|
{
|
||||||
h[0]++;
|
host_idx[0]++;
|
||||||
h[1] = ordered ? h[0]+1 : 0;
|
host_idx[1] = ordered ? host_idx[0]+1 : 0;
|
||||||
h[2] = ordered ? h[1]+1 : 0;
|
host_idx[2] = ordered ? host_idx[1]+1 : 0;
|
||||||
if ((ordered ? h[2] : h[0]) >= hosts.length)
|
if ((ordered ? host_idx[2] : host_idx[0]) >= hosts.length)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -391,7 +396,7 @@ function all_combinations(osd_tree, count, ordered)
|
||||||
return r;
|
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 = {};
|
const per_osd = {};
|
||||||
for (const pg_name in weights)
|
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];
|
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 = {};
|
const per_osd = {};
|
||||||
for (const pg of pgs)
|
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;
|
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
|
// 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
|
// => an OSD is selected in x/N cases
|
||||||
// => total space * x/N <= OSD weight
|
// => total space * x/N <= OSD size
|
||||||
// => total space <= OSD weight * N/x
|
// => total space <= OSD size * N/x
|
||||||
let space;
|
let space;
|
||||||
for (let osd in per_osd)
|
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)
|
if (space == null || space > space_estimate)
|
||||||
{
|
{
|
||||||
space = space_estimate;
|
space = space_estimate;
|
||||||
|
|
|
@ -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);
|
|
@ -24,12 +24,13 @@ my $osd_tree = {
|
||||||
},
|
},
|
||||||
500 => {
|
500 => {
|
||||||
4 => 3.58498,
|
4 => 3.58498,
|
||||||
9 => 3.63869,
|
|
||||||
# 8 => 3.58589,
|
# 8 => 3.58589,
|
||||||
|
9 => 3.63869,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
my $prev = LPOptimizer::optimize_initial($osd_tree, 256);
|
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;
|
$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);
|
Loading…
Reference in New Issue