Mojolicious template cache is stale

試著忘記壹切 提交于 2019-12-14 01:20:07

问题


I'm currently developing a small single-page Web app using Mojolicious. The app has a Javascript frontend (using Backbone) that talks to a REST-ish API; the layout of the source is roughly:

use Mojolicious::Lite;

# ... setup code ...

get '/' => sub {
    my $c = shift;
    # fetch+stash data for bootstrapped collections...
    $c->render('app_template');
};

get '/api_endpoint' => sub {
    my $c = shift;
    # fetch appropriate API data...
    $c->render(json => $response);
};

# ... more API endpoints ...

app->start;

The app template uses EP, but very minimally; the only server-side template directives just insert JSON for bootstrapped collections. It's deployed via Apache as a plain CGI script. (This isn't optimal, but it's for low-traffic internal use, and more intricate server configuration is problematic in context.) Perl CGI is configured via mod_perl.

This works most of the time, but occasionally the renderer somehow gets the idea that it should cache the template and ignore changes to it. The debug records in error_log show "Rendering cached template" rather than the normal "Rendering template", and my new changes to the template stop appearing in the browser. I can't find a reliable way to stop this, though it will eventually stop on its own according to conditions I can't discern.

How can I make the app notice template changes reliably? Alternatively, how can I disable template caching completely?


回答1:


How can I make the app notice template changes reliably?

This is what the morbo development server is for. Morbo wouldn't be used for your live code deployment, but for a development environment where you are continually changing your code and templates. Generally changes to live code and templates are meant to be handled by restarting the application server, or Apache in your case. (Hypnotoad has a hot-restart capability for this purpose)

Alternatively, how can I disable template caching completely?

To do this, add the following setup code (outside of routes, after use Mojolicious::Lite):

app->renderer->cache->max_keys(0);



回答2:


For old answer see below.

I turned the findings of this answer into a plugin and released it on CPAN as Mojolicious::Plugin::Renderer::WithoutCache after discussing wit Grinnz on IRC, where they encouraged a release.

You can use it like this:

use Mojolicious::Lite;
plugin 'Renderer::WithoutCache';

It will create a new Cache object that does nothing, and install that globally into the renderer. That way, it doesn't need to be created every time like my initial answer below did.

In theory, this should be faster than Grinnz' approach (which is more sensible), and since you explicitly don't want to cache, you obviously want things to be as fast as possible, right? It's supposedly faster because the real Mojo::Cache would still need to go and try to set the cache, but then abort because there are no more free keys, and it also would try to look up the values from the cache every time.

I benchmarked this with both Dumbbench and Benchmark. Both of them showed negligible results. I ran them each a couple of times, but they fluctuated a lot, and it's not clear which one is faster. I included output of a run where my implementation was faster, but it still shows how minuscule the difference is.

Benchmark with Dumbbench:

use Dumbbench;
use Mojolicious::Renderer;
use Mojolicious::Controller;
use Mojolicious::Plugin::Renderer::WithoutCache::Cache;

my $controller         = Mojolicious::Controller->new;
my $renderer_zero_keys = Mojolicious::Renderer->new;
$renderer_zero_keys->cache->max_keys(0);

my $renderer_nocache = Mojolicious::Renderer->new;
$renderer_nocache->cache( Mojolicious::Plugin::Renderer::WithoutCache::Cache->new );

my $bench = Dumbbench->new(
    target_rel_precision => 0.005,
    initial_runs         => 5000,
);

$bench->add_instances(
    Dumbbench::Instance::PerlSub->new(
        name => 'max_keys',
        code => sub {
            $renderer_zero_keys->render( $controller, { text => 'foobar' } );
        }
    ),
    Dumbbench::Instance::PerlSub->new(
        name => 'WithoutCache',
        code => sub {
            $renderer_nocache->render( $controller, { text => 'foobar' } );
        }
    ),
);

$bench->run;
$bench->report;

__END__
max_keys: Ran 8544 iterations (3335 outliers).
max_keys: Rounded run time per iteration: 5.19018e-06 +/- 4.1e-10 (0.0%)
WithoutCache: Ran 5512 iterations (341 outliers).
WithoutCache: Rounded run time per iteration: 5.0802e-06 +/- 5.6e-09 (0.1%)

Benchmark with Benchmark:

use Benchmark 'cmpthese';
use Mojolicious::Renderer;
use Mojolicious::Controller;
use Mojolicious::Plugin::Renderer::WithoutCache::Cache;

my $controller         = Mojolicious::Controller->new;
my $renderer_zero_keys = Mojolicious::Renderer->new;
$renderer_zero_keys->cache->max_keys(0);

my $renderer_nocache = Mojolicious::Renderer->new;
$renderer_nocache->cache( Mojolicious::Plugin::Renderer::WithoutCache::Cache->new );

cmpthese(
    -5,
    {
        'max_keys' => sub {
            $renderer_zero_keys->render( $controller, { text => 'foobar' } );
        },
        'WithoutCache' => sub {
            $renderer_nocache->render( $controller, { text => 'foobar' } );
        },
    }
);

__END__
                 Rate     max_keys WithoutCache
max_keys     190934/s           --          -2%
WithoutCache 193846/s           2%           --

I recon in a heavy load environment with lots of calls it would eventually make a difference, but that is very hard to prove. So if you don't like to think about the internals of the cache, this plugin might be useful.


Old answer:

Looking at Mojolicious::Plugin::EPRenderer I found out that there is a cache. It's a Mojo::Cache instance, which has the methods get, set and max_keys, and inherits from Mojo::Base (like probably everything in Mojolicious).

The ::EPRenderer gets a $renderer, which is a Mojolicious::Renderer. It holds the Mojo::Cache instance. I looked at $c with Data::Printer, and found out that there is a $c->app that holds all of those.

Knowing this, you can easily make your own cache class that does nothing.

package Renderer::NoCache;
use Mojo::Base -base;

sub get {}
sub set {}
sub max_keys {}

Now you stick it into $c.

package Foo;
use Mojolicious::Lite;

get '/' => sub {
    my $c = shift;

    $c->app->renderer->cache( Renderer::NoCache->new );

    $c->render(template => 'foo', name => 'World');
};

app->start;

__DATA__

@@ foo.html.ep
Hello <%= $name =%>.

Now every attempt to get or set the cache simply does nothing. It will try caching, but it will never find anything.

Of course it's not great to make a new object every time. It would be better to make that object once at startup and get it into the internal permanent version of app. You have CGI, so it might not make a difference.


You could also just monkey-patch the get out of Mojo::Cache. This more hacky approach will do the same thing:

package Foo;
use Mojolicious::Lite;

*Mojo::Cache::get = sub { };

get '/' => sub {
    my $c = shift;

    $c->render(template => 'foo', name => 'World');
};

app->start;

But beware: we just disabled fetching from every cache in your application that uses Mojo::Cache. This might not be what you want.



来源:https://stackoverflow.com/questions/41750243/mojolicious-template-cache-is-stale

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!