srchulo srchulo - 2 months ago 19
Perl Question

Catalyst Model cache dbix calls

I'm writing a Catalyst application and I have a Model that represents a small settings table whose values won't change. Rather than querying the database for this every time, I would like to cache the responses. Within the model I am using DBIx::Class, and I did see this DBIx::Class::Cursor::Cached, which looks like an option.

However, what I ended up doing was using Memoize to cache the return values in my model. I have two questions about this solution:


  1. Are Catalyst Models only created once, and then used for the lifetime of the application? Or does
    $c->model()
    create a new Model class each time?

  2. If I'm running my application with fastcgi and there are 6 threads, this means that each thread of my app will get its own model class and will have to make queries and hapve 6 separate caches, correct?



Is this a bad solution? Should I be doing something else?

Answer

Are Catalyst Models only created once, and then used for the lifetime of the application? Or does $c->model() create a new Model class each time?

That depends on your implementation of the model. If you have an ACCEPT_CONTEXT method in your model, Catalyst will make a new object every time you call $c->model(). If not, it will be instantiated once during startup of the application. That's documented in Catalyst::Manual::Internals under Initialization.

At YAPC::EU 2016 in Cluj the current maintainer of Catalyst, John Napiorkowski, gave a (remote) talk about Catalyst that explains this very aspect starting from about 53 minutes into the talk. The whole thing as well as his other talk there are worth watching.

If I'm running my application with fastcgi and there are 6 threads, this means that each thread of my app will get its own model class and will have to make queries and hapve 6 separate caches, correct?

I'm not sure about that one. I believe it starts up once and then forks out. So it would create one instance and copy it to the forks.


Using DBIx::Class::Cursor::Cached for your approach looks like it would work. If you need the DBIx::Class ResultSet objects, that's good. If not, you can just attach the results as lazy attributes to your model. It could look something like this:

package My::Model;
use Mooose;
use My::DB::Schema;
extends 'Catalyst::Model';

has stuff => (
    is => 'ro',
    isa => 'HashRef',
    traits => ['Hash'],
    handles => {
        has_stuff => 'exists',
        get_stuff => 'get',
        # ...
    },
    lazy => 1,
    builder => '_build_stuff',
);

sub _build_stuff {
    my ($self) = @_;

    # get stuff from the DB here, convert it to a config hash
    # or whatever you need and store it in our stuff attribute

    # that works well if your config looks something like this:

    # {
    #     color => 'red',
    #     price => 13.37,
    #     things => [ qw/ foo bar baz / ],
    #     # ...
    # }

    # you can then use the Hash trait on your attribute to access it
}

1;

That way it will load it the first time you use it, and then you're done. It will just get stored in the object.

If you want to investigate if that actually works and doesn't do more queries, you can turn on DBIC's tracing output using the DBIC_TRACE=1 environment variable. It will dump colorful queries into your Catalyst log.


Alternatively, CHI is great to build caches of things, though here that's probably a bit overkill.