2008-12-05 6 views
24

La charge DB sur mon site devient très élevé donc il est temps pour moi de mettre en cache des requêtes communes qui sont appelées 1000s de fois par heure où les résultats ne changent pas. Ainsi, par exemple, sur mon modèle de ville je fais ce qui suit:Rails DB Requêtes et Mise en cache les meilleures pratiques

def self.fetch(id) 
    Rails.cache.fetch("city_#{id}") { City.find(id) } 
end 

def after_save 
    Rails.cache.delete("city_#{self.id}") 
end 

def after_destroy 
    Rails.cache.delete("city_#{self.id}") 
end 

Alors maintenant quand je peux City.find (1) la première fois que je frappe la DB, mais les 1000 prochaines fois que je reçois le résultat de la mémoire. Génial. Mais la plupart des appels à la ville ne sont pas City.find (1), mais @ user.city.name où Rails ne pas utiliser l'opération d'extraction mais interroge à nouveau la DB ... ce qui est logique, mais pas exactement ce que je veux faire.

je peux faire City.find (@ user.city_id), mais ce qui est laid.

Donc ma question à vous les gars. Que font les gens intelligents? Qu'est-ce que la bonne façon de le faire?

Répondre

-1

Découvrez cached_model

+0

memoization enveloppe juste la Rails.cache. Je ne pense pas que cela aidera les associations modèles comme vous le souhaitez. – Bill

+1

La mémorisation n'emballe pas le cache Rails. Rails.cache est généralement un magasin de cache partagé entre les processus (de sorte que vous obtenez réellement des avantages de mise en cache). La mémorisation ne se produit que dans le processus en cours. – Michael

0

Je voudrais aller de l'avant et jeter un oeil à Memoization, qui est maintenant dans Rails 2.2.

« Memoization est un motif d'initialisation d'un procédé une fois, puis stashing sa valeur de distance pour une utilisation répétition . »

Il y avait un très bon Railscast episode récemment, cela devrait vous aider à bien fonctionner.

exemple de code rapide du Railscast:

class Product < ActiveRecord::Base 
    extend ActiveSupport::Memoizable 

    belongs_to :category 

    def filesize(num = 1) 
    # some expensive operation 
    sleep 2 
    12345789 * num 
    end 
    memoize :filesize 
end 

More on Memoization

+0

Quel est l'avantage de memoization sur Rails.cache et comment cela résout le problème de User.city.name où Rails fait une recherche sur City au lieu d'utiliser des données en cache? –

+9

Memoization enregistre uniquement le résultat par processus de serveur et n'invalide pas, en tant que tel, il est relativement inapproprié pour votre situation et n'est pas destiné à être utilisé de cette manière. – Michael

23

En ce qui concerne la mise en cache, quelques points mineurs:

Il est intéressant d'utiliser slash pour la séparation du type d'objet et id , qui est la convention de rails. Encore mieux, les modèles ActiveRecord fournissent la méthode d'instance cacke_key qui fournira un identifiant unique du nom et de l'identifiant de la table, "cities/13" etc.

Une correction mineure à votre filtre after_save. Puisque vous avez les données en main, vous pourriez aussi bien l'écrire dans le cache plutôt que de le supprimer. Cela vous permet d'économiser un voyage à la base de données;)

 
def after_save 
    Rails.cache.write(cache_key,self) 
end 

Quant à la racine de la question, si vous tirez continuellement @ user.city.name, il y a deux choix réels:

  • Dénormalise le nom de la ville de l'utilisateur sur la ligne de l'utilisateur. @ user.city_name (conserve la clé étrangère city_id). Cette valeur doit être écrite au moment de la sauvegarde.

-ou-

  • Mettre en oeuvre votre méthode de User.fetch à la charge désireux de la ville. Ne le faites que si le contenu de la ligne de ville ne change jamais (par exemple, nom, etc.), sinon vous pouvez potentiellement ouvrir une boîte de Pandore en ce qui concerne l'invalidation du cache.

Opinion personnelle: Mettre en œuvre id de base en fonction des méthodes chercher (ou utiliser un plug-in) pour intégrer memcached et dénormaliser le nom de la ville à la ligne de l'utilisateur. Je ne suis pas personnellement un grand fan des plugins de style de modèle en cache, je n'en ai jamais vu un qui ait économisé une quantité significative de temps de développement dont je n'ai pas grandi à la hâte.

Si vous obtenez trop de requêtes de base de données, il vaut vraiment la peine de vérifier le chargement impatient (via: include) si vous ne l'avez pas déjà fait. Cela devrait être la première étape pour réduire la quantité de requêtes de base de données.

+1

EG 'User.where (id: 3) .include (: ville) .first'. Le sous-estimé est 'join-merge' pour de nombreuses à plusieurs relations. EG 'City.joins (: restaurants) .merge (user.favorite_restaurants)', retournera toutes les villes où un utilisateur a beaucoup de restaurants préférés, et les restaurants ont beaucoup de villes. Cochez [includes and joins] (http://guides.rubyonrails.org/active_record_querying.html#joining-tables). Vraiment utile d'apprendre à réduire les appels avec des requêtes puissantes plutôt que de passer par les douleurs de la mise en cache. –

0

Si vous devez accélérer les requêtes sql sur des données qui ne changent pas au fil du temps, vous pouvez utiliser des vues matérialisées.

Un matview stocke les résultats d'une requête dans une structure semblable à la table de lui-même, à partir de laquelle les données peuvent être demandées. Il n'est pas possible d'ajouter ou de supprimer des lignes, mais le reste du temps, il se comporte exactement comme un tableau réel . Les requêtes sont plus rapides et le matview lui-même peut être indexé . Au moment où j'écris ces lignes, les maquettes sont disponibles dans Oracle DB, PostgreSQL, Sybase, IBM DB2 et Microsoft SQL Server. MySQL ne fournit pas de support natif pour les mappages, malheureusement, mais sont des alternatives open source.

Voici quelques bons articles sur la façon d'utiliser matviews dans Rails

sitepoint.com/speed-up-with-materialized-views-on-postgresql-and-rails

hashrocket.com/materialized-view-strategies-using-postgresql

Questions connexes