In which scope would you store a cachedWithin query within a function?

Our scenario is as follows:

  1. The applicationTimeout setting is 1 day.
  2. The query that the function returns should remain unchanged for the duration of the application.

What would you do? Store the query in var scope, in application scope or in some other scope?

public query function getRecords() {

    /* Code that initializes sqlString and sqlParamsStruct, based on the arguments */
    var sqlString=someString;
    var sqlParamsStruct=someStruct;
   
    /*** First alternative ***/
    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 
    return recordsQuery;

   /*** Second alternative ***/
    /***
    application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 
    return application.recordsQuery;
    ***/

}

You can cache this query pretty easily using cachedWithin:

public query function getRecords() cachedWithin=createtimeout(1,0,0,0){

...

}

This means that if you pass different arguments to the getRecords() function you will have it nicely cached.

This would be only cached in the server that you called it in, so in clustered environments you might what to create a shared cache and set it to be the default for queries.

You can also cache the query for a specfic request (if it is for that person only)

2 Likes

Thanks for your suggestion, @markdrew .
Our code originates from Adobe ColdFusion. Hence, the var and application scopes. I wondered whether anyone would want to share what they consider to be the pros and cons of combining such scopes with cachedWithin.

That said, your suggestion is a cracker. It’s an approach we shall definitely take on board.

I have just discovered that your suggestion introduces a new scope. :slightly_smiling_face:
The following code shows that the function is held in memory in the variables scope.

public query function getRecords() cachedWithin=createtimeout(1,0,0,0){
...
}
writedump(variables);

The question is then what the pros and cons are of creating a cachedWithin query in combination with the var, application or variables scope. Is any of the scopes the most preferable?

It depends on what you want to accomplish with the caching. Is the data cached for a user or something static that many users will benefit from (by being in a shared scope)?
Also, make sure you pay attention to how much data you are caching. Memory goes fast.

This might help with the Scopes.

1 Like

Thanks, @Phillyun.
In answer to your question:

  1. The applicationTimeout setting is 1 day.
  2. The aim is to cache the query - for reuse by all users - for the duration of the application.

There are a number such queries throughout the application. That is why I want to weigh the pros and cons of each of the scopes.

To answer your initial question about being inside a function, I would use the local scope in most cases and would not cache (going to the DB doesn’t usually take very long).

application scope
will be shared across all users/requests (useful for your described use-case of many users READING the cached data as there is only one copy in memory).

Clarifying: In most cases, I wouldn’t use caching unless all of the following are true:

  • You need data in-memory for performance
  • It is expensive to fetch even post optimizations
  • It needs to be used many times (can benefit from being cached)
  • You have memory to spare
  • A plan for cache invalidation (when data changes) is in place

If you’re using cachedWithin, you can still continue to use the local scope inside the function.

When you cfdump the query, you will either see Cached: No or Cached: Yes along with the cache type used which might be helpful to see during development in confirming your cachedWithin time is working as intended.

So in the lucee admin you can define WHERE you store the cache. Lucee will manage the cache for your queries so it wont “fill up”. You can use an external fast store like redis to store your query cache too for example.

1 Like

Thanks, @Phillyun. You wrote:

If you’re using cachedWithin, you can still continue to
use the local scope inside the function.
When you cfdump the query, you will either see Cached: No or Cached: Yes

In our use-case, Cached: No is not allowed. That is why we also considered

 application.recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 
    return application.recordsQuery;

@Phillyun wrote:

application scope
will be shared across all users/requests
(useful for your described use-case of many users READING
the cached data as there is only one copy in memory).

That is indeed our use-case. The first user to run the query is the only one who suffers delay, the martyr. The query is then cached for the duration of the application. So every subsequent user will read the query in zero time.

You need data in-memory for performance

Yes.

It is expensive to fetch even post optimizations

Yes, very expensive.

It needs to be used many times (can benefit from being cached)

Yes.

You have memory to spare

We have at the moment. This point is in fact one of my main motivations for reaching out. On the subject of cachedWithin queries within a function, how do the scopes (var, application, variables) compare when it comes to memory-use, efficiency, reliability, and so on?

For example, owing to a problem in the underlying cache engine, the following code yielded a NullPointerException on two occasions:

 var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 

A plan for cache invalidation (when data changes) is in place

That is the reason for using a query within the Lucee application. The data (in the database) will keep changing, but not within the applicationTimeout period.

oh, got the stacktrace for the NPE?

Hi @Zackster ,
Stacktrace attached.
Stacktrace_NullPointerException_var_scoped_cachedWithin_Query.txt (10.6 KB)

@markdrew:
So in the lucee admin you can define WHERE you store the cache.
Lucee will manage the cache for your queries so it wont “fill up”.
You can use an external fast store like redis to store your query
cache too for example.

Thanks for the tip. I took it for granted that Lucee is optimized to do that for you - and more besides - when your applicationTimeout is 1 day and you run the code:

queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)})

lucee.runtime.exp.NativeException: java.lang.NullPointerException at ortus.extension.cache.redis.RedisCache.put(RedisCache.java:231) at lucee.runtime.cache.tag.timespan.TimespanCacheHandler.set(TimespanCacheHandler.java:90) at lucee.runtime.tag.Query._doEndTag(Query.java:734) at lucee.runtime.tag.Query.doEndTag(Query.java:565) at lucee.runtime.functions.query.QueryExecute.call(QueryExecute.java:86) at devxxxx.studiereader.service.classcodes_cfc$cf

ok that’s probably one for @lmajano, which version of the ortus redis extension are you using?

@Zackster : which version of the ortus redis extension are you using?

ortus.extension.rediscache-2.0.0.6427 on Lucee-5.3.9.146

1 Like

What do you make of the following puzzle?

  • Tha var scope implies that you should assume the object may no longer be in memory after the function returns. A subsequent call of the function - for the same object (that is, same SQL statement, params, data source, query name, user name, and password.) - might therefore result in a NullPointerException.

  • The cachedWithin implies you may assume the object is held in memory for 1 day.

public query function getRecords() {

    /* Code that initializes sqlString and sqlParamsStruct, based on the arguments */
    var sqlString=someString;
    var sqlParamsStruct=someStruct;
   
    var recordsQuery=queryExecute(sqlString, sqlParamsStruct, {cachedWithin: createTimeSpan(1, 0, 0, 0)}); 

    return recordsQuery;

}

So, it looks like we have a Schrödinger cat in our hands. Or have we? Is the code actually creating an inconsistent state?

The question is, might Lucee and RedisCache in fact be working as expected? Afterall, the NullPointerException is consistent with the design-by-contract principle.

According to this principle, when you var-scope a variable within a function, you’re telling the application that the corresponding object may be deleted from memory after the function returns.

If so, then the moral is clear. When you create a cachedWithin query within a function, store the variable in a persistent scope, such as application, rather than in the var scope.

when you use cachedWithin, it creates a hash of the arguments passed in and then checks for a matching cache entry according to the period defined in cachedWithin.

Most likely the NPE is just a coding error in the extension?

Some context: the same code (var-scoped cachedWithin query created inside a function) results, occasionally, in a NullPointerException in recent versions of Adobe ColdFusion.

I think there’s an update available? 2.0.0+6428