Sam & Max » redis http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Utilisateurs en ligne avec le sorted set de Redis 7 http://sametmax.com/utilisateurs-en-ligne-avec-le-sorted-set-de-redis/ http://sametmax.com/utilisateurs-en-ligne-avec-le-sorted-set-de-redis/#comments Sat, 25 Jul 2015 08:48:50 +0000 http://sametmax.com/?p=16676 hyperloglog et les sorted sets. C'est de ces derniers dont on va parler.]]> Beaucoup de gens sous exploitent Redis en se cantonnant à un usage clé/valeur, voire pour les plus aguérris à la liste ou le hash.

Mais derrière sa simplicité d’usage, l’outil possède une grande richesse de fonctionnalités comme le PUB/SUB, l’hyperloglog et les sorted sets.

C’est de ces derniers dont on va parler.

En effet, dernièrement j’ai dû mettre en place un petit compteur des visiteurs en ligne, et c’est typiquement quelque chose qui fusille une base de données car ça fait une écriture ou une lecture à chaque changement de vue.

Redis est idéal pour ça, le truc tenant 100 000 écritures / seconde, et perdre 4 secondes d’historique du compteur en cas de crash est le cadet de mes soucis.

Pour compter les visiteurs, il nous faut non seulement un moyen d’incrémenter le compteur, mais aussi de le décrémenter quand le visiteur n’est plus en ligne. J’utiliserais WAMP, Crossbar pourrait me donner l’information à tout moment via l’API meta, mais sur un site HTTP only il n’y a aucun événement qui nous signale “l’utilisateur n’est plus connecté”.

Le seule moyen de savoir qu’un utilisateur n’est plus connecté est de garder une trace de lui et de la faire expirer quand on n’a pas de nouvelle depuis un temps supérieur à une durée raisonnable (calculée avec précision ) 10 minutes par l’INDMS).

Problème, on peut bien faire expirer des clés avec Redis, mais on ne peut pas lister ces clés (la fonction keys() le fait mais avec des perfs désastreuses et l’auteur du logiciel la conseille uniquement pour regarder l’état de son serveur depuis la console). D’un autre côté on a des listes, mais l’expiration concerne toute la liste, pas juste un élément.

Une manière de contourner le problème est d’utiliser un sorted set, une structure de données Redis qui a les caractéristiques suivantes :

  • Clé/valeur, mais la clé est forcément du texte, et la valeur forcément un chiffre.
  • Les clés sonts uniques.
  • Les valeurs peuvent être en doublons.
  • Les paires sont toujours ordonnées peu importe le nombre d’insertions.
  • L’ordre se fait sur les valeurs, qu’on appelle le score.

En gros, c’est comme un dictionnaire, mais ordonné, qui ressemble à :

{
    'cle': 789789
    'cle2': 78999
    'cle3': 6
}

Comme toutes les structures de données un peu exotiques, on se demande à quoi ça sert d’avoir des caratéristiques aussi précises et ce qu’on peut bien en faire.

La réponse est comme d’hab, “plein de choses”, mais nous on va en faire un compteur :

import datetime
 
import redis
 
# connection à redis
con = redis.StrictRedis()
 
# On appelle cette vue avec ajax, ignorant les
# utilisateurs sans JS qui de toute façon sont des 
# anarco communistes qui ne rapportent
# pas d'argent et fument des joins
# Cette partie dépend de votre framework
@ajax('/counter')
def add_user_count(request):
 
    # la clé d'accès à notre sorted set
    counter_key = "users:online"
 
    # un id unique pour notre visiteur. Ca dépend du
    # framework web
    user_id = request.session.id
 
    # Calcul du timestamp de maintenant et d'il y a 10 minutes
    # car ils vont nous servir de score max et min.
    now = (datetime.utcnow() - datetime(1970, 1, 1)).total_seconds()
    ten_minutes_ago = now - (60 * 10)
 
    # On ajoute l'utilisateur dans le sorted set. S'il existait déjà, son
    # ancienne valeur est remplacée, sinon elle est créée. Comme c'est
    # forcément le plus récent timestamp, il est tout en haut du sorted set
    con.zadd(counter_key, now, user_id)
 
    # Le sorted set ressemble donc à ça :
 
    # {
 
    #     "userid": 809809890, # timestamp de dernière requete
    #     "userid2": 809809885, # timestamp plus ancien
    #     "userid3": 809809880 # timestamp encore plus ancien
    #     ...
    # }
 
    # On retire toutes les entrées du sorted set avec un score
    # qui est plus petit que le timestamp d'il y a 10 minutes
    # histoire de pas saturer la mémoire.
    con.zremrangebyscore(online_user_key, 0, ten_minutes_ago)
 
    # Et on récupére le nombre de visiteurs en ligne entre maintenant
    # et 10 minutes.
    visitors = con.zcount(online_user_key, ten_minutes_ago, now)
 
    return visitors

Notez que toutes ces étapes sont très rapides, que ce soit l’insertion, la suppression ou le compte du total grâce aux fantastiques perfs de Redis. J’aime Redis. Je suce la bite de Redis goulument.

La précision n’est pas parfaite, mais compter les utilisateurs est plus un art (divinatoire) qu’une science et avoir un chiffre précis à 2, 3% prêt est suffisant pour nous.

]]>
http://sametmax.com/utilisateurs-en-ligne-avec-le-sorted-set-de-redis/feed/ 7
Redis : pourquoi et comment ? 23 http://sametmax.com/redis-pourquoi-et-comment/ http://sametmax.com/redis-pourquoi-et-comment/#comments Tue, 14 Oct 2014 11:31:17 +0000 http://sametmax.com/?p=12279 Redis fait partie de ces technologies tellement utiles et simples à mettre en oeuvre qu’il est facile d’oublier toutes les personnes qui ne savent toujours pas ce que c’est. D’autant plus qu’on l’associe avec beaucoup d’étiquettes : cache, queues, pub/sub, base de données, nosql… Et qu’est-ce que Redis comparé à Memcache, MongoDB, MySQL, RabbitMQ, des technos qui n’ont rien à voir et auxquelles on le compare ?

Bref, c’est pas clair tout ça.

Hello redis

D’abord, installer le bouzin.

Si vous êtes sous Linux, Redis est dans votre gestionnaire de paquets. Par exemple, sous Ubuntu :

sudo apt-get install redis-server

Pour Mac, via macport :

sudo port install redis
sudo port load redis

Je crois que brew install redis marche aussi pour les amateurs de homebrew.

Pour Windows, il y a un exe à télécharger ici.

Mais le plus fun avec redis, c’est que même quand il y a pas de binaire, c’est le truc le plus facile du monde à compiler. Et Dieu sait que je hais la compilation, donc quand je vous dis que c’est simple, c’est que c’est mega, ultra, simple.

Ensuite lui faire dire bonjour.

Utiliser Redis se fait à base de commandes, dont la liste est sur le site officiel. On peut envoyer ces commandes depuis n’importe quel langage, mais Redis fournit un shell qui permet de rentrer ces commandes directement:

$ redis-cli # lancer le shell redis
127.0.0.1:6379>  ECHO "Hello"
"Hello"

Redis comme base de données clés/valeurs expirables

