CRDTs em Csharp e Go: Implementando Estruturas de Dados Distribuídas Livre de Conflitos
Definição
Os Conflict-free Replicated Data Types (CRDTs) constituem uma classe formal de estruturas de dados distribuídas projetadas para garantir convergência determinística em ambientes replicados e assíncronos, sem dependência de coordenação central ou bloqueios globais. Fundamentam-se em propriedades algébricas específicas, associatividade, comutatividade e idempotência, que asseguram a fusão consistente de estados ou operações concorrentes. Formalmente, CRDTs são modelados sobre join-semilattices, garantindo monotonicidade do estado e permitindo alcançar Consistência Eventual Forte (Strong Eventual Consistency – SEC), mesmo sob partições de rede (SHAPIRO et al., 2011).
Do ponto de vista teórico, CRDTs posicionam-se como uma alternativa prática dentro do teorema CAP, priorizando disponibilidade e tolerância a partições, enquanto oferecem convergência determinística sem a necessidade de consenso distribuído síncrono (BURCKHARDT et al., 2014).
Tipos de CRDTs
CRDTs de Estado (CvRDTs – State-based)
CRDTs baseados em estado propagam snapshots completos entre réplicas. Cada nó mantém um estado monotonicamente crescente, e a função de merge é definida como um operador de join em um semilattice parcialmente ordenado. A convergência é garantida porque o merge é matematicamente idempotente, comutativo e associativo, permitindo sincronização via mecanismos simples como anti-entropy ou gossip (ALMEIDA, 2024).
CRDTs Operacionais (CmRDTs – Operation-based)
CRDTs operacionais disseminam apenas operações incrementais, reduzindo overhead de rede. Nesse modelo, pressupõe-se entrega confiável e causal das mensagens, frequentemente implementada com vetores de versão ou mecanismos happens-before. A correção depende de que cada operação seja intrinsecamente comutativa ou transformada de forma que preserve invariantes semânticos globais (BURCKHARDT et al., 2014).
Propriedades Formais
Ambas as categorias compartilham garantias essenciais:
- Convergência: todas as réplicas atingem o mesmo estado após propagação completa.
- Monotonicidade: estados evoluem apenas em direção ao join do semilattice.
- Tolerância a concorrência: operações paralelas não geram conflitos.
- Ausência de coordenação global: não há dependência de locks distribuídos ou consenso síncrono.
Aplicações
CRDTs são amplamente empregados em sistemas colaborativos, plataformas distribuídas e aplicações offline-first. Editores colaborativos, bancos de dados geo-replicados e plataformas de mensageria utilizam CRDTs para permitir atualizações concorrentes mantendo coerência lógica. Um exemplo clássico são editores colaborativos, nos quais múltiplos usuários modificam documentos simultaneamente sem conflitos estruturais (KLEPPMANN; BERESFORD, 2017).
Implementação em C# (PN-Counter)
Um PN-Counter (Positive-Negative Counter) é um CRDT de estado que suporta incrementos e decrementos concorrentes sem coordenação entre réplicas, sendo composto pela combinação de um P-Counter (incrementos) e um N-Counter (decrementos), ambos monotônicos. Cada réplica mantém estados separados indexados por nó, e o valor do contador é obtido pela diferença entre as somas desses estados. A função de merge é definida como um operador de join (máximo elemento a elemento) sobre um join-semilattice, garantindo convergência determinística mesmo sob concorrência, atrasos ou partições de rede (SHAPIRO et al., 2011).
Abaixo está um exemplo simplificado de um PN-Counter (Positive-Negative Counter), um CRDT formalmente correto, que mantém contadores independentes por réplica e realiza merge via máximo por nó.
public class PNCounter
{
private readonly Dictionary<string, int> _increments = new();
private readonly Dictionary<string, int> _decrements = new();
private readonly string _replicaId;
public PNCounter(string replicaId)
{
_replicaId = replicaId;
_increments[_replicaId] = 0;
_decrements[_replicaId] = 0;
}
public void Increment() => _increments[_replicaId]++;
public void Decrement() => _decrements[_replicaId]++;
public int Value() => _increments.Values.Sum() - _decrements.Values.Sum();
public void Merge(PNCounter other)
{
foreach (var kv in other._increments)
_increments[kv.Key] = Math.Max(_increments.GetValueOrDefault(kv.Key), kv.Value);
foreach (var kv in other._decrements)
_decrements[kv.Key] = Math.Max(_decrements.GetValueOrDefault(kv.Key), kv.Value);
}
}
Comparação com Go
Go é frequentemente adotada em implementações de CRDTs devido ao suporte nativo à concorrência e ao modelo leve de goroutines, favorecendo arquiteturas baseadas em gossip e replicação assíncrona.
type PNCounter struct {
inc map[string]int
dec map[string]int
id string
}
func NewPNCounter(id string) *PNCounter {
return &PNCounter{
inc: map[string]int{id: 0},
dec: map[string]int{id: 0},
id: id,
}
}
func (c *PNCounter) Increment() { c.inc[c.id]++ }
func (c *PNCounter) Decrement() { c.dec[c.id]++ }
func (c *PNCounter) Value() int {
sum := 0
for _, v := range c.inc { sum += v }
for _, v := range c.dec { sum -= v }
return sum
}
func (c *PNCounter) Merge(o *PNCounter) {
for k, v := range o.inc {
if c.inc[k] < v { c.inc[k] = v }
}
for k, v := range o.dec {
if c.dec[k] < v { c.dec[k] = v }
}
}
Casos de Uso
- Colaboração em tempo real: editores distribuídos e whiteboards.
- Sistemas offline-first: sincronização posterior sem conflitos.
- Plataformas geo-replicadas: bancos de dados distribuídos e caches globais.
- Telemetria e contadores distribuídos: métricas agregadas sem coordenação.
Conclusão
Do ponto de vista arquitetural, CRDTs deslocam a complexidade do controle de consistência do plano operacional para o plano matemático do modelo de dados, simplificando significativamente a engenharia de sistemas geo-replicados e aplicações colaborativas. Essa abordagem possibilita arquiteturas mais resilientes, alinhadas aos princípios de disponibilidade contínua e tolerância a falhas descritos pelo teorema CAP, ao mesmo tempo em que preserva invariantes semânticos essenciais.
Entretanto, a adoção de CRDTs exige rigor conceitual: implementações ingênuas podem violar propriedades formais e comprometer a convergência. A correta modelagem, como exemplificado pelo uso de PN-Counters, semilattices e merges monotônicos, é indispensável para garantir Strong Eventual Consistency. Além disso, aspectos como causalidade, granularidade de estado e custo de sincronização devem ser cuidadosamente avaliados conforme o domínio da aplicação.
Referências
-
SHAPIRO, Marc et al. Conflict-free replicated data types. In: Symposium on Self-Stabilizing Systems. Berlin, Heidelberg: Springer Berlin Heidelberg, 2011. p. 386-400.
-
BURCKHARDT, Sebastian et al. Replicated data types: specification, verification, optimality. ACM Sigplan Notices, v. 49, n. 1, p. 271-284, 2014.
-
KLEPPMANN, Martin; BERESFORD, Alastair R. A conflict-free replicated JSON datatype. IEEE Transactions on Parallel and Distributed Systems, v. 28, n. 10, p. 2733-2746, 2017.
-
SHAPIRO, Marc. A comprehensive study of convergent and commutative replicated data types. 2011.
-
ALMEIDA, Paulo Sérgio. Approaches to conflict-free replicated data types. ACM Computing Surveys, v. 57, n. 2, p. 1-36, 2024.