Envoyer des notifications iOS : une question de flux

Par Christian,
dans Actualité digitale

Récemment, dans le cadre du développement d’un back office web pour une application mobile, nous avons été confronté à une problématique de performance sur l’envoi de notifications mobiles.

Le Back-Office permet au clientd’écrire des messages qu’ils peuvent envoyer via notification à tous les utilisateurs de l’application. A la livraison de la fonctionnalité, pas de problème. On a 3.000 device token enregistrés en base de données, l’envoi des notification passe bien. Passent des mois, et le nombre d’application installées augmente. Arrive un moment où l’envoi de notification prend du temps, beaucoup de temps. Trop de temps.

Le développement qui avait été fait initialement pour envoyer les notifications n’a pas tenu la charge.

Une fois le problème détecté, plusieurs tentatives d’amélioration ont été faites. Elles avaient pour objectif de minimiser l’impact sur le code existant. Ces tentatives ont été infructueuses : soit le temps passé à l’envoi n’était pas significativement plus faible, soit l’envoi était très instable.

Force était de constater que l’envoi des notifications ne pouvait plus se reposer sur ce code « historique » (les développeurs parmi vous comprendront) : il faut reposer des bonnes bases et partir du bon pied avec en tête cette contrainte de temps et de nombre de tokens à gérer.

Les bibliothèques existantes que j’ai pu tester n’étaient pas satisfaisantes – à mon goût du moins – et une solution spécifique a été développée pour l’occasion pour répondre au cas d’utilisation du projet concerné : Envoyer le même message à un grand nombre de device tokens. Cette solution prend la forme d’une bibliothèque PHP réutilisable : ApnsSender

Dans le cas d’envoi via APNS (Apple Push Notification Service) ce n’est pas si simple, il faut passer par un socket. Le socket programming n’est pas forcément trivial et en tant que développeur Web en l’occurrence mon expérience était limitée. Notamment ici la difficulté était dans la gestion des erreurs.

Alors, vous qui voulez envoyer un grand nombre de notification via le service Apple, lisez ces quelques lignes, où je vais essayer de vous aider à comprendre les subtilités du fonctionnement qui m’échappaient avant le développement de ce composant.

Pour envoyer des notifications, il faut en premier lieu ouvrir un socket, établir une connexion avec le service d’Apple. Pour chaque notification, il faut écrire dans ce socket sous un format dicté par Apple. Jusqu’ici rien de fou, tout un tas de tutoriels existent, vous voulez envoyer une deuxième notification ? Pas de soucis, il suffit d’écrire dans ce même socket à nouveau. C’est la base.

Mais voilà parfois il y a des erreurs. En l’occurrence, si une erreur se produit (par exemple le device token utilisé n’est plus valide car l’utilisateur a désinstallé son application), alors l’APNS va écrire une erreur dans le socket et le rendre inaccessible en écriture. Cela a pour conséquence que toutes les tentatives d’écritures vont échouer à partir du token en erreur. Mais vous n’êtes pas forcément au courant ! Entre le moment où vous avez envoyé une notification sur un token invalide et le moment où APNS vous répond, vous avez peut-être empilé d’autres notifications dans le socket. Si vous envoyez, par exemple, 1.000 notifications par seconde (c’est le cas de notre composant), c’est même sûr. Toutes ces notifications sont perdues. Vous êtes censés reprendre l’envoi à partir de la notification en erreur.

Il faut donc conserver la fille d’attente initiale pour pouvoir revenir en arrière et reprendre l’envoi à partir du token suivant celui pour lequel une erreur a été rencontrée. Il faut également réinitialiser la connexion, car le socket étant maintenant inaccessible en écriture, il n’est plus utilisable. Il faut le fermer et en ouvrir un nouveau. Mais attention ! Il ne faut pas le fermer avant d’avoir lu l’erreur qu’y a écrite l’APNS.

Du coup, vous avez parcouru votre pile de device tokens, vous avez tout envoyé, c’est top, ça a marché, fin de bal, merci messieurs et mesdames et bonne soirée ! Finalement c’était pas si compliqué ! Tout ça est derrière nous. Plus qu’à aller à la picole et attaquer un autre projet demain matin. Faux ! Si une erreur s’est produite sur les dernières notifications envoyées, vous ne le saurez qu’un peu plus tard. Après avoir envoyé toutes les notifications, il faut attendre un peu (au moins 100ms), vérifier s’il n’y a pas eu une erreur, et si oui, rebelotte. Jusqu’à ce que tout aille bien. Vous n’êtes pas sorti d’affaire avec votre foreach initial.

Voilà les fonctionnements qui ont posé problèmes, les pièges dans lesquels on peut tomber, les réflexes qui ont été pris en développant cette solution. On n’invente rien, c’est probablement du basique de socket programming. Mais il faut connaître. C’est une logique bien différente d’un développement procédural ou objet classique. J’ai passé sous silence dans cet article un bon nombre de choses (comment initialiser la connexion à la base, gérer le fait que les écritures dans le socket ne soient pas bloquantes, le SSL, etc.). Il y a moins de pièges de ce côté là. Mais le composant fait tout ça pour vous quand même.

L’idée d’ApnsSender est d’abstraire tout ça et d’être le plus robuste possible. Si une erreur est rencontrée, en fonction de l’erreur, on réessaie, ou on passe à la suite, on réinitialise correctement la connexion, on gère la pile de tokens, etc. Au final on retourne la liste des erreurs rencontrées pour que vous les traitiez comme vous voulez. En l’occurrence pour ce projet, en fonction du code d’erreur remonté par l’APNS, on va désactiver les tokens dans notre base de données pour ne pas les renvoyer et donc faciliter les envois suivants.

Alors, si vous envoyez des batchs de notifications, prenez garde. Munissez vous de quelqu’un qui sait manier les sockets. Utilisez notre composant si votre cas d’utilisation correspond à celui que nous avons rencontré, dites-nous ce que vous en pensez.

par Christian

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Rechercher