Comment ça marche, la fédération de diaspora* ?

English version below.

Pour rappel, on désigne par « fédération » l’échange des données entre les différentes instances de diaspora* (appelées pods). Il s’agit donc du protocole qui permet aux serveurs de communiquer entre eux.

Avec le travail en cours pour extraire le code s’occupant de la fédération du reste du code de diaspora (oui, ce code devrait devenir une gem à part et donc être utilisable par n’importe quel projet qui veut parler avec diaspora* !), il me semble qu’un petit article de vulgarisation sur la fédération n’est pas de trop. J’ai régulièrement des remarques sur différents points qui m’obligent à réexpliquer un peu comment tout ça marche, alors un joli article à la limite du technique mais compréhensible par tous, ça me semble la solution parfaite, je n’aurai plus qu’à donner ce petit lien… 😀

Alors, comment les serveurs de diaspora* communiquent-ils, pour réussir à faire qu’être inscrit sur n’importe lequel d’entre eux permet (presque) la même chose que si tout le monde était sur le même serveur ? (comment ça, presque, je vous entends dire ? Oui, il y a quelques cas où vous n’aurez pas le même résultat après avoir effectué la même action quand vous êtes sur deux pods différents. Ce billet a justement comme but de vous expliquer pourquoi).

Il y a globalement deux solutions pour l’accès aux données dans un réseau décentralisé : le pull, ou le push. Pour la première, c’est le serveur qui a besoin de l’information qui la réclame au serveur qui la possède. Pour la seconde, c’est le serveur qui a l’information qui l’envoie aux serveurs qui peuvent en avoir besoin. Petite illustration, pour être sûr que tout le monde ait compris.

Si la communication est faite en pull, lorsqu’un contact écrit un message sur un serveur A, celui-ci l’enregistre dans sa base de données locale, mais ne fait rien de plus. Lorsqu’un utilisateur d’un serveur B se connecte et affiche son flux par exemple, le serveur B va interroger A pour récupérer les messages de la base de données de A et les afficher.

Si la communication est faite en push, lorsqu’un contact écrit un message sur le serveur A, celui-ci le stocke dans sa base de données locale puis l’envoie à tous les serveurs des contacts de l’utilisateur. Les serveurs réceptionnent le message et l’enregistrent dans leur base de données locale. Quand un utilisateur se connecte sur le serveur B, le serveur peut regarder directement dans sa base locale, sans avoir à interroger A, pour afficher le message qu’il a reçu précédemment.

Alors, quelles sont les avantages et les inconvénients de ces deux solutions ? Pour la première, le message n’est présent que dans une base de données au lieu d’être dupliqué partout, en conséquence, on a clairement un gain de place, un meilleur contrôle des données (qui restent sur un seul serveur), et une facilité de modification (si le message est supprimé ou édité, les autres serveurs venant le consulter verront la version à jour). Pour la seconde, on a une copie en locale du message. En conséquence, on a un accès très rapide (pas besoin d’aller se balader sur le réseau, demander le message, que l’autre serveur vérifie qu’on a les autorisations, puis nous le renvoie), accès qui est conservé même si l’autre serveur est éteint.

Pour ce qui est des inconvénients de chacune des solutions, il suffit d’inverser les avantages de l’autre solution… Stocker en local prend beaucoup d’espace disque et oblige à modifier toutes les copies si modification il y a, mais interroger à chaque fois est très couteux en réseau.

C’est la solution push qui a été retenue pour diaspora*. Je ne suivais pas le projet au moment où cette décision a été prise, je ne sais pas si les débats ont été longs ou si cette solution a été élue rapidement. Ce qui est vrai, c’est que l’espace disque est moins coûteux que la bande passante, et qu’une version pull impliquerait d’interroger tous les serveurs à chaque fois que l’on réactualise son flux, ce qui semble en effet irréaliste (impossible d’avoir un serveur à la maison avec une quantité de requêtes pareille).