L’usage de base de Redis, c’est de stocker des valeurs associées à des clés. Le serveur Redis est comme un gigantesque Hash Map, dictionnaire Python, object Javascript, Array associatif en PHP… En premier lieu, donc, on utilise Redis pour stocker des choses ainsi :

"cle1" => valeur1
"cle2" => valeur2
etc

La clé doit être une chaîne de caractères, de préférence ASCII. La valeur peut être n’importe quoi, vraiment, mais généralement c’est un gros bloc de texte, un entier ou un blob binaire.

Ca s’utilise avec les commandes SET et GET, qui peuvent, comme toutes les commandes, être tapées dans le shell de Redis :

127.0.0.1:6379> GET une_cle
(nil)
127.0.0.1:6379> SET une_cle "Une valeur"
OK
127.0.0.1:6379> GET une_cle
"Une valeur"
127.0.0.1:6379> SET une_cle 780708
OK
127.0.0.1:6379> GET une_cle
"780708"

Ce genre d’usage est surtout sollicité pour stocker des paramètres de configurations ou des compteurs.

En effet, la plupart des opérations sur les clés sont atomiques, rendant ce genre d’usage idéal. On peut même incrémenter ou décrémenter une valeur avec INCR et DECR atomiquement :

127.0.0.1:6379> INCR une_cle
(integer) 780709
127.0.0.1:6379> INCR une_cle
(integer) 780710
127.0.0.1:6379> DECR une_cle
(integer) 780709
127.0.0.1:6379>

Vous allez me dire, mais quel intérêt ? Je peux déjà faire ça avec une variable. Certes, mais Redis est accessible depuis n’importe quel programme de votre serveur. Vous pouvez donc facilement partager des valeurs entre vos processus. Par exemple, avec Django, on a souvent plusieurs workers WSGI, et Redis permet donc de partager des informations entre ces workers. Pour cette raison, on peut utiliser Redis pour créer un lock partagé.

Ainsi, un paramètre dynamique peut être changé et lu depuis tous les process de votre projet en utilisant Redis, et la valeur sera garantie d’être à jour. On peut bien entendu faire ça avec une base de données ordinaire, mais Redis a un plus : la performance.

En effet, Redis est par défaut configuré pour garder toutes les données de sa base en mémoire vive. Pour cette raison, il est très, très rapide, supportant 100000 lectures/écritures par seconde. Si vous comptez le nombre de visiteurs en ligne pour l’afficher sur chaque page, votre base de données vous dira merci de plutôt demander à Redis.

Plus encore, les clés peuvent expirer :

127.0.0.1:6379> SET une_autre_cle "Tu ne le sais pas mais tu es deja mort"
OK
127.0.0.1:6379> EXPIRE une_autre_cle 5 # cette clé expire dans 5 secondes
(integer) 1
127.0.0.1:6379> GET une_autre_cle
"Tu ne le sais pas mais tu es deja mort"
127.0.0.1:6379> GET une_autre_cle
"Tu ne le sais pas mais tu es deja mort"
127.0.0.1:6379> GET une_autre_cle
"Tu ne le sais pas mais tu es deja mort"
127.0.0.1:6379> GET une_autre_cle
(nil)
127.0.0.1:6379> GET une_autre_cle
(nil)

La limite en taille de ce qu’on peut stocker par couple est assez large.

On peut également utiliser EXPIREAT et un timestamp unique si on a une date en tête.

Cette caractéristique le rend similaire à Memcache, qui est basé sur des clés/valeurs en mémoire qui peuvent expirer. Et comme Memcache, cela fait de Redis un outil idéal pour gérer du cache : faites une opération longue, stockez-là avec une clé, mettez lui une date d’expiration et pouf, vous avez une valeur cachée globale à votre app.

Redis a néanmoins 3 différences majeures qui le sépare de Memcache :

  • Redis sauvegarde régulièrement toutes les données sur le disque. Si vous rebootez le serveur, vous perdez au pire, quelques secondes de données. Avec Memcache, vous perdez tout.
  • Memcache possède des fonctionalités de clusterisation que n’a pas encore Redis, bien que ce soit actuellement en beta.
  • Redis peut faire bien plus que du stockage clé/valeur.

Redis comme base NoSQL zarbie

Redis n’a pas besoin d’un schéma particulier à définir pour pouvoir sauvegarder ses données, mais n’est pas pour autant limité à une forme d’organisation basique. En fait, des types très proches de ceux qu’on trouve dans le langage Python sont disponibles pour stocker internalement les données, et ils sont décrits dans son excellente doc.

Liste

Une liste est juste une collection ordonnée d’éléments. On utilise toujours la logique de clé, mais au lieu d’une valeur, on a une séquence d’éléments.

Cela permet d’avoir :

cle => [
	valeur1,
	valeur2,
	...
]

Dans le shell :

127.0.0.1:6379> lpush batman na
(integer) 1
127.0.0.1:6379> lpush batman na
(integer) 2
127.0.0.1:6379> lpush batman na
(integer) 3
127.0.0.1:6379> lpush batman na
(integer) 4
127.0.0.1:6379> lpush batman na
(integer) 5
127.0.0.1:6379> lpush batman na
(integer) 6
127.0.0.1:6379> llen batman
(integer) 6
127.0.0.1:6379> LINDEX batman 3
"na"
127.0.0.1:6379> LINDEX batman 10
(nil)
127.0.0.1:6379> LRANGE batman 0 -1 # du début au dernier élément
1) "na"
2) "na"
3) "na"
4) "na"
5) "na"
6) "na"
127.0.0.1:6379> LRANGE batman 2 3
1) "na"
2) "na"
127.0.0.1:6379>

On peut faire toutes les opérations qu’on a l’habitude de faire sur des listes : récupérer un élément, en ajouter un, en retirer un, faire du LIFO, du FIFO, du pipo, etc.

Utile pour créer des files d’attente, des journaux d’évènements (pensez jeux vidéos) ou plus simplement une valeur de config plus complexe qu’une entrée.

Hash

Le Hash se comporte comme un Hash Map, dictionnaire Python, object Javascript, Array associatif en PHP… Bref, comme Redis lui-même en fait, mais sans expiration de clé. Cela permet d’avoir :

cle => {
	cle : valeur,
	cle : valeur,
}

Dans le shell :

127.0.0.1:6379> HSET scores titi 1
(integer) 1
127.0.0.1:6379> HSET scores grosminet 0
(integer) 1
127.0.0.1:6379> HSET scores tom 0
(integer) 1
127.0.0.1:6379> HSET scores jerry 1
(integer) 1
127.0.0.1:6379> HGET scores jerry
"1"
127.0.0.1:6379> HGETALL scores
1) "titi"
2) "1"
3) "grosminet"
4) "0"
5) "tom"
6) "0"
7) "jerry"
8) "1"
127.0.0.1:6379>

Notez encore une fois qu’il n’est pas utile de vérifier que la clé existe avant de rajouter une valeur dans le hash, bien qu’il y ait une commande pour le faire. Si on essaye de récupérer une clé qui n’existe pas, Redis retourne nil.

Le hash est fort pratique pour toute forme de compteurs groupés, ou juste pour mettre en cache des relations. Redis n’ayant pas de JOIN, on utilise parfois un hash pour faire le lien entre deux listes par exemple.

