PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Mise à jour

Mise à jour de la base de données, veuillez patienter...

Comment écrire une annonce pour recruter des dev

vendredi 17 octobre 2014 à 13:41

Je critique souvent les offres d’emploi, mais l’art est difficile, alors voici quelques lignes directrices si cela vous arrive un jour.

Ne mettez pas les RH en avant

Les RH ne doivent PAS rédiger l’annonce. Le moyen de contact ne doit PAS être celui des RH.

Je ne dis pas que les RH ne doivent pas faire partie du processus de recrutement. Mais je recommande qu’ils s’en tiennent à l’administratif et à l’accompagnement de l’équipe technique dans la démarche.

Ils ne sont tout simplement pas qualifiés pour ça.

Ce n’est pas une insulte, c’est un constat que je fais tous les jours. Ils ne sont pas capables d’évaluer un profil technique correctement. Ils recalent trop facilement de bons profils pour délit de sale gueule (courant en info). Ils ne savent pas lire un CV tech, ils vont juste comparer les besoins de l’annonce avec le papier.

Mettez les membres de l’équipe qui va accueillir la recrue comme contact, et demandez leur de rédiger l’annonce.

Cela a de nombreux avantages :

Bien entendu, il est important que les RH les accompagnent dans le processus. Les techos n’y connaissent rien en recrutement. C’est un travail d’équipe. Mais ils doivent être ce qui est mis en avant, le premier filtre, et le dernier mot.

Les infos importantes

Trop souvent je vois des annonces qui ne listent pas les infos essentielles. Voici une liste des choses qui DOIVENT figurer sur l’annonce :

A l’inverse, supprimez le bruit : expressions à la mode, jargon corporate, formules toutes faites… Les devs sont sensibles à ce genre de chose, et il est facile de passer pour des charlots en voulant rajouter du nutella sur la confiture.

Les trucs à oublier :

Évitez que votre annonce ressemble à ça.

Soyez pragmatique

Tous les parents trouvent qu’ils ont le plus beau bébé du monde, mais ils vous les cassent quand ils vous montrent les photos en vous clamant qu’il est fantastique.

Votre boîte pète peut-être du Chanel numéro 5 à vos yeux, mais à moins d’avoir une attractivité incroyable (auquel cas vous n’avez probablement pas besoin de mettre une annonce), tout le monde s’en fout.

Bref, n’exigez pas un bac+x pour votre poste, il n’en a très probablement pas besoin. Mettez uniquement les talents requis techniques et humains. Ceux indispensables. Oubliez la limite d’âge, de diplôme et autres caractéristiques purement sociétales.

La seule chose qui compte, c’est que le mec fasse son job, et ça, il n’y a qu’un moyen de le vérifier : le voir bosser. Donc préparez des tests techniques, et organisez la période d’essai. Souvenez-vous qu’un gars débrouillard se formera sur le tas sans problème en info, c’est l’essence de notre taff.

Enfin, si vous voulez un mec bon, il va falloir se montrer attractif. On lui proposera 80k à Londres pour le même poste, trouvez un moyen de matcher ça.

Et si vous voulez un crack, souvenez-vous qu’il peut avoir 200k aux US. Le fait qu’il soit en France montre qu’il est intéressé par autre chose que l’argent. Cherchez ce que c’est, et tentez de devenir attractif en rapport avec ce point. Il y a bien d’autres choses que du pognon : télétravail, horaires flexibles, matériel de qualité, aide pour la famille s’il a des mômes, responsabilités inhabituelles pour quelqu’un d’aussi jeune, etc.

Mais la plupart du temps vous n’avez pas besoin d’un tueur. Vous avez besoin d’un mec normal, qui fait son boulot normalement. Alors ne faites pas votre processus de recrutement comme si vous cherchiez le prochain Linus Torvalds. Soyez réaliste, et vous trouverez plus rapidement ce dont vous avez besoin.

Je ne veux pas dire qu’il faut recruter n’importe quel boulet. Mettez les (nombreux) incompétents de côté rapidement, et efficacement, sans remord. Néanmoins, descendez de vos grands chevaux. Trop de recruteurs visent bien plus haut que ce dont ils ont besoin, mais aussi de ce qu’ils valent. Et de ce qu’ils peuvent payer.

