mercredi, 30 décembre 2009

Sequences scalability

Sequences are central part of most computer systems. They are used both in technical part (generate unique identifier for object) and in business part (count the remaining free tickets which can be sold).

When the load on the application goes higher, sequences become quickly central bottlenecks of systems. So the goal here is to first categorize different types of sequences and then gives some keys to see how sequences be distributed to increase their scalability, and hence the global scalability of the whole system.

Characteristics of sequences :

Order : the number generate should be used in a given order (ascendant or descendant), or the order do not matter. I will talk about ordered or unordered sequence.

Missing values : some values can be missing in the sequence. For example the sequence miss the value 4 should not alter the behavior of the application. I will talk about continued or discontinued sequence.

Finite sequence : all the values of the sequence are know and countable. Countable sequences are particular case of finite sequence where the sequence has a reasonable amount of values, and thus could be represented by one object per values. I will talk about finite or infinite sequence, and countable sequence.

Key to distribute sequences

Discontinued sequences are easy to scale : the application can pick numbers n by n in the sequence, reducing the amount of calls to the sequence by n. Good example for of discontinued sequences are objects identifier generators.

Unordered sequences are also easy to scale : a counter can be decomposed in n subcounter, picking at random one of the subcounter to increment or decrement will reduce the contention on one counter by n. A typical example is a quantity of object to sell. N subcounters are initialized with the sum is the total amount to sell. The object cannot be sold anymore when every subcounters are at 0.

Countable sequences can be scaled by representing every value of the sequence as a single object in the database. Updating an unused object is a easy task.

Finaly infinite, continued ordered sequence are very hard to scale because synchronization between all callers is required, so a single point of contention is mandatory.

Conclusion

Sequences are choke point in most systems, but as we saw above they can scale relatively simply with two simple tricks : read number n by n instead of 1 by 1, or decompose a counter in n subcounters.

dimanche, 15 novembre 2009

Call different services, use the first to reply

The needs


My needs were the following : I have an arbitrary amount of webservices to call, but I don't know, for a particular query, which one will return the answer. More than that, some are slower and others are faster, but again, I don't know which is the fastest for my particular query.

So what I will do is to parallelize the queries, and use the value of the first which return my a good value.

Use an Executor

The first component to implement such a requirement is an Executor.
The Executor will run all my queries, in as many threads as I want, permitting to fine tune the behavior of the system.
As many queries will input the system and will be translated in many more threads, it is important to use a limited amount of threads to not crash the system.

Synchronization on result

A specific object will be used to synchronize the result : the first result will be returned to the caller, letting the running threads die, and no more threads will be run for this query.

Some basic primitive for concurrent programming (java.util.concurrent) will be used, such as CountDownLatch : the caller will block on the latch, and once a thread will find the answer, it will set the value into the result and count down the latch. The caller will be freed and the value will be available.

Watchdog if all the queries fail

One point to not forget is if every queries fail, the caller should be informed of this fail.
A watchdog will be used : it will block till all the threads executing the query will finish, and will then be run. Its only task is to free the caller. This one will continue the processing, but the result will not be available. This will be the responsibility of the caller to run user code to handle this special case.

Strong requirements

The requirements are the following :
  • Use as little synchronization as possible
  • Use as much concurrent primitives (java.util.concurrent) as possible
  • Do it as generic as possible
Java implementation

The interesting part of the implementation is given here below.
The Executor is simply an java.util.concurrent.Executor. I'm not good enough to implement a better solution than they do.
The result object is composed of the following attributes :
public class FutureResult {
private T result;
private boolean done;
private AtomicInteger threadCount = new AtomicInteger(0);
private CountDownLatch latch = new CountDownLatch(1);
}
A generic type for the value, a AtomicInteger to count how many other threads are working on this result, and a latch to make the caller wait.

