Support and describe simulation-based model

master
Vitaliy Filippov 2021-12-29 01:53:23 +03:00
parent 28a26f4867
commit e53288339f
3 changed files with 114 additions and 12 deletions

View File

@ -10,7 +10,7 @@ https://yourcmc.ru/afr-calc/
https://yourcmc.ru/git/vitalif/vitastor/
## Логика расчёта
## Теоретическая модель
- Вероятность потери данных равна вероятности того, что в течение года выйдет из строя любой 1 диск
и при этом в течение времени, которое восстанавливается недостающая копия данных, выйдут из строя
@ -45,7 +45,7 @@ https://yourcmc.ru/git/vitalif/vitastor/
AFR сервера эмпирически поделен на число дисков, чтобы "размазать" вероятность отказа сервера
по его дискам.
## Парадокс дней рождений
### Парадокс дней рождений
- PG почти гарантированно пересекаются, особенно в небольших кластерах. Степень их пересечения
очень полезно учитывать.
@ -69,3 +69,64 @@ https://yourcmc.ru/git/vitalif/vitastor/
- Зная число участвующих в восстановлении дисков, среднюю скорость восстановления в пересчёте на 1 диск,
оцениваемую с учётом пропускной способности сети, а также объём дисков, мы можем рассчитать
ожидаемое время восстановления данных одного диска или одного хоста.
## Симуляция (переборная модель)
К сожалению, при теоретическом расчёте по вышеприведённой модели корректно учесть степень
пересечения вероятностей выхода из строя разных PG всё равно не получается, из-за чего вероятность
оказывается завышенной.
Чтобы попробовать оценить вероятность более реально, придумана вторая модель - переборная.
Идея в том, чтобы сначала сгенерировать заданное количество случайных PG с учётом распределения
данных по хостам, а потом перебрать все варианты комбинаций событий их выхода из строя, по
принципу:
- PG 1 вышла из строя в течение года
- PG 1 не вышла из строя в течение года, но вышла из строя PG 2
- PG 1 и 2 не вышли из строя в течение года, но вышла из строя PG 3
- И так далее...
Как же подсчитать вероятности выхода из строя PG? Начнём с простого - N-кратной репликации:
- Берём очередную PG. Допустим, она включает диски 1, 2, ..., N.
- Поделим все варианты событий следующим образом:
- Диск №1 умирает в течение года
- Вероятность этого события равна AFR диска 1 = AFR1
- Диск №2 умирает в диапазоне +- времени восстановления от диска №1 (либо до диска №1, либо после)
- Вероятность этого события = AFR1 * AFR2 * 2 * время_восстановления / год
- Диск №3 умирает в диапазоне +- времени восстановления от дисков №1 и №2
- Вероятность этого события `AFR2 * коэффициент(2) * время_восстановления/год`
- Коэффициент(N+1) - это среднее пересечение N+1-ого отрезка с предыдущими N,
при условии, что центр каждого равномерно распределён в интервале от -1 до 1
и длина равна 2.
- Путём несложных умозаключений можно понять, что это 2 * (0.5 + объём_N-мерной_пирамиды/объём_N-мерногоуба)
- Объём N-мерной пирамиды = 1/N * Площадь_основания * Высота = 1/N * 2^(N-1)
- Так что коэффициент(N+1) равен просто (1 + 1/N)
- И так далее для всех последующих дисков PG
- Диск №4 не умирает в этом диапазоне => PG умереть уже не может (одна копия данных точно жива)
- Диск №2 не умирает в этом диапазоне => PG умереть не может
- Диск №1 не умирает в течение года => PG умереть не может
- После каждого шага мы знаем, что учли всю вероятность выхода из строя диска №1
- А также часть `(AFR1)` вероятности выхода из строка диска №2
- А также часть `(AFR1 * AFR2)` вероятности выхода из строка диска №3
- И так далее...
- Поэтому для последующих шагов вероятность выхода из строя диска №1 приравнивается к 0,
а дисков №2 - №N умножается на `(1 - AFR1 * ... * AFRi-1)`
Эту же схему легко расширить до EC N+K - кстати, N реплик, по сути, то же самое, что "EC" 1+(N+1).
Нужно только в рамках каждой PG перебирать комбинации отказов дисков:
- Начать с `PREV=1` и `PGFAIL=0`
- Диск №1 умирает в течение года: `AFR1`
- Число умерших дисков: `M=1`
- Вероятность отказа текущей комбинации: `CUR=PREV*AFR1`
- Для каждого последующего диска:
- Диск №i умирает в диапазоне времени восстановления предыдущих: `X = Коэффициент(M+1)*Время*AFRi`
- Если `M+1 > K`, добавить CUR к вероятности отказа PG и остановить ветку перебора
- Иначе повторить перебор для остальных дисков с `M=M+1` и `CUR=CUR*X`
- Диск №i не умирает в этом диапазоне
- Уменьшить AFRi: `AFRi = AFRi * (1-CUR)`
- Повторить перебор для остальных дисков с `M=M` и `CUR=CUR*(1-X)`
- Диск №1 не умирает в течение года: `1-AFR1`
- Умножить `PREV = PREV*(1-AFR1)`
- Приравнять оставшуюся (неучтённую) вероятность отказа диска 1 к 0: `AFR1 = 0`
- Повторить перебор, начиная с последующих дисков, кроме последних K
- Общую вероятность отказа умножить на `(1-PGFAIL)`
- Перебрать таким же образом все последующие PG

