Troc de Chambre ; le dernier né

Je vais faire comme chez moi et je ne vais pas me gêner pour vous parler un peu du dernier projet sorti de chez Autrement, la boîte où je bosse depuis plusieurs années :

Troc de Chambre, où comment revendre sa chambre d’hôtel non-annulable et trouver des bons plans à prix réduits.

Troc de chambre

En plein marasme d’automne, on a eu l’idée, on l’a travaillée, mise à l’épreuve de notre milieu et on a décidé de se lancer dans le développement d’une version bêta pour la tester en vrai.

Un grand nombre de réservations d’hôtels ont des conditions qui les rendent non-annulables et/ou non-remboursables. Changement de planning, incident, maladie … sont autant de raisons qui empêchent trop souvent de profiter de ces réservations. Tout ou partie du prix payé est perdu. Pourquoi ne pas revendre cette réservation (lorsque les conditions le permettent), moins cher pour les rendre attractives, et récupérer une partie de la perte ?

C’est ce que nous avons voulu vérifier par nous mêmes. Et comme on a un peu d’expérience sur le sujet des hôtels en ligne et du dev web, on a réussi à lancer une bêta en peu de temps.

Voilà quelques éléments plus ou moins techniques pour les curieux :

DigitalOcean

On a voulu tester une plateforme très bon marché, quasi prête à l’emploi et ressemblant plus ou moins à du cloud.

C’est sûr que 10$/mois pour une machine permettant de faire tourner un petit site, tout en ayant la main complètement sur le système sous-jacent, c’est alléchant. Malheureusement, le bilan n’est pas si positif que ça. C’est probablement notre manque d’expérience dans l’automatisation … qui n’a pas aidé.

On va revenir à nos amours habituelles, chez Evolix, qui gère hyper bien nos serveurs dédiés depuis le début.

Ruby on Rails 4.2

On est assez rôdé sur Rails, le choix était donc assez simple, mais c’est bien de temps en temps de lancer un projet vierge sur la dernière version d’un framework, sans le poids de l’existant, et de pouvoir tester les bonnes pratiques du moment.

PostgreSQL 9.3

Utilisateurs principalement de MySQL et un petit peu de SQLite, on savait depuis longtemps que PostgreSQL avait des choses intéressantes à proposer. On a donc sauté le pas, un peu lutté au début, mais on est bien content du résultat. Ça vaut le coup. Surtout que Rails 4.x exploite vraiment de plus en plus les spécificités de PostgreSQL.

Mailjet

Pour le moment, c’est Mailjet qui gère la partie « envoi des mails ». je ne suis pas un fan absolu, mais ça fait le boulot. Et puis c’est une boîte française, donc j’apprécie de les faire bosser.

MangoPay

Un point important de Trocdechambre ; nous sommes tiers de confiance entre l’achat sur le site et le paiement du vendeur une fois le séjour passé.

Il était évidemment hors de question de tout gérer nous-mêmes, pour des raisons techniques et légales. On a choisi de travailler avec MangoPay qui propose une API bien fichue qui permet de gérer des paiements entrants (par CB), des paiements sortants (virements SEPA) et tous les transferts internes qu’on souhaite au niveau de portefeuilles électroniques.

Le coût n’est pas négligeable (1,8% + 18cts, pour chaque paiement entrant) mais la richesse et la facilité de prise en main de l’API en valent le coup, du moins tant qu’on ne gère pas des millions d’euros.

Dans un contexte légal européen, les contraintes sont assez fortes, mais apportent une vraie confiance dans le système. Le dossier d’ouverture de compte est épais et riche, mais ça aussi, ça vaut le coup.

SSL/TLS et certificats X.509

Pour un site de e-commerce qui manipule des données personnelles, il faut bien proposer un sécurisation des échanges. L’acquisition de compétence sur le sujet m’a poussé à l’écriture d’un guide complet sur la mise en place d’un certificat SSL/TLS.

Au final, toute la navigation est en https, avec le maximum de ce qu’on sait faire à ce jour en terme de sécurité.

Un thème pas cher

Pas moyen de se lancer dans une grosse phase de webdesign, identité visuelle, etc. sans savoir si l’idée va plaire. On a acheté un thème pas cher, qu’on a légèrement adapté. C’est bien sûr plein de défauts, mais ça donne un résultat assez propre, compatible, responsif … en peu de temps.

Il sera toujours temps de changer plus tard si la phase de beta-test est concluante.

La suite du projet

Actuellement on est clairement en version bêta. On a un produit qui marche pour de vrai ; vous pouvez vendre, vous pouvez acheter et tout se passera bien. Mais les fonctionnalités sont limitées et on va devoir faire beaucoup de choses à la main.

On va continuer à ajouter des fonctionnalités, polir l’interface utilisateur, enrichir notre compréhension des singularités de la réservation hôtelière en ligne, tisser des relations avec les acteurs de l’industrie pour favoriser l’adhésion à l’idée.

On va aussi beaucoup écouter nos premiers clients et utilisateurs, communiquer sur les réseaux pour faire connaître l’idée.