The watchdog class will simply wait till all the threads are finish and free the caller if it is not already the case :
class WatchDogRunnable implements Runnable {
public void run() {
while (result.hasThreadsWorking()) {
synchronized (result) {
result.wait(2000);
}
}
latch.countDown();
}
}
And finally an abstract callable worker which can be easily extended to run the wanted tasks. The call function will increment the AtomicInteger, do the work if it is not already done, if a result is found, free the caller, decrement the AtomicInteger and return the value if it is set. Not that the worker implementation will implement the callInternal function.
public abstract class AbstractCallableWorker
implements Callable {
private FutureResult result;
public final T call() {
result.setOneMoreThread();
if (!result.isDone()) {
T callResult = callInternal();
if (callResult != null) {
setResult(callResult);
latch.countDown();
}
}
result.setOneLessWorkingThead();
return result.getResult();
}
protected abstract T callInternal();
}
Everything put together, you can implement as much worker as you want and call as many webservice as you want, with the only guaranty to get the caller continue with the fastest service to reply !

samedi, 14 novembre 2009

Clustering Jackrabbit 1.5.x with DataStore configuration

Jackrabbit is the reference implementation of JSR-170 : JCR, Java Content Repository.

One day we felt the needs to run a Jackrabbit repository in a clustered environment for reliability. So a JCR runs on two separated servers, backed by the an Oracle db and a NFS shared datastore.


It exists some difficulties to run such architecture, mainly because both JCR nodes (JCR1 and JCR2) do not know each others. So if we insert some content on one node, it takes some time before we can read it on the other node.

In Jackrabbit 1.5.x, Session.refresh do not work correctly, so we need to do some trick to enable cluster (synchronization) features.

First of all, we add a custom ServletFilter which handle the GET (read) request and transform it on a HEAD one (test if the content exists) using apache.commons.HttpClient.
If the return of the query says the content exists, then we run the normal request. If the content do not exists, we way for some arbitrary time and relaunch the HEAD request.

This filter will let enough time to the node to synchronize itself, and then perform the query in the right way.

Other possibilities like HEAD on the current node and then HEAD on a different node should also be possible, but this will require every nodes to know about every other nodes. No the best idea.

One another tricky point is to store all the content to the configured datastore. Be carefull to not rely on a JNDI persistence manager, but be sure to use a subclass of a bundle persistence manager, otherwise you will have some surprise with the size of the database.

samedi, 17 janvier 2009

Des nombres à usage unique (nonce)

Afin de répondre à une problématique de transmission d'informations sensibles sur un réseau pas sûr (Internet donc...), une solution est l'utilisation de nonce, diminutif de number used once, nombre à usage unique.

Ils sont utilisé pour mettre du sel dans des informations sensibles qui seront ensuite hashées et transmises en clair sur le réseau. Mais leur efficacité n'est effective seulement si on a la garantie qu'un même nonce ne va jamais être utilisé 2 fois !

Exemple type : vous développez un service d'authentification, mais celui-ce ne peut se faire sur du SSL. Vous souhaitez toutefois que si une personne intercepte les informations, elle ne puisse pas les réutiliser pour s'identifier à son tour.

Le seul hashage du mot de passe ne suffisant pas car la personne qui a interceptée le mot de passe hashé pourra rejouer la séquence pour simuler sa connexion, une solution est de hasher le mot de passe concaténé au nonce (du style sha256("#" + mot de passe + "#" + nonce + "#")), puis de transmettre le nom d'utilisateur, le nonce utilisé et le résultat du hashage. Le serveur disposera ainsi de toutes les informations pour vérifier si le mot de passe utilisé pour le hashage était effectivement correcte ou pas. Les prochaines requêtes faites avec ce nonce seront systématiquement refusées.

C'est pour faciliter l'accès à ces nombres que je mets à disposition un petit service de génération de nombre à unique, accessible publiquement à l'adresse

http://nonce.noisette.ch/next

Chaque appel à cette url retourne un chaine de 32 caractères composées des lettres a-z, A-Z et les chiffres 0-9. Notez que les chaines retournées sont donc senible à la casse.

Le nombre de nonce possible est donc (26+26+10)^32 = 2.27 * 10^57, soit à peu près 6-parasite, ou beaucoup plus que d'atomes dans l'univers.

A utiliser sans modération pour toute application nécessitant la transmission d'informations sensibles sur un réseau non sécurisé.

dimanche, 8 juin 2008

Implémentation d'un serveur proxy HTTP