Set

Comme les sets en Python, le set est une collection NON ordonnée d’éléments uniques. Il ne peut pas y avoir de doublons dans un set, et vérifier si un élément fait partie d’un set est une opération très rapide. Ils permettent aussi de faire des opérations ensemblistes de manière performante, par exemple vérifier quels éléments d’un set sont ou non dans un autre set.

cle => { valeur1, valeur2, ...}

Dans le shell :

127.0.0.1:6379> SADD ip 192.168.1.1
(integer) 1
127.0.0.1:6379> SADD ip 192.168.1.1 # ajouter 2x le même ne fait rien
(integer) 0
127.0.0.1:6379> SADD ip 192.168.1.1
(integer) 0
127.0.0.1:6379> SADD ip 192.168.1.2
(integer) 1
127.0.0.1:6379> SADD ip 192.168.1.3
(integer) 1
127.0.0.1:6379> SCARD ip
(integer) 3
127.0.0.1:6379> SMEMBERS ip
1) "192.168.1.2"
2) "192.168.1.1"
3) "192.168.1.3"
127.0.0.1:6379> SADD ip:banned 192.168.1.3 # le ":" est une séparateur courant pour les clés
(integer) 1
127.0.0.1:6379> SADD ip:banned 192.168.1.10 # ip:banned est un AUTRE set
(integer) 1
127.0.0.1:6379> SINTER ip ip:banned # trouver les IP qui sont dans les 2 sets
1) "192.168.1.3"

On va utiliser les sets pour éviter les doublons (tirage au sort par exemple) ou pour vérifier facilement une appartenance (notion de groupes, de permissions, etc.).

Le set possède une variante, le set ordonné, qui associe à chaque élément du set un score. On peut changer ce score, l’incrémenter ou le décrémenter atomiquement, avoir deux scores égaux… Et au final, récupérer le set dans l’ordre ascendant ou descendant des scores.

cle => { valeur1 (1), valeur2 (3), ...}

Dans le shell :

127.0.0.1:6379> zadd participants 1 staline
(integer) 1
127.0.0.1:6379> zadd participants 1 hitler
(integer) 1
127.0.0.1:6379> zadd participants 2 "pol pot"
(integer) 1
127.0.0.1:6379> zadd participants 1999 "rainbow dash"
(integer) 1
127.0.0.1:6379> zrange participants 0 - 1
(error) ERR value is not an integer or out of range
127.0.0.1:6379> zrange participants 0 -1 
1) "hitler"
2) "staline"
3) "pol pot"
4) "rainbow dash"
127.0.0.1:6379> zrange participants 0 -1 withscores
1) "hitler"
2) "1"
3) "staline"
4) "1"
5) "pol pot"
6) "2"
7) "rainbow dash"
8) "1999"

HyperLogLog

J’ai déjà parlé de l’HyperLogLog ici et celui de Redis fonctionne pareil. Cette structure de données permettra de faire des compteurs d’éléments uniques, comme par exemple un compteur de visiteurs ou de gens connectés, qui soit approximatif (+ ou – 1%) mais prenne une taille fixe en mémoire.

Dans le shell :

127.0.0.1:6379> PFADD connectes toto
(integer) 1
127.0.0.1:6379> PFADD connectes toto
(integer) 0
127.0.0.1:6379> PFADD connectes toto
(integer) 0
127.0.0.1:6379> PFADD connectes tata
(integer) 1
127.0.0.1:6379> PFADD connectes titi
(integer) 1
127.0.0.1:6379> PFADD connectes tutu
(integer) 1
127.0.0.1:6379> PFADD connectes tutu
(integer) 0
127.0.0.1:6379> PFADD connectes tutu
(integer) 0
127.0.0.1:6379> PFADD connectes tete
(integer) 1
127.0.0.1:6379> PFADD connectes bob
(integer) 1
127.0.0.1:6379> PFcount connectes
(integer) 6
127.0.0.1:6379>

Redis comme super pote de Python

Vu que Redis est simple à installer et à configurer, c’est un outil qu’on dégaine facilement, même pour un petit script, pas forcément pour une grosse app Web. Par exemple, je fais une analyse de mon serveur, plutôt que de stocker le résultat dans un fichier, je peux mettre tout ça dans Redis, c’est tellement pratique.

D’abord, la lib pour communiquer avec Redis est à un pip du clavier :

pip install redis

Ensuite, si vous utilisez le client StrictRedis, l’API est exactement la même que les commandes du shell :

>>> import redis
>>> r = redis.StrictRedis()
>>> r.hgetall('scores')
{b'titi': b'1', b'tom': b'0', b'jerry': b'1', b'grosminet': b'0'}

Il existe aussi des drivers asynchrones pour tornado, twisted et asyncio.

Redis comme message broker

J’aime le pub/sub, je pense qu’après mon enthousiasme pour WAMP.ws, c’est clair.

Redis met à disposition des primitives pour créer des files d’attente de messages et les lire, comme une version simplifiée de RabbitMQ ou Crossbar.io. Pour que ce soit intéressant, il nous faut deux process. D’abord un shell Python qui écoute les messages arrivant sur la file “sametmax” :

>>> import redis
>>> r = redis.StrictRedis()
>>> listener = r.pubsub()
>>> listener.subscribe(['sametmax'])
>>> for item in listener.listen():
...     print(item)
...

Le code va bloquer, et affichera quelque chose à chaque fois qu’il reçoit un message.

Du coup si je fais ceci dans le shell Redis :

127.0.0.1:6379> publish sametmax yolo
(integer) 1
127.0.0.1:6379> publish sametmax carpediem
(integer) 1
127.0.0.1:6379> publish sametmax wololo
(integer) 1
127.0.0.1:6379> publish sametmax2 oyooyo
(integer) 0
127.0.0.1:6379>

Mon shell python va afficher :

{'type': 'subscribe', 'pattern': None, 'channel': b'sametmax', 'data': 1}
{'type': 'message', 'pattern': None, 'channel': b'sametmax', 'data': b'yolo'}
{'type': 'message', 'pattern': None, 'channel': b'sametmax', 'data': b'carpediem'}
{'type': 'message', 'pattern': None, 'channel': b'sametmax', 'data': b'wololo'}

Il n’a pas affiché oyooyo puisque c’est sur une autre channel.

C’est une méthode assez simple de faire du pub/sub. C’est bas niveau, il n’y a pas de RPC, il faut boucler à la main et créer soit-même la mécanique pour arrêter d’écouter ou faire du multitask, mais c’est facile pour débuter. Pour cette raison, plein de gens utilisent une solution bricolée là-dessus pour faire du pub/sub.

Et donc

