freemarker-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Daniel Dekany <ddek...@freemail.hu>
Subject [FM3] Solving FM2 caching problems due to locale etc.
Date Sat, 18 Mar 2017 22:47:26 GMT
The problem in FM2
------------------

In FM2, if you write `cfg.getTemplate("foo.ftl", Locale.GERMANY)`,
then FreeMarker will return a Template where template.getLocale() is
Locale.GERMANY, even if there's no foo_de.ftl or such, and it has just
fallen back to the most generic foo.ftl template. In the template
cache, we will have an entry like

  ("foo.ftl", GERMANY) -> Template where getLocale() returns GERMANY

Then if you do `cfg.getTemplate("foo.ftl", Locale.ITALY)`, then you
will have yet another cache entry:

  ("foo.ftl", ITALY) -> Template where getLocale() returns ITALY

So far you loaded (I/O!) and parsed foo.ftl twice, and you also store
the parsed template (the AST, which includes all the static text too)
in the memory twice. You see the problem; you don't reuse a single
Template object, despite there's not foo_de.ftl or foo_it.ftl. It's
wasteful, and users has complained about it too (if you use the
preferred locale of the browser of the visitor, the bloat can be
disastrous).

Actually, the are multiple Template properties that have the same
problem (cache inefficiency):
- locale: See earlier
- name: This stores the name used for the lookup (such as for the
  localized lookup, but note that the lookup strategy is pluggable).
  This is the normalized form of what you pass to
  Configuration.getTemplate(...).
- customLookupCondition: Used by some custom lookup strategies. Also
  passed to Configuration.getTemplate(...)
- (There's also `encoding` and `parsed`, but those will get out of the
  way according the proposal from 2017-02-06.)


Possible solution  in FM3
-------------------------

We could introduce something like LookupIndependentTemplate, which
stores:
- The parsed template (the AST)
- The sourceName of the template (it's the name used as the parameter
  of the TemplateLoader that has actually loaded the template "file",
  so this is past lookup)
- The template specific configuration settings (provided by
  the Configuration.templateConfigurations setting, also by the #ftl
  header in the template)
- Reference to the Configuration

Then there's Template, which is familiar from FM2, but this time it
only contains:
- The lookup parameters:
  - locale used for the lookup
  - name (the one used for the lookup)
  - customLookupCondition
- The LookupIndependentTemplate. Multiple Template-s may use the same
  LookupIndependentTemplate.

The template cache split to two layers:
- Level 1: Looks up the Template with the lookup parameters. So this
  is very similar to the FM2 cache.
- Level 2: Looks up the UnboundTempalate based solely on the
  sourceName (the TemplateLoader parameter).

So if you have a level 1 cache miss (or stale entry hit), then during
the lookup, we get the templates from the level 2 cache. So in the
original example, you will still have two Template-s (one for GERMANY
and one for ITALY), but they are quite light and share the single
UnboundTemplate that belongs to "foo.ftl". You have two entries in the
level 1 cache merely to speed up the lookup. (Though the lookup on top
of a level 2 cache that has already warmed up is quite fast, because
just as in FM2, we would cache negative results, on both levels. Like
we cache the fact that there was no "foo_de_DE.ftl" and "foo_de.ftl".)

A tricky corner case is when LookupIndependentTemplate itself stores a
non-null Locale (it can, as a TemplateCofiguration can contain all
runtime configuration settings), which differs from the Locale for
which the template was successfully looked up. I guess then that
should take precedence over Template.locale. (A possible use case: you
might don't use localized lookup, and instead the template itself
dictates the locale. After all, the static text in the template uses
some language.)

Any thoughts?

-- 
Thanks,
 Daniel Dekany


Mime
View raw message