La question à la base de cet article est la suivante :

Comment faire pour que mes concurrents ne soient pas au courant que j'observe (très) régulièrement une section particulière de leur site internet ?
La première étape bien évidemment est de la légitimer cette question. Comment un concurrent pourrait savoir que je consulte régulièrement son site ?

La réponse est simple. Prenons un cas concret pour illustrer : ELCA Informatique possède les plages d'adresses ips 193.72.144.0 - 193.72.147.255 (bloc /22 = 1024 ips, hosté chez Cablecom) et 193.73.238.0 - 193.73.238.255 (bloc /24 = 256 ips, hosté chez Sunrise)

Ce genre d'info se trouve très facilement via un mélange whois, traceroute et autres informations DNS.

Une fois qu'on a ces ips, il suffit de rechercher dans les logs les hits faits par ces ips et on pourra dresser un profil précis des intérêts des collaborateurs d'ELCA sur notre site.

Donc comment faire pour éviter cette traçabilité ?

Connexion Internet à adresse ip dynamique (xDSL)

Une première solution est d'utiliser une connexion xDSL pour toutes les connexions humaines à Internet, et de dédier exclusivement les plages d'ips fixes et repérables aux services de l'entreprise. Cette pratique est de plus en plus courante, surtout avec l'arrivée des connexions VDSL et de boitiers qui permettent d'agréger plusieurs lignes xDSL afin d'une part d'avoir un redondance, et d'autre part de partager la somme des débits des lignes entre les utilisateurs. Le seul détail concernant cette solution est de vérifier que l'adresse ip change réellement, faute de quoi il faudrait forcer ce changement.

Serveur proxy

Une autre solution, que je vais détailler ici car elle est plus intéressante d'un point de vue ingénierie informatique, est de faire passer le traffic des utilisateurs par un proxy. Par proxy j'entends tout type de connexion indirect à Internet, donc autant les serveurs proxy HTTP, SOCKS, etc que les réseaux de proxy comme par exemple Tor.
Le fait d'accéder à Internet par l'intermédiaire d'un proxy permet de visualiser un site avec une adresse ip qui n'est pas notre, mais appartient à un prestataire de ce genre de service. Cependant l'anonymité à 100% n'existe pas : il existera toujours un intermédiaire qui saura qui a accédé où. La confiance des intermédiaires est donc de mises dans cette configuration.

Il existe une grande quantité de serveur proxy logiciel de qualité, open source ou commercial, comme par exemple Squid, Privoxy, Apache mod_proxy, etc... Mais une fois de plus, l'utilisation d'un logiciel existant nous priverait du plaisir de développer de notre propre implémentation.

Principe de base

Le principe d'un serveur proxy est donc d'intercepter la requête d'un navigateur, d'ouvrir une connexion vers le site cible, transmettre la requête original, lire le résultat et finalement renvoyer ce résultat au navigateur.

Protocole HTTP

La partie centrale d'un serveur proxy HTTP réside sa compréhension du protocole HTTP. Celui-ci comporte des particularités qui vont être détaillées.

Le serveur proxy va recevoir une requête du navigateur, dont la première ligne comporte l'action à exécuter, l'url de la cible et une éventuelle version du protocole utilisé :
GET http://www.noisette.ch/ HTTP/1.0
Cette première ligne va donc directement nous renseigner sur le serveur cible à connecter. Le reste de la requête est composée de lignes de header, contenant diverses informations sur la requête comme le navigateur utilisé ou le referer (en français on dit comment ?).

Transfer de la requête au serveur cible

La prochaine étape est le transfert de la requête au serveur cible. A ce moment on peut éventuellement modifier ou enlever des headers du client, voir en ajouter d'autres. Plus précisément, il peut être intéressant de filtrer tous les headers des clients qui pourraient trahir l'utilisateur du proxy (information leakage).
Reste la lecture du résultat, qui n'est de loin pas la partie la plus triviale.

Ne pas bufferiser la lecture du résultat

L'implémentation d'un serveur proxy dans laquelle on lit le résultat en entier (via un lib du style httpclient) avant de l'envoier ensuite au navigateur n'est à mon avis pas bonne : le délai entre la requête et la réponse risque d'en prendre un coup, surtout pour les grosses pages.
L'idée est donc de retourner directement les bytes lus du serveur web au client.