L’offre et la demande ne sont pas en votre faveur, et ça ne va pas s’améliorer. Je vois tous les jours les résultats désastreux de mauvaises embauches, si vous voulez éviter ça, il faut se remettre en question.

Exemple

Cherche Dev Backend Python

Titre simple, court, qui permet d’être scanné facilement. Votre annonce ne sera pas lue par 99% des chercheurs, elle sera listée sur une page au milieu de centaines d’autres. Pour cette raison, mettez des mots-clés qui permettent facilement de la lister. “cherche”, “dev” et “python” font une query simple pour Google, Twitter, ou le meta moteur des sites de jobs.

Mon équipe de 3 dev a besoin d’un(e) pythonista pour travailler sur notre crawler de pages. La mission sera dans un premier temps de rajouter de nouvelles sources d’information à parser. Cela inclut une batterie de tests et de la documentation. D’autres tâches ponctuelles comme de la correction de bugs de notre ancien système et de la refactorisation des précédents crawlers sont à prévoir.

On commence par le quoi. C’est ce qui intéresse le plus la recrue, il veut savoir ce qu’il va foutre là-bas. Pas besoin de rentrer dans trop de détails, il faut juste en donner assez pour être clair, et pas trop pour l’inciter à appeler pour en savoir plus.

On utilise la première personne, ce qui donne un sentiment de proximité. “pythonista” est un terme spécifique à ce type de dev, montrant que l’annonce a été rédigée par quelqu’un qui s’y connaît.

Nous : LambdaBoite est spécialisée dans la vente de LambdaProduit, notre but étant de satisfaire LambdaBesoin de LambdaTypeDeClient.

Vous : on se fiche de savoir si vous avez 23 ans, portez des piercings ou si vous êtes sur une chaise roulante pourvu que vous livriez les features à temps.

On présente rapidement l’entreprise de manière générale. Un lien vers une page plus détaillée peut être inséré. On note les exigences humaines. Si la ponctualité est importante, mettez le. Ces lignes sont la relation personnelle que vous voulez établir. C’est vous, et lui/elle. C’est aussi le moment de mettre les recrues à l’aise en faisant un contraste. Ici, c’est carte blanche. Si vous avez des valeurs, exposez les. Si vous avez des choses amusantes à dire, dites les. Si ce sont des phrases marketings dictées par le département com interne (“we value…”), faites-les sauter.

Profil technique :
– Capable d’écrire en Python : un décorateur maison, un code téléchargeant des ressources en ligne avec gestion des erreurs, une interface en ligne de commande avec argparse.
– sait créer et déployer un daemon sous Linux Fedora en prod via supervisor.
– sait faire une branche et un merge git.
Vous serez testé à l’entretien.
Sur place vous travaillerez avec :
– de la programmation asynchrone.
– PosGres.
– Le framework Scrapy.
Ces notions ne seront pas testées, vous pourrez les apprendre sur le tas.

Plutôt que d’utiliser des termes vagues comme “bon niveau”, “senior”, etc., donnez des moyens précis de mesurer un niveau. Notez qu’on ne parle pas de diplôme ici. Le meilleur prog que je connais a Bac -2.

On sépare l’indispensable du “est un plus”. Mais plutôt que de le tourner en exigence, on le liste comme quelque chose d’attractif.

Date de recrutement souhaitée : 3 mars
Contrat : CDD dans un premier temps, possible recrutement en CDI.
Rémunération : 30-40k
Contact : wololo@lambdaboite.com ou 0123456789 (Linette Michalon, CTO et votre futur boss)
Adresse : 69 avenue du coin de la rue 69777 La villette sur roustifailles (votre bureau vous y attend)
Site Web : http://lambdaboite.com/travaillez-chez-nous

Nature du contrat, contact avec qui, quoi et où puis lien vers une carte.

On notifie que l’adresse est bien l’endroit où l’on va travailler et pas juste le centre administratif.

Un lien vers plus de détails sur les conditions de travail (temps pour se déplacer, transport, photos des bureaux, liens vers l’équipe, présentation des produits, etc) est bienvenu.

Et oui, rédiger une annonce prend du temps. Recruter correctement prend du temps.

Copier / coller

Comme je sais que les gens aiment bien les templates, voici la version intégrale, prête à être copiée.

Mais je déconseille de l’utiliser telle quelle. Tout comme un CV doit être réécrit pour chaque poste qu’on vise, une annonce doit l’être pour chaque poste qu’on veut pourvoir.