Non seulement Redis est facile à installer, simple à utiliser et performant, mais en plus il est accessible depuis de nombreux langages. Ajoutez à cela ses très nombreuses fonctionnalités, et vous avez là un système qui est installé par défaut sur la plupart de mes serveurs. Par ailleurs, suivez le blog de l’auteur, ce mec est un génie. Il écrit pas souvent, mais quand il écrit, c’est passionant, et humble. C’est beau.

]]>
http://sametmax.com/redis-pourquoi-et-comment/feed/ 23
Le compteur mal connu : l’HyperLogLog 13 http://sametmax.com/le-compteur-mal-connu-lhyperloglog/ http://sametmax.com/le-compteur-mal-connu-lhyperloglog/#comments Tue, 07 Oct 2014 14:02:58 +0000 http://sametmax.com/?p=12353 ce n'est pas une faute de frappe. l'HyperLogLog est un algo qui permet de compter approximativement des éléments uniques en prenant très peu de mémoire. ]]> Non, ce n’est pas une faute de frappe. l’HyperLogLog est un algo qui permet de compter approximativement des éléments uniques en prenant très peu de mémoire.

Prenez par exemple un compteur d’IP uniques. En Python, on l’implémenterait en mettant les adresses IP dans un set() (puisqu’ils éliminent les doublons) et pour obtenir le nombre d’IP uniques, on ferait len() sur le set.

Une bonne stratégie, performante (les sets sont très rapides pour ce genre d’usage), simple, mais avec un défaut : la taille du set ne va cesser d’augmenter au fur et à mesure qu’on le remplit d’IP puisqu’il nous faut l’historique de toutes celles rencontrées pour éviter les doublons.

L’HyperLogLog répond à cette problématique : il tient un journal probabiliste qui va se remplir au fur et à mesure qu’on rencontre des nouveaus éléments. On peut ensuite demander au journal combien d’éléments uniques il a rencontré, et il répond avec une marge d’erreur.

Avantage : la taille en mémoire est fixe.
Désavantage : le compteur n’est pas parfaitement précis.

La précision obtenue est dépendante de la place en mémoire, par exemple si on on tolère 1% d’erreur, le journal prendra au maximum 12kb, permettant de compter jusqu’à 2^64 items.

Bref, si vous faites juste un compteur à afficher en pied de page de votre site, c’est un très bon compromis. On peut accepter d’avoir un peu plus ou un peu moins de visiteurs que la réalité qui s’affiche, sachant que la stat elle-même n’est pas vraiment réprésentative de la réalité (IP != de visiteurs uniques).

En Python, il existe une lib (uniquement 2.7 il me semble) pour ça :

>>> import hyperloglog, random
>>> hll = hyperloglog.HyperLogLog(0.01)  # on accepte une erreur de 1%
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("119.250.66.95")
>>> print len(hll)  
1
>>> hll.add("219.81.118.147")
>>> print len(hll)  
2
>>> for x in xrange(1000):
... ip = ".".join(str(random.randint(1, 255)) for x in range(4))
... print ip
... hll.add(ip)
114.208.49.91
11.72.239.16
67.56.229.66
191.62.59.163
61.104.232.43
110.58.69.141
246.123.30.234
244.246.65.219
98.93.193.114
185.143.143.69
191.177.161.213
...
>>> print len(hll) # 1000 items unique. Environ :)
1004

Vous pouvez également profiter de l’HyperLogLog via une extension PostGres ou en utilisant une version récente de Redis.

La plupart des compteurs sur les sites sont complètement bidons, alors vous, honnête que vous êtes, embrassez l’approximatif ! C’est presque la vérité. Presque.

]]> http://sametmax.com/le-compteur-mal-connu-lhyperloglog/feed/ 13 NoSQL : arrêtons de dire n’importe quoi 39 http://sametmax.com/nosql-arretons-de-dire-nimporte-quoi/ http://sametmax.com/nosql-arretons-de-dire-nimporte-quoi/#comments Sat, 22 Mar 2014 10:26:06 +0000 http://sametmax.com/?p=9844 J’ai regardé le mouvement NoSQL évoluer au fil des années. On y retrouve à peu près tout ce qui fait l’informatique depuis que le monde IT est monde : brillance et troll, hype et génie, utile et gadget, buzz et fact, sam et max, etc.

De plus on peut mettre n’importe quoi sous le label NoSQL, et du coup ça a été fait. En fait un fichier est déjà une base de données NoSQL :)

Mais rant mise à part, des projets comme redis, riak, elastic search ou mongodb changent vraiment la donne.

Malheureusement, tout comme d’autres technos du moment (prog asychrone, tout-http, pre-processeurs, generateurs…), les gens ont tendance à l’utiliser comme la barre de fer, la silver bullet, le passe-partout, le tournevis sonique, bref, le truc à tout faire.

L’adage populaire dit “quand on a un bon marteau, tous les problèmes ressemblent à des clous”. Or, je constate qu’au dessus de ça, les dev appliquent aussi souvent le dicton préféré d’un de mes colocs : “arrête de taper si fort, prend un plus gros marteau”.

Ca donne du NoSQL utilisé partout, pour tout, brandi comme LA solution, vendu à des débutants comme une panacée de traitement d’informations. Zob, vous vous doutez bien que ça pose problème, non ?

Anti-fact 1 : NoSql, c’est plus facile pour démarrer

Il n’existe pas à l’heure actuelle de base NoSQL embarquée qui arrive à la cheville de SQLite : ça marche partout, dans tous les langages, sans rien à avoir installer ou configurer pour la plupart des langages.

Dès que vous demandez à un débutant d’installer un truc, vous rajoutez une barrière d’entrée énorme.

De plus, il y a beaucoup, beaucoup, beaucoup plus d’hébergeurs qui fournissent du SQL que du NoSQL en solution par défaut. Et comme toute les technos legacy, il y a 100 fois plus de doc.

Enfin, il y a la fameuse question du “quoi” ? Quel système allez-vous installer ? Couch ? Casssandra ? Mongo ? On parle de NoSQL, ou de schemaless ? C’est pas la même chose ? Memcache et Redis, c’est que pour le cache ? Elastic Search, c’est que pour la recherche de texte ? Les données géographiques, je les mets dans quoi, mongo ou un GIS spécialisé ? Attends, j’ai entendu parler d’une super bdd de graph…

L’abondance de solutions, le manque de recul et les informations contradictoires disponibles rendent non seulement le choix difficile, mais en plus hasardeux. Car contrairement au monde du SQL, se gourrer en NoSQL peut vous pourir toute votre archi.

Souvenez-vous qu’il est beaucoup plus difficile de migrer son système de base de données NoSQL d’une solution à une autre car il n’y a pas ce petit détail en commun entre les produits : le SQL justement.

Anti-fact 2 : Avec NoSql, pas besoin de réfléchir à son modèle de données

Je crois que c’est ce qui me fait le plus grincer des dents. Les gens qui disent qu’on peut tout mettre dedans, hop, et on verra plus tard. J’ai vu les pires modèles de données possibles stockés en MongoDb ou Redis, parceque les gars qui avaient travaillé dessus avait juste dumpé leurs données sans réfléchir.

Une base NoSQL ne vous oblige pas à formaliser votre schéma, mais ça ne veut CERTAINEMENT PAS dire qu’il ne faut pas le faire. L’auteur de Redis a très bien expliqué le problème (je graisse pour donner une effet dramatique et puissant au message) :

Redis is not the kind of system where you can insert data and then argue about how to fetch those data in creative ways. Not at all, the whole idea of its data model, and part of the fact that it will be so fast to retrieve your data, is that you need to think in terms of organising your data for fetching. You need to design with the query patterns in mind.

