No QCon New York 2017, a engenheira de software da Samsara Kavya Joshi, fez uma detalhada apresentação sobre o princípio do happens-before.
Ela explicou como a ferramenta de armazenamento de key-value distribuídos, Riak, utiliza relógios vetoriais para estabelecer causalidade através dos nós. Ela também analisou a concorrência primitiva em Go, explicando como ele e os happens-before das restrições são expressados naturalmente.
Joshi explicou que, em sistemas modernos, a computação geralmente é dividida em diferentes nós ou threads para escalar, o que pode levar às corridas de dados:
"Uma corrida de dados é quando duas threads acessam simultaneamente uma localização de memória compartilhada e pelo menos um acesso é uma gravação."
Como as corridas de dados tendem a ser não-deterministas, ou têm consequências indefinidas, Joshi explicou como elas podem ser particularmente difíceis de depurar. Ela afirmou que a chave para lidar com elas é entender o happens-before.
Essencialmente, happens-before é um meio para determinar a ordem dos eventos em um sistema paralelo, de modo que X <Y se:
- X e Y aconteceram no mesmo ator. Isso ocorre porque o pedido em um único nó ou segmento é garantido para ser seqüencial.
- Eles são um par de sincronização. Por exemplo, se algo for obtido de um nó e atualizado em outro, ou estiver bloqueado com um mutex.
- Implícito a partir da transitividade. Por exemplo, um evento intermediário, como X < Z < Y, pode provar a ordem.
Se nenhum desses critérios forem atendidos, os eventos devem ser simultâneos, o que significa que deve haver algum tipo de resolução de conflito. Joshi então delineou três estratégias para lidar com essa situação:
- Última gravação ganha (o evento com timestamp mais alto). Isso é bom se você tiver dados imutáveis como um cache, mas pode levar à perda de dados.
- Mesclar automaticamente os dados.
- Retornar conflitos para a aplicação para lidar com isso.
Tomando o Riak como exemplo, um armazenamento de key-value distribuídos eventualmente consistente, Joshi mostrou como ela faz uso de um relógio vetorial para estabelecer um happens-before. Este é essencialmente um relógio lógico armazenado em cada nó, que pode ser comparado por meio de um máximo de pares para suas contrapartes para determinar se os eventos são concorrentes ou não. Para poder comparar os relógios, os clientes da Riak passam por um objeto de contexto causal.
Joshi também apresentou sobre a concorrência em Go, focando principalmente em canais em comparação com mutexes como um meio seguro para compartilhar dados entre goroutines. Ela foi capaz de mostrar isso usando canais: o código poderia ser simplificado e ser mais fácil de raciocinar. Ela também destacou que sua semântica de wait-until-empty remove a necessidade de padrões como wait-and-notify, explicando:
"Os canais permitem e forçam o usuário a expressar happens-before das restrições naturalmente " .
Para concluir, Joshi ressalta que, embora um armazenamento de key-value como a Riak e uma linguagem como Go sejam diferentes, ambos tomam abordagens semelhantes para resolver a concorrência, nomeando as corridas de dados e a resolução de conflitos. Ela também aponta que, enquanto happens-before é uma idéia antiga, formulada em 1978, é um princípio importante que ainda é aplicável aos sistemas simultâneos hoje.