ceph-afr-calc/main.js

279 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import * as preact from 'preact';
/** @jsx preact.h */
import { cluster_afr, cluster_afr_bruteforce } from './afr.js';
class Calc extends preact.Component
{
state = {
hosts: 10,
drives: 10,
afr_drive: 3,
afr_host: 0,
capacity: 8,
speed: 20,
pg_per_osd: 10,
ec: false,
replicas: 2,
ec_data: 2,
ec_parity: 1,
eager: false,
same_host: true,
result: 0,
error: null,
use_speed: true,
sim: true,
}
_timer = null
calc(st, force)
{
const empty_st = !st;
st = { ...this.state, ...(st||{}) };
if (st.sim && !force)
{
if (this._timer)
{
clearTimeout(this._timer);
}
this.setState(st);
this._timer = setTimeout(() => this.calc(null, true), 200);
return;
}
// !( >= ) not the same as < because of NaN
if (!(parseInt(st.hosts) >= (st.ec ? parseInt(st.ec_data)+parseInt(st.ec_parity) : parseInt(st.replicas))))
{
st.error = 'Число серверов (доменов отказа) меньше числа дисков в PG, расчёт невозможен';
st.result = 0;
}
else
{
const fn = st.sim ? cluster_afr_bruteforce : cluster_afr;
st.error = null;
st.result = 100*fn({
n_hosts: st.hosts,
n_drives: st.drives,
afr_drive: st.afr_drive/100,
afr_host: st.afr_host/100,
capacity: st.capacity*1000,
speed: st.use_speed ? st.speed/1000 : null,
disk_heal_hours: st.use_speed ? null : st.disk_heal_hours,
ec: st.ec,
ec_data: st.ec_data,
ec_parity: st.ec_parity,
replicas: st.replicas,
pgs: st.pg_per_osd,
osd_rm: !st.same_host,
degraded_replacement: st.eager,
down_out_interval: 600,
});
}
this.setState(empty_st ? { error: st.error, result: st.result } : st);
}
setter(field)
{
if (!this.setter[field])
{
this.setter[field] = (event) =>
{
this.calc({ [field]: event.target.value });
};
}
return this.setter[field];
}
setSim = () =>
{
if (!this.state.sim)
{
// Слишком много PG замедляет расчёт
this.calc({ sim: true, pgs: this.state.pgs > 10 ? 10 : this.state.pgs });
}
}
setThe = () =>
{
this.calc({ sim: false });
}
setRepl = () =>
{
this.calc({ ec: false });
}
setEC = () =>
{
this.calc({ ec: true });
}
setEager = (event) =>
{
this.calc({ eager: event.target.checked });
}
useSpeed = () =>
{
this.calc({ use_speed: true, speed: this.state.speed || 20 });
}
useTime = () =>
{
this.calc({ use_speed: false, disk_heal_hours: 12 });
}
setSameHost = (event) =>
{
this.calc({ same_host: event.target.checked });
}
format4 = (n) =>
{
if (n >= 1 || n <= -1)
return ''+(Math.round(n*10000)/10000);
if (n == 0)
return '0';
let s = '0.', i = 0, c = 0;
if (n < 0)
{
s = '-0.';
n = -n;
}
while (n && i < 4)
{
n = n*10;
s += (n|0);
c = c || (n|0);
n = n-(n|0);
if (c)
i++;
}
return s[s.length-1] == '.' ? '0' : s;
}
componentDidMount()
{
this.calc({});
}
render(props, state)
{
return (<div style="width: 750px; margin: 20px; padding: 20px; box-shadow: 0 19px 60px rgba(0, 0, 0, 0.3), 0 15px 20px rgba(0, 0, 0, 0.22);">
<h2 style="text-align: center; font-size: 150%; margin: 10px 0 20px 0; font-weight: bold">
Калькулятор вероятности отказа кластера Ceph/Vitastor
</h2>
<p>
Вероятность потери данных в кластере зависит от числа серверов и дисков
(чем их больше, тем вероятность больше), от схемы избыточности, скорости ребаланса (восстановления),
и, конечно, непосредственно вероятности выхода из строя самих дисков и серверов.
</p>
<p>
Расчёт ведётся в простом предположении, что отказы распределены равномерно во времени.
</p>
<div style={{textAlign: 'center'}}>
<div style={{display: 'inline-block'}}>
<label class={"switch l"+(state.sim ? "" : " sel")}>
<input type="radio" name="sim" checked={!state.sim} onclick={this.setThe} /> Теоретический расчёт (оценка сверху)
</label>
<label class={"switch r"+(state.sim ? " sel" : "")}>
<input type="radio" name="sim" checked={state.sim} onclick={this.setSim} /> Переборная модель (точно, но медленно)
</label>
</div>
</div>
<table>
<tr>
<th>Число <abbr title="Серверов либо других доменов отказа, если у вас домен отказа другой">серверов</abbr></th>
<td><input type="text" value={state.hosts} onchange={this.setter('hosts')} /></td>
</tr>
<tr>
<th>Число дисков в сервере</th>
<td><input type="text" value={state.drives} onchange={this.setter('drives')} /></td>
</tr>
<tr>
<th>Ёмкость дисков</th>
<td><input type="text" value={state.capacity} onchange={this.setter('capacity')} /> ТБ</td>
</tr>
<tr>
<th>Схема избыточности</th>
<td>
<label class={"switch l"+(state.ec ? "" : " sel")}>
<input type="radio" name="scheme" checked={!state.ec} onclick={this.setRepl} /> Репликация
</label>
<label class={"switch r"+(state.ec ? " sel" : "")}>
<input type="radio" name="scheme" checked={state.ec} onclick={this.setEC} /> EC (коды коррекции ошибок)
</label>
</td>
</tr>
{state.ec ? null : <tr>
<th>Число реплик</th>
<td><input type="text" value={state.replicas} onchange={this.setter('replicas')} /></td>
</tr>}
{state.ec ? <tr>
<th>Число дисков данных</th>
<td><input type="text" value={state.ec_data} onchange={this.setter('ec_data')} /></td>
</tr> : null}
{state.ec ? <tr>
<th>Число дисков чётности</th>
<td><input type="text" value={state.ec_parity} onchange={this.setter('ec_parity')} /></td>
</tr> : null}
<tr>
<th>
{state.use_speed ? 'Оценочная' : 'Оценочное'}&nbsp;
<span className="icombo">
{state.use_speed ? 'скорость' : 'время'} <span className="icon-arw-down"></span>
<span className="options">
<span className="option" onClick={this.useSpeed}>скорость</span>
<span className="option" onClick={this.useTime}>время</span>
</span>
</span>
<br />восстановления на 1 OSD
</th>
{state.use_speed
? <td><input type="text" value={state.speed} onchange={this.setter('speed')} /> МБ/с</td>
: <td><input type="text" value={state.disk_heal_hours} onchange={this.setter('disk_heal_hours')} /> час(ов)</td>}
</tr>
<tr>
<th><abbr title="Среднее число уникальных групп чётности (пар/троек и т.п.), включающих каждый отдельный диск. В Ceph нормой считается 100 PG на OSD">PG на OSD</abbr></th>
<td><input type="text" value={state.pg_per_osd} onchange={this.setter('pg_per_osd')} /></td>
</tr>
<tr>
<th><abbr title="Annualized Failure Rate, вероятность отказа в течение года в %">AFR</abbr> диска</th>
<td><input type="text" value={state.afr_drive} onchange={this.setter('afr_drive')} /> %</td>
</tr>
<tr>
<th><abbr title="Вероятность отказа сервера сразу со всеми дисками, без возвращения их в строй">AFR сервера</abbr></th>
<td><input disabled={state.sim} title={state.sim ? 'Не поддерживается в режиме симуляции' : ''}
type="text" value={state.afr_host} onchange={this.setter('afr_host')} /> %</td>
</tr>
</table>
<p>
<label><input type="checkbox" checked={state.same_host} onchange={this.setSameHost} />
При отказе диска данные распределяются только по другим дискам того же сервера,
как в Ceph
</label>
</p>
<p>
<label><input type="checkbox" checked={state.eager} onchange={this.setEager} />
Я нетерпеливый и заменяю отказавший диск сразу, не давая данным уехать на остальные диски
(либо данным уезжать некуда, например, сервера всего 3 при 3 репликах)
</label>
</p>
<div style="text-align: center; font-size: 150%; margin: 20px 0; font-weight: bold">
Вероятность потери данных в течение года:
</div>
{state.error
? <div style="text-align: center; color: red; margin: 20px 0">
{state.error}
</div>
: <div style="text-align: center; font-size: 200%; margin: 20px 0; font-weight: bold">
{this.format4(state.result)} %
</div>}
<div style="text-align: center; color: #aaa; margin: 10px 0">
&copy; Виталий Филиппов 2020+ <a style="color: inherit" href="https://yourcmc.ru/git/vitalif/ceph-afr-calc">(исходники)</a>
</div>
</div>);
}
}
preact.render(<Calc />, document.body);