Cherche Dev Backend Python

Mon équipe de 3 dev a besoin d’un(e) pythonista pour travailler sur notre crawler de pages. La mission sera dans un premier temps de rajouter de nouvelles sources d’information à parser. Cela inclut une batterie de tests et de la documentation. D’autres tâches ponctuelles comme de la correction de bugs de notre ancien système et de la refactorisation des précédents crawlers sont à prévoir.

Nous : LambdaBoite est spécialisée dans la vente de LambdaProduit, notre but étant de satisfaire LambdaBesoin de LambdaTypeDeClient.

Vous : on se fiche de savoir si vous avez 23 ans, portez des piercings ou si vous êtes sur une chaise roulante pourvu que vous livriez les features à temps.

Profil technique :

– Capable d’écrire en Python : un décorateur maison, un code téléchargeant des ressources en ligne avec gestion des erreurs, une interface en ligne de commande avec argparse.
– sait créer et déployer un daemon sous Linux Fedora en prod via supervisor.
– sait faire une branche et un merge git.

Vous serez testé à l’entretien.

Sur place vous travaillerez avec :

– de la programmation asynchrone.
– PosGres.
– Le framework Scrapy.

Ces notions ne seront pas testées, vous pourrez les apprendre sur le tas.

———-

Contrat : CDD dans un premier temps, possible recrutement en CDI.
Rémunération : 30-40k.
Contact : wololo@lambdaboite.com ou 0123456789 (Linette Michalon, CTO et votre futur boss)
Adresse : 69 avenue du coin de la rue 69777 La villette sur roustifailles (votre bureau vous y attend)
Site Web : http://lambdaboite.com/travaillez-chez-nous

Sortir du cadre

Ce que je viens de faire là, c’est une annonce générique. La vérité c’est que je n’écrirais pas une annonce comme ça moi-même. Une fois que vous avez maîtrisé ce qui est important pour vous et la recrue, vous pouvez adapter le style pour en faire quelque chose de plus personnel, et souvent plus court. L’idéal est de faire transparaître l’état d’esprit de la boîte via l’annonce.

Bon évidemment si vous recrutez pour une énorme boîte dinosaure type EDF, vous êtes baisé.

Pour finir, n’oubliez pas que l’annonce n’est pas la manière à préférer pour recruter. Le bouche à oreille, les événements techniques (conf, rassemblements, etc.), les sites communautaires et même les restos / bars sont de bien meilleurs sources. Donc mettez une annonce en ligne, puis allez à la Pycon à Lyon la semaine prochaine ou allez boire un verre au Cardinal sur Richelieu à Paname. Ca marche mieux.

Ceci n’est pas une pipe

jeudi 16 octobre 2014 à 11:26

Ni un forum. Ni un chan IRC. C’est un blog.

Pour cette raison, il est inutile de demander de l’aide en commentaire. Faites-le dans un endroit qui est adapté pour cela.

Exemple, pour Python :

Un commentaire se doit d’être en lien avec l’article posé, ou un autre commentaire. La seule exception est pour les anciens du blog, qui ont carte blanche car ils ont participé à la création de la communauté, et c’est un peu chez eux.

Abordons maintenant le point délicat du formulaire de contact. Il peut servir à poser des questions qui ne sont pas liées à un article, pourvu que :

Si vous ne le faites pas et que je suis de mauvais poil, la réponse risque de ne pas être celle attendue. Pour vous donner un exemple, voici cet article tel que je l’aurais écrit un jour grincheux :

Ceci n’est pas une pipe

Ni un forum. Ni un chan IRC. C’est un blog.

Si vous avez une question posez la plutôt sur un forum d’entre-aide, vous aurez une chance d’avoir une réponse, et vous nous éviterez de vous ajouter à la liste des milliers de personnes qui nous ont fait perdre du temps avant vous.

Car oui vous êtes le 9878eme, non, on ne vous doit rien, et enfin, ce blog demande des centaines d’heures de travail par mois. Bref, on a pas que ça à foutre de vous modérer parce que vous n’avez pas eu la politesse de vous sortir les doigts du cul pour faire une recherche sur google et trouver un endroit qui est FAIT pour poser des questions.

Exemple, pour Python :

