ExpressionEngine CMS
Open, Free, Amazing

Thread

This is an archived forum and the content is probably no longer relevant, but is provided here for posterity.

The active forums are here.

Caching Models with Memcached?

December 15, 2008 12:42pm

Subscribe [6]
  • #1 / Dec 15, 2008 12:42pm

    dmorin

    258 posts

    I’m looking at caching some of my models in Memcached.  This way, I can cache the db queries required to populate the model and I can hook into my model’s save method to update the cache when changes occur.  However, when serializing the object, it includes lots of other CI stuff because of the way CI extends “$this” to include references to all of the other loaded assets.

    Does anyone have any experience using Memcached with CI?  Are you just caching query arrays or entire objects?

  • #2 / Dec 15, 2008 1:33pm

    Jamie Rumbelow

    546 posts

    I’ve been using memcached on work projects for years now, and I usually end up caching the DB queries - models themselves arn’t really helpful untill they have data in them, so you might as well skip that and just cache the database queries.

  • #3 / Dec 15, 2008 1:40pm

    dmorin

    258 posts

    Thanks for replying!

    I know most use models to simply add methods with each method being responsible for calling 1 query.  However, I’m using models in more of an ORM sense where each instance of the model represents one database record.  The models have their own get and save methods. Also, if there’s something processor intensive, I save it to a class var in case I need to reference it multiple times within a page load.  Because of all of this, I thought it would be ideal to cache the object instances themselves instead of just the db results.  It would also be much easier to invalidate these versus invalidating individual queries.

    How do you invalidate individual queries within your applications?  I’m fairly new to caching so I’m still trying to figure out the best practices.

  • #4 / Dec 15, 2008 5:09pm

    Jamie Rumbelow

    546 posts

    You could write a library that created a new, blank object and then looped through the model’s data and assigned it to that object, thereby skipping all the junk that CodeIgniter throws in.

    Something like this:

    class Cached_Model {}
    
    $cache = new Cached_Model;
    $model =& $this->model_name;
    
    foreach ((array)$model as $key => $value):
    
    $cache->$key = $value;
    
    endforeach;

    I’ve never used memcache in an ORM situation, except from a Rails application, but that handled it all for me. Sorry I can’t be of more help.

  • #5 / Dec 15, 2008 5:18pm

    dmorin

    258 posts

    Thanks for the reply.  I was thinking the best (used loosely) this to do might just be to not have my models extend the base model class and then just use get_instance() anytime I needed access to the CI core.  Not a nice solution at all, but it might have to work.

    Thanks again for the response.

  • #6 / Dec 21, 2008 12:35am

    Jonathon Hill

    45 posts

    However, when serializing the object, it includes lots of other CI stuff because of the way CI extends “$this” to include references to all of the other loaded assets.

    If you’re using PHP5 (you should be) you could create a __sleep() magic method and unset() the CI stuff.

  • #7 / Dec 21, 2008 6:15pm

    dmorin

    258 posts

    That’s really interesting.  I’ve seen that but hadn’t thought about using it in this case.  Thanks for the suggestion!

  • #8 / Jan 13, 2009 11:18pm

    freakylp

    6 posts

    However, when serializing the object, it includes lots of other CI stuff because of the way CI extends “$this” to include references to all of the other loaded assets.

    If you’re using PHP5 (you should be) you could create a __sleep() magic method and unset() the CI stuff.

    I think that extending $this->db will be much easier.

    class My_model extends Model{
    
      
     publuc function __construct(){
           parent::__contstruct();
     }
    
    
     public function db_query($query){
       .....
       if( $this->memcached->get(.....) != false){
           return $this->memcached->get(....);
       } 
       return $this->db->query($query)->result();
     }

    }

  • #9 / Jan 13, 2009 11:24pm

    dmorin

    258 posts

    Agreed, however, if you’re doing any processing intensive operations, those can’t be cached through the query hence the idea for caching the model.

    Edit:
    It’s also much harder to invalidate every query on an insert/update versus just invalidating one object.

  • #10 / Jan 13, 2009 11:59pm

    freakylp

    6 posts

    Agreed, however, if you’re doing any processing intensive operations, those can’t be cached through the query hence the idea for caching the model.

    Edit:
    It’s also much harder to invalidate every query on an insert/update versus just invalidating one object.

    I’m not sure about that.But there is a tricky stuff.
    If you want to make update & inserts without caching the result you will need only of 1 parameter.

    Just some not so good but working example / not tested

    <?php
        
        // Memcached object
    
        class Memcached{
            
            
            
            public $memcache;
            
            
            
            
            
            public function __construct()
            {
                $this->memcache     =     new Memcache;
                $this->conn         =     $this->memcache->connect('localhost', 11211);
                $this->time            =    1800;
            }
            
            
            
            
            
            
            public function set( $key , $value  )
            {
                $this->memcache->set( md5( $key ).$_SERVER['SCRIPT_FILENAME'] , $value , 0 , $this->time );
            }
            
            
            
            
            public function get( $key )
            {
                if( strpos( $key , 'UPDATE' ) == FALSE || strpos( $key , 'INSERT' ) == FALSE || strpos( 'DELETE' ) == FALSE )
                {
                    $this->load->database();
                    $this->db->query($key);
                    return false;
                }
                return $this->memcache->get( md5( $key ).$_SERVER['SCRIPT_FILENAME'] );
            }
            
            
            
        }
    
    
    
        // My_Model 
    
        class My_Model extends  Model{
                
                
                
                public function __construct()
                {
                    parent::__construct();
                    
                }
                
    
                
                public function db_query( $query )
                {
                    
                    if( $this->memcached->get( $query ) == false )
                    {
                        $this->load->database();
                            
                        
                        $q        =    $this->db->query( $query );
                        $this->memcached->set( $query , $q->result()  );
                        
                        return $q->result();
                        
                    }
                    return $this->memcached->get( $query );
                }
                
                
                
                
        }
        
        
    
        
        // Example "real" model
        
        class Example_model extends My_Model{
            
            
            
            public function __construct()
            {
                parent::__construct();
            }
            
            
            public function delete_by_id_bla( $id )
            {
                $id = (int)$id;
                $query = $this->db_query("DELETE FROM sometable WHERE id = '$id' LIMIT 1");
            }
            
            public function delete_by_id_bla( $id )
            {
                $id = (int)$id;
                $query = $this->db_query("UPDATE sometable  SET is_active = 0 WHERE id = '$id'");
            }
            
            public function select_by_id( $id )
            {
                $id     = (int)$id;
                $query  = $this->db_query("SELECT * FROM sometable WHERE id > '$id'");
                
                return $query;
            }
            
            
            
        }
    
    
    ?>

    edit: with some stuff like this one I was able to run my scripts 20 times faster without loading database so much as normally . 😉

  • #11 / Jan 14, 2009 10:38am

    dmorin

    258 posts

    I understand where you’re coming from, but I have a few questions.  In your example above (unless I’m reading it wrong), a write query would just be passed to the database to do its work but it wouldn’t invalidate the select query that’s already cached.

    For example, let’s say I run your select query and get all rows where id is greater than 0.  The query result is cached and I can use it again next time.  Now, I insert 100 rows, update 20, and delete 5.  Because the write queries don’t invalidate that select query, running the select query again will get the original results from memcached which are now invalid and out of date.  You might say that you can simply invalidate this one query from each of the write queries, but when you have more than one select query that could have potentially cached a row, especially when you start talking about joins, it gets even more complex.

  • #12 / Jan 14, 2009 10:54am

    freakylp

    6 posts

    I understand where you’re coming from, but I have a few questions.  In your example above (unless I’m reading it wrong), a write query would just be passed to the database to do its work but it wouldn’t invalidate the select query that’s already cached.

    For example, let’s say I run your select query and get all rows where id is greater than 0.  The query result is cached and I can use it again next time.  Now, I insert 100 rows, update 20, and delete 5.  Because the write queries don’t invalidate that select query, running the select query again will get the original results from memcached which are now invalid and out of date.  You might say that you can simply invalidate this one query from each of the write queries, but when you have more than one select query that could have potentially cached a row, especially when you start talking about joins, it gets even more complex.

    Yeah I agree , but there is no problem with that.

    When you need to delete and update $this->load->database() is called.
    You need first update or delete to be able to retrieve valid results from the database.
    This is the first way to have fresh data.
    Other way is set again ( rewrite ) old data. I didnt think about.It depends only of your needs.If you dont need of fresh data anytime that is not a bad idea.

    If you want fresh data you can cache data only for one client ( user ). / I mean personal cache.

    When you need of more complex stuff , you need of more complex solution.

  • #13 / Jan 14, 2009 11:13am

    dmorin

    258 posts

    When you need of more complex stuff , you need of more complex solution.

    Which was the original point of this thread, optimal way of caching data in the processed state while making sure you always had the most up to date information.  Because of the way I use models, caching the model object seems like the way to go.

  • #14 / Jan 14, 2009 11:45pm

    freakylp

    6 posts

    Im not so quite sure about caching models.
    First of all you dont have the same queries except if you have only static queries ( with static I mean queries without any dynamic parameters ).

    Caching models is not solution.If you cache the model you will not have fresh data too.
    Lets say that we have News_model which has 3 methods.
    News_model.php

    public function Get_News_By_Id( $news_id )
        {
            $news_id = (int)$news_id;
            $query = $this->db->query("SELECT * FROM news WHERE id = '$news_id'");
            return $query->result();
        }
        
        
        
        public function Get_News_Title( $news_id )
        {
            $news_id = (int)$news_id;
            $query = $this->db->query("SELECT title FROM news WHERE id = '$news_id'");
            return $query->result();
        }

    Lets say that we have a News_Controller:
    News_Controller

    class News_Controller extends Controller{
    
        public function __construct()
        {
            parent::__construct();
            $this->load->model('News_Model','news');    
        }
        
        
        public function Index()
        {
            $this->data['news']     = $this->news->Get_News_By_Id(5);
            $this->data['title']    = $this->news_Get_News_Title(5);    
        }
        
    }

    If we cache $this->news and parameter of methods of News_Model are not the same we will have 1 copy of News_model object resource but we dont have the same data.

    What we shall do ?

  • #15 / Jan 15, 2009 10:51am

    dmorin

    258 posts

      However, I’m using models in more of an ORM sense where each instance of the model represents one database record.  The models have their own get and save methods. Also, if there’s something processor intensive, I save it to a class var in case I need to reference it multiple times within a page load.  Because of all of this, I thought it would be ideal to cache the object instances themselves instead of just the db results.  It would also be much easier to invalidate these versus invalidating individual queries.

    That’s from reply #2 in this thread.  I use models differently than you.  Mine actually model the information in the database instead of just providing a repository for queries.  Anytime I need to modify a record in the database, I do so with this model.  That’s the difference.

    Post #5 about using __sleep() is probably the best answer for this situation.

.(JavaScript must be enabled to view this email address)

ExpressionEngine News!

#eecms, #events, #releases