En pseudo code, ca donne quelque chose du style :
 // fromWeb : InputStream du socket de la connexion vers le serveur source
// toBrowser : OutputStream du socket de la connexion vers le browser
while ((i = fromWeb.read(buffer)) != -1) {
toBrowser.write(buffer, 0, i);
toBrowser.flush();
}

Fermeture des connexions

La fermeture des connexions est très importante afin de libérer les ressources et ne pas avoir un serveur qui explose après 20 requêtes. Facile en théorie, la fermeture de la connexion est la sous partie non triviale. Dans la mesure où le serveur ne ferme pas systématiquement la connexion à la fin du transfert si il reçoit un header "Connection: keep-alive", il faut détecter quand la réponse du serveur est complète pour pouvoir fermer la connexion.

Dans une grande majorité des réponses, la taille de la réponse est spécifiée via un header "Content-Length: 82". Il suffit donc de lire autant de bytes que nécessaire et la connexion peut être fermée. Mais quelques réponses ne vont pas retourner de taille. C'est le cas par exemple lors du streaming d'un fichier. Dans ce cas il faut lire la réponse jusqu'à ce que le serveur ferme lui-même la connexion.

Transfer-Encoding: chunked

Une autre difficulté à la lecture de la réponse est l'encodage chunked. Dans ce mode de transfert, le serveur sépare la réponse en plusieurs parties, les chunks. Ca lui permet d'envoyer une partie de la réponse tout en calculant la suite. Par exemple Google utilise ce mode de réponse : les 2-3 premiers résultats sont dans un cache direct, et Google les retourne dans un premier chunk. Puis il doit aller chercher les 7-8 autres dans un deuxième cache, opération un peu plus coûteuse, qui se fait pendant que le navigateur du client lit ce premier chunk. Ca permet au serveur de ne pas attendre d'avoir entièrement cherché les 10 résultats, et de profiter du temps de transfert avec le client. Du point de vu du client, une fois qu'il a fini de lire le premier chunk, les suivants sont prêts à être lus. L'impression de fluidité est donc parfaite. Mais malheureusement peu d'implémentations tirent intelligemment profit de ce type de transfert.

En pratique, la réponse d'une encodage chunked donne quelque chose du style :
taille du chunk en hexa \r\n
données \r\n
taille du prochain chunk \r\n
données \r\n
...
0\r\n
\r\n
Pour lire la réponse, il faut donc lire la taille du chunk, lire les données du chunk, lire la taille du prochain chunk, etc jusqu'à ce qu'on ait un chunk de taille null, signifiant la fin de la réponse.
HTTP/1.1 200 OK¶
Date: Fri, 31 Dec 1999 23:59:59 GMT¶
Content-Type: text/plain¶
Transfer-Encoding: chunked¶

1a¶
abcdefghijklmnopqrstuvwxyz¶
10¶
1234567890abcdef¶

Caching

Prochaine étape dans l'implémentation d'un serveur de cache utile est efficace, c'est l'ajout d'une couche de cache au niveau du proxy : le serveur cible va retourner une information sur la date de dernière modification de la page. Cette indication peut être utilisé pour retourner la page mise en cache plutôt que de relire la réponse du serveur.

Authentification

Dernier point important si on met un serveur proxy sur internet, il faut ABSOLUMENT implémenter un ACL (access control list) afin de restreindre l'accès et l'utilisation du proxy aux personnes authentifiées uniquement. Faute de quoi votre tout beau serveur proxy sera vitre trouvé et utilisé par des personnes malveillantes pour spammer, consulter des sites illégaux etc.

Prochaine étape : au travail !

jeudi, 5 juin 2008

Mettre à mal un réseau d'entreprise avec Outlook (TM, R & CO)

Suite à une expérience involontaire d'un collègue de travail, on a eu 30 ordinateurs bloqués une quinzaine de minute :