http://www.afpy.org/forums/forum_python”>Forum de l’afpy
http://www.developpez.net/forums/f96/autres-langages/python-zope/”>Forum de devloppez.com
http://fr.openclassrooms.com/forum/categorie/langage-python”>Forum d’openclassroom
http://forum.hardware.fr/hfr/Programmation/Python/liste_sujet-1.htm”>Forum de hardware.fr

Oua, ça m’a pris 10 minutes pour trouver ces liens, et ce n’est QUE la partie fr.

Si vous n’êtes pas foutu de bouger votre fion pour trouver ces endroits, sérieusement, pourquoi voudriez-vous que je frémisse le petit doigt pour votre tronche de feignasse ?

Et si vous vous demandez pourquoi le ton de l’article est agressif, c’est parce que la masse de connards passifs me casse les couilles, et que si j’ai posté le lien, vous en faites partie.

Vous comprenez donc qu’il est préférable de garder cet espace joyeux, libre de haine et plein d’arc-en-ciels dans le cœur.

Sauvez un chaton, écrivez vos demandes d’aide sur un forum.

Appliquer un traitement à tous les fichiers d’un dossier en Python

mercredi 15 octobre 2014 à 10:35

Opération courante en informatique et on a tous eu besoin de chercher comment faire une fois.

Soit l’arborescence :

test
├── dossier
│   ├── fichier.py
│   ├── fichier.txt
│   └── pas_un_dossier.txt
├── Dossier
│   ├── dOssier
│   │   └── faichier
│   └── fichiiiiiiiiier
├── .fichier
├── fichier
├── fIchier
└── Fichier

Lister le contenu d’un dossier

>>> import os
>>> os.listdir('.')
['dossier', 'Dossier', 'fichier', 'fIchier', '.fichier', 'Fichier']

On récupère les noms des dossiers et les fichiers, y compris cachés, mais pas les dossiers spéciaux types .. et ..

Le type des noms retournés est str que ce soit en Python 2 ou 3. Ça a l’air cool et homogène comme ça, jusqu’à ce qu’on se souvienne que str sont des bits en Python 2 et de l’unicode en Python 3. Donc gaffe quand vous portez votre code d’une version à l’autre. Les noms de fichiers contiennent des caractères non ASCII dans la vraie vie vivante.

Si vous voulez récupérer uniquement les dossiers ou les fichiers, il va falloir filtrer :

>>> for element in os.listdir('/tmp/test'):
...     if os.path.isdir(element):
...         print("'%s' un dossier" % element)
...     else:
...         print("'%s' est un fichier" % element)
'dossier' un dossier
'Dossier' un dossier
'fichier' est un fichier
'fIchier' est un fichier
'.fichier' est un fichier
'Fichier' est un fichier

Pareil si on veut filtrer par extensions :

>>> for element in os.listdir('/tmp/test/dossier'):
...     if element.endswith('.txt'):
...         print("'%s' est un fichier texte" % element)
...     else:
...         print("'%s' n'est pas un fichier texte" % element)
'fichier.txt' est un fichier texte
'pas_un_dossier.txt' est un fichier texte
'fichier.py' n'est pas un fichier texte

Néanmoins, Python vient avec le module glob qui permet de demander le listing du contenu d’un dossier en appliquant des filtres Unix :

>>> import glob
>>> glob.glob('/tmp/test/dossier/*.txt')
['/tmp/test/dossier/fichier.txt', '/tmp/test/dossier/pas_un_dossier.txt']
>>> glob.glob('./dossier/*.txt')
['./dossier/fichier.txt', './dossier/pas_un_dossier.txt']

Mais comme vous pouvez le voir, le comportement n’est pas le même que listdir : les éléments de la liste sont des chemins relatifs à celui passé en paramètre si il est lui-même relatif, ou absolus si celui passé en paramètre est absolu. N’oubliez donc pas de normaliser l’entrée ou la sortie avec os.path.realpath qui retournera un chemin canonique.

Même problème Python 2/3 : le type est str dans les 2 cas.

Parcours récursif

La fonction os.walk permet de lister récursivement tous les fichiers et les dossiers à partir d’un point dans l’arborescence. C’est un générateur, et sa valeur de retour est un peu particulière et se récupère via unpacking:

for dossier, sous_dossiers, fichiers in os.walk('/tmp/test'):
    print('##### %s #####' % dossier)
    print("Sous dossiers : %s" % sous_dossiers)
    print("Fichiers : %s" % fichiers)