C’est vrai pour tout système dans lequel on met ses données, SQL, NoSQL, fichier, mémoire, le tiroir de votre bureau…

Il faut penser au type des données, leurs formats, les relations entre les éléments, comment et à quelle fréquence vous allez les écrire, les lire, garantir la consistence de leurs relations et leur fraicheur (ou pas d’ailleurs, mais il faut en faire le choix). Les bases SQL sont contraignantes parce qu’elles vous obligent à penser, dès le début, en ces termes.

C’est vrai, vous êtes de grandes personnes, a priori vous savez ce que vous faites, vous n’avez pas besoin qu’on vous FORCE à le faire. C’est pour ça que j’aime le typage dynamique. Je ne veux pas que tu me demandes mes papiers pour déclarer une variable, je sais ce que je fais.

Seulement il faut le faire, et ce n’est souvent pas fait. Pire, le modèle n’est généralement jamais formalisé NULLE PART. Un schéma, c’est une doc. Sans doc, le coût d’entrée dans votre projet est élevé, sa maintenance est galère, le potentiel de bug lors de l’évolution est plus grand. Mais une doc c’est chiant à écrire et à tenir à jour.

C’est un des intéressants effets secondaires des ORMs : les classes de définition sont le modèle documenté dans sa structure, ses relations, ses limites, ses contraintes, ses tests et vérifications, etc. La doc par le code, j’adore.

Anti-fact 3 : NoSql, c’est plus performant

A chaque fois qu’on lit “x est plus performant que z”, il faut faire une pause et réfléchir deux minutes. Généralement il y a un piège.

Les performances, ça dépend toujours du contexte. Par exemple, Redis est plus performant à la lecture et l’écriture, mais les données doivent tenir en RAM, sinon ça bouffe sur la mémoire virtuelle. Autre chose, Redis est très lent à démarrer sur des gros jeux de données (ça peut aller à plusieurs minutes si vous avez des Go). MongoDB doit normalement pouvoir tenir une augmentation de charge de manière prédictive en rajoutant des noeuds. Mais sur un seul noeud, c’est toujours moins performant qu’un PostGres. Et 0.01 % des sites ont besoin de plus d’un serveur.

Par ailleurs, les performances sont très dépendantes de l’anti-fact 2. Il faut créer les bons index, avoir un cache correctement ajusté, faire des requêtes intelligentes. Pour tous les systèmes.

Bref, encore une fois, NoSQL n’est pas une techno magique. Il est contre-productif, et j’ai envie de dire même irrespectueux envers ses collègues, de la vendre comme telle.

Anti-fact 4 : NoSQL remplace le SQL

Tweeter tourne sur MySQL ET Memcache.

Stackoverflow utiliser SQL Server 2008 ET Redis.

Il y a carrément des sites qui utilisent PostGres et MongoDb en parallèle. En fait, il y a des outils pour les faire collaborer.

Nous sur notre plus gros site on utilise Redis pour les sessions, les compteurs, les crawlers, les queues et passer des données entre process. Pas juste pour le cache. On utilise PostGres pour les données complexes avec des queries lourdes. Et on utilise Solr pour le moteur de recherche.

Les bases NoSQL sont des nouveaux outils, qui sont mieux adaptés à CERTAINS usages ou à CERTAINS contextes. Pas tout, tout le temps, partout. C’est un outil en plus, pas un obligatoire remplaçant.

Par ailleurs, on peut utiliser PostGres comme une base de données clé-valeur ou JSON, on peut mettre SQLite complètement en mémoire vive, on peut utiliser MySQL comme un moteur de recherche de texte… Multiplier les points of failures dans une archi n’est pas toujours une bonne idée. Ces outils qu’on considère comme de l’histoire ancienne ont beaucoup plus de ressource que vous ne l’imaginez. Ils sont ultra performants. Des années et des années d’optimisation.

Le monde de la tech n’est jamais lisse. Jamais.

]]>
http://sametmax.com/nosql-arretons-de-dire-nimporte-quoi/feed/ 39
La stack techno qu’on utilise pour faire un site Web, et pourquoi 29 http://sametmax.com/la-stack-techno-quon-utilise-pour-faire-un-site-web-et-pourquoi/ http://sametmax.com/la-stack-techno-quon-utilise-pour-faire-un-site-web-et-pourquoi/#comments Mon, 11 Nov 2013 06:40:38 +0000 http://sametmax.com/?p=7648 Une stack techno n’est pas une référence. Il n’y a pas de combo absolu qui rox absolument tout, c’est une question de contexte technique, financier, humain…

Mais c’est vrai que ça aide bien d’avoir sous les yeux les pratiques des autres.

Je ne vais pas expliquer pourquoi Python, je l’ai déjà fait.

Commençons plutôt par la partie purement Web, pour laquelle on utilise Django, le framework Web Python.

Max et moi avons tout deux fait du PHP avant, j’ai tâté des frameworks internes, du Symfony et plus tard du Zope. J’ai regardé du côté de Pyramid et de ses prédécesseurs, et Django est celui qui me plaît le plus. J’ai juste un peu forcé la main à Max :-)

Car oui, le framework a été avant tout un choix de goût.

Ce n’est pas un choix de performances : le framework n’a aucun impact dessus. Aucun. Les architectures ont un impact. Le framework, non. Votre bottleneck sera sur les IO, pas sur le CPU. Le choix de technos asynchrones peut avoir un impact, mais ce n’est pas une question de framework. Tornado, Twisted ou NodeJS, on s’en fout.

Donc Django, essentiellement parce qu’il me plait. Et il me plaît pour ces raisons :

  • Il y a un bon équilibre entre découplage et intégration. En général c’est soit très découplé et mal intégré, soit très bien intégré et très couplé.
  • C’est bien foutu et bien documenté. Et c’est stable. Vraiment très stable. Les core devs sont hyper sérieux.
  • C’est très versatile et ça peut faire plein de trucs out of the box, petits comme gros.
  • C’est assez facile à apprendre. Ça reste un framework, donc ce n’est pas la plus simple des démarches, mais dans le royaume des frameworks de cette taille, ça reste vraiment le plus simple.
  • La communauté est fantastique : il y a des centaines d’apps qui couvrent pratiquement tous les besoins.
  • Et bien entendu, c’est en Python.

En terme de base de données, on a fait du MySQL pendant longtemps. Ça a plutôt bien marché. Maintenant je commence mes nouveaux projets avec PostGres, qui est plus solide. Parfois je fais juste du Sqlite, parce que ça suffit.

Pas de NoSQL. Après plusieurs expériences avec MongoDB et CouchDB, je n’ai pas été convaincu que les bénéfices dépassaient le coût. Il faudrait un article complet là-dessus (qu’on m’a d’ailleurs demandé).

Question OS. c’est du CentOS avec Max (il a plus l’habitude) ou du Ubuntu Server pour mes autres projets. Je reste sur les LTS. Ce n’est pas un choix très réfléchi, c’est surtout par habitude.