Notre serveur SVN envoie un mail à chaque commit à une mailinglist interne, à laquelle une trentaine de collaborateurs est abonné. Le commit incriminé était certes quelque peu conséquent, une colonne ajoutée dans un fichier CSV de 5000 lignes, mais la conséquence était désastreuse !

Le mail faisait ~ 5Mb, et le diff du commit était formaté en HTML. Tous les collaborateurs qui ont cliqué sur le mail avec le preview activé n'ont pas eu d'autre choix que de tuer Outlook après avoir eu l'ordinateur bloqué un bon moment.

Moi qui n'était déjà pas un grand amoureux d'Outlook et de son immense capacité à accomplir des recherches performante parmi les mails, me voici comblé.

Mais pourquoi donc autant d'entreprise s'accrochent-ils à ce logiciel ? Dans tous les cas, si vous voulez forcer un peu la main pour le passage à un autre logiciel, vous savez ce qu'il reste à faire.

samedi, 23 février 2008

Une liste de USER AGENTS parsable

Il est toujours utile d'avoir sous la main une liste de USER AGENTS facilement parsable.

La mienne, qui ne contient que des USER AGENTS ^Mozilla/.* se trouve à l'adresse http://www.noisette.ch/useragents/. Elle est mises-à-jour journalièrement sur la base des navigateurs qui se connectent sur mon serveur, tous sites confondus. Elle va donc suivre l'évolution des numéros de version des Firefox et autres IE...

Cette liste, couplée à la fonction url_get_contents définie antérieurement, permet facilement de disposer d'un USER AGENT pour faire ce qu'il est possible de faire avec un USER AGENT généré :)

Illustration en PHP :

$useragents = explode("\n", url_get_contents('http://www.noisette.ch/useragents/list.txt'));
$useragent = $useragents[rand(0, count($useragents) - 1)];

lundi, 18 février 2008

De la concurrence des requêtes SQL

Sans cité Montaigne, voici un cas académique dans lequel la concurrence des requêtes SQL est souvent oubliée.

Nous avons une table hits qui contient 4 champs : une date (nommé date), une clé étrangère pointant vers un objet du système (nommée object_id) et un compteur implémenté sous ca forme la plus simple : un entier (nommé counter). Puisqu'on travail avec une framework qui implémente de l'active record on ajoute un champ id qui fera office de clé primaire.
A chaque utilisation de l'objet référencé par la clé étrangère on souhaite incrémenter le compteur et recommencer à 0 tous les minuits afin d'avoir un historique journalier de l'utilisation de l'objet. Alors comme on a quand même un peu réfléchi à la performance du système (un billet sur le sujet est en préparation), on définit un index composé des champs date et object_id. Puis on définit la fonction suivante, donnée ci-dessous en pseudo-code, qui incrémente notre compteur et insère un nouveau tuple si aucun n'existe pour le jour courant :

void function hit ( int $object_id ) {
SELECT FROM hits WHERE object_id = $object_id AND date = NOW()
if (row_exists)
UPDATE hits SET counter = counter + 1 WHERE object_id = $object_id AND date = NOW()
else
INSERT INTO hits (object_id, date, counter) VALUES ($object_id, NOW(), 1)
}
Tout se passe bien jusqu'au jour où on se rend compte qu'il y a plusieurs lignes par objet et par jour dans la base de données.

Ce problème vient du fait qu'il peut se passer un temps indéterminé entre la requête SELECT et l'INSERT. Si à 00h01 2 requêtes sont faites en même temps sur le même objet, il est fort probable que 2 requêtes SELECT soient faites avant un INSERT, et donc les 2 tests if (row_exists) vont retourner faux et 2 INSERT seront fait.

Les solutions pour résoudre ce problème sont multiples, elles passent de la redéfinition de la clé primaire en date, object_id plutôt que notre champ id, ce qui aurait comme comportement de faire échouer le 2ème INSERT, erreur qui pourrait être capturée et traitée spécifiquement. Une autre solution serait de mettre un verrou sur la fonction, chose très aisée en Java avec le mot-clé synchronized. La fonction deviendrait void synchronized function hit(int object_id), évitant ainsi ce type de problème de concurrence.

