Suivez moi sur Twitter Mon portfolio, mes projets A propos de ce blog

Quelques fonctions Ruby sur les Array

- 09 May 2012 -

Initialement Ruby propose de nombreuses solutions pour manipuler des données. J’ai regroupé ci-desous quelques fonctions très utiles et parfois peu connues.

# --- Définition des Array
array1 = ['Array 1 - row 1',
          'Array 1 - row 2',
          'Array 1 - row 3']

array2 = ['Array 2 - row 1',
          'Array 2 - row 2',
          'Array 2 - row 3',
          'Array 2 - row 4']

# --- méthode ZIP. Permet de parcourir en même temps deux Array
array1.zip(array2).each do |row_array1, row_array2|
  puts '-'*30
  puts "#{row_array1} | #{row_array2}"
end

# --- Ce qui permet de calculer rapidement la distance euclidienne de deux vecteurs
def euclidean_distance(vector1, vector2)
  sum = 0
  vector1.zip(vector2).each do |v1, v2|
    component = (v1 - v2)**2
    sum += component
  end
  Math.sqrt(sum)
end

vector1 = [1,2,3]
vector2 = [3,2,6]
puts euclidean_distance(vector1, vector2)


# --- Permet de réaliser des fenêtres/groupes de données
array1.each_slice(2).each do |row1, row2|
  puts '-'*30
  puts "#{row1}; #{row2}"
end


# --- Même chose mais avec des duplication de données.
array1.each_cons(2).each do |row1, row2|
  puts '-'*30
  puts "#{row1}; #{row2}"
end

Utilisation de Redis avec Ruby

- 04 May 2012 -

Il y a plusieurs années lorsqu’il était question de faire de la persistance de données au sein d’une architecture logiciel, les développeurs avaient principalement recours aux bases de données SQL. On parlait de SGDB.
Celles-ci apportaient plus de richesse que les fichiers plats, notamment en termes de concurrences, réplications, transactions, etc.
L’approche ACID était au centre de ces stratégies.
Aussi, différents acteurs étaient présents. Ils garantissaient aux utilisateurs des niveaux d’exigences importants particulièrement grâce aux supports techniques (Oracle, SQLServer, etc.). Le marché était très mature et proposait des compétences de haut niveau. Puis des bases de données Open Source, MySQL, Postgresql ont été acceptées au même titre que les solutions propriétaires. Le marché de l’administration s’est peu à peu ouvert aux développeurs, voir aux non informaticiens avec notamment Access.

Depuis maintenant trois ou quatre ans, les bases nommées NoSQL sont arrivées sur un marché relativement fermé aux innovations. En effet, les approches NoSQL sont différentes : les architectures de données notamment sur les aspects que l’on retrouve au sein des « applications sociales » n’imposent pas nécessairement la mise en place de SGDB SQL classique. Certains types de données peuvent être sauvées sous forme de documents ou de graph.
Sur ce point, les puristes diront que c’est un sacrilège de ne pas respecter l’atomicité des données. Mais si les performances peuvent être sauvées, on peut dire que c’est un mal pour un bien.

Je passe sur le phénomène des Big Data qui en soit n’apporte rien de nouveau.
A mon sens, il s’agit clairement d’un recyclage de discours commerciaux pour vendre plus ce qu’on fait depuis longtemps. La volumétrie de données a certes augmentée mais uniquement dans certains secteurs, comme la téléphonie, les média sociaux. Mais ils ont des outils, des techniques et des approches spécifiques en mesure de faire face aux grosses volumétries.
Pour 95% des entreprises qui souhaitent données de l’intelligence à leurs données il n’est pas nécessaire de faire appel aux Big Data.

Puis, il y a aussi les systèmes à base de Clés/Valeurs, notamment Redis que j’affectionne particulièrement depuis de nombreux mois.

Dans un logiciel, les données sont stockées dans des tableaux, matrices, Hash, Set…
Redis repose sur cette une approche qui est vraiment complémentaire. En effet, Redis permet de partager ce type de données entre différents systèmes logiques (comprendre différents logiciels).

Ceci à plusieurs avantages. D’une part les données sont envoyées sur un serveur (redis) en mesure de gérer de façon autonome ses réplications. Donc d’une certaine manière résister aux pannes. Les données sont centralisées sur une architecture n tiers.
D’autre part, les traitements peuvent être répartis sur plusieurs autres machines autonomes et dédiés. Ce qui a pour conséquence de favoriser la scalabilité horizontale. On peut imaginer des machines spécialisées dans des traitements spécifiques en mesure de venir chercher/échanger les données par l’intermédiaire des serveurs Redis. Si le nombre de données augmente, nous n’avons plus qu’à augmenter le nombre de machines.
Initialement Redis est véritablement optimisé pour l’échange de données principalement temporaires et volatiles. Inutile donc de défendre Redis pour gérer des transactions bancaires :)

Dans ce contexte, Redis est souvent utilisé pour gérer les sessions http entre plusieurs serveurs web.