Pas de machine virtuelle. On a essayé, sans y trouver un grand intérêt :

  • Il faut quand même faire des scripts de migration, donc autant s’en servir pour le déploiement.
  • On perd en perfs.
  • Les erreurs liées au mal-fonctionnement d’une VM sont absolument indébuggables.
  • Si on ne fait pas la VM soit-même, il faut mettre ses couilles dans les mains d’un prestataire de service. J’ai horreur de ça.
  • Trouver des gens avec la compétence pour gérer une VM, c’est difficile. Un script de déploiement, c’est du code que tout dev saura déjà lire. Par extension ça veut dire que je m’y replonge facilement des semaines plus tard.

Et donc pour le déploiement, j’utilise fabric, avec fabtools.

Ce n’est pas la solution la plus efficace, d’autant que ça limite à Python 2.7, mais c’est la plus simple. C’est juste du code Python. N’importe qui peut comprendre le déploiement en 15 minutes. Ça se modifie vite, s’adapte facilement.

Il faut comprendre qu’on a jamais plus d’une dizaine de serveurs pour un projet, ces choix sont donc faits en fonction de cela. Il va sans dire que si vous gérez un parc de centaines de machines, ça ne sera pas du tout le même choix technique. Peut être que Chef ou des VM seront alors carrément plus intéressants. Peut être que le NoSQL et sa capacité au scaling sera bien plus rentable.

Il ne s’agit pas de décrier les technos que nous n’utilisons pas. Il s’agit juste de dire, voilà les choix que nous avons faits, dans tel contexte, pour telles (bonnes ou mauvaises) raisons.

Durant les dernières années, on a ajouté Redis à notre stack. C’est un outil fantastique qui sert à tout : de la base de données pour les trucs simples (il y a des fois ou un schéma est overkill) à la solution de caching. C’est ce qu’on a de plus proche du NoSQL.

L’outil est tellement simple à installer (vraiment le degré zero de la maintenance, c’est beau) et à utiliser que ça ne vaut juste pas le coup de s’en priver.

Du coup, plus de memcache. Toutes les grosses requêtes sont sauvegardées dans Redis, dès qu’on fait un script qui a besoin de persistance temporaire, Redis, pour communiquer entre plusieurs process, Redis, pour toutes les opérations qui ont besoin de grosses perfs comme les stats, Redis. Vive Redis.

D’ailleurs on utilise Redis aussi comme broker pour notre gestionnaire de queues et de taches : celery. Si vous pythonez, je vous recommande chaudement celery pour toutes les tâches en background, les crawlers, les chaînes de process, etc.

On a aussi du moteur de recherche. Là on tape dans du Solr (avec haystack). C’est très puissant, en tout cas syntaxiquement car ça ne fait pas de sémantique. Ne vous attendez donc pas à rattraper Google. Mais c’est aussi méga chiant à configurer et très lourd. Je pense qu’un jour on va migrer sur ElasticSearch, mais c’est pas la priorité. Don’t fix what ain’t broken.

Devant tout ça on a Nginx. Comme beaucoup on a fait Apache => Cherokee => lighttp => nginx. Et franchement, je ne reviendrai jamais en arrière : plus léger, plus rapide, plus facile à installer et à configurer, plus versatile. Nginx fait tout, et mieux.

En proxy on a du gunicorn. Parce qu’on avait la flemme de configurer uwsgi et qu’on a pris l’habitude.

Après on utilise plein de libs, de petits outils, etc. Mais ça c’est le gros de notre archi.

]]>
http://sametmax.com/la-stack-techno-quon-utilise-pour-faire-un-site-web-et-pourquoi/feed/ 29
Files de tâches et tâches récurrentes avec Celery 24 http://sametmax.com/files-de-taches-et-taches-recurrentes-avec-celery/ http://sametmax.com/files-de-taches-et-taches-recurrentes-avec-celery/#comments Sat, 27 Jul 2013 07:57:12 +0000 http://sametmax.com/?p=6880 Quand on a à traiter des choses bloquantes, avec des dépendances, des flux complexes ou des actions répétitives, créer des files d’attente peut se révéler très judicieux.

Par exemple lancer la génération d’un gros zip sur le clic d’un utilisateur, télécharger plein fichiers en parallèle pour son site de cul, lancer des calculs sur plusieurs machines et récupérer le résultat, encoder des videos en arrière plan, etc.

Le problème, c’est que fabriquer des files d’attente à la main, ça mène généralement à une grosse galère. La première boîte dans laquelle j’ai travaillé avait tout un système de queues à base de PHP + SQL fait à la main qui tapait dans du MySQL, c’était pas marrant du tout

Je fais une pause, et je note que le potentiel de jeux de mots sur cet article est fortement élevé. Mais je resterai fort.

Locking, priorité, dépendance, asynchronicité, concurrence, sérialisation, encoding, stockage, accessibilité, load balancing… Toutes ces problématiques sont bien vicieuses et chronophages. Il vaut mieux utiliser une lib solide et éprouvée.

Je resterai fort.

Kombu est une telle lib, mais elle est lourde et complexe à utiliser. J’avais fais le choix de la prendre pour un gros projet avec Max, je le regrette sur le long terme : c’est dur à maintenir et à faire évoluer. Le code est vraiment pas sympa.

Heureusement il existe une bibliothèque qui se met au dessus de Kombu pour et nous expose juste les fonctionnalités que l’on souhaite : celery.

Fort.

Celery est simple pour démarrer, mais très puissant si on rentre dans le détail, et croyez moi, le détail, on peut y rentrer très très profondément.

F…

Installation

Qui dit file d’attente, dit stockage. Il faut bien mettre les tâches quelque part et communiquer avec ce quelque part. En base de données ? Dans un gestionnaire de messages ? En mémoire ? Dans un cache ?

Celery résout le problème en proposant la même interface, quelque soit le support. Actuellement, on peut utiliser :

  • RabbitMQ
  • Redis
  • MongoDB
  • Beanstalk CouchDB
  • SQLAlchemy ou l’ORM Django (et donc toutes les bases de données supportées comme Sqlite, MySQL, PostGres…)
  • Amazon SQS

Dans notre exemple, nous allons le faire avec Redis car :

  • Redis est très simple à installer et à configurer.
  • Nous on utilise déjà du Redis partout.
  • Aucun risque de locking.

Pour ceux qui ont pas redis, c’est généralement dans les dépôts. Par exemple sur Ubuntu :

sudo apt-get install redis-server

Il n’y a rien à faire de plus, ça tourne, c’est configuré avec des valeurs par défaut qui sont saines. Je vous l’ai dis, redis, c’est fantastiquement bien foutu.

Ensuite on install celery et la lib d’accès à redis en Python qui porte un nom très original :

pip install celery redis

Ca devrait compiler un peu, et comme hab avec les extensions en C, assurez vous d’avoir un compilateur et les headers en place comme indiqué dans l’article sur pip.

Ensuite on peut créer ses tâches. Créez un module, par exemple tasks.py :

import urllib2
 
from collections import Counter
 
from celery import Celery
 
# Configuration de celery. Ceci peut aussi se faire dans un fichier de config.
# Ici on dit à celery que pour le module 'tasks', on va utiliser redis
# comme broker (passeur de massage) et comme result backend (stockage du
# resultat des tâches).
celery = Celery('tasks', broker='redis://localhost', backend='redis://localhost')
 
 
# Et voici notre première tâche. C'est une fonction Python normale, décorée
# avec un decorateur de celery. Elle prend une URL, et calcule le nombre
# de lettre "e" qu'il y a dans la page.
@celery.task
def ecount(url):
    return Counter(urllib2.urlopen(url).read())['e']

