diff --git a/README.md b/README.md index 8d0bc89..55edd12 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/afr.js b/afr.js index b778a9c..57f2003 100644 --- a/afr.js +++ b/afr.js @@ -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) { diff --git a/main.js b/main.js index c0bc524..e46651e 100644 --- a/main.js +++ b/main.js @@ -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 и, конечно, непосредственно вероятности выхода из строя самих дисков и серверов.

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

+
+
+ + +
+
@@ -192,7 +231,8 @@ class Calc extends preact.Component - +
Число серверов
AFR сервера % %