De mon coté, j’utilise Redis pour faire du traitement mathématique/statistiques de données. Notamment en ce qui concerne du data mining et text mining.
Les données initiales sont stockées, soient dans des bases SQL (MySQL ou Postgresql), soient dans des bases NoSQL(mongodb).

Des Workers, c’est à dire des processus autonomes répartis entre plusieurs machines viennent récupérer les données initiales puis appliquent des calculs et stockent les résultats dans une architecture Redis. D’autres Workers, viennent récupérer ces résultats, effectuent leurs traitements puis échangent/sauvent leurs résultats grâce à Rédis.
Ceci permet partager très rapidement des Hash & Sets, ainsi que des fils d’attentes (Queues).

Dès lors, en fonction des enjeux opérationnels, je peux très rapidement répartir la charge de mes workers entre les actions à effectuer.
Certaines machines sont optimisées pour de la communications réseaux, d’autres pour les calculs. Il suffit d’augmenter ou diminuer le nombre de processus par machines pour gérer les performances.

Que propose Redis ?

Comme je l’ai expliqué plus haut, Redis permet de stocker des données sur un serveur et de les partager avec différents clients.

Chaque donnée est associée à une clé qui est une chaine de caractères. Différentes algorithmes permettent de parser et d’indexer ces éléments. Je vous laisse rejoindre Google pour en savoir plus.

Stockage Simple avec des clés de type Strings

Avec Ruby, pour stocker une donnée de ce type j’utilise deux lignes de code. Une pour la connexion, une pour l’affectation.

redis = Redis.new
redis.set 'mykey', 'My value !'

Redis stock ces données en « binary safe ». Ces clés peuvent contenir différents types de données. Des chaines de caractères, des entiers, du json, du binaire, notamment du JPEG, ou des objets sérialisé.
Cependant, la valeur de chaque clé est limitée à 500Mo. Ce qui laisse de la place :)
Il est possible d’utiliser différentes opérations sur chaque clé : affectation (set), lecture (get), incrémentations atomiques (incr, decr…), ajout (append).
Chaque donnée peut aussi être stockée grâce à des namespaces. Un peu comme une url au moyen de séparateurs ( :, /). Ceci permet d’appliquer des patterns de reconnaissance pour l’extraction.

Un autre élément intéressant est qu’il est possible d’attribuer des dates d’expiration aux données (TTL) qui vont mourir toutes seules.

Un peu de code ;)

puts '--- connexion'
redis = Redis.new

puts '--- ajout, identification, récupération, édition, suppression'
redis.set 'mykey', 'my value'
puts redis.type 'mykey'
v = redis.get 'mykey'
puts "mykey = #{v}"
redis.rename 'mykey', 'macle' # use RENAMENX: only if the new key does not exist
puts redis.exists 'mykey'
puts redis.exists 'macle'
redis.del 'macle'
puts redis.exists 'mykey'

puts '--- uitilisation des namespaces'
redis.set 'mylists:mykey1', 'my value 1' # use : or /
redis.set 'mylists:mykey2', 'my value 2'
puts redis.get 'mylists:mykey1'
puts redis.get 'mylists:mykey2'
redis.keys('mylists:*').each do |key|
  puts redis.get key
end

puts '--- plus loin avec les clés de type String'
redis.set 'mykey', 'My value'
redis.append 'mykey', ' is small'
puts redis.get 'mykey'

class MyClass
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end
my_class = MyClass.new('Hello!')
redis.set 'mydump', Marshal.dump(my_class)
mc = Marshal.load redis.get('mydump')
puts mc.name

redis.set 'mykey', 123
redis.incr 'mykey'
redis.incrby 'mykey', 3
puts redis.get 'mykey'
redis.decr 'mykey'
puts redis.get 'mykey'

# utiliser
#   setnx -> set the value of a key, only if the key does not exist
#   strln -> get the length of the value stored in a key

redis.set 'mykey', 'abcdef'
puts redis.getrange 'mykey', 0, 2

redis.set 'mykey', 'abc'
# set the string value of a key and return its old value
puts redis.getset 'mykey', 'zzz'
puts redis.get 'mykey'

redis.set 'key1', 'value1'
redis.set 'key2', 'value2'
redis.set 'key3', 'value3'
# or use:
redis.mset 'key1', 'v1', 'key2', 'v2', 'key3', 'v3'
# use MSETNX: Set multiple keys to multiple values, only if none of the keys exist
redis.mget('key1', 'key2', 'key3').each do |v|
  puts v
end


puts '--- expiration temporelle de données'
redis.set 'mykey', 'my value'
redis.expire 'mykey', 5 # in seconds
8.times do
  v = redis.get 'mykey'
  ttl = redis.ttl 'mykey'
  puts "ttl: #{ttl}"
  v.nil? ? puts("mykey expired !") : puts("mykey = #{v}")
  sleep 1
end
# or use:
#   setex: Set the value and expiration in milliseconds of a key
#
redis.del 'mykey'