Mais on voit tout de suite une des restrictions de l’implémentation en push : si le serveur n’a pas reçu le message, il ne lui est pas possible de le redemander, en tout cas pas pour le moment au vu de l’implémentation de diaspora*. Cela implique, par exemple, que lorsque vous commencez à partager avec quelqu’un (ou même tout simplement affichez son profil) qui n’est pas connu de votre serveur (personne de votre serveur ne partage avec lui), son profil est vide. En effet, comme vous n’étiez pas dans les contacts de cette personne, son serveur n’a pas envoyé ses messages à votre serveur. Mais par contre, si vous êtes sur le même serveur, ou que quelqu’un de votre serveur partageait déjà avec ce contact, votre serveur aura reçu ses messages, et il pourra vous les afficher. Voilà donc l’explication d’un des « bogues » qu’on me fait souvent remonter, où l’on n’a pas la même chose depuis deux serveurs différents. (comme c’est assez pénible, en attendant la possibilité de demander les messages qui arrivera sûrement avec l’API, il y a un hack en javascript censé récupérer les ~8 derniers messages de l’utilisateur, excluant les reshare et les images, bref, c’est dégueux pour l’instant :p)

Que se passe-t-il donc, quand un serveur push vers un autre et que ce dernier ne reçoit pas le message, parce qu’il est indisponible (éteint, ou surchargé, comme joindiaspora) ? Et bien, le serveur replace le message dans la file d’attente d’envoi, et réessaye 30 secondes plus tard, puis 2 minutes, puis 10, puis une heure, etc de façon exponentielle, jusqu’à avoir essayé 10 fois (ceci est réglable par l’administrateur du pod). Si au bout de ces essais, le message n’est toujours pas arrivé, le serveur arrête d’essayer, et le message n’atteindra jamais l’autre serveur. Attention, quand je parle de message, je parle de message au sens large, les messages envoyés sur le réseau. Le message en question peut être un message posté sur diaspora, mais il peut aussi être un commentaire, un message privé, une notification d’ajout dans un aspect, ou même une notif de suppression d’une de ces interactions. Si l’indication « supprime ce commentaire » n’arrive pas sur l’autre pod, le commentaire ne sera donc pas supprimé là bas.

Vous comprenez donc l’importance d’avoir des serveurs réactifs : non seulement un serveur lent manque des messages, mais en plus il fait travailler plus les autres serveurs qui voient leur file d’attente s’agrandir et se fatigue à réessayer. Or, diaspora* est pensé pour fonctionner de manière décentralisée. Voir quelques pods contenir la majorité des utilisateurs (joindiaspora.com, diasp.org, pod.geraspora.de…), c’est ralentir l’ensemble du réseau. (ceci est un message pour vous inciter à monter votre propre pod o/, c’est facile, rendez-vous sur le wiki !)

Bon, maintenant que vous avez une idée de comment diaspora* communique, venons-en au deuxième bogue dont on me fait régulièrement part : les tags. Le problème ? Le résultat obtenu en cherchant un tag sur un pod n’est pas le même que la même action sur un autre pod. La cause ? Les tags ne sont pas fédérés, ce qui veut dire que les autres pods ne sont pas au courant que vous suivez un tag. En conséquence, si le message n’est pas envoyé sur votre pod par le mécanisme classique « un contact du posteur est sur votre pod », le message n’arrivera pas sur votre pod, et donc, ne sera pas connu et ne s’affichera pas dans la page de recherche du tag. En fait, quand vous recherchez un tag, votre pod regarde simplement dans sa base de données locale les messages qui contiennent ce tag. Si les messages n’y sont pas arrivés avant, ils n’apparaitront pas.

Il n’y a pas de solution facile à ce problème (il serait corrigé depuis longtemps sinon :p). En effet, on ne peut pas dire aux pods d’envoyer à tous les autres pods tous les messages publics écrits au cas où quelqu’un ferait une recherche d’un tag : la base de données locale exploserait littéralement sous le nombre de message. Si vous avez une idée de comment résoudre ce problème, vous êtes invités à venir contribuer !

