Developer ‘Tiqs & Tricks
May 2025
Cache Services
If you’ve been building Vantiq applications for a while, then you know this. Perchance you’ve even stitched it on a pillow, or crossed out “Live, Laugh, Love” on a wall hanging in favor of:
“Performance
predominates
persistence.”
We mean, of course, that avoiding resource-intensive database interactions by taking advantage of State management features in Vantiq is optimal for achieving real-time reactivity in application systems. There’s no way the slogan could be interpreted any other way.
Service State Management
Services maintain the State required to perform application logic. You can set a replication factor for that State to enhance fault-tolerance. Multiple Activity Tasks in Visual Event Handlers make use of program state, and the Service automatically generates procedures that developers can execute to manage it.
Mitigating Database Interactions
State is good as the “working” memory of the running application, but sometimes even applications need to look up additional information, or store something in the database for reference later. Within Services, there are tools to keep database reads and writes to a minimum. One example is CachedEnrich, which will preserve a database read operation in memory for a set period of time, so future look-ups in that timeframe will use the in-memory material rather than instigate a new read. Scheduled Procedures can persist something in State asynchronously at automated time intervals. And of course, developers know better than to persist all the events that come in. Be selective, and only keep what will be useful for later retrieval.
Cached Services
It’s time to unveil another feature Vantiq provides for protecting application performance from ill-timed or excessive database interactions. You’ve seen it; every time you create a new Service, you’re asked to select which kind:

In the Foundations class, we (politely, I’m sure) ignored Cache Services in favor of the “regular” flavor, but it turns out that Cached Services are a powerful tool in “Database Interaction Minimalism.” Thus, they deserve some attention.
Upon clicking the “New Cache Service” choice, you’re immediately confronted with another one:

So, there are two kinds of Cached Services:
Lookup Cache:
This is perfect for information already in a database that will be referenced often, but doesn’t change nearly as often. In other words, this behaves much like a CachedEnrich.
The type selected must have a unique Cache Key. This is a unique property or set of properties that uniquely identifies records stored in the type.
When the Service opens, you’ll see that there is already an automatically-generated service public procedure created for you. Curiously, it’s not in the “generated” listing. This is because developers are encouraged to make changes to the procedure as needed by the program. A little “light reading” of this code shows that the procedure does what you expect it would:
- Check if the desired record already exists in State
- If not, select it from the type, and add it to State
- Return the desired record

Update Cache:
This type of Cache Service works something like a scheduled procedure, allowing the developer to set how often upserts occur on a standard type, asynchronously. At minimum, that interval is per-minute, but it can be set to “weeks,” too.

Just like in the Lookup Cache, the Update Cache requires a unique Cache Key for performing writes on the type.

When the new Service opens, you’ll notice that there are three Service procedures already written for you:

The “GetBy” procedure we saw from the Lookup Cache, so you can use this Service as a Lookup Cache as well.
The “UpdateBy” procedure isn’t what you might think it is! This is doing no writes to the database, but rather is updating the type record… in State only.
“WriteAll” collects all the records in the State partition, and then performs a bulk upsert operation for all of those records at once. Using a “Pessimistic Locking” model, (which assumes there will be frequent updates of this State), after collecting the current records in the State partition, the procedure clones the collection, and thereafter works with just the clone, leaving the State partition alone to continue changing for the next WriteAll execution.
Use Case #1: Replace a CachedEnrich with a Cache Service Procedure
Remember this Visual Event Handler from the Foundations Class?

After we took in pump sensor readings, we combined them by PumpId, then sent them into this VEH. The leftmost branch filters for anomalous readings, adds pump location information from the PumpDetails type, and sends the bad news on, where we’ll deal with it in another Service. That AddDetails task, right after the filter for bad readings, is a CachedEnrich. Let’s use a Cache Service Procedure instead.
Step 1: Create the Cache Service
From the Add menu, choose Service, then New Cache Service, then Cache Service. This will be just a Lookup Service:

Step 2: Execute the Procedure from the VEH
There’s a mini-step here first. If you read through the GetBy_PumpId procedure in the new Cache Service, you’ll note that the selection is already taking place by the PumpId. This means we don’t need to use the SplitByGroup task upstream. We’ll remove this branch and make a new one to start from the Event Stream, like this:

So now the branch we’re working on has moved to the center. Let’s add our new Procedure task to the Filter:

…and configure it to the “GetBy…” procedure in the PumpDetailsCache. Be sure to set the parameter to “event.PumpId.” To make this procedure task behave just like the AddDetails CachedEnrich task, set the Return Behavior to Attach Return Value to returnProperty, and name that property PumpDetails like so:

After that, move the AddressLookup task to the Procedure task, then delete the AddDetails task.

Time to run the new VEH, and it’s acting just as it did before:

But, why?
Great, you’re thinking. We’ve replaced one working CachedEnrich for a Service we had to make, then call the procedure for it. What possible advantage could that serve? Oh, I am so glad you asked.
Recall that when you’re configuring the CachedEnrich, you have to set the Refresh Interval, or let the default go at an hour. Of course, if the application’s been running for 15 minutes, and your coworker updates the type, it means that the changes won’t be seen in the application for another 45 minutes. Reduce the interval, and you’re possibly adding needless database reads to the load. If only there were a way to detect a change in the type, then clear the cache immediately thereafter.
If only… Oh, wait, there is. Let’s go back to our Cached Service, and make an Event Handler to listen for a write to the type, whereupon we could wipe the cache.
First, we’ll choose to add a Source Event Handler (Implement tab) and call it CleanUpOnWrite. In the configuration for the new Event Stream, choose types as the inboundResource, pump.monitor.PumpDetails as the inboundResourceId, UPSERT as the op:

…and now we can link in a procedure that does the cache wipe:

After this, the State will only refresh when the PumpDetails type actually changes!
Use Case #2: Write Type State to a Database, Automatically in Intervals
We’re not done with that VEH from the Analyze Service, used in Use Case #1. That StateArray task was a multi partition procedure that collected all the readings from each pump, then the HoldState task was an AccumulateState task that kept it in State for later use by our AnalyzeReport Client. I’ve decided that I want to persist this StateArray in the database … once in a while.
Step 1: Make a Standard Type to Hold the Data
Right now, what’s coming into the VEH is a schema type. You can click on the context menu next to that type in the Project Contents and make a duplicate, only this time as a Standard type. I called mine “PumpStateType.”
Step 2: Make a Cached Service Based on the Standard Type
This time it’s an Update Cache. I set the interval at first to once per minute, but that’s changeable later. And the Cache Key is still PumpId, which is a unique index and natural key in the type, too.
Step 3: Check the “WriteAll…” Schedule

As you can see, it’s already set to one minute, but you can change it here. I’m only keeping it this short for testing purposes.
Step 4: Make State for the Type
I’ve added a Procedure task called MakeTypeState to the PumpStatusStream event stream. This is configured to call the “UpdateBy..” procedure in my Cache Service for this type. The parameters are the PumpId for the key, the event for the “event” parameter, and “false” for isPartial.

After 10 events, I did a record search on the PumpStateType:

…waited for a minute, and refreshed:

