2010-01-04 6 views
3

Je construis, fondamentalement, un serveur publicitaire. C'est un projet personnel que j'essaie d'impressionner mon patron, et j'adorerais toute forme de retour sur mon design. J'ai déjà implémenté la plupart de ce que je décris ci-dessous, mais il n'est jamais trop tard pour refactoriser :)Considérations sur les très grandes tables SQL?

Ceci est un service qui fournit des bannières publicitaires (http://myserver.com/banner.jpg liens vers http://myserver.com/clicked) et fournit des rapports sur des sous-ensembles de données.

Pour chaque impression d'annonce générée et chaque clic, je dois enregistrer une ligne qui a (ID, valeur) [où la valeur est la valeur monétaire de cette transaction; par exemple. - $ .001 par bannière publicitaire diffusée à un CPM de 1 $, ou +25 $ pour un clic); ma sortie est basée sur les gains par impression [abrégé EPC]: (SUM(value)/COUNT(impressions)), mais sur des sous-ensembles de données, comme "Gains par impression où browser == 'Firefox'". L'objectif est de produire quelque chose comme "Votre EPC global est de 0,50 $, mais où browser ==" Firefox ", votre EPC est de 1,00", de sorte que l'utilisateur final peut rapidement voir des facteurs importants dans leurs données.

Parce qu'il ya un très grand nombre de ces sous-ensembles (dizaines de milliers), et la production de rapports n'a besoin que d'inclure les données de synthèse, je précalcul l'EPC-par sous-ensemble avec une tâche cron de fond, et le stockage ces valeurs récapitulatives dans la base de données. Une fois dans chaque 2-3 hits, un Hit doit interroger la table Hits pour d'autres Hits récents par un Visiteur (par exemple "find the REFERER du dernier Hit"), mais généralement, chaque Hit n'effectue qu'un INSERT, donc pour garder la réponse fois, j'ai divisé l'application sur 3 serveurs [bgprocess, mysql, hitserver].

À l'heure actuelle, j'ai structuré tout cela en 3 tables normalisées: Hits, événements et visiteurs. Les visiteurs sont uniques par session de visiteur, un hit est enregistré chaque fois qu'un visiteur charge une bannière ou clique, et les événements mappent la relation plusieurs-à-plusieurs des visiteurs aux hits (par exemple, un événement est "Visiteur X à la bannière Y" ", qui est unique, mais peut avoir plusieurs hits). La raison pour laquelle je conserve toutes les données de hit dans la même table est parce que, alors que mon exemple ci-dessus ne décrit que "Banner impressions -> clickthroughs", nous suivons également "clickthroughs -> pixel feux" clickthrough "et" second clickthrough -> pixel de la page de vente ".

Mon problème est que la table des hits se remplit rapidement, et ralentit ~ linéairement avec la taille. Mes données de test n'ont que quelques milliers de clics, mais mon traitement en arrière-plan ralentit déjà. Je peux lancer plus de serveurs, mais avant de lancer l'alpha, je veux m'assurer que ma logique est bonne.

Alors je vous demande SO-gourous, comment structureriez-vous ces données? Suis-je fou d'essayer de calculer toutes ces tables? Puisqu'il est rare que je doive accéder aux enregistrements Hit de plus d'une heure, est-ce que je pourrais partager la table Hits avec ProcessedHits (avec toutes les données historiques) et UnprocessedHits (avec les données de la dernière heure) ou faire indexer la colonne Hit.at Date? ce superflu?

Cela a probablement besoin d'une certaine élaboration, désolé si je ne suis pas clair, je travaille depuis ~ 3 semaines directement sur ce point jusqu'à présent :) TIA pour toutes les entrées!

Répondre

1

Vous devriez être capable de construire une application comme celle-ci de manière à ne pas ralentir linéairement avec le nombre de résultats.D'après ce que vous avez dit, il semble que vous ayez deux principaux goulots d'étranglement au niveau des performances. Le premier est des inserts. Si vous pouvez avoir vos inserts à la fin de la table, cela réduira la fragmentation et maximisera le débit. S'ils sont au milieu de la table, la performance va en souffrir à mesure que la fragmentation augmente.

La deuxième zone est constituée par les agrégations. Chaque fois que vous effectuez une agrégation significative, veillez à ce que tous les tampons en mémoire ne soient pas purgés afin de libérer de l'espace pour les données entrantes. Essayez de minimiser la fréquence à laquelle les agrégations doivent être effectuées, et soyez intelligent sur la façon dont vous regroupez et comptez les choses, pour minimiser le mouvement de la tête du disque (ou peut-être envisager d'utiliser des disques SSD). Vous pouvez également effectuer certaines des accumulations au niveau Web en vous basant uniquement sur les données entrantes plutôt que sur les nouvelles requêtes, avec éventuellement une solution de repli si le serveur tombe en panne avant l'écriture des données collectées. la DB.

Utilisez-vous INNODB ou MyISAM?

Voici quelques principes de performance:

  1. Réduire au minimum allers-retours à partir du niveau Web à la DB
  2. Réduire au minimum l'agrégation des requêtes
  3. Réduire la fragmentation sur disque et optimiser la vitesse d'écriture en insérant à la fin de la table si possible
  4. Optimiser la configuration matérielle
+0

Ceci est une réponse géniale, merci! J'utilise MyISAM et je cours tout sur EC2. À l'heure actuelle, le serveur Web et bgprocess sont sur de petites instances, avec MySQL sur une instance moyenne. La charge est tout sur le serveur bgprocess; Comme je l'ai dit, je peux le faire évoluer, mais je m'attends à ce que cela dépasse les données de test. Je n'avais pas du tout pensé au mouvement de la tête de disque, mais la table Hits and Events utilise des touches d'incrémentation; est-ce que vous voulez dire en insérant à la fin de la table? En ce moment, j'utilise l'ORM de Django pour toutes mes requêtes; Je suppose que je devrais les réécrire à la main? – linked

+0

Oui, les clés d'incrémentation devraient mettre de nouveaux enregistrements à la fin de la table (alors qu'une clé de chaîne ne le ferait probablement pas). Les ORM peuvent être géniaux pour un développement rapide, mais ils peuvent aussi être très bavards. Si cela finit par être * trop * bavard, il vaut généralement la peine de réécrire les requêtes si vous le pouvez. – RickNZ

0

Généralement, vous avez des tables "accumulatrices" détaillées où les enregistrements sont écrits en temps réel. Comme vous l'avez découvert, ils deviennent gros rapidement. Votre backend résume généralement ces enregistrements bruts en cubes ou autres «seaux» à partir desquels vous rédigez des rapports. Vos cubes se définiront probablement une fois que vous aurez cartographié ce que vous essayez de rapporter et/ou de facturer. N'oubliez pas la détection de fraude si c'est un vrai projet.

+0

Merci beaucoup, réponse rapide! Cela ressemble beaucoup à la façon dont je l'ai configuré maintenant: Mon processus d'arrière-plan "reduce.py" passe par Hits (WHERE à> = datetime_of_last_bg_job #pour de nouveaux hits), et met à jour les "buckets", qui sont stockés à la fois résumés généraux et résumés divisés en temps (p. ex. résumé quotidien, à des fins de représentation graphique). Est-ce que vous dites que c'est une approche courante dans la conception de logiciels, et que mes douleurs de croissance sont normales? Avez-vous des liens vers des ressources pour construire ces types de systèmes? – linked

Questions connexes