On lance ensuite le processus celery dans un terminal (en production, mettez ça dans supervisord ou systemd pour que ça démarre automatiquement) :

[test] sam ~/Bureau/celery_test $ celery -A tasks worker -B --loglevel=info

 -------------- celery@sam v3.0.21 (Chiastic Slide)
---- **** -----
--- * ***  * -- Linux-3.2.0-48-generic-x86_64-with-Ubuntu-12.04-precise
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> broker:      redis://localhost:6379//
- ** ---------- .> app:         tasks:0x2a2fa50
- ** ---------- .> concurrency: 4 (processes)
- *** --- * --- .> events:      OFF (enable -E to monitor this worker)
-- ******* ----
--- ***** ----- [queues]
 -------------- .> celery:      exchange:celery(direct) binding:celery


[Tasks]
  . tasks.ecount

[2013-07-26 13:22:21,631: INFO/Beat] Celerybeat: Starting..
[2013-07-26 13:04:51,274: WARNING/MainProcess] celery@sam ready.
[2013-07-26 13:04:51,280: INFO/MainProcess] consumer: Connected to redis://localhost:6379//.

-A précise le module à importer, -B démarre le beat (on verra ça plus tard), worker dit à celery que démarrer des processus de consommation de files d’attente (par défaut 4 qui travaillent en parallèle), et --loglevel=info va nous permettre d’avoir un affichage verbeux pour comprendre ce qui se passe.

Votre file d’attente est prête, et frétille d’impatience.

Lancer une tâche

A partir de là, vous pouvez envoyer des tâches dans la file d’attente, depuis n’importe où :

  • Un script.
  • Un serveur Web (par exemple une vue Django).
  • Un programme sur un autre serveur (même si il faudrait alors configurer redis pour qu’il écoute sur les ports extérieurs, ce qui n’est pas le cas ici par simplicité).
  • etc

Plusieurs programmes peuvent envoyer plein de tâches, en même temps, et elles vont se loger dans la file d’attente, sans bloquer le programme qui les a envoyé.

Par exemple, depuis le shell :

>>> from tasks import ecount
>>> res = ecount.delay('http://danstonchat.com')

Ceci ne bloque pas mon shell, la ligne s’exécute immédiatement. La fonction ecount n’est pas appelée depuis le shell, elle est dans la file d’attente et sera appelée par un des processus (les fameux ‘worker’) qui consomment la queue. Du côté de la file, on peut voir dans le log :

[2013-07-26 14:18:08,609: INFO/MainProcess] Got task from broker: tasks.ecount[599a52ea-ef6b-4499-981d-cd17fab592df]
[2013-07-26 14:18:09,070: INFO/MainProcess] Task tasks.ecount[599a52ea-ef6b-4499-981d-cd17fab592df] succeeded in 0.446974039078s: 1242

On a donc notre tâche qui a bien été traitée.

On peut récupérer le résultat dans le shell :

>>> res.state
'PENDING'

Ah… La tâche n’est pas encore terminée. Et un peu plus tard :

>>> res.state
'SUCCESS'
>>> res.result
1242

Lancer une tâche est bien entendu peu intéressant, les listes d’attente sont vraiment sympa quand on a plein de tâches à lancer, par plein de processus différents :

results = [ecount.delay(url) for url in ('http://google.com', 'http://sametmax.com', 'http://sebsauvage.com', 'http://multiboards.com', 'http://0bin.net', 'http://danstonchat.com')]
[2013-07-26 14:25:46,646: INFO/MainProcess] Got task from broker: tasks.ecount[5d072a7b-29f8-4ea6-8d92-6a4c1740d724]
[2013-07-26 14:25:46,649: INFO/MainProcess] Got task from broker: tasks.ecount[402f6a4f-6b35-4f62-a786-9a5ba27707d2]
[2013-07-26 14:25:46,650: INFO/MainProcess] Got task from broker: tasks.ecount[bbe46b1b-4719-4c42-bd2f-21e4d72e613e]
[2013-07-26 14:25:46,652: INFO/MainProcess] Got task from broker: tasks.ecount[8fb35186-66e2-4eae-a40c-fc42e500ab9d]
[2013-07-26 14:25:46,653: INFO/MainProcess] Got task from broker: tasks.ecount[fc63f5db-8ade-4383-b719-c3d6390ca246]
[2013-07-26 14:25:46,654: INFO/MainProcess] Got task from broker: tasks.ecount[8434e21d-79ea-4559-a90e-92e2bc2b9dc7]
[2013-07-26 14:25:47,144: INFO/MainProcess] Task tasks.ecount[bbe46b1b-4719-4c42-bd2f-21e4d72e613e] succeeded in 0.479865789413s: 27
[2013-07-26 14:25:47,242: INFO/MainProcess] Task tasks.ecount[5d072a7b-29f8-4ea6-8d92-6a4c1740d724] succeeded in 0.578661203384s: 609
[2013-07-26 14:25:47,501: INFO/MainProcess] Task tasks.ecount[fc63f5db-8ade-4383-b719-c3d6390ca246] succeeded in 0.35736989975s: 263
[2013-07-26 14:25:47,645: INFO/MainProcess] Task tasks.ecount[8434e21d-79ea-4559-a90e-92e2bc2b9dc7] succeeded in 0.403187036514s: 1270
[2013-07-26 14:25:47,815: INFO/MainProcess] Task tasks.ecount[8fb35186-66e2-4eae-a40c-fc42e500ab9d] succeeded in 1.14100408554s: 23
[2013-07-26 14:25:49,010: INFO/MainProcess] Task tasks.ecount[402f6a4f-6b35-4f62-a786-9a5ba27707d2] succeeded in 2.34633708s: 3158

Car du coup on sait que ces multiples tâches ne vont pas bloquer le processus en cour, mais qu’en plus la charge sera répartie sur le nombre de workers qu’on a décidé au départ, ni plus (surcharge du serveur), ni moins (traitement trop lent).

Comment je sais quand une tâche est terminée ?

On peut attendre que la tâche soit terminée :

>>> print res.wait()
9999

Mais ce n’est pas vraiment le but. On cherche avant tout à ce que les tâches soient non bloquantes, et exécutées dans un processus à part voir potentiellement distribuées sur plusieurs serveurs.

Par ailleurs, Celery n’est pas un remplacement d’un système de traitement asynchrone comme Tornado ou NodeJS, il n’est pas fait pour envoyer des réponses asynchrones à l’utilisateur. Il est fait pour faire des tâches en background, répartir la charge et ordonner le traitement. Bien entendu, on peut faire communiquer un système asynchrone avec celery comme ici ou ici, mais c’est une autre histoire.

Concentrons nous sur les tâches.

La question de “Comment je sais quand une tâche est terminée ?” est souvent traduisible par “comment je réagis à une tâche pour lancer du code quand elle s’est terminée sans erreur ?”.

Et là, il y une solution toute simple :

res = tache1.s(arg1, arg2) | tache2.s() | tache3.s(arg1)

