1 Introdução
Hoje em dia muitos serviços de larga escala são hospedados na Internet, tendo alguns deles milhões de utilizadores diários. De modo a tolerar falhas e providenciar acesso rápido, esses serviços precisam de replicar os seus dados por diversos centros de dados. Num mundo perfeito, os serviços teriam consistência forte, alta disponibilidade e tolerância a partições da rede. Infelizmente, o teorema CAP [6] afirma que é impossível fornecer estas três garantias simultaneamente.
Tendo em conta esta limitação, muitos sistemas preferem focar-se em fornecer alta disponibilidade e tolerar partições na rede, sacrificando a consistência forte. Tipicamente esses sistemas oferecem antes uma forma de consistência fraca denominada de consistência eventual, a qual apenas garante que, eventualmente, todas as réplicas convergem para o mesmo estado. Este modelo permite que as operações sejam executadas concorrentemente em diferentes réplicas, sem ser necessário sincronização. Contudo, as operações concorrentes precisam de ser combinadas de modo a garantir convergência de estado.
Uma possível solução é usar os Tipos de Dados Replicados sem Conflitos (CRDTs) [9], sendo que estes fornecem por construção uma política de resolução de conflitos. Os CRDTs permitem que as operações executem localmente na réplica fonte e que estas sejam propagadas assincronamente para as réplicas remotas. Existem vários CRDTs que representam tipos de dados diferentes, como por exemplo contadores, mapas, conjuntos, listas [9, 8].
Os CRDTs fornecem uma política de resolução de conflitos determinística de modo a poderem lidar com operações concorrentes que não comutam de forma natural. Por exemplo, um conjunto CRDT providência duas operações: add() adiciona o elemento ao conjunto; e remove() remove o elemento do conjunto. Contudo, as operações de adição e remoção do mesmo elemento não são comutativas. Como tal, é necessário arbitrar um estado final para o objeto para quando essas operações são executadas concorrentemente. Uma possível semântica de concorrência, conhecida como add-wins (resp. remove-wins), é de dar prioridade à operação de adição (resp. remoção), ou seja, o elemento ficar presente (resp. não ficar presente) no conjunto.
Apesar de uma política add-wins poder ser adequada para algumas aplicações outras podem, contudo, preferir uma política remove-wins. Outras podem ainda querer a possibilidade de usar ambas as políticas para diferentes elementos ou situações. Por isso, neste trabalho, propomos um conjunto CRDT que fornece, simultaneamente, as políticas add-wins e remove-wins.
1.1 Exemplos de utilização

Serviço de conversa online.
Para ilustrar a aplicabilidade do CRDT proposto, considere-se um serviço que disponibiliza uma sala de conversa online
O serviço mantém nas várias réplicas um conjunto S que indica quais os utilizadores que estão atualmente online, sendo apenas necessário cada cliente estar ligado a um dos servidores para ser considerado como online. Quando um utilizador se (re)conecta a um servidor, é realizado um add em S. Um remove é executado quando a conexão é perdida ou o utilizador faz logout.


