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 (

Калькулятор вероятности отказа кластера Ceph/Vitastor

Вероятность потери данных в кластере зависит от числа серверов и дисков (чем их больше, тем вероятность больше), от схемы избыточности, скорости ребаланса (восстановления), и, конечно, непосредственно вероятности выхода из строя самих дисков и серверов.

Расчёт ведётся в простом предположении, что отказы распределены равномерно во времени.

{state.ec ? null : } {state.ec ? : null} {state.ec ? : null} {state.use_speed ? : }
Число серверов
Число дисков в сервере
Ёмкость дисков ТБ
Схема избыточности
Число реплик
Число дисков данных
Число дисков чётности
{state.use_speed ? 'Оценочная' : 'Оценочное'}  {state.use_speed ? 'скорость' : 'время'} скорость время
восстановления на 1 OSD
МБ/с час(ов)
PG на OSD
AFR диска %
AFR сервера %

Вероятность потери данных в течение года:
{state.error ?
{state.error}
:
{this.format4(state.result)} %
}
© Виталий Филиппов 2020+ (исходники)
); } } preact.render(, document.body);