Skip to content

Commit

Permalink
Merge branch 'master' into chore/update-deps
Browse files Browse the repository at this point in the history
  • Loading branch information
diogotcorreia committed Mar 20, 2024
2 parents a3019c0 + 7667006 commit e7dd733
Show file tree
Hide file tree
Showing 29 changed files with 924 additions and 461 deletions.
12 changes: 0 additions & 12 deletions .puppeteerrc.cjs

This file was deleted.

7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,6 @@ Antes de fazer um commit, é recomendado executar o `prettier` (se usarem um edi
yarn format
```

### Configurações Avançadas

Quando se está a configurar o _deployment_, de forma a incluir o browser nas pastas
que ficam em cache, pode ser necessário definir
a _environment variable_ `PUPPETEER_IN_PROJECT_DIRECTORY`, que guarda o browser
do Puppeteer na pasta do projeto em vez de na _home directory_.

## Parceiros

[![Powered by Vercel](./src/images/powered-by-vercel.svg)](https://vercel.com/?utm_source=leic-pt&utm_campaign=oss)
335 changes: 334 additions & 1 deletion content/sd/0004-coordenacao-e-consenso.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ adaptado, os processos colocam na fila de espera pedidos pendentes em ordem
_happened-before_, garantindo assim que o requisito
[ME3](/sd/coordenacao-e-consenso/#algoritmos-de-exclusão-mútua) também seja satisfeito.

(ver o exemplo ilustrativo de [Ordem Total baseada em acordo coletivo](/sd/replicacao-e-tolerancia-a-faltas/#difusão-atómica:~:text=Ordem%20total%20baseada%20em%20acordo%20coletivo))

:::

### Comparação dos algoritmos
Expand Down Expand Up @@ -534,10 +536,341 @@ _"eventually"_) perfeito.

:::

## Problema do Consenso

O consenso é um dos problemas mais difíceis e estudados de Sistemas Distribuídos.
Foi provado que, num sistema assíncrono em que podem ocorrer falhas, este problema
[**não tem solução**](#impossibilidade-em-sistemas-assíncronos) (resultado conhecido como
[**FLP**](<https://en.wikipedia.org/wiki/Consensus_(computer_science)#Solvability_results_for_some_agreement_problems>)).

:::info[Definição de Consenso]

Dado um conjunto de $N$ processos:

1. Cada processo propõe um valor (_input_)
2. Todos os processos decidem o mesmo valor (_output_)

**Notas**:

- **O valor decidido deve ser um dos valores propostos**
- invalidando assim uma solução que decide sempre um valor por omissão
independentemente dos _inputs_ dados
- Pode ser qualquer um dos valores propostos:
- **Não tem de ser o valor proposto por mais processos**
- **Não existe** qualquer tipo de **hierarquia de processos** ou **critério de
qualidade** que distinga os valores (i.e. não existem valores nem processos
melhores que os outros)

:::

:::info[Tipos específicos de consenso]

Note que já utilizámos consenso anteriormente:

- na exclusão mútua, os processos concordam sobre o processo que pode entrar
na secção crítica
- na eleição de líder, os processos concordam sobre o processo eleito
- no _multicast_ totalmente ordenado, os processos concordam sobre a ordem de
entrega das mensagens

Existem diversos protocolos para tipos específicos de consenso. No entanto, nesta
secção vamos considerar formas mais gerais de consenso, analisando características
e soluções comuns.

:::

Podemos concluir assim três propriedades do Consenso:

1. **Terminação**: todos os processos correctos decidem ("_alguma-vez_")
2. **Acordo uniforme**: se dois processos decidem, decidem o mesmo valor
3. **Integridade**: o valor decidido (_output_) foi proposto por um processo

Quanto a soluções para este problema em sistemas:

- **síncronos**: ou seja, onde é possível concretizar um detetor de falhas perfeito,
iremos abordar o algoritmo [_FloodSet_](#floodset-consensus)
- **assíncronos**: ou seja, onde é possível concretizar um detetor de falhas
"alguma-vez" perfeito, não iremos abordar nenhum algoritmo, mas podem consultar
o [algoritmo "Paxos"](<https://en.wikipedia.org/wiki/Paxos_(computer_science)>)
do Lamport

:::info[Falhas bizantinas]

Os processos podem falhar de formas arbitrárias (falhas bizantinas), enviando
valores aleatórios para os restantes processos (estes valores aleatórios podem
resultar de _bugs_ ou operações maliciosas).

Nesta cadeira não iremos estudar algoritmos que têm este tipo de falhas em
consideração. Se tiveres interesse em aprender mais recomendamos o
[vídeo](https://youtu.be/LoGx_ldRBU0?list=PLeKd45zvjcDFUEv_ohr_HdUFe97RItdiB)
introdutório do Martin Kleppmann ao _"Byzantine generals problem"_ e o
[_PBFT Consensus Algorithm_](https://medium.com/tronnetwork/an-introduction-to-pbft-consensus-algorithm-11cbd90aaec).

:::

### _Floodset Consensus_

A ideia essencial deste algoritmo é cada processo enviar para todos os outros
o seu valor (_input_), de forma a que no fim todos conheçam todos os valores possíveis
e possam tomar a mesma decisão de forma determinística.

O funcionamento do algoritmo é baseado em rondas:

- em cada ronda, cada processo faz _broadcast_ do seu valor
- ao receber um valor de outro processo, adiciona-o ao seu conjunto
- ao fim de $f \op{+} 1$ rondas, é escolhido o _output_ com base num critério
determinístico utilizado por todos os processos
- em que $f$ é o número de processos que pode falhar

:::details[Pseudocódigo]

$\text{Algorithm for process } P_i \in g; \text{algorithm proceeds in } f \op{+} 1
\text{ rounds}$

$\text{On initialization}\\$
$\qquad Values_i^1 := \set{v_i} ; Values_i^0 = \set{};$

$\text{In round } r~(1 \leq r \leq f \op{+} 1)\\$
$\qquad \text{B-multicast}(g, Values_i^r \op{—} Values_i^{r-1});$ // Send only values that have not been sent
$\\ \qquad Values_i^{r+1} := Values_i^r;\\$
$\qquad \text{while } (\text {in round } r)~\{\\$
$\qquad \qquad \text{On B-deliver}(V_j) \text{ from some } p_j\\$
$\qquad \qquad \qquad Values_i^{r+1} := Values_i^{r+1} \cup V_j\\$
$\qquad \}$

$\text{After } (f \op{+} 1) \text{ rounds}\\$
$\qquad \text{Assign } d_i = minimum(Values_i^{f+1});$

**NOTA**: o critério utilizado neste algoritmo para a escolha do _output_ foi
encontrar o valor mínimo, mas pode ser qualquer critério!

:::

:::details[Exemplo]

Exemplo de execução com $f = 1$:

![Diagrama de execução](./assets/0005-floodset-consensus-example.svg#dark=3)

:::

Algumas notas acerca do algoritmo:

- Pressupõe um sistema síncrono
- se um processo $p_i$ não recebe o valor de outro processo $p_j$ no turno $n$
então o processo $p_j$ falhou de certeza (e não participa nos próximos turnos)
- É possível adaptar o algoritmo de forma a utilizar um detetor de falhas perfeito:
- Caso um processo $p_i$ não tenha recebido o valor de um processo $p_j$ num
turno $n$, apenas avança para o turno $n \op{+} 1$ caso o detetor de falhas
declare $p_j$ como falhado
- Em alguns casos, caso não ocorram falhas, é possível terminar em menos turnos

:::info[Propriedade]

Qualquer algoritmo desenhado para resolver o consenso permitindo até $f$ falhas,
requer pelo menos $f + 1$ rondas de trocas de mensagens, independentemente da
forma como foi construído.

:::

### Problemas relacionados

Iremos agora abordar dois exemplos de problemas que são semelhantes ao problema
do Consenso e que podem ser utilizados para o resolver ou utilizá-lo na construção
da sua solução.

#### Coerência Interativa

O problema da coerência interativa é outra variante de consenso, na qual cada
processo propõe um único valor. O objetivo do algoritmo é fazer com que os processos
corretos concordem com um vetor de valores, um para cada processo. Por exemplo, o
objetivo poderia ser para cada processo de um grupo obter a mesma informação sobre
os estados respectivos (de cada processo).

- Conjunto de $N$ processos
- Cada processo $p_i$ propõe um valor ($\text{input}_i$)
- Todos os processo decidem o mesmo vetor $V$ ($\text{output}$)
- O vetor $V$ decidido tem uma entrada por cada processo em que:
- ou $V[i] = \text{input}_i$
- ou $V[i] = null$

Propriedades:

1. **Terminação**: todos os processos correctos decidem ("_alguma-vez_")
2. **Acordo uniforme**: se dois processos decidem, decidem o mesmo vetor $V$
3. **Integridade**: se o processo $p_i$ não falhar, $V[i] = \text{input}_i$

:::details[Pseudocódigo das implementações]
Consenso usando Coerência Interativa:

```php
Quando Consenso.propoe(valor):
CoerenciaInteractiva.propoe(valor)

Quando CoerenciaInteractiva.decide(vector):
valor = primeiraEntradaDiferenteDeNull(vector);
Consenso.decide(valor)

```

Coerência Interativa usando consenso:

```php
// este codigo é executado por todos os processos

fun PRONTO(vector):
se para todo o p_x: p_x não pertence a falhados e tivermos vector_proposta[x] != null
retorna VERDADEIRO
caso contrário
retorna FALSO

Init:
falhados = {}
para cada valor de i < N
vector_proposta[i] = null;

Quando falha(p_x):
falhados = falhados U {p_x}

Quando IC.propoe(valor_i)
DifusaoFiavel.envia(p_i, valor_i)

Quando DifusaoFiavel.entrega(p_j, valor_j)
vector_proposta[j] = valor_j;

Quando PRONTO(vector_proposta)
Consenso.propoe(vector_proposta) // só propõe uma vez

Quando Consenso.decide(vector)
IC.decide(vector)
```

:::

#### Derivar Consenso a partir de Coerência Interativa

Por vezes é possível derivar uma solução para um problema utilizando uma solução
para outro. Esta propriedade é muito útil porque aumenta a nossa compreensão dos
problemas e economiza esforço de implementação.

Suponha que existem as seguintes soluções para o consenso (C) e para a consistência
interativa (IC):

- $C_i(v_1, v_2, ..., v_N)$ retorna o valor de decisão do processo $p_i$ numa
execução da solução para o problema do consenso, onde $v_1, v_2, ..., v_N$ são
os valores propostos pelos processos
- ${IC}_i(v_1, v_2, ..., v_N)[j]$ retorna o j-ésimo valor no vetor de decisão do
processo $p_i$ numa execução da solução para o problema da consistência interativa,
onde $v_1, v_2, ..., v_N$ são os valores propostos pelos processos

Caso a maioria dos processos estejam corretos, construímos uma solução executando
IC para produzir um vetor de valores em cada processo, e depois aplicando uma certa
função sobre os valores do vetor para derivar um único valor:

$$
C_i(v_1, ..., v_N) = majority({IC}_i(v_1, ..., v_N)[1], ..., {IC}_i(v_1, ..., v_N)[N])
$$

:::info[Nota]

Em sistemas com falhas, resolver o consenso é equivalente a resolver o
_multicast_ confiável e totalmente ordenado: dada uma solução para um, podemos
resolver o outro. Implementar o consenso com base em operações de _multicast_
confiável e totalmente ordenado $\text{(RTO-multicast)}$ é trivial.

Dado um grupo de processos $g$, para alcançar o consenso, cada processo $p_i$
executa $\text{RTO-multicast}(g, v_i)$. Em seguida, cada processo $p_i$ escolhe
$d_i = m_i$, onde $m_i$ é o primeiro valor que $p_i$ $\text{RTO-delivers}$.

:::

#### Difusão com terminação

- Conjunto de $N$ processos
- Um processo pré-definido $s$ envia uma mensagem $m$
- Se o processo $s$ é correto, todos os processos corretos entregam $m$
- Se o processo $s$ falha, os processos entregam $m$ ou $null$
- Todos os processos corretos entregam o mesmo valor (ou $m$ ou $null$)

Propriedades:

1. **Terminação**: todos os processos correctos decidem ("_alguma-vez_")
2. **Acordo uniforme**: se dois processos decidem, decidem o mesmo valor $v$
3. **Integridade**: se o processo $s$ não falhar, $v = m$

:::details[Pseudocódigo das implementações]

Difusão com Terminação usando Consenso:

```php
Quando ConsensoPropoe(v) no processo i
i.DifusaoComTerminacaoEnvia(v)

Para todo o i
i.DifusaoComTerminacaoEntrega(v_i)

Escolhe v_final como sendo o menor v_i: v_i != null
ConsensoDecide(v_final)
```

Consenso usando Difusão com Terminação:

```php
No emissor:
Quando DifusaoComTerminacao(m)
DifusaoFiavelEnvia(m)
ConsensoPropoe(m)


No restantes processos, executa um e apenas um destes passos:
Quando DifusaoFiavelEntrega(m)
ConsensoPropoe(m)
Quando suspeita a falha do processo "s"
ConsensoPropoe(null)

Em todos os processos:
Quando ConsensoDecide(v)
DifusaoComTerminacaoEntrega(m)
```

:::

### Impossibilidade em sistemas assíncronos

As soluções para o consenso que abordámos assumem que o sistema é síncrono, ou
seja, assumem que um processo falhou se não lhes enviou uma certa mensagem dentro
da ronda desejada (atraso máximo excedido).

Fischer et al. [1985] provaram que nenhum algoritmo pode garantir alcançar o consenso
num sistema assíncrono, visto que os processos podem responder a mensagens com
latências arbitrárias, fazendo com que um processo que realmente falhou seja
indistinguível de um lento.

Note que este resultado não significa que os processos não podem alcançar o consenso
distribuído num sistema assíncrono. Este permite que o consenso possa ser alcançado
com alguma probabilidade maior que zero, confirmando o que sabemos na prática (por
exemplo, existem sistemas de transações assíncronos que têm alcançado o consenso
regularmente há anos).

Ainda assim, existem diversas técnicas (não abordadas em aula) para contornar o
resultado da impossibilidade. Por exemplo:

- **Mascarar falhas**: envolve técnicas como o uso de armazenamento persistente e
replicação de componentes para ocultar falhas, permitindo que os processos
continuem a funcionar corretamente
- **Consenso usando detetores de falhas**: envolve o uso de detetores de falhas
(não perfeitos) em sistemas assíncronos para alcançar o consenso, seja considerando
processos não responsivos como falhados ou permitindo que processos suspeitos
continuem a participar no consenso
- **Consenso usando randomização**: envolve introduzir aleatoriedade no comportamento
dos processos para neutralizar os efeitos negativos dos sistemas assíncronos,
permitindo que o consenso seja alcançado em tempo (esperado) finito

## Referências

- Coulouris et al - Distributed Systems: Concepts and Design (5th Edition)
- Secções 15.1, 15.2 e 15.3
- Secções 15.1-15.5
- Departamento de Engenharia Informática - Slides de Sistemas Distribuídos (2023/2024)
- SlidesTagus-Aula03a
- SlidesTagus-Aula04
- SlidesAlameda-Aula08
Loading

0 comments on commit e7dd733

Please sign in to comment.