Ceci va créer une chaîne de tâches. Quand la première se termine, la deuxième se lance en recevant le résultat de la première en argument.

s() fabrique une sous-tâche, c’est à dire une tâche à envoyer dans la file plus tard avec des arguments pré-enregistrés. Dans notre exemple, celery va lancer tache1 avec deux arguments, puis si ça marche, va appeler tache2 en lui passant le résultat de tache1 comme argument, puis si ça marche, va appeler tache3 avec le résultat de tache2 en premier argument et arg1 en second argument.

En fait, celery vient avec tout un tas d’outils pour exécuter des tâches dépendantes les unes des autres : par groupes, par chaînes, par morceaux, etc. Mais de toute façon, vous pouvez appeler une tâche… à l’intérieur d’une autre tâche. Donc parti de là vous pouvez faire pas mal de choses.

Comment je fais pour faire une tâche récurrente ?

C’est là qu’intervient le “beat” dont j’ai parlé tout à l’heure. Avec cette option, celery va vérifier toutes les secondes si il n’y a pas une tâche répétitive à lancer, et la mettre dans une file d’attente, à la manière d’un cron.

Il suffit de définir une tâche comme periodic_task pour qu’elle soit lancée régulièrement.

import smtplib
 
from celery.schedules import crontab
from celery.decorators import periodic_task
 
# va executer la tâche à 5h30, 13h30 et 23h30 tous les lundi
# run_every accepte aussi un timedelta, pour par exemple dire "toutes les 10m"
@periodic_task(run_every=crontab(hour='5,13,23', minute=30, day_of_week='monday')
def is_alive():
    """
        Vérifie que le blog est toujours en ligne, et si ce n'est pas le cas,
        envoie un mail en panique.
    """
    if urllib2.urlopen('http://sametmax.com').code != 200:
        mail = 'lesametlemax__AT__gmail.com'.replace('__AT__', '@')
        server = smtplib.SMTP('smtp.gmail.com:587')
        server.starttls()
        server.login('root', 'admin123')
        server.sendmail(mail, mail, msg)
        server.quit()

Il y a de bons exemples sur la syntaxe sur crontab() dans la doc.

D’une manière générale, la doc de Celery est très très riche, donc plongez vous dedans si cet article ne répond pas à vos besoins, car si ça peut être mis dans une file, ça peut être fait par Celery.

Note de fin

Celery n’autoreload pas le code, donc redémarrez les workers à chaque fois que vous modifiez vos tasks.

Attention aussi aux tâches récurrentes, la suivante peut se lancer avant que la précédente soit terminée. C’est à vous de faire des tâches idempotentes, ou alors de mettre en place un système de locking.

]]>
http://sametmax.com/files-de-taches-et-taches-recurrentes-avec-celery/feed/ 24
Comment Redis et Memcache font expirer leurs données 12 http://sametmax.com/comment-redis-et-memcache-font-expirer-leurs-donnees/ http://sametmax.com/comment-redis-et-memcache-font-expirer-leurs-donnees/#comments Thu, 28 Feb 2013 12:04:02 +0000 http://sametmax.com/?p=5184 Redis et Memcache ont des opérations d’insertion de complexité O(1) (très rapide). Malgré ça ils proposent tout deux l’expiration de clé.

Et je me suis demandé : comment ils font ? Comment je pourrais fais ça si je devais le coder en Python ?

En effet, rechercher une clé expirée prend du temps. J’ai pensé à utiliser heapq, mais l’insertion serait en 0(log(n)) (temps dépendant du nombre d’items), donc rapide, mais loin des perfs de Redis et Memcache.

Même si on garde à l’esprit qu’ils sont codés en C, et donc plus rapide, ça m’a turlupiné. Comment ils font ces cons ?

La réponse est surprenant : ils font la même chose que 0bin !

Memcache :

Memcache doesn’t evict keys when their expiration time expires, as doing so isn’t possible while still guaranteeing O(1) operation times. Instead expiration is more of a way to say how long until a key should be considered stale. When a GET is performed, Memcache checks if the key’s expiration time is still valid before returning it.

Redis :

Redis keys are expired in two ways: a passive way, and an active way. A key is actively expired simply when some client tries to access it, and the key is found to be timed out. Of course this is not enough as there are expired keys that will never be accessed again. This keys should be expired anyway, so periodically Redis test a few keys at random among keys with an expire set. All the keys that are already expired are deleted from the keyspace.

Memcache et Redis ne cherchent donc pas les clés, ils vérifient l’expiration à l’accès de la clé et suppriment le contenu si c’est dépassé.

Redis va plus loin en régulièrement prenant des clés au hasard et en checkant leur date d’expiration.

Gif de Tealc' buvant un café très chaud

Comment il fait ? Ah bah il fait pas.

]]>
http://sametmax.com/comment-redis-et-memcache-font-expirer-leurs-donnees/feed/ 12
‘Redis’ object has no attribute ‘connection’ http://sametmax.com/redis-object-has-no-attribute-connection/ http://sametmax.com/redis-object-has-no-attribute-connection/#comments Wed, 20 Feb 2013 11:10:21 +0000 http://sametmax.com/?p=4461 Solution très conne à un problème très con.

pip install --upgrade redis django-redis-cache django-redis-sessions

L’api de la lib Python Redis a changé, et si vous utilisez des libs qui en dépendent, certains seront plus à jour. C’est balot, mais ça nous a turlupiné.

]]>
http://sametmax.com/redis-object-has-no-attribute-connection/feed/ 0
Les limites théoriques de redis 4 http://sametmax.com/les-limites-theoriques-de-redis/ http://sametmax.com/les-limites-theoriques-de-redis/#comments Sun, 22 Jul 2012 19:55:14 +0000 http://sametmax.com/?p=1267 Nombre de clés max: 2^64-1
Nombre d’éléments max dans un set: 2^64-1
Nombre d’éléments max dans une liste: 2^32-1
Nombre d’éléments max dans une liste: 2^64-1
Taille maximum d’une clé: 2^31 octets
Taille maximum d’une valeur de type string: 512 Mo
Nombre max de DB: virtuellement infinie, mais s’attendre à des problèmes de perf si on dépasse 1000, et le fichier de conf met la limite à 16 par défaut.

Ca va, il y a de la marge.

]]>
http://sametmax.com/les-limites-theoriques-de-redis/feed/ 4
Redis – Effacer plusieurs clefs à l’aide la commande * (wildcard) http://sametmax.com/redis-effacer-plusieurs-clefs-a-laide-la-commande-wildcard/ http://sametmax.com/redis-effacer-plusieurs-clefs-a-laide-la-commande-wildcard/#comments Sun, 17 Jun 2012 12:37:53 +0000 http://sametmax.com/?p=927 Lorsque l’on utilise Redis on a du mal à ne pas y mettre de tout et n’importe quoi, du coup on se retrouve avec plein de données en RAM. Il arrive au moment d’une update où l’on aimerait bien effacer certaines clefs en mémoire. Voici une petite astuce qui m’a permis de sélectivement effacer la clef video-id.

redis-cli KEYS "video-id:*" | xargs -0 redis-cli DEL
]]>
http://sametmax.com/redis-effacer-plusieurs-clefs-a-laide-la-commande-wildcard/feed/ 0