puts '--- persistence des données expirées'
redis.set 'mykey', 'my value'
puts "ttl: #{redis.ttl('mykey')}"

redis.expire 'mykey', 3
puts "ttl: #{redis.ttl('mykey')}"
sleep 1
puts "ttl: #{redis.ttl('mykey')}"

redis.persist 'mykey'
puts "ttl: #{redis.ttl('mykey')}"

Les Sets

Les Sets sont des collections d’objets non ordonnés. Pour faire simple, il est possible d’ajouter des lignes de données à un objet Set.
L’avantage est que chaque élément est unique. En exploitant la méthode SADD, il est donc inutile de vérifier si les données existent ou non avant l’insertion.
Clairement les Sets sont utiles pour ranger des listes de données par exemple des liens, résultats, etc.

Redis propose nativement différents opérateurs comme UNION, INTERSECTION, etc. La taille maximum d’un Set est d’un peu plus de 4 milliards d’entrées (4 294 967 295).

Les Listes

Redis propose aussi de socker des listes de données. Celles-ci s’apparente plus à des systèmes de Queue/Stack. C’est à dire des zones de données dans lesquelles on empile et dépile hiérarchiquement des données. On peut utiliser les opérateurs PUSH, POP, etc.
Cette approche est très puissante pour réaliser des systèmes de taches à accomplir au moyen d’ordres partagés entre différents systèmes logiques.
Les listes permettent de créer des modèle de timelines pour des réseaux sociaux : on empile chaque données et on détruit régulièrement les données périmées.
Aussi exemple, le gem reuby Resque utilise ces fonctionnalités PUSH/POP.
La taille maximum d’une liste est d’un peu plus de 4 milliards d’entrées (4 294 967 295).

puts '---- gestion des Lists: queue, stack, etc.'
# push data
puts redis.llen 'mylist'
redis.lpush 'mylist', 'aaa'
puts redis.llen 'mylist'
redis.lpush 'mylist', 'bbb'
puts redis.llen 'mylist'
redis.lpush 'mylist', 'ccc'
puts redis.llen 'mylist'
redis.lpush 'mylist', 'ddd'
puts redis.llen 'mylist'
redis.lpush 'mylist', 'eee'
puts redis.llen 'mylist'
 
# pop data
puts redis.lpop 'mylist'
puts redis.llen 'mylist'
puts redis.rpop 'mylist'
puts redis.llen 'mylist'

Les Hashes

Les hash permettent d’aller plus loin dans la persistance de données en proposant des objets évolués.
Cette approche est très utile, par exemple pour stocker des profils de données ayant plusieurs champs. Par exemple des utilisateurs avec prénom, nom, email, mot de passe, age, etc.
Tous les champs sont stockés dans un Hash User.

puts '--- gestion des Hash'
redis.hset 'myhash', :aaa, 123
redis.hset 'myhash', :bbb, 456
# or hsetnx: Set the value of a hash field, only if the field does not exist
h = {}
h[:aaa] = redis.hget 'myhash', :aaa
h[:bbb] = redis.hget 'myhash', :bbb
puts h[:aaa]
puts h[:bbb]
puts redis.hlen 'myhash'
h2 = redis.hgetall 'myhash'
puts h2['aaa']
puts h2['bbb']
redis.hkeys('myhash').each do |k|
  puts k
end
redis.hvals('myhash').each do |v|
  puts v
end

Les Sorted Sets

Les Sorted Sets sont très similaires aux Sets. Redis permet de les ordonner du stockage de ce type d’objets.
Toutefois, comme tous les algo. de ce type le temps d’accès à ce type d’objet est proportionnel au logarithme du nombre des éléments. En gros, plus y a d’éléments, plus ca rame…
S’il n’est pas nécessaire d’ordonner les données, rien ne sert donc d’exploiter ce type d’objet.

Une histoire d'espaces insécables

- 21 Mar 2012 -

En utilisant la command grep, principalement avec un pipe sur la sortie de “ps aux”, il m’est (trop?) souvent arrivé d’avoir une erreur de type:
command not found: grep
Je n’ai jamais vraiment trouvé de réponse précise à ce problème.

Il s’agit en fait d’une histoire d’espace insécable. Les espaces insécables sont des caractères invisibles empéchant le retour à la ligne. Par exemple en HTML, il s’agit de  
Ces caractères se déclenchent grâce à une combinaison des touches alt-space.
D’où le problème avec le pipe | lorsque l’on tape trop rapidement sur son clavier.

Pour en savoir plus, il y a l’article de af83: http://dev.af83.com/2012/03/20/les-espaces-insecables-pour-les-codeurs.html

Deux vulnérabilités dans ImageMagick corrigées

- 08 Mar 2012 -

Deux vulnérabilités dans ImageMagick viennent d’être corrigées. Cette librairie est très souvent utilisées coté serveur pour redimensionner et convertir des images après un upload. Le menace est dangereuse dans la mesure où elle permet l’exécution de code arbitraire à distance et le déni de service. Les versions affectées sont ImageMagick antérieures à 6.7.5.3.