3
afr.js
View File

@ -291,7 +291,8 @@ function pg_death_combinations(maydie, pg, ec_parity, heal_time, cur, i, deadn)
return 0;
}
let drive_death = cur * pyramid(deadn) * heal_time * maydie[pg[i]];
maydie[pg[i]] -= drive_death;
// intersecting deaths are accounted for and non-intersecting deaths are accounted for too
maydie[pg[i]] *= (1 - cur);
let is_dead = 0, not_dead = 0;
if (deadn > ec_parity)
{

58
main.js
View File

@ -1,6 +1,6 @@
import * as preact from 'preact';
/** @jsx preact.h */
import { cluster_afr } from './afr.js';
import { cluster_afr, cluster_afr_bruteforce } from './afr.js';
class Calc extends preact.Component
{
@ -8,10 +8,10 @@ class Calc extends preact.Component
hosts: 10,
drives: 10,
afr_drive: 3,
afr_host: 5,
afr_host: 0,
capacity: 8,
speed: 20,
pg_per_osd: 100,
pg_per_osd: 10,
ec: false,
replicas: 2,
ec_data: 2,
@ -20,12 +20,27 @@ class Calc extends preact.Component
same_host: true,
result: 0,
use_speed: true,
sim: true,
}
calc(st)
_timer = null
calc(st, force)
{
st = { ...this.state, ...st };
st.result = 100*cluster_afr({
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;
}
const fn = st.sim ? cluster_afr_bruteforce : cluster_afr;
st.result = 100*fn({
n_hosts: st.hosts,
n_drives: st.drives,
afr_drive: st.afr_drive/100,
@ -42,7 +57,7 @@ class Calc extends preact.Component
degraded_replacement: st.eager,
down_out_interval: 600,
});
this.setState(st);
this.setState(empty_st ? { result: st.result } : st);
}
setter(field)
@ -57,6 +72,20 @@ class Calc extends preact.Component
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 });
@ -128,8 +157,18 @@ class Calc extends preact.Component
и, конечно, непосредственно вероятности выхода из строя самих дисков и серверов.
</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>Число серверов</th>
@ -192,7 +231,8 @@ class Calc extends preact.Component
</tr>
<tr>
<th><abbr title="Вероятность отказа сервера сразу со всеми дисками, без возвращения их в строй">AFR сервера</abbr></th>
<td><input type="text" value={state.afr_host} onchange={this.setter('afr_host')} /> %</td>
<td><input disabled={state.sim} title={state.sim ? 'Не поддерживается в режиме симуляции' : ''}
type="text" value={state.afr_host} onchange={this.setter('afr_host')} /> %</td>
</tr>
</table>
<p>