Dans tous les cas les applications web sont aussi (voir même plus) soumises aux problèmes de concurrence, et l'expérience nous montre que la technique de l'autruche de même que les phrases du style "Il ne PEUT PAS y avoir 2 requêtes en même temps" sont à bannir absolument.

mardi, 29 janvier 2008

Vos données sont-elles réellement privées ?

Donnez des informations pour exister

Le principe premier du Web 2.0 est la participation des utilisateurs. Pour les sites sociaux comme MySpace et Facebook, où la participation se calcule en quantité d'informations données sur soi-même.

Collecte indirecte d'informations

L'aspect de collaboration entre les membres résulte en une masse d'information indirecte enregistrée souvent au dépend de la personne ciblée :

Facebook par exemple enregistre des informations sur vous par rapport aux détails que donnent vos amis (appelé Social map) : à chaque ajout de nouvel ami, vous avez la possibilité de spécifier comment vous l'avez connu. Grâce à ce système subtile, mon profile Facebook sait maintenant que j'ai fini l'EPFL en 2007, que je fais partie d'autres associations, ... alors que je n'ai rien renseigner sur ces sujets précis.

Le business des données personnelles


Le fait d'être encouragé à compléter son profil qui n'est complet qu'a 88% comme nous l'indique de manière bien visible Linkedin n'est pas entièrement désintéressé : le fait de connaitre précisément ses utilisateurs et d'en dresser des profils augmentent largement les marges sur les revenus publicitaires qui seront mieux ciblés donc plus percutant donc plus intéressant pour l'utilisateur. Revenus publicitaires qui sont, rappelons-le, le business model principal des ces sites sociaux.

Protection des données

L'aspect à ne pas perdre de vu si on utilise ce genre de sites, c'est que ça n'est pas une simple cas à cocher "Rendre mon profil privé" qui protègera réellement les données de votre profile : les données sont chez une personne tierce qui d'un jour à l'autre peut décider d'en faire autre chose. Ou, comme c'est le cas sur MySpace, une vulnérabilité du site permet d'outerpasser cette coche ridicule...

La réponse au titre du billet est clairement NON ! Le fait de déposer vos données sur un service externe quelconque ne les rendent pas privées. Donc soit vous ne mettez que des informations réellement publiques sur votre profil, soit vous faites confiance à 100% au service et vous ne vous demanderez pas d'où viennent les données sur vous retrouvées dans la nature...

Ils en parlent

dimanche, 30 décembre 2007

Je télécharge mes MP3 et les écoute légalement.

Le parlement et le conseil national ont accepté le 5 Octobre 2007 presque sans aucune résistance une loi fédérale sur le droit d'auteur et les droits voisins.

Pour toute question à propos du référendum lancé, que dit en passant j'encourage fortement à signer, rendez-vous sur le site No Swiss DMCA.
Je n'essayerai pas non plus de déterminer à quelles mesures techniques, notées efficaces, le texte de loi fait peut bien faire référence.

Cette loi, plus précisément l'article 39a alinéa 4 donné ci-après, combiné à l'approbation du Tribunal fédéral (TF) sur l'introduction d'une redevance sur les supports numériques (baladeurs MP3, iPod et autres enregistreurs à disques durs) entrée en vigueur au 1er septembre 2007, me mène à la conclusion suivante :

Je vais télécharger de la musique sur mon réseau peer-to-peer préféré, ripper des CDs et retirer les DMA de mes MP3 restants (autrement dit : contourner les mesures techniques efficaces) et l'écouter (exclusivement) sur mon baladeur MP3 récemment acquis. Celui-ci étant taxé, je m'affranchis de toute autre taxe, et je deviens entièrement légal. Idem pour les divx !

Art 39a
4 L’interdiction de contourner ne peut pas frapper celui qui contourne une mesure technique efficace exclusivement dans le but de procéder à une utilisation licite.
PS : cet article n'est pas illégale au sens de l'Art 39a 3a, c'est-à-dire que cet article ne fait pas promotion visant à contourner des mesures techniques efficaces.
PPS : la critique de la double imposition à une taxe sur les droits d'auteurs des musiques achetées sur un online music shop et écoutées sur un baladeur taxé fera l'objet d'un autre article.