Un dernier point où vous pouvez avoir une différence entre deux pods : la recherche de personnes. Lorsque vous faîtes une recherche, si vous voulez être certain de trouver une personne, il faut entrer son handle (c’est à dire sonpseudo@lenomdesonpod). En effet, il faut indiquer à votre pod où chercher ! Si vous cherchez un utilisateur en entrant uniquement son pseudo (ou son nom actuel, celui qui peut être changé), votre pod va chercher dans sa base de données locale s’il connait cet utilisateur. Pour qu’il le connaisse, deux possibilités : l’utilisateur est inscrit sur le serveur, ou le serveur a déjà reçu un message de cet utilisateur. Si vous n’êtes pas dans ce cas, votre serveur ne trouvera rien ! Essayez en cherchant Fla, vous ne me trouverez probablement pas. En cherchant fla@diaspora-fr.org par contre, vous devriez me trouver 🙂

Voilà, vous avez maintenant une vague idée de comment les serveurs diaspora* communiquent, et de pourquoi certains profils apparaissent vides et pourquoi vous ne trouvez pas la même chose quand vous faîtes une recherche sur un tag ou un pseudo. Si vous voulez en savoir plus, je vous renvoie au wiki officiel, particulièrement aux pages dédiées à la fédération (mais attention, c’est anglais, et là, c’est technique) : federation protocol overview, federation message semantic, diaspora message passing

Et n’hésitez pas à poster ici ou sur diaspora si vous avez des questions 😉

English version

« Federation » is what we call the method of exchanging data between the various servers running Diaspora* (called « pods »). It is the protocol which enables Diaspora*’s pods to communicate with each other.

At the moment the vital task of extracting the code dealing with federation from the rest of Diaspora*’s code is in progress. This will help to make Diaspora*’s code more stable, so that features will be less likely to be broken by necessary changes in the future, and it is also an important step in getting Diaspora* to communicate with other networks and apps. It therefore seems to me that an article explaining federation is needed. I regularly see comments on Diaspora* concerning various elements of federation and feel obliged to try to explain federation each time, so an article about the technicalities of federation which can be understood by everyone seems to me the perfect solution.

How do the servers comprising the Diaspora* network communicate so that accessing the network from any one of them gives (almost) the same experience as if everyone were on the same server? (Why « almost », I hear you ask? Well, there are some cases in which carrying out the same action from two different pods will not produce the same result. This article aims to explain why!)

In a decentralized network there are two solutions for sharing of data: « pull » and « push ». In the case of a « pull », the receiving server initiates the transfer of information by requesting it from the server which has the information. A « push », on the other hand, is initiated by the server which has the information sending it to any server(s) which it believes need the information. Here’s a little illustration, to make sure that everyone understands.

In a pull scenario, when a user writes a message on server A, this server records it in its local database, but does not do anything more. However, when one of that users’ contacts logs into their pod (server B) and displays their stream, server B will query server A to recover any messages from server A’s database so it can display them.

If the communication is a push, then when a user writes a message on server A, it stores it in its local database and also sends it to all the servers on which the contacts of the user have accounts. The servers take delivery of the message and record it in their local databases. When a contact connects to server B, the server already has the post in its local database, and can display the message without then having to query server A.

So, what are the advantages of these two solutions? With a system of communication reliant on pulls, the message is stored in only one database instead of being duplicated everywhere. This clearly saves space, gives better control of the data (which remain on only the server to which the user has registered), and a better facility for modification: if the message is removed or altered, other servers will always display the up-to-date version. If information is « pushed », on the other hand, each server has its own copy of the message. Consequently, there is very fast access (no need to go wandering about the network, to request the message, to wait for the remote server to check that one has authorization, and to receive the message), access which is preserved even if the other server is off-line.

To see the disadvantages of each solution, simply turn the advantages of the other solution on their head… The disadvantages of pushing messages are that storing each message on all pods which may need to display it takes a lot of disk space, and there will need to be a mechanism for making any modifications made to the original message reliably and instantly to all the copies. One the other hand, using pulls to request data each time a page is refreshed uses a lot of network resources, so is really slower.

The developers who created Diaspora* chose to adopt the push solution. I was not a part of the project when this decision was made, and I do not know if the debates were long or if this solution was quickly chosen. It is true that disk space costs less than bandwidth, and therefore a push solution saves money in a decentralized network. It is also more practical: a pod based on a home computer would not be able to cope with the quantity of requests that would be generated by a pull system, so this solution would be really slow.