Toute l’équipe d’Autrement est remontée à bloc, et ça fait du bien.

Publié dans Autrement, Informatique, Uncategorized | Marqué avec , | 3 commentaires

Gandi SSL avec Nginx

Je me suis récemment penché sur un sujet complètement nouveau pour moi : les certificats SSL (pour chiffrer la consultation d’un site web).

J’étais frustré du peu d’information « à mon niveau » que je trouvais sur le web. J’ai décidé de prendre les choses en main et écrire le guide que j’aurais aimé trouver.

Le guide est ici : http://jlecour.github.io/ssl-gandi-nginx-debian/

Toute le code source de ce guide et les fichiers associés sont disponibles sur GitHub : https://github.com/jlecour/ssl-gandi-nginx-debian

Publié dans Informatique | Marqué avec , , , | Laisser un commentaire

Empathie

Je viens de lire ce tweet de Sam Sheppard :

Saying « Why are you depressed? Your life is great. » is like saying « What do you mean you have asthma? There is plenty of air in here. »

En français :

Dire « Pourquoi es-tu déprimé, la vie est belle. » c’est comme dire « Comment ça tu as de l’asthme ? Il y a plein d’air partout. »

Pour moi ça révèle un manque d’empathie, une incapacité à se mettre dans le peau de l’autre.

J’adhère parfaitement à ce propos, et pourtant j’ai déjà été coupable de ce genre de remarque. J’ai déjà dit des choses similaires à un de mes enfants lorsqu’il éclatait de colère et disait que je ne pouvais pas comprendre à quel point sa vie est difficile et que ses problèmes sont durs à gérer.

C’était un moment de colère partagé, où ma capacité à écouter véritablement mon enfant n’était pas à son comble. Nous communiquions à des niveaux différents. Je n’entendais pas l’expression de son ressenti, je voulais juste arrêter cette crise et être obéi.

J’ai renvoyé violemment à mon enfant l’injustice de son propos en argumentant piètrement qu’il fallait un peu garder les pieds sur terre, qu’on vit confortablement, sans avoir faim ni froid, avec de l’éducation et des loisirs, … bref, qu’on a une belle vie.

Ce n’est qu’après que j’ai compris que mon enfant exprimait sincèrement son ressenti et qu’il méritait d’être entendu et reconnu comme tel. J’ai également compris à quel point j’avais de la chance d’avoir un enfant qui parle de ses sentiments aussi clairement, sans peur d’être jugé ou incompris et que je n’avais pas le droit de l’en dégouter.

Plus généralement, pas juste pour un enfant, j’estime qu’un sentiment est une des choses les plus authentiques. On peut mentir en parole, avoir des actes sans intention ou conviction, mais un sentiment est obligatoirement authentique.

NB : Cet article est très brouillon. Je l’ai rédigé presque sans relecture, en pleine journée de travail. Je n’ai pas voulu reporter pour prendre le temps de peaufiner mon propos, j’ai préféré la spontanéité.

Publié dans Personnel | Laisser un commentaire

Deviens CTO

Il y a quelques jours, j’étais présent à /dev/var (une conférence locale, thématique web) et j’y ai rencontré une personne qui cherchait à rencontrer des personnes « techniques » pour la rejoindre dans une aventure d’entreprise naissante. C’était juste après mon intervention durant laquelle je parlais de mon enthousiasme dans mon travail et de certains aspects technologiques sur lesquels j’interviens.

Le lendemain soir, je reçois un mail sur ma boîte perso (mon adresse est facile à trouver, notamment à la fin de mes présentations). Le contenu de cette e-mail m’a fortement surpris. Continuer la lecture

Publié dans Personnel | Marqué avec | 9 commentaires

Retry after errors, with exponential backup (in Ruby)

There are situations where some errors can occur. Let’s say you connect to a remote service, like a database or an API over HTTP. An error raised by your client is not always permanent. It might be a network glitch or something else.

Here is an attempt (in Ruby) to retry on error, with a longer sleep time between attempts.

class WhateverException < StandardError; end
debug_counter = 0

sleep_times = [0.1, 0.2, 0.5, 1]
begin
    fail WhateverException, "counter=#{debug_counter += 1}"
rescue WhateverException
    if time = sleep_times[(nb_retries ||= 0)]
        sleep time
        puts "retry #{nb_retries} after #{time}s"
        nb_retries += 1
        retry
    else
        raise
    end
end

The 2 first lines are just context ; an exception class and a counter for debugging purposes.

sleep_times = [0.1, 0.2, 0.5, 1] is an array of times in seconds that I want to wait at each attempt.

The begin/rescue block allow to rescue the exception when it occurs, but also the retry (see later).

