RFC: Koha::Persistent - for plack and general performance
I would like to propose new module which would keep track of all persistant values for rest of Koha code. We are currently using following caching strategies: 1. our $cache variables 2. memoize (in-memory) 3. memoize_memcache (in memcache only) As opposed to C4::Cache* which never got implemented in all code, I propose new Koha::Persistant[1] module which provides methods for caching designed in such a way to reduce code size in modules which use it, see [2] for authorised_value and [3] for marc_subfield_structure 1: https://github.com/dpavlin/Koha/blob/plack/Koha/Persistant.pm 2: https://github.com/dpavlin/Koha/commit/6cd524f99b1bbc03eb1ad7110b0b970165693... 3: https://github.com/dpavlin/Koha/commit/70a414d93bd0ae309e9d6097ea87a17e3341f... I implemented just two function to see if this approach makes sense. I started with DBI Profile to track down same or similar queries and try to make nice API which satisfies requirements above. It also provides $Koha::persistant::stats which can be used to track hit/miss ratio of cache so we can easily evaluate caching impact. There is even Plack development panel, see "Persistant" at 1:01-1:30 http://blog.rot13.org/koha/koha-plack-debug.ogv Koha::Persistant should also include invalidation functions for all structures which it addresses, so they can be inserted in corresponding places to provide correct values when changing data directly in database. This will be done for existing functions before I submit it to Koha to keep everything clean from the start. It would also provide memoize function, which would replace memoize_memcahe and provide in-memory caching together with memcache, but still cache in memory if memcache isn't available. As shown in http://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=7879 we do get performance benefit from it, since our code is using MySQL query cache a lot which means we are quering for same data over and over again, see: http://wiki.koha-community.org/wiki/Performance#MySQL_tuning In first phase, I would propose to reset in-memory cache and memoize functions on each request to be consistant with CGI implementation and side-step caching bugs which we see with plack right now. This style of implementation will require large patches which touch many different places of code at same time. I think that 3.10 relese cycle is good way to introduce this changes, but I would need commitment from developers to Sign-off and QA patches fast, since those changes will be hard to keep up with development. p.s. I will create RFC wiki page after discussion, but I would really need some response before continuing down this path. -- Dobrica Pavlinusic 2share!2flame dpavlin@rot13.org Unix addict. Internet consultant. http://www.rot13.org/~dpavlin
Le 04/04/2012 17:48, Dobrica Pavlinusic a écrit : > I would like to propose new module which would keep track of all > persistant values for rest of Koha code. Hi Dobrika, > As opposed to C4::Cache* which never got implemented in all code, > I propose new Koha::Persistant[1] module which provides methods for caching > designed in such a way to reduce code size in modules which use it, > see [2] for authorised_value and [3] for marc_subfield_structure I agree a good cache handling is needed, and we need to think about cache/persistence with plack in our mind. How Koha works currently in CGI mode : * a page is called / Perl compiler is loaded / script is compiled and run * the script calls some subs that does SQL calls * if sub A needs something in authorized_values, a SQL request is issued, then if sub B needs something in authorized_values, maybe the same SQL request is issued again The "ideal" situation: * a page is called * plack has it already in one thread * the script calls some sub that need authorized_values, that are available without any SQL request -caching- Your work with Koha::Persistent is a step in the proper direction, but I think it's not the best we should/have We discussed, a long time ago, of splitting .pm in 2 parts: Data Accessors and Business Logic. Everybody agreed it's the way we want to go, but for now, it's only a theory, nothing has been made in this direction. The code could be rewritten this way: Koha::DataAccess::authorized_values.pm is the ONLY way to retrieve authorized values. This package contains something like authorized_values->get_value($category, $authorized_value) that returns the result of SELECT * FROM authorized_values WHERE category=? and authorized_value=? the get_value is Memoized => It means it's cached. I've investigated Memoize, and have found 2 interesting things: * there's a facility called "flush_cache": > "flush_cache(function)" will flush out the caches, discarding all the cached data. The argument may be a > function name or a reference to a function. For finer control over when data is discarded or expired, see the > documentation for "Memoize::Expire", included in this package. > > Note that if the cache is a tied hash, "flush_cache" will attempt to invoke the "CLEAR" method on the hash. If > there is no "CLEAR" method, this will cause a run-time error. > > An alternative approach to cache flushing is to use the "HASH" option (see above) to request that "Memoize" use > a particular hash variable as its cache. Then you can examine or modify the hash at any time in any way you > desire. You may flush the cache by using "%hash = ()". There's also a Memoize::Expire package that could let us define expiration rules for memoized functions. Why is this not so interesting in CGI mode ? because the CGI mode make everything die after each request. It means it's quite probable that a memoized function will be called only a few times, usually 1, on each call, the the script dies, all what is memoized is gone. The memcached is a workaround according to me: it put the result in a memcached server, so doesn't care of the fact that the CGI dies, but the price of reaching the page is very high, limiting the gain (chris_c said many times that memcached is not a matter of performance, but of scalability) === Flushing the cache in a Plack environment === For now, by default, plack threads have a 30 pages duration. If you run starman with 6 threads, it means, if I understand well, that a change in a cached data could be ignored for *at most* 180 pages. That's not a big deal, but: * that's a deal * if we want to continue improve performances, the best goal would be to be able to serve 1000 pages before dying How could be address this problem ? I haven't found how to "flush thread" in plack, if there's a way to do it, i'll be happy to learn ;-) If there is one, we could split the Koha::DataAccess in 2 sub-parts: parameters and non parameters. Like Koha::DataAccess::Parameter::Authorized_value->get_... Any change in one of the value meaning "hey, we must flush plack because we must flush all memoized values" OTOH, if we do that *each time* there is a value changed, that will be very CPU consuming, a better way would be to have something like: * if something has been updated on admin-home.pl page, rise a flag that: - warn the user who tries to exit admin-home.pl without restarting the plack - add a link to restart Plack. The more I think of it, the more I like it: that would be a first step in the direction of business logic / data access, plus an interesting boost in performance and scalability. == SQL load == this discussion is also related to "Performance issues with mysql and Koha on 2 physical servers" == Conclusion == My preference strongly goes to a long-term solution, and the Koha::DataAccess::Authorized_value is more long term that your proposition. So I would favor it. If someone can/want to throw an alternate long-term proposition, feel free ! If I get some positive feedback, I'll try to throw some code soon ;-) (but with the 3.8 release in 2 weeks, that will probably be in may !) -- Paul POULAIN http://www.biblibre.com Expert en Logiciels Libres pour l'info-doc Tel : (33) 4 91 81 35 08
On 5 April 2012 23:20, Paul Poulain <paul.poulain@biblibre.com> wrote: > Le 04/04/2012 17:48, Dobrica Pavlinusic a écrit : >> I would like to propose new module which would keep track of all >> persistant values for rest of Koha code. > Hi Dobrika, > >> As opposed to C4::Cache* which never got implemented in all code, >> I propose new Koha::Persistant[1] module which provides methods for caching >> designed in such a way to reduce code size in modules which use it, >> see [2] for authorised_value and [3] for marc_subfield_structure > I agree a good cache handling is needed, and we need to think about > cache/persistence with plack in our mind. > How Koha works currently in CGI mode : > * a page is called / Perl compiler is loaded / script is compiled and run > * the script calls some subs that does SQL calls > * if sub A needs something in authorized_values, a SQL request is > issued, then if sub B needs something in authorized_values, maybe the > same SQL request is issued again > > The "ideal" situation: > * a page is called > * plack has it already in one thread > * the script calls some sub that need authorized_values, that are > available without any SQL request -caching- > > Your work with Koha::Persistent is a step in the proper direction, but I > think it's not the best we should/have > > We discussed, a long time ago, of splitting .pm in 2 parts: Data > Accessors and Business Logic. Everybody agreed it's the way we want to > go, but for now, it's only a theory, nothing has been made in this > direction. > > The code could be rewritten this way: > Koha::DataAccess::authorized_values.pm is the ONLY way to retrieve > authorized values. > This package contains something like > authorized_values->get_value($category, $authorized_value) > that returns the result of > SELECT * FROM authorized_values WHERE category=? and authorized_value=? > > the get_value is Memoized => It means it's cached. > > I've investigated Memoize, and have found 2 interesting things: > * there's a facility called "flush_cache": >> "flush_cache(function)" will flush out the caches, discarding all the cached data. The argument may be a >> function name or a reference to a function. For finer control over when data is discarded or expired, see the >> documentation for "Memoize::Expire", included in this package. >> >> Note that if the cache is a tied hash, "flush_cache" will attempt to invoke the "CLEAR" method on the hash. If >> there is no "CLEAR" method, this will cause a run-time error. >> >> An alternative approach to cache flushing is to use the "HASH" option (see above) to request that "Memoize" use >> a particular hash variable as its cache. Then you can examine or modify the hash at any time in any way you >> desire. You may flush the cache by using "%hash = ()". > > There's also a Memoize::Expire package that could let us define > expiration rules for memoized functions. > > Why is this not so interesting in CGI mode ? because the CGI mode make > everything die after each request. It means it's quite probable that a > memoized function will be called only a few times, usually 1, on each > call, the the script dies, all what is memoized is gone. > > The memcached is a workaround according to me: it put the result in a > memcached server, so doesn't care of the fact that the CGI dies, but the > price of reaching the page is very high, limiting the gain (chris_c said > many times that memcached is not a matter of performance, but of > scalability) > I disagree, it is not a workaround, centralised caching is vital. Whether it be memcached, reddis, or true globals. What we cannot have is the situation where different threads are using different values. Also the memoize_memcached module has an issue where it works twice as hard as it needs to, Dobrika has a patch for this. > === Flushing the cache in a Plack environment === > For now, by default, plack threads have a 30 pages duration. If you run > starman with 6 threads, it means, if I understand well, that a change in > a cached data could be ignored for *at most* 180 pages. That's not a big > deal, but: It is a big deal, because its 6 threads .. with 30 requests and those threads may be returning different values, unless they are using a shared cached (or shared memory). > * that's a deal > * if we want to continue improve performances, the best goal would be to > be able to serve 1000 pages before dying > > How could be address this problem ? I haven't found how to "flush > thread" in plack, if there's a way to do it, i'll be happy to learn ;-) > > If there is one, we could split the Koha::DataAccess in 2 sub-parts: > parameters and non parameters. Like > Koha::DataAccess::Parameter::Authorized_value->get_... > > Any change in one of the value meaning "hey, we must flush plack because > we must flush all memoized values" > OTOH, if we do that *each time* there is a value changed, that will be > very CPU consuming, a better way would be to have something like: > * if something has been updated on admin-home.pl page, rise a flag that: > - warn the user who tries to exit admin-home.pl without restarting the > plack > - add a link to restart Plack. > > > The more I think of it, the more I like it: that would be a first step > in the direction of business logic / data access, plus an interesting > boost in performance and scalability. I dislike this, we should solve the problem properly not introduce hacks to get around the fact we have done a bad job of engineering, that's just technical debt we have to pay back. Having worked on projects with huge load, and persistance, I have learnt proper caching and cache invalidation is vitally important and getting it right at the start is a lot easier than trying to do it at the end. > > == SQL load == > this discussion is also related to "Performance issues with mysql and > Koha on 2 physical servers" > > == Conclusion == > My preference strongly goes to a long-term solution, and the > Koha::DataAccess::Authorized_value is more long term that your > proposition. So I would favor it. If someone can/want to throw an > alternate long-term proposition, feel free ! > I think Dobrika is on the right path, we should build a proper caching framework first, one that wont cause data inconsistencies under persistance, your Koha::DataAccess::Authorized_value subroutine could then use that. Chris > If I get some positive feedback, I'll try to throw some code soon ;-) > (but with the 3.8 release in 2 weeks, that will probably be in may !) > -- > Paul POULAIN > http://www.biblibre.com > Expert en Logiciels Libres pour l'info-doc > Tel : (33) 4 91 81 35 08 > _______________________________________________ > Koha-devel mailing list > Koha-devel@lists.koha-community.org > http://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-devel > website : http://www.koha-community.org/ > git : http://git.koha-community.org/ > bugs : http://bugs.koha-community.org/
Le 06/04/2012 10:14, Chris Cormack a écrit :
>> The memcached is a workaround according to me: it put the result in a
>> memcached server, so doesn't care of the fact that the CGI dies, but the
>> price of reaching the page is very high, limiting the gain (chris_c said
>> many times that memcached is not a matter of performance, but of
>> scalability)
>>
> I disagree, it is not a workaround, centralised caching is vital.
I agree with this.
> Whether it be memcached, reddis, or true globals. What we cannot have
> is the situation where different threads are using different values.
I agree with this. My main concern is to define our goal properly.
What do we want ?
I think it's:
* caching techniques as efficient as possible, to reduce the SQL
overhead. We can also probably use caching techniques to reduce XSLT
processing overhead (for libraries that have XSLT activated)
* manage the multi-threading problem
* centralized code in Koha, for a better modularity
do we agree on those goals ?
>> === Flushing the cache in a Plack environment ===
>> For now, by default, plack threads have a 30 pages duration. If you run
>> starman with 6 threads, it means, if I understand well, that a change in
>> a cached data could be ignored for *at most* 180 pages. That's not a big
>> deal, but:
>
> It is a big deal, because its 6 threads .. with 30 requests and those
> threads may be returning different values, unless they are using a
> shared cached (or shared memory).
My english was probably incorrect here. I just wanted to say that 180 is
not a big number (for a library having 100 000 hits per day), but it's a
number to deal with.
>> The more I think of it, the more I like it: that would be a first step
>> in the direction of business logic / data access, plus an interesting
>> boost in performance and scalability.
>
> I dislike this, we should solve the problem properly not introduce
> hacks to get around the fact we have done a bad job of engineering,
> that's just technical debt we have to pay back.
OK you dislike it, and I understand your argument. What would be a
proper technique then ?
> Having worked on projects with huge load, and persistance, I have
> learnt proper caching and cache invalidation is vitally important and
> getting it right at the start is a lot easier than trying to do it at
> the end.
Agreed !
> I think Dobrika is on the right path, we should build a proper caching
> framework first, one that wont cause data inconsistencies under
> persistance, your Koha::DataAccess::Authorized_value subroutine could
> then use that.
Maybe there's something I'm missing, but I think what Dobrica (with a c,
you're right, I don't know why I always write it with a k !) proposal
don't manage cache invalidation. It appear to me as a shared memory (our
$_cache) that contains $_cache->{$key} = $value, but I don't see a way
to reset the cache. Am I missing something ?
My feeling is that Dobrica proposal has exactly the problem you're
describing (as my proposal has)
As you're experimented in caching, a question: I think cache
invalidation can be made by 2 different techniques:
* when a data make the cache invalid, it send a message to all cache to
say "hey, cache is reseted". Cache is reseted, and next time a data is
requested it won't be in the cache.
* when a data make the cache invalid, it just set a flag saying "the
cache is invalid", and everytime a data is requested, the cache handler
check if the cache must be reseted, and if it must, reset and re-load
the real data.
Am I right if I say that the 1st technique is the best and is what is
achieved by ->flush_cache() method of (memoize::)memcached ?
Other question: I see in memcached that the server can be
/var/sock/memcached. Is it possible that it would be faster than a full
IP addresse (for libraries that would have the memcache server on the
same server as the Koha server) ?
--
Paul POULAIN
http://www.biblibre.com
Expert en Logiciels Libres pour l'info-doc
Tel : (33) 4 91 81 35 08
Would it be practical and efficient to cache shared data to file (or a
series of files)? My understanding is that reading files is faster than
doing SQL queries, but not as fast as accessing memcached. But, the file
would have the advantage of being non-threaded. I don't think
configuration things like sysprefs, frameworks or issuing rules would
change frequently enough for us to need to worry about concurrent writes,
but I suppose that should also be factored in...
-Ian
On Fri, Apr 6, 2012 at 06:23, Paul Poulain <paul.poulain@biblibre.com>wrote:
> Le 06/04/2012 10:14, Chris Cormack a écrit :
> >> The memcached is a workaround according to me: it put the result in a
> >> memcached server, so doesn't care of the fact that the CGI dies, but the
> >> price of reaching the page is very high, limiting the gain (chris_c said
> >> many times that memcached is not a matter of performance, but of
> >> scalability)
> >>
> > I disagree, it is not a workaround, centralised caching is vital.
> I agree with this.
>
> > Whether it be memcached, reddis, or true globals. What we cannot have
> > is the situation where different threads are using different values.
> I agree with this. My main concern is to define our goal properly.
> What do we want ?
> I think it's:
> * caching techniques as efficient as possible, to reduce the SQL
> overhead. We can also probably use caching techniques to reduce XSLT
> processing overhead (for libraries that have XSLT activated)
> * manage the multi-threading problem
> * centralized code in Koha, for a better modularity
> do we agree on those goals ?
>
> >> === Flushing the cache in a Plack environment ===
> >> For now, by default, plack threads have a 30 pages duration. If you run
> >> starman with 6 threads, it means, if I understand well, that a change in
> >> a cached data could be ignored for *at most* 180 pages. That's not a big
> >> deal, but:
> >
> > It is a big deal, because its 6 threads .. with 30 requests and those
> > threads may be returning different values, unless they are using a
> > shared cached (or shared memory).
> My english was probably incorrect here. I just wanted to say that 180 is
> not a big number (for a library having 100 000 hits per day), but it's a
> number to deal with.
>
> >> The more I think of it, the more I like it: that would be a first step
> >> in the direction of business logic / data access, plus an interesting
> >> boost in performance and scalability.
> >
> > I dislike this, we should solve the problem properly not introduce
> > hacks to get around the fact we have done a bad job of engineering,
> > that's just technical debt we have to pay back.
> OK you dislike it, and I understand your argument. What would be a
> proper technique then ?
>
> > Having worked on projects with huge load, and persistance, I have
> > learnt proper caching and cache invalidation is vitally important and
> > getting it right at the start is a lot easier than trying to do it at
> > the end.
> Agreed !
>
> > I think Dobrika is on the right path, we should build a proper caching
> > framework first, one that wont cause data inconsistencies under
> > persistance, your Koha::DataAccess::Authorized_value subroutine could
> > then use that.
> Maybe there's something I'm missing, but I think what Dobrica (with a c,
> you're right, I don't know why I always write it with a k !) proposal
> don't manage cache invalidation. It appear to me as a shared memory (our
> $_cache) that contains $_cache->{$key} = $value, but I don't see a way
> to reset the cache. Am I missing something ?
> My feeling is that Dobrica proposal has exactly the problem you're
> describing (as my proposal has)
>
>
> As you're experimented in caching, a question: I think cache
> invalidation can be made by 2 different techniques:
> * when a data make the cache invalid, it send a message to all cache to
> say "hey, cache is reseted". Cache is reseted, and next time a data is
> requested it won't be in the cache.
> * when a data make the cache invalid, it just set a flag saying "the
> cache is invalid", and everytime a data is requested, the cache handler
> check if the cache must be reseted, and if it must, reset and re-load
> the real data.
>
> Am I right if I say that the 1st technique is the best and is what is
> achieved by ->flush_cache() method of (memoize::)memcached ?
>
> Other question: I see in memcached that the server can be
> /var/sock/memcached. Is it possible that it would be faster than a full
> IP addresse (for libraries that would have the memcache server on the
> same server as the Koha server) ?
> --
> Paul POULAIN
> http://www.biblibre.com
> Expert en Logiciels Libres pour l'info-doc
> Tel : (33) 4 91 81 35 08
> _______________________________________________
> Koha-devel mailing list
> Koha-devel@lists.koha-community.org
> http://lists.koha-community.org/cgi-bin/mailman/listinfo/koha-devel
> website : http://www.koha-community.org/
> git : http://git.koha-community.org/
> bugs : http://bugs.koha-community.org/
>
2012/4/6 Ian Walls <koha.sekjal@gmail.com>:
Would it be practical and efficient to cache shared data to file (or a series of files)? My understanding is that reading files is faster than doing SQL queries, but not as fast as accessing memcached. But, the file would have the advantage of being non-threaded. I don't think configuration things like sysprefs, frameworks or issuing rules would change frequently enough for us to need to worry about concurrent writes, but I suppose that should also be factored in...
If you are going to cache to files, you might as well just cache to memcached or redis, which are also non-threaded and a lot faster. Chris
As you're experimented in caching, a question: I think cache invalidation can be made by 2 different techniques: * when a data make the cache invalid, it send a message to all cache to say "hey, cache is reseted". Cache is reseted, and next time a data is requested it won't be in the cache. * when a data make the cache invalid, it just set a flag saying "the cache is invalid", and everytime a data is requested, the cache handler check if the cache must be reseted, and if it must, reset and re-load the real data.
Am I right if I say that the 1st technique is the best and is what is achieved by ->flush_cache() method of (memoize::)memcached ?
The third option, and best option imho is when a value is change, it is injected into the cache at that point. The second best option is to remove the object from the cache (option 1) Frederic is right, the big when is in caching serialised objects, not sql results, things like the rdbms and DBD::Gofer cache sql results well.
Other question: I see in memcached that the server can be /var/sock/memcached. Is it possible that it would be faster than a full IP addresse (for libraries that would have the memcache server on the same server as the Koha server) ?
Could be, but like I was saying we shouldn't be using memcached (or application level caching) for simple sql queries, we should be caching the things that either are slow queries or are the result of a lot of queries. We should be caching processed results, things that are computational expensive to generate. Also redis seems to be the clear leader in key-value stores now, we should definitely investigate that more. Chris
Le 06/04/2012 12:40, Chris Cormack a écrit :
Am I right if I say that the 1st technique is the best and is what is achieved by ->flush_cache() method of (memoize::)memcached ?
The third option, and best option imho is when a value is change, it is injected into the cache at that point. Agreed, I have missed this option !
The second best option is to remove the object from the cache (option 1) Frederic is right, the big when is in caching serialised objects, not sql results, things like the rdbms and DBD::Gofer cache sql results well. I think I don't understand what you mean here.
Could be, but like I was saying we shouldn't be using memcached (or application level caching) for simple sql queries, we should be caching the things that either are slow queries or are the result of a lot of queries. We should be caching processed results, things that are computational expensive to generate. Also redis seems to be the clear leader in key-value stores now, we should definitely investigate that more. So how should we do to reduce the load caused by the repeated SQL queries for systempreferences, languages, itemtypes, patron categories,... ?
You think we should use something that is not caching ? You suggest memcache caching for complex things and another method for "simple" queries ? which method ? Could it be an option to build dynamically a perl file that contains, for example, a hash with all sysprefs, that is loaded through a use Koha::Cache::Sysprefs; Koha::Cache::Sysprefs.pm being my $syspref = { 'IndependantBranche' => 0, 'LibraryName'=> "my library", ...} that is just build automatically, and only once ? Aren't the sysprefs and some other parameters stable enough to accept flushing Plack threads when something is changed ? (I'm not sure at all this idea is good, just want to avoid missing something interesting ) -- Paul POULAIN http://www.biblibre.com Expert en Logiciels Libres pour l'info-doc Tel : (33) 4 91 81 35 08
Since Koha is a multi-threading app, with or without Plack, caching is not that easy. There is already some caching but at thread level. Memcached can be a solution for sharing cached data structure (like framework for example) between threads, and even between threads running on multiple servers. MySQL can even been used in place of memcached: we replace multiple queries by one query to a MySQL 'cache' table. If we agree that multi-application-servers is not a requirement, other caching solution are possible, in memory caching like Cache::FastMmap for example. SQL queries optimization is another issue. If data structures requiring a lot of SQL queries to be build are cached, we don't care that much if those queries are all but perfect. Of course, we still can focus on optimizing other SQL queries: on biblios/items, etc. Dobrica Koha::Persistant module make sense to me. It has thread in-memory caching now, but can be extended to implement multi-thread in-memory or in memcached (or redis, MySQL...) caching. The question is more: is it necessary to mix caching and SQL querying? Isn't it too much or too complex? The key in Dobrica cache is calculated based on query parameters: is it robust enough? There is also a coming issue to solve when caching will be done in multi-threading: the data structure will have to be deserialized when getting it, or serialized when put in cache (storable, something else). To avoid having to get & deserialize data, again and again, we can have a two level caching, in thread memory, and in-memcached, but then the invalidation issue come back... My limited thoughts... -- Frédéric DEMIANS http://www.tamil.fr/u/fdemians.html
Le 06/04/2012 12:08, Frédéric Demians a écrit :
Dobrica Koha::Persistant module make sense to me. It has thread in-memory caching now, but can be extended to implement multi-thread in-memory or in memcached (or redis, MySQL...) caching. The question is more: is it necessary to mix caching and SQL querying? Isn't it too much or too complex? The key in Dobrica cache is calculated based on query parameters: is it robust enough? That's a question I have too. Another one: couldn't this be replaced by a memoize _sql_cache => memoize return the result from cache if the parameters are the same as one that have already be sent.
So, the _sql_cache could be rewritten: use Memoize; memoize('_sql_cache'); sub _sql_cache { my $sql = shift; my @var = @_; my $dbh = C4::Context->dbh; my $sth = $dbh->prepare( $sql ); $sth->execute( @var ); my $v = $sth->fetchrow_hashref; } Dobrica, aren't those 2 subs (yours and mine) equivalent ? -- Paul POULAIN http://www.biblibre.com Expert en Logiciels Libres pour l'info-doc Tel : (33) 4 91 81 35 08
I will try to include answers to all questions which where addressed to me in thread in this single message. If I missed something, sorry :-) I played around the code a bit more (and plan to spend a few more days on it to have some idea how much of the *measurable* change it will make), but I will try to simplify my proposal a bit: My goal are short-term, one release cycle as opposed to long-term refactor (with which I agree) 1) existing code accessors cleanup Existing code already has accessors which can be reused and memoized, so I started submitting cleanups on on existing code which make use of those as preparation for memoize. I'm hoping that those patches are nice code cleanups which will help code maintenance along with primary goal of providing memoizable functions in single place. See: (I'm including github links because it looks even scarier with color diff) https://github.com/dpavlin/Koha/commit/eb1102ab3c096b80663b878a378741c08369f... https://github.com/dpavlin/Koha/commit/ae926c02fe1e4b579ff42f880681bc3069260... Q: my current strategy is to create one commit per changed function. I'm planning to attach all of them to Bug 7872, is that OK? I don't think that test scenarios are best way to test this changes (since most of them might be "run any code that uses this function", so what can I do to make SO and QA easier?
From what I can see, they will touch C4::Items and C4::Biblio (for which I will open separate bug)
2) smarter memoize with per-request and persistent cache Existing memoize does not have correct cache invalidation under plack (--max-requests is gross hack and I would recommend running only anonymous OPAC with that ;-) so I will write alternative memoize which will call unmemoize on each request. This mimics CGI, provides performance improvement and should be safe to run. Existing memoize_memcached functions will become memoize_persistent which will be able to cache in memcache, shared memory (why run memcache when you don't need to for small instances), Redis or DB_File (Ian mentioned files). We could use Memoize::Expire for that purpose to keep in-memory cache under control. My goal it to have pluggable caching back-ends without changing Koha code (or via system preference!) Q: for this I will try to submit bug perl memoize function, together with cache invalidation hooks needed to make it work under plack. OK? I would really love to move to Redis (with which I have very good experience since I'm original author of perl bindings ;-) mostly because it has per-key cache invalidation which memcache lacks (and this invalidates whole cache at once). Redis ability to lookup cache keys using globs (e.g. *item*12342*) might help a *lot* in cache invalidation. memoize_persistant definitions should be accompanied with calls to invalidation. I do agree that inserting new value into cache is better solution so I will have that in mind. 3) caching of full database tables This point is subtly different than memoize: sometimes we benefit from caching whole data structure from database (frameworks, itemtypes, languages, systempreferences) but in a way that allows us to retrieve single items. Another example are functions which would be memoizable but use some kind of parameter ($opac, $user, $branch, $selected or so) which would require to memoize whole structure again and again. This is now we are using our $cache variables now. I propose to move all of them to Koha::Persistent so we can do full_size on that class to get memory usage and enable correct invalidation in single place (per-request) or have proper invalidation functions. This is also a reason why sql_cache function tries to create proper multi-level hash with cached values instead of just memoizing $sth->fetchrow_hashref. With proper invalidation those values might be stored in shared memory so that all plack threads have access to them. 4) performance patches Non-caching related but still important, like missing indexes or my favorite example of this (so far): Bug 7846 - get_batch_summary reimplements GROUP BY in perl code http://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=7846 (which needs SO :-) 5) testing and statistics I do intend to run this code in production. To be honest, plack didn't bright all performance improvements I was hoping for, and I think that we can fix it in 3.10 release cycle (search page in my favorite one to test with). To achieve that, I will collect statistical data about cache usage (hit/miss). I found it very valuable for developing so far and it's always nice to have some idea how cache is performing. I must say that developing under plack is a joy: fast page load time is nice and you get used to it quite quickly, so slow parts of code pop up even without DBIProfile or NYTProf :-) -- Dobrica Pavlinusic 2share!2flame dpavlin@rot13.org Unix addict. Internet consultant. http://www.rot13.org/~dpavlin
Food for though... With Plack, we are in a persistent environment. It means we can keep Koha configuration information in each thread. Config info are all data that doesn't change that much and are not too large (like biblio catalog). In each thread, at startup, loading configuration data is required. In Koha: frameworks, authorized_values, item types, branches, etc. It implies a lot of SQL requests. If we have a application-level cache, rather than loading info from DB or config files (koha-conf.xml XML parsing), we can get them from the global cache (memcached, redis, MySQL based, in-memory, etc.). A key/value cache doesn't handle Perl native data type. So it means that data has to be deserialized. It consumes CPU, less than getting data from DB/config files, but still. So in a persistent environment, we should try to avoid that, and cache locally, in each thread, configuration data. We have something like in this schema: thread-1 thread-2 thread-n | | | local local local cache cache cache ( | ) \ | / \ | / +--- application----+ cache | DB or config files So if thread-2 modify something in its local cache, it can cascading update application cache and DB / config files. The difficulty is to notify other threads to update their local cache. The question is: is there such a mechanism in Plack? I don't think so. Another solution could be to define a configurable timeout for each task. Every n seconds, the thread is restarted. Or we can have directly in Koha a timer which force reloading local cache. In both cases, the timeout can be adjusted. About the application cache, we may take a look at CHI module which seem to be standard, and allow to switch easily cache repository and cache serializer. -- Frédéric DEMIANS http://www.tamil.fr/u/fdemians.html
participants (5)
-
Chris Cormack -
Dobrica Pavlinusic -
Frédéric Demians -
Ian Walls -
Paul Poulain