But one of the restrictions of the push implementation is immediately apparent: if a pod did not receive the message when it was pushed, it is not possible for that pod to know later that it needs to request it (at least, not within the scope of Diaspora*’s current implementation). This implies, for example, that when you start to share (or even simply display his profile) with somebody who has no established connection with anyone on your pod, his profile will be displayed as empty: as you were not in the contacts list of this person when their profile updates were made, his pod did not push its messages to your pod. If you are on the same pod, on the other hand, or if somebody on your pod is already sharing with this contact, their post will already have been pushed to your pod for those people already sharing with them, and it can display them to you. This explains of one of the « bugs » that I often encounter, where doing the same thing on two different pods does not produce the same result. (There is a hack in Javascript which is supposed to recover the last 8 messages of a user, excluding reshares and images, as a proper solution to this problem is likely to be a long way off, but it is a messy solution at the moment.)



What then happens when a pod tries to push data to another which doesn’t receive the message because it is unable to communicate at that moment (either offline, or overloaded, as is often the case with joindiaspora.com)? The sending pod puts the message back into its send queue, and tries again 30 seconds later, then 2 minutes, then 10, then one hour, and so on, until it has tried to send the data to that pod ten times. If after these attempts, the pod has still not been able to successfully send the information to the receiving pod, the pod stops trying, and that particular information will never reach the other pod. The information in question can be a message posted on Diaspora*, a comment, a private message, a sharing notification, or even a command to block one of these interactions. If the command to « remove this comment » is not received by the other pod, the comment will not be removed from the copy on that pod, and thus users of that pod who are able to see the post will still see the comment.

So you can understand the importance of all pods being responsive all the time: it’s not only that a « slow » pod can miss out on messages, but also that it creates a lot more work for the other pods, which see their communication queue increase and have to use resources to keep trying to communicate with the slow pod. Diaspora* is intended to be fully decentralized, with a peer network of « equal » pods. However, as things stand at the moment, a few very large pods (joindiaspora.com, diasp.org, pod.geraspora.de, and so on) contain the majority of the network’s users, and this tends to slow down the whole of the network. (I say this to encourage you to install and run your own pod! It is easy, have a look at our wiki for more information.)

The second « bug » which is encountered regularly concerns tags. The problem? The result obtained by a tag search on one pod is not the same one as on another pod. The cause? Tags are not federated, which means that other pods are not up to date with which tags you follow; if, therefore, a message is not sent to your pod by the mechanism described above, because the message’s author has a contact on your pod, the message will not arrive on your pod, and thus cannot be displayed in the search results for that tag. In fact, when you search for a tag, your pod simply looks in its local database for messages which contain this tag. If messages have not already been sent to your pod, they will not appear in search results.

There is no easy solution to this problem (if there was, it would have been corrected a long time ago). Indeed, it would not be possible to tell pods to send every public message to all other pods in case somebody might search for a tag, because each pod’s local database would collapse under the weight of messages. If you have an idea how to solve this problem, we’d love you to contribute!

One last point where you can experience a difference between two pods is when searching for people. When you search, to be certain to find the person you’re looking for, you may need to enter their handle (i.e. username@podname.org). By doing this, you are at least telling your pod which pod to look on! If you search for a user by entering their screen name (the one which can be changed), your pod will search its own local database for users with that name. For the person to be found in this way, one of two things must be the case: either they are registered on the same pod as you, or your pod has already received a message from this user (because one of the user’s contacts is on your pod). If neither of these are the case, your pod won’t be able to find them! If you simply search « Fla », you’re not sure to find me. However, by searching for me using my Diaspora* handle – fla@diaspora-fr.org – you should find me.

I hope this article has helped you to understand how pods in the Diaspora* network communicate with each other, and why certain profiles appear empty and why you do not always get the same result when you search for a tag from different pods. If you want learn more, I recommend reading the official wiki, particularly the pages dedicated to federation (but be aware, they are far more technical).

Diaspora* federation protocol overview

Diaspora* message passing

Diaspora* federation message semantics