When an expected exception occurs, Ruby executes the body of the rescue part. It takes the first sleep time, wait that long, puts a debug line of text (that you'll want to remove or change to an audit log message), increments the number of attempts and executes the retry statement.

A retry statement rolls back to the previous begin block and executes it again, without any condition. That's why we have to deal with a maximum number of attempts or it will loop forever.

If we reach the end of the sleep_times array of times, Ruby will return nil and the if condition will fail. The original exception is raised again, as is.

Here is the output of this "script" :

ruby ~/tmp/retry.rb
retry 0 after 0.1s
retry 1 after 0.2s
retry 2 after 0.5s
retry 3 after 1s
/Users/jlecour/tmp/retry.rb:6:in `': counter: 5 (WhateverException)

Remember that in Ruby raise and fail are exactly the same method, but as Jim Weirich was saying :

Because I use exceptions to indicate failures, I almost always use the « fail » keyword rather than the « raise » keyword in Ruby. Fail and raise are synonyms so there is no difference except that « fail » more clearly communcates that the method has failed. The only time I use “raise” is when I am catching an exception and re-raising it, because here I’m not failing, but explicitly and purposefully raising an exception.

Publié dans Informatique, Personnel | Marqué avec | Laisser un commentaire

Rsync to just delete files on destination when missing from source

I have this situation where I have a huge number of images (about 50 millions, with 3-4 versions of each one), organized in a nested tree of directories, like images/103/045/475/example-{format}.jpg.

This immense catalog of images is replicated from our internal « master » to a CDN-like box. Sometimes, the replication is out of sync and some images a destroyed on the master but on the slave.

It’s not a surprise that Rsync has the right set of options to deal with this :

rsync --recursive --delete --ignore-existing --existing --prune-empty-dirs --verbose src/ dst/

Let me explain each option.

--recursive will explore the whole directory tree, not just the first level.

--delete will remove files in dst that are not in src.

--ignore-existing will not update any file in dst

--existing will not create any file in dst.

--prune-empty-dirs will remove empty directories in dst, not just deleting files.

--verbose will log what it does.

By not trying to compare the files, it’s much faster, but of course it’s only cleanup, not a real synchronization.

You can also run this a first time with --dry-run to print each action instead of executing them, to verify that Rsync does what you want.

The complete list of options is available in the man page

Publié dans Informatique | Marqué avec | Un commentaire

How to use a different ActiveRecord connection pool between Unicorn and Sidekiq?

I work on a Rails (4.1) application, sitting behind Unicorn and backed by a couple of Sidekiq processes.

A quick reminder : Unicorn is an application server (for Rack-compatible Ruby applications) based on the multi-process master-workers model, and Sidekiq is a background processing based on the multi-threaded model.

The jobs we put in the Sidekiq queue are also multi-threaded and need to access the database behind ActiveRecord in parallel. It forces us to have a bigger connection pool than usual : 20 instead of the default 5.

But the main part of the application, which run solely inside Unicorn, doesn’t need to use that much connections. In fact we have 16 Unicorn workers, so the bigger the pool is the more connections are opened.

Even if they are mainly idle, it’s still a waste of resource and it make the supervision of resources consumption more difficult.

Here how I’ve managed to have a separate pool size for Sidekiq and Unicorn.

Unicorn

A typical Unicorn configuration contains something like this

before_fork do |server, worker|
  # the following is recommended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end
end

after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

Before forking, the master process close all its connections, and after forking, every worker re-open their connections.

ActiveRecord::Base.establish_connection can be called with a connection « name » if there is something by that name in the config/database.yml file.

Let’s create such a configuration, by overriding only what’s necessary :

production: &production
  adapter: mysql2
  host: localhost
  port: 3306
  database: database_name
  username: my_app
  password: password
  encoding: utf8
  reconnect: true
  pool: 5

production_unicorn:
  <<: *production
  username: my_app_unicorn
  pool: 5

You can see that I’ve kept the same pool setting’s default value. That way It’s easier to have a Unicorn setting different from the default setting, used when I run Rake tasks, …

I’ve also changed the username because I want to differentiate them when looking at the database opened connections. It’s completely optional.

Then we nee to configure Unicorn to use that new configuration :

after_fork do |server, worker|
  if defined?(ActiveRecord::Base)
    spec = "#{Rails.env}_unicorn"
    if Rails.application.config.database_configuration.key?(spec)
      ActiveRecord::Base.establish_connection(spec.to_sym)
    else
      ActiveRecord::Base.establish_connection
    end
  end
end

Sidekiq

For Sidekiq it’s quite similar. We begin by adding another override in the config/database.yml file :

production_sidekiq:
  <<: *production
  username: my_app_sidekiq
  pool: 20

NB : I set Sidekiq up in the config/initializers/sidekiq.rb file, but you can also put this in many other places. Consult Sidekiq’s documentation for more details.

Let’s use our custom connection inside Sidekiq :

Sidekiq.configure_server do |config|
  if defined?(ActiveRecord::Base)
    spec = "#{Rails.env}_sidekiq"
    if Rails.application.config.database_configuration.key?(spec)
      ActiveRecord::Base.establish_connection(spec.to_sym)
    else
      ActiveRecord::Base.establish_connection
    end
  end
end
Publié dans Informatique | Marqué avec , , , | Un commentaire