The scheduled procedure in the Cache Service for the type is working perfectly.
Conclusion:
There are many advantages for having Services dedicated to keeping the State and operations for Standard Types, as Cached Services do. One is just sheer organization: Services for Types, Services for Sources, Services for Event Processing, Services for AI Agents, etc. A place for everything and everything in its place. Testing for each Service is much easier if purpose and functions are clearly defined as well. Keeping track of what an application system does is orders of magnitude easier if all the operations on a Type are held in the same Service, etc.
Cached Services are helpful tools to keeping application systems readable, testable, scalable and performant.
Attachments:
You must be
logged in to view attached files.
Developer ‘Tiqs & Tricks
May 2025
Cache Services
If you’ve been building Vantiq applications for a while, then you know this. Perchance you’ve even stitched it on a pillow, or crossed out “Live, Laugh, Love” on a wall hanging in favor of:
We mean, of course, that avoiding resource-intensive database interactions by taking advantage of State management features in Vantiq is optimal for achieving real-time reactivity in application systems. There’s no way the slogan could be interpreted any other way.
Service State Management
Services maintain the State required to perform application logic. You can set a replication factor for that State to enhance fault-tolerance. Multiple Activity Tasks in Visual Event Handlers make use of program state, and the Service automatically generates procedures that developers can execute to manage it.
Mitigating Database Interactions
State is good as the “working” memory of the running application, but sometimes even applications need to look up additional information, or store something in the database for reference later. Within Services, there are tools to keep database reads and writes to a minimum. One example is CachedEnrich, which will preserve a database read operation in memory for a set period of time, so future look-ups in that timeframe will use the in-memory material rather than instigate a new read. Scheduled Procedures can persist something in State asynchronously at automated time intervals. And of course, developers know better than to persist all the events that come in. Be selective, and only keep what will be useful for later retrieval.
Cached Services
It’s time to unveil another feature Vantiq provides for protecting application performance from ill-timed or excessive database interactions. You’ve seen it; every time you create a new Service, you’re asked to select which kind:
In the Foundations class, we (politely, I’m sure) ignored Cache Services in favor of the “regular” flavor, but it turns out that Cached Services are a powerful tool in “Database Interaction Minimalism.” Thus, they deserve some attention.
Upon clicking the “New Cache Service” choice, you’re immediately confronted with another one:
So, there are two kinds of Cached Services:
Lookup Cache:
This is perfect for information already in a database that will be referenced often, but doesn’t change nearly as often. In other words, this behaves much like a CachedEnrich.
The type selected must have a unique Cache Key. This is a unique property or set of properties that uniquely identifies records stored in the type.
When the Service opens, you’ll see that there is already an automatically-generated service public procedure created for you. Curiously, it’s not in the “generated” listing. This is because developers are encouraged to make changes to the procedure as needed by the program. A little “light reading” of this code shows that the procedure does what you expect it would:
Update Cache:
This type of Cache Service works something like a scheduled procedure, allowing the developer to set how often upserts occur on a standard type, asynchronously. At minimum, that interval is per-minute, but it can be set to “weeks,” too.
Just like in the Lookup Cache, the Update Cache requires a unique Cache Key for performing writes on the type.
When the new Service opens, you’ll notice that there are three Service procedures already written for you:
The “GetBy” procedure we saw from the Lookup Cache, so you can use this Service as a Lookup Cache as well.
The “UpdateBy” procedure isn’t what you might think it is! This is doing no writes to the database, but rather is updating the type record… in State only.
“WriteAll” collects all the records in the State partition, and then performs a bulk upsert operation for all of those records at once. Using a “Pessimistic Locking” model, (which assumes there will be frequent updates of this State), after collecting the current records in the State partition, the procedure clones the collection, and thereafter works with just the clone, leaving the State partition alone to continue changing for the next WriteAll execution.
Use Case #1: Replace a CachedEnrich with a Cache Service Procedure
Remember this Visual Event Handler from the Foundations Class?
After we took in pump sensor readings, we combined them by PumpId, then sent them into this VEH. The leftmost branch filters for anomalous readings, adds pump location information from the PumpDetails type, and sends the bad news on, where we’ll deal with it in another Service. That AddDetails task, right after the filter for bad readings, is a CachedEnrich. Let’s use a Cache Service Procedure instead.
Step 1: Create the Cache Service
From the Add menu, choose Service, then New Cache Service, then Cache Service. This will be just a Lookup Service:
Step 2: Execute the Procedure from the VEH
There’s a mini-step here first. If you read through the GetBy_PumpId procedure in the new Cache Service, you’ll note that the selection is already taking place by the PumpId. This means we don’t need to use the SplitByGroup task upstream. We’ll remove this branch and make a new one to start from the Event Stream, like this:
So now the branch we’re working on has moved to the center. Let’s add our new Procedure task to the Filter:
…and configure it to the “GetBy…” procedure in the PumpDetailsCache. Be sure to set the parameter to “event.PumpId.” To make this procedure task behave just like the AddDetails CachedEnrich task, set the Return Behavior to Attach Return Value to returnProperty, and name that property PumpDetails like so:
After that, move the AddressLookup task to the Procedure task, then delete the AddDetails task.
Time to run the new VEH, and it’s acting just as it did before:
But, why?
Great, you’re thinking. We’ve replaced one working CachedEnrich for a Service we had to make, then call the procedure for it. What possible advantage could that serve? Oh, I am so glad you asked.
Recall that when you’re configuring the CachedEnrich, you have to set the Refresh Interval, or let the default go at an hour. Of course, if the application’s been running for 15 minutes, and your coworker updates the type, it means that the changes won’t be seen in the application for another 45 minutes. Reduce the interval, and you’re possibly adding needless database reads to the load. If only there were a way to detect a change in the type, then clear the cache immediately thereafter.
If only… Oh, wait, there is. Let’s go back to our Cached Service, and make an Event Handler to listen for a write to the type, whereupon we could wipe the cache.
First, we’ll choose to add a Source Event Handler (Implement tab) and call it CleanUpOnWrite. In the configuration for the new Event Stream, choose types as the inboundResource, pump.monitor.PumpDetails as the inboundResourceId, UPSERT as the op:
…and now we can link in a procedure that does the cache wipe:
After this, the State will only refresh when the PumpDetails type actually changes!
Use Case #2: Write Type State to a Database, Automatically in Intervals
We’re not done with that VEH from the Analyze Service, used in Use Case #1. That StateArray task was a multi partition procedure that collected all the readings from each pump, then the HoldState task was an AccumulateState task that kept it in State for later use by our AnalyzeReport Client. I’ve decided that I want to persist this StateArray in the database … once in a while.
Step 1: Make a Standard Type to Hold the Data
Right now, what’s coming into the VEH is a schema type. You can click on the context menu next to that type in the Project Contents and make a duplicate, only this time as a Standard type. I called mine “PumpStateType.”
Step 2: Make a Cached Service Based on the Standard Type
This time it’s an Update Cache. I set the interval at first to once per minute, but that’s changeable later. And the Cache Key is still PumpId, which is a unique index and natural key in the type, too.
Step 3: Check the “WriteAll…” Schedule
As you can see, it’s already set to one minute, but you can change it here. I’m only keeping it this short for testing purposes.
Step 4: Make State for the Type
I’ve added a Procedure task called MakeTypeState to the PumpStatusStream event stream. This is configured to call the “UpdateBy..” procedure in my Cache Service for this type. The parameters are the PumpId for the key, the event for the “event” parameter, and “false” for isPartial.
After 10 events, I did a record search on the PumpStateType:
…waited for a minute, and refreshed:
The scheduled procedure in the Cache Service for the type is working perfectly.
Conclusion:
There are many advantages for having Services dedicated to keeping the State and operations for Standard Types, as Cached Services do. One is just sheer organization: Services for Types, Services for Sources, Services for Event Processing, Services for AI Agents, etc. A place for everything and everything in its place. Testing for each Service is much easier if purpose and functions are clearly defined as well. Keeping track of what an application system does is orders of magnitude easier if all the operations on a Type are held in the same Service, etc.
Cached Services are helpful tools to keeping application systems readable, testable, scalable and performant.
Attachments:
You must be logged in to view attached files.