Considere-se agora as seguintes situações:
-
A conexão do Bob à replica R1 é perdida (remove). Concorrentemente, o Bob liga-se à replica R2 (add). Quando as réplicas sincronizarem, se for aplicada a política add-wins, o Bob fica como online, conforme é pretendido (Figura 2). Se fosse aplicada a política remove-wins, o Bob ficaria como offline, o que não seria correto;
-
Depois da situação acima referida, a conexão do Bob à replica R1 regenera (add). Concorrentemente, o Bob realiza um logout na réplica R2 (remove). Quando as réplicas sincronizarem, se for aplicada a política add-wins o Bob fica como online, mesmo já não estando a utilizar o serviço (Figura 3). Neste cenário, seria desejável que fosse aplicada a política remove-wins.
Em suma, as duas situações constituem um caso onde é vantajoso ter um conjunto CRDT que forneça tanto a política add-wins como remove-wins.
Serviço com super utilizador.
Outro cenário no qual um conjunto CRDT com ambas as políticas é útil consiste num serviço no qual existam utilizadores especiais cujas operações têm maior prioridade em relação às dos outros utilizadores. Para exemplificar este cenário, considere-se um serviço que controla quais os utilizadores que têm acesso a uma pasta partilhada pelo dono da mesma. Assumindo que se pretende que certos utilizadores (definidos pelo dono) possam convidar outros, é desejável que quando o dono remove o acesso a um utilizador, esta remoção não seja sobreposta por uma adição desse utilizador. Por outro lado, quando dois utilizadores que não sejam o dono adicionam e removem concorrentemente o mesmo utilizador, é preferível que esse utilizador fique com acesso à pasta. Em suma, um conjunto CRDT com ambas as políticas é adequado a situações onde se pretenda que certos utilizadores (elementos) tenham prioridade diferente dos restantes utilizadores (elementos) aquando da execução das suas operações.
1.2 Contribuições
Tendo como motivação o exemplo apresentado na Secção 1.1, neste trabalho fazemos as seguintes contribuições:
-
A proposta de um mecanismo para suportar múltiplas políticas no mesmo CRDT, através da adição de operações extra;
-
Duas definições state-based (uma delas otimizada) e op-based de um conjunto CRDT que fornece as políticas add-wins e remove-wins;
-
Uma avaliação experimental do CRDT proposto, sendo comparado o seu desempenho com CRDTs similares.
O resto do artigo organiza-se nas seguintes secções: as Secções 2 e 3 apresentam, respectivamente, as especificações e a avaliação experimental do CRDT proposto; a Secção 4 compara com trabalho relacionado; por último a Secção 5 resume os contributos do artigo e aponta alguns tópicos a explorar no futuro.
2 Conjunto remove&add-wins CRDT
Especificação:
Para criar um conjunto remove&add-wins CRDT é necessário sinalizar quais as operações de remoção que ganham sobre as de adição. Uma possibilidade é estender a interface do conjunto com uma operação adicional de remoção, removeWins(). Assim, o conjunto remove&add-wins CRDT tem uma interface com três operações: (i) removeWins(), para remover o elemento com máxima prioridade; (ii) add(), para adicionar o elemento ; e (iii) remove(), para remover o elemento .
Para um dado conjunto de operações , e considerando a relação de
happens-before
[7] estabelecida entre as operações, é possível definir de modo preciso o estado do conjunto como sendo:
Um elemento pertence ao conjunto se existir uma operação de add() para a qual: (i) não existe nenhum remove() que tenha acontecido depois do add(); e (ii) todos as operações de removeWins() aconteceram antes do add().
Desenho:
Um dos CRDTs presente na literatura que implementa a política add-wins é o Observed-Remove Set (OR-Set), introduzido por Shapiro et. al. [9]. Neste CRDT, quando um elemento é adicionado, um identificador único é criado e associado ao elemento. No remove, todos os identificadores únicos associados a esse elemento que são conhecidos na replica fonte são marcados como removidos. Com esta abordagem, se acontecer um add e remove concorrentes para o mesmo elemento, como o remove não viu o identificador único associado ao add, esse identificador único não é marcado como removido e, por isso, o elemento continua no conjunto.
A intuição por detrás do nosso conjunto remove&add-wins CRDT é similar, mas estendendo a associação de identificadores únicos tanto ao removeWins como ao add. Assim, de modo a que o removeWins ganhe sobre um add concorrente, gera-se um identificador removeWins único no removeWins e marcam-se todos estes identificadores únicos conhecidos para remoção no add. Para que o add ganhe sobre um remove concorrente, gera-se um identificador add único no add e marcam-se os identificadores únicos conhecidos para remoção no remove.
Seguindo a especificação apresentada anteriormente, um elemento estará no conjunto se existir um identificador add único que não tenha sido marcado para remoção por um remove e se não existir nenhum identificador removeWins único que não tenha sido marcado por remoção por algum add.
No Algoritmo 1 apresentamos uma especificação state-based não otimizada que implementa a referida ideia. O estado é composto por: (i) mapa R que mantém, para cada elemento, os identificadores únicos criados por uma operação removeWins; (ii) mapa A que mantém, para cada elemento, os identificadores únicos criados por uma operação add; (iii) conjunto T que mantém os identificadores únicos marcados para remoção quer por uma operação de add ou remove.
A operação removeWins() gera um identificador único e adiciona-o ao mapa R, o qual é usado para que o removeWins ganhe sobre um add concorrente.
A operação add() gera um identificador único e adiciona-o ao mapa A, o qual é usado para que o add ganhe sobre um remove concorrente. Para além disso, também adiciona todos identificadores únicos associados ao elemento do mapa R ao conjunto de identificadores únicos removidos (T). Isto permite cancelar os efeitos dos removeWins que aconteceram antes do add.
A operação remove() adiciona todos identificadores únicos associados ao elemento do mapa A ao conjunto dos identificadores únicos removidos (T). Isto permite cancelar os efeitos dos add que aconteceram antes do remove.
Nesta versão não otimizada, a operação de merge simplesmente junta o estado dos mapas R e A e do conjunto T de ambas as réplicas.
A operação de lookup(e) considera que um elemento e está no conjunto se: (i) todos os identificadores únicos em tiverem sido removidos (estão em ); (ii) existe pelo menos um identificador único em que não foi removido (ou seja, não está em ).
O conjunto remove&add-wins state-based comporta-se como um conjunto sequencial para operações sequenciais. Operações concorrentes em elementos diferentes comutam naturalmente. Para operações concorrentes no mesmo elemento, o removeWins ganha sobre add concorrentes que, por sua vez, ganha sobre remove concorrentes. A convergência eventual é garantida desde que o grafo formado pela sincronização das réplicas seja conexo – nenhuma informação é alguma vez apagada.
A especificação op-based do conjunto remove&add-wins encontra-se no Algoritmo 2. A especificação usa a mesma aproximação da versão state-based, mas explora o facto das operações serem entregues por ordem causal para remover imediatamente dos mapas R e A os identificadores únicos, dado que em qualquer réplica uma operação que remova um dado identificador único é executada sempre após ter sido executada a operação que o criou.
2.1 Versão otimizada
A versão apresentada do conjunto remove&add-wins não está otimizada, visto que os identificadores únicos que são adicionados nunca são removidos do estado do CRDT. Seguindo a abordagem proposta por Bieniusa et. al. [2] apresenta-se no Algoritmo 3 a versão otimizada do conjunto remove&add-wins.
O objetivo por detrás da versão state-based otimizada é que, à medida que as operações vão sendo executadas, os identificadores únicos sejam removidos, como na versão op-based. Seria, por isso, desejável que, na remoção, fosse possível apagar do estado os identificadores únicos que estão a ser removidos. Assim, o CRDT seria mais eficiente em termos de espaço e, possivelmente, na execução do merge.
O problema de remover os identificadores únicos imediatamente é que quando se efetua a operação de merge, caso uma réplica contenha um identificador único e outra não, não é possível saber se a réplica que não tem o identificador porque ainda não foi criado ou porque já foi removido. Para resolver este problema, cada réplica pode manter um vetor v que tem um “sumário” dos identificadores únicos observados. Assim, no merge, pode-se consultar esse vetor para perceber se um certo identificador já foi ou não removido. Uma solução eficiente para implementar esse vetor é, para cada réplica, ter um contador: nesse caso os identificadores únicos passariam a ser um par (identificador da réplica, contador). O valor de entrada em cada posição do vetor v será o maior valor de contador observado para a réplica nessa posição.
Em termos de alterações ao algoritmo do conjunto remove&add-wins, com a alteração referida já não é necessário manter o conjunto dos identificadores removidos. Como tal, tanto o remove como o add apagam logo os identificadores que devem ser removidos do conjunto A e R, respetivamente. O add e removeWins geram identificadores conforme discutido acima, atualizando também o vetor v. O merge junta os estados, tendo em conta quais os identificadores que já foram apagados através da informação dada pelo vetor v em ambos os estados.
3 Avaliação experimental
Nesta secção é avaliado experimentalmente o conjunto remove&add-wins através da comparação com o conjunto OR, ambos na versão state-based otimizada. Nomeadamente, pretende-se avaliar qual o custo extra de utilizar o conjunto remove&add-wins em termos de: (i) tempo de execução; (ii) espaço ocupado.
3.1 Características dos testes
As experiências foram realizadas através da simulação de um sistema com 3 clientes, cada um com uma cópia local do CRDT a ser testado. Todos os clientes são executados na mesma máquina, não sendo por isso considerado nem o tempo de transferência nem a latência da rede.
Em cada teste, cada cliente realiza 4 milhões de operações modificadoras do estado (add, remove ou removeWins) na sua réplica local. Sendo que cada operação escolhe, aleatoriamente, um elemento de entre um alfabeto de 20000 elementos. Cada cliente propaga o estado a outro cliente a cada x operações executadas.
De modo a poder avaliar qual o impacto dos metadados e computação extra do conjunto remove&add-wins em relação ao conjunto OR, variou-se os seguintes parâmetros entre testes:
-
Percentagem de operações de adição e remoção: foram consideradas as percentagens de, respetivamente, 50%-50% e 90%-10% (no caso do conjunto remove&add-wins, metade das remoções são remove e a outra metade removeWins). Estas definições permitem avaliar a diferença de desempenho entre adições e remoções;
-
Frequência do envio de estado para merge: testou-se tanto enviar o estado a cada 200000 operações (20 merges no total) como apenas o enviar depois de o teste estar completo. Esta diferença permite avaliar qual o impacto no desempenho que tem realizar a operação de merge. Destaca-se que é expectável que a operação de merge seja lenta, visto que tem complexidade O(n), sendo n o número de elementos na união dos conjuntos.
Cada teste foi executado 5 vezes, de modo a obter resultados mais precisos.
3.2 Resultados
Nas subsecções seguintes serão apresentados e discutidos os resultados mais relevantes, não sendo por isso incluídas todas as combinações dos parâmetros referidos na Secção 3.1.
Para cada gráfico, é apresentado o resultado médio dos 3 clientes, ao longo de 5 execuções. Por exemplo, o tempo de execução apresentado é a média do tempo de execução de cada cliente em cada execução, ou seja, uma média de 15 valores.
3.2.1 Frequência de sincronização
![]() |
![]() |
A Figura 4 apresenta o tempo de execução e o tamanho dos metadados médio de cada teste, para as percentagens de adição e remoção de 50%-50%.
No gráfico do tempo, é possível verificar que existe uma diferença não desprezável entre o caso sem merge e o caso com merge a cada 200000 operações. Tal diferença deve-se ao custo do merge, sendo esperado que se o merge fosse mais frequente ou o número de elementos maior, a diferença também fosse maior.
Em relação ao espaço, este mantém-se relativamente constante - em média, cerca de metade dos elementos máximos (20000) estão no conjunto. Destaca-se que, conforme era esperado, o conjunto remove&add-wins ocupa mais espaço em relação ao conjunto OR, devido à existência do mapa para guardar as remoções. Na prática, em média, para o remove&add-wins cerca de 10000 elementos estão no mapa das adições e 5000 no das remoções (a percentagem de removeWins é de 25%).
3.2.2 Percentagem de adições e remoções
![]() |
![]() |
Os resultados relacionados com a diferença no tempo de execução e tamanho dos metadados para os casos de 50% adições + 50% remoções; 90% adições + 10% remoções encontram-se na Figura 5. Em ambos os casos, os clientes sincronizam a cada 200000 operações.
Tanto no conjunto OR como no remove&add-wins, é visível uma diferença no tempo de execução entre os casos de 50%-50% e 90%-10%. Essa diferença é, contudo, maior no caso do conjunto OR - tal facto deve-se a que as operações de removeWins e add executam computações que demoram, aproximadamente, o mesmo. Em ambos os conjuntos otimizados, a operação mais rápida é o remove.
Em termos do espaço ocupado, no teste de 90%-10% existe uma tendência para estarem mais elementos no conjunto em relação ao de 50%-50%, o que leva a que mais espaço seja ocupado. No caso do conjunto remove&add-wins, a diferença é menor devido aos elementos removidos por removeWins também serem guardados. Estes resultados permitem concluir que, quanto maior a percentagem de add (ou menor a de removeWins), menor a diferença entre os dois tipos de conjuntos no tamanho dos metadados - para 90%-10% a diferença é praticamente desprezável.
3.2.3 Conjunto OR VS remove&add-wins
![]() |
![]() |
Na Figura 6 encontram-se os resultados da divisão do tempo\espaço do conjunto remove&add-wins pelo OR.
Em termos do tempo de execução, o custo extra por utilizar o conjunto remove&add-wins é relativamente baixo, sendo no pior caso cerca de 25% extra. No caso do teste de 90%-10% (ou noutros casos com poucos removeWins), o custo extra é ainda menor (neste caso, 12.5% extra). Contudo existirá sempre um custo extra devido ao passo extra que é necessário fazer no add. A diferença é maior no caso com sincronização, devido ao merge ter que iterar mais um mapa.
Para o espaço utilizado, o custo extra é praticamente desprezável para o teste de 90%-10%, devido a existirem poucos elementos no mapa dos removeWins. No caso de 50%-50% a diferença é elevada, sendo no pior caso 70% extra, devido aos elementos do removeWins. Espera-se que, mesmo com muitos add, se a percentagem de removeWins for baixa, o custo extra também seja baixo.
4 Trabalho relacionado
Os princípios necessários para a especificação de tipos de dados replicados têm sido estudados intensivamente na literatura, tanto para ambientes com consistência forte [11] como para ambientes com consistência fraca [3, 9].
Entre as várias abordagens para a consistência fraca, destacam-se duas: OT (Operational Transformation) [10] e CRDTs (Conflict-Free Replicated Data Type) [9]. O princípio do OT para lidar com conflitos de concorrência é de transformar os argumentos das operações remotas de modo a, depois de transformadas, as operações serem comutativas. Por outro lado, os CRDTs baseiam-se em tornar as operações comutativas desde o início.
Existem diversos CRDTs para representar diferentes tipos de dados, como por exemplo contadores, registos, conjuntos, mapas, listas ordenadas, top-K, etc [9, 8, 4]. Para o mesmo tipo de dados, pode existir várias especificações diferentes, variando no modo como são propagados os dados (state-based, op-based ou delta-based), [9, 1], na resolução de conflitos [9, 5] e na eficiência [5, 2]. Contudo, não é do nosso conhecimento que já tenha sido proposta alguma abordagem para ter num só CRDT várias políticas de resolução de conflitos.
5 Conclusão
Neste artigo discutiu-se o problema dos CRDTs apenas providenciarem uma única política para a resolução de conflitos. Ilustrou-se o problema com base num cenário de exemplo no qual seria útil ter a opção de escolher entre mais que uma política de resolução de conflitos para o mesmo conjunto CRDT.
Propusemos uma solução para o referido cenário através da introdução de um conjunto CRDT que providência, através de duas operações de remoção diferentes, duas políticas para resolver conflitos: add-wins (sobre a remoção) e remove-wins (sobre a adição). Este CRDT dá ao programador a liberdade de selecionar qual a política que quer para cada cenário. Usando uma aproximação semelhante, seria possível criar um conjunto CRDT que combina as duas políticas referidas através de duas operações de adição diferentes.
Realizou-se uma avaliação do desempenho do CRDT proposto em relação ao conjunto OR CRDT, concluindo-se que o custo extra é aceitável, sendo por vezes até desprezável se a percentagem de operações de removeWins for baixa.
De momento estamos a investigar uma aproximação mais genérica para este problema, na qual será possível especificar, na execução de uma operação, uma política definida pelo programador.
Agradecimentos:
Este trabalho foi parcialmente financiado pela FCT/MCTES através do projecto NOVA LINCS (UID/CEC/04516/2013) e a EU através do projecto LightKone (732505).
Referências
- [1] Almeida, P.S., Shoker, A., Baquero, C.: Efficient state-based crdts by delta-mutation. In: International Conference on Networked Systems. pp. 62–76. Springer (2015)
- [2] Bieniusa, A., Zawirski, M., Preguiça, N., Shapiro, M., Baquero, C., Balegas, V., Duarte, S.: An optimized conflict-free replicated set. Rapport de recherche RR-8083, INRIA (Oct 2012), http://hal.inria.fr/hal-00738680
- [3] Burckhardt, S., Gotsman, A., Yang, H.: Understanding eventual consistency. Microsoft Research Technical Report MSR-TR-2013-39 (2013)
- [4] Cabrita, G., Preguiça, N.: Non-uniform replication. arXiv preprint arXiv:1711.07733 (2017)
- [5] Deftu, A., Griebsch, J.: A scalable conflict-free replicated set data type. In: Distributed Computing Systems (ICDCS), 2013 IEEE 33rd International Conference on. pp. 186–195. IEEE (2013)
- [6] Gilbert, S., Lynch, N.: Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services. Acm Sigact News 33(2), 51–59 (2002)
- [7] Lamport, L.: Time, clocks, and the ordering of events in a distributed system. Communications of the ACM 21(7), 558–565 (1978)
- [8] Preguica, N., Marques, J.M., Shapiro, M., Letia, M.: A commutative replicated data type for cooperative editing. In: Distributed Computing Systems, 2009. ICDCS’09. 29th IEEE International Conference on. pp. 395–403. IEEE (2009)
- [9] Shapiro, M., Preguiça, N., Baquero, C., Zawirski, M.: A comprehensive study of convergent and commutative replicated data types. Ph.D. thesis, Inria–Centre Paris-Rocquencourt; INRIA (2011)
- [10] Sun, C., Ellis, C.: Operational transformation in real-time group editors: issues, algorithms, and achievements. In: Proceedings of the 1998 ACM conference on Computer supported cooperative work. pp. 59–68. ACM (1998)
- [11] Wiesmann, M., Pedone, F., Schiper, A., Kemme, B., Alonso, G.: Understanding replication in databases and distributed systems. In: Distributed Computing Systems, 2000. Proceedings. 20th International Conference on. pp. 464–474. IEEE (2000)