Dans le cadre de l'amélioration du site je suis en train de mettre en place un système de notification plus global sur le site. Le principe est d'être alerté en temps réel des nouveaux tutoriels / commentaires / contenus de manière général. Pour le côté push, il n'y a pas trop de problème, en revanche côté persistance je suis un peu mitigé niveau organisation des données. Je pense qu'il peut être intéréssant d'avoir des avis sur le sujets pour voir les différentes approches possible :

Les problématiques

Avant de réfléchir à la structure voici les trucs qui sont à prendre en compte

  • On veut éviter des INSERT massif lors de la création d'un commentaire ou un post sur le forum. On essaiera de limiter au maximum les insertions
  • On veut des notifs individuelles "X à répondu à votre commentaire" mais aussi des notifs globales pour tout le monde "Un nouveau tutoriel est dispos"
Première idées

Ma première approche est de représenter les données sous forme de 2 tables distinctes. Une première table permet de stocker les notifications à destination d'un utilisateur.

  • notifications
    • user_id ID de l'utilisateur à notifier
    • notifiable_type Relation polymorphique vers le contenu qui a été affecté (commentaire, article...)
    • notifiable_id
    • created_at
    • seen_at
    • read_at

Cette table ne permet pas de résoudre le souci des notifications globales, et on ne peut pas faire 10 000 INSERT à chaque tutoriel. La solution est donc de créer une nouvelle table pour tout ce qui est global.

  • notifications_global
    • notifiable_type Relation polymorphique vers le contenu qui a été affecté (commentaire, article...)
    • notifiable_id
    • created_at

Lorsqu'un utilisateur charge une nouvelle page, on regarde les nouvelles notifications globales depuis sa dernière visite, et on crée des notifications "locales" pour chaque nouvelle notification globale. Cela permet de diluer les INSERT tout en gardant la possibilité pour chaque utilisateur de gérer l'état de lecture de cette notification.

Cette solution pose juste un problème au niveau du forum / commentaires. Sur un topic suivi par 100 utilisateurs cela veut dire que chaque réponse génère alors 100 INSERT.

Si vous avez des idées ou que vous voulez discutez de cette approche allez-y :D

10 réponses


Sur mon site j'ai la chose suivante :

  • id
  • user_id
  • sender_id
  • type
  • data
  • reference
  • date
  • isRead

Explication :
type : surement comme toi, un INT qui permet de définir de quoi on va parler (un nouveau commentaire, un like, une visite, ...)
data : c'est un champs assez libre qui me permet de stocker des informations comme le titre du topic. Il se présente comme un array que gère doctrine. ["data1", "data2"]
reference : c'est l'id du model concerné. Je pourrais le mettre dans data, mais je fais beaucoup de recherche la dessus donc c'est plus rapide. Par exemple c'est l'id d'un topic.
isRead : simplement pour savoir si ça a été lu ou non et afficher le background dans une autre couleur un peu à la Facebook.

En pratique ça donnerait quelque chose du genre :

  • id : 23314
  • user_id : 123 -> hotgeart
  • sender_id : 1 -> Grafikart
  • type : 5
  • data : ['Grafikart', 1, "Structure de table pour Notifications" ]
  • reference : 23314
  • date : ...
  • isRead : 0

Ce qui me permet de faire après dans les notifications :
<a href="{{ data[1] }}">{{ data[0] donc Grafikart }}</a> a répondu au topic : <a href="{{ reference }}">{{ data[2] }}</a>

Après c'est sur s'il y a 10.000 membres sur un topic il y a 10.000 INSERT, mais bon sans vouloir minimiser ce site c'est rarement le cas. Il y a rarement + de 20 réponses sur un topic

Et pour une notification globale franchement aucune idée de comment éviter 5.000.000 d'INSERT, moi je mettrais ça dans un cookie|HTML5 Local Storage.

Salut

Perso je tenterai une table a mi chemin entre tes deux tables. Si on part du principe que l'on veut eviter 100 insert, il faut en faire un seul pour tout le monde et regrouper les user id dans une colonne en les séparant par un tiret (et gerer ensuite en php)

Quant a savoir comment persister les vues, on pourrait faire pareil avec une autre colonne du meme style sauf qu´on y mettrait des valeurs binaires (1 ou 0) separees par des tirets (dans le meme ordre que les user id). Le reste se gere avec PHP et un algorithme qui ne devrait pas etre bien compliqué a ecrire.