##### /tmp/test #####
Sous dossiers : ['dossier', 'Dossier']
Fichiers : ['fichier', 'fIchier', '.fichier', 'Fichier']
##### /tmp/test/dossier #####
Sous dossiers : []
Fichiers : ['fichier.txt', 'pas_un_dossier.txt', 'fichier.py']
##### /tmp/test/Dossier #####
Sous dossiers : ['dOssier']
Fichiers : ['fichiiiiiiiiier']
##### /tmp/test/Dossier/dOssier #####
Sous dossiers : []
Fichiers : ['faichier']

Du coup, si vous voulez avoir la liste des chemin des fichiers absolus, il faut reconstituer à la main :

for dossier, sous_dossiers, fichiers in os.walk('/tmp/test'):
    for fichier in fichiers:
        print(os.path.join(dossier, fichier))
/tmp/test/fichier
/tmp/test/fIchier
/tmp/test/.fichier
/tmp/test/Fichier
/tmp/test/dossier/fichier.txt
/tmp/test/dossier/pas_un_dossier.txt
/tmp/test/dossier/fichier.py
/tmp/test/Dossier/fichiiiiiiiiier
/tmp/test/Dossier/dOssier/faichier

Encore une fois, c’est du str partout, alors attention. L’article sur l’encoding en Python reste un des plus consultés de la categ prog.

Avec une lib qui va bien

Je n’ai jamais caché mon amour immodéré pour path.py (qui enterre tous ses concurrents, dont unipath), qui encapsule toutes les opérations des fichiers de manière simple et élégante et qui marche sous Python 2 et 3.

pip install path.py

Et c’est quand même plus facile pour le parcours récursif :

>>> from path import path
>>> for f in path('.').walkfiles():
    print f
   ....:     
./dossier/fichier.txt
./dossier/pas_un_dossier.txt
./dossier/fichier.py
./Dossier/fichiiiiiiiiier
./Dossier/dOssier/faichier
./fichier
./fIchier
./.fichier
./Fichier

En plus, f est de type path sous Python 2 et 3. C’est homogène, et ça permet de faire toutes les opérations magiques que ce type permet.

Néanmoins, si vous êtes sous Python 3.4 et que vous ne voulez pas ajouter une dépendance externe, vous pouvez utiliser le module pathlib :

>>> from pathlib import Path
>>> for p in  Path('.').glob('./**/*'):
...    if p.is_file():
...        print(p)
fichier
fIchier
.fichier
Fichier
dossier/fichier.txt
dossier/pas_un_dossier.txt
dossier/fichier.py
Dossier/fichiiiiiiiiier
Dossier/dOssier/faichier

Redis : pourquoi et comment ?

mardi 14 octobre 2014 à 13:31

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 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.

Django pleure ‘MySQL server has gone away’

lundi 13 octobre 2014 à 10:52

Dans certaines circonstances, par exemple une transaction ouverte pendant trop longtemps, MySQL ferme la connexion avec son client.

Cela arrive par exemple quand on l’utilise comme broker pour celery. On a des tâches qui plantent, et quand on met du debug, on lit un (2006, 'MySQL server has gone away') bien cryptique.

Généralement je recommande de changer de backend ici. Passer à redis pour cet usage par exemple.

Mais parfois on ne peut pas. La solution est alors de forcer Django à réinitialiser la connexion en la fermant. Il faut le faire au niveau où on a remarqué que la requête échouait. Dans notre cas, au début de chaque tâche celery :

from django.db import connection 
 
@task
def do_stuff():
    connection.close()
    # le reste du code

Voyant la connexion fermée, Django va en ouvrir une nouvelle à la prochaine requête automatiquement.

Cela a, évidement, un impact sur les performances, donc choisissez bien entre mettre une rustine et changer la roue.

Error happened! 0 - count(): Argument #1 ($value) must be of type Countable|array, null given In: /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php:428 http://ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/?88 #0 /var/www/ecirtam.net/autoblogs/autoblogs/autoblog.php(999): VroumVroum_Blog->update() #1 /var/www/ecirtam.net/autoblogs/autoblogs/sametmaxcom_a844ada43a979e3b1395ab9acb6afafb84340999/index.php(1): require_once('...') #2 {main}