Un peu tiré par les cheveux mais si tu as pas enormement de suivi sur chaque topic ca fera l'affaire.

@Grafikart tu saurais nous dire sur quelle base de donnée tu tournes ? Parce que sous MYSQL 5.7 (si je me trompe pas) il y a un DATA TYPE json

Et du coup on pourrait faire un truc ainsi :

  • notifications
    • user_id ID de l'utilisateur à notifier (null si globale)
    • notifiable_type Relation polymorphique vers le contenu qui a été affecté (commentaire, article...)
    • notifiable_id L'id du contenu qui a été affecté
    • created_at La date de création de la notification
    • seen_at Ici on pourrait utiliser le data type json ? Après aucune idée de ce que ca vaut :/ et genre tu pourrais avoir un object de ce type : {userId: Date à laquelle la notification a été vue}
    • read_at pareil, data type json, même format

Ou alors, sans utiliser json, je ferais plutôt qqchose comme ceci :

  • notifications
    • id ID de la notification
    • user_id ID de l'utilisateur à notifier (null si globale)
    • notifiable_type Relation polymorphique vers le contenu qui a été affecté (commentaire, article...)
    • notifiable_id L'id du contenu qui a été affecté
    • created_at La date de création de la notification
  • notification_visits (ou quelque chose ainsi)
    • notification_id ID de la notification
    • user_id ID de l'utilisateur qui a vu/lu la notification
    • seen_at la date à laquelle la notification a été vue
    • read_at la date à laquelle la notification a été lue

Et on pourrait mettre une clé composite sur user_id et notification_id

Salut,

Je pense qu'un système de notification doit être géré en asynchrone. Si un sujet est mis à jour, et qu'il y a 100 inserts à faire, tu peux retourner la page à l'utilisateur, et gérer les inserts plus tard. J'aurai tendance à utiliser NodeJS et une Base de Données NoSQL pour ce genre de système. (Même si ton site est PHP et SQL), ça ne doit pas être très dur à intégrer dans une infrastructure.
Ensuite, pour récupérer les notifications, tu peux plus ou moins le faire en asynchrome aussi, tu charges la pages, puis requete Ajax pour les notifs.
Je pense que les notifications globales, elles, peuvent être gérées comme tu l'as expliqué.

Le problème le plus important c'est pour les réponses à des sujets de forum. Donc je pense qu'il faudrait créer une table où on stocker à chaque fois qu'un utilisateur suit un sujet. Ensuite quand quelqu'un répond à un sujet, il suffit d'envoyer une notification à tous ceux qui suivent ce sujet. Donc il y aurait 1 insert à chaque fois qu'un utilisateur suit un sujet + 1 insert à chaque fois qu'une réponse est postée.

@dorianamouroux le site grafikart.fr utilise du Ruby en back ;)
@Grafikart Il faudrait plutôt traiter les notifs concernant le forum comme une notif 'semi-globale' en celà qu'au lieu de faire tes 100 INSERT, tu n'en fais qu'un avec un champ contenant les ids des users suivant le thread. Perso je ne suis pas fan de ce genre de solution bricolée, mais j'ai du mal à voir un autre moyen de faire (j'avoue aussi que je n'y ai pas plus réfléchis que ça ^^). Après, gérer les insertions de manière asynchrone ou déportées sur un process enfant (si c'est possible bien entendu) peut être une solution pour éviter une grosse perte de perfs à chauqe fois.
Quoi qu'il en soit, tu devrais déjà mettre en place le système de notifs global, et ensuite voir pour le 'local' (la solution proposée par @tete0148 pourrait convenir).

@betaWeb Comment tu peux garder une trace de si la notification a été lue par l'utilisateur ? Je pense que le traitement de données en tout les cas doit être fait le plus possible sur la base de données, car les bdd sont faites pour ça. Enregistrer tous les ids de user dans un seul champ, et en suite traiter ça toi même en ruby peut-être désastreux.

@dorianamouroux D'où ma remarque "Perso je ne suis pas fan de ce genre de solution bricolée" ;)

@dorianamouroux il suffit de refaire une entrée quand l'utilisateur lit la notification. Au final il y aura toujours 100 requêtes SQL mais pas en même temps pour les forums suivis.

@JohnDoe : Ton idée avec Redis et les lists / sorted lists peut fonctionner, le souci étant que si la cible d'une notification est supprimée (commentaire supprimé) retrouver les notifications associées sur Redis avec être un vrai enfer non ?