You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Improve caching to reduce delay between request and XForms processing #1239
static state must be cached based on those dependencies
form definition can also change independently in db (e.g. remote publish) ⇒ ETag
The mechanism must also work with the form version number headers:
requires using Vary header
use ETag and If-None-Match
what ETag could be
persistence layer provides it
digest of the form definition
row id of form definition in database
form version made available in the pipeline (which is input to caching too)
form-version can be provided as URL parameter to FR page
BUT if document id is provided, form-version is ignored and overridden by version returned by persistence layer via orbeon-form-definition-version (or 1 if not found)
server can respond with
ETag
Last-Modified and no Etag
none of those
Q: should persistence layer be required to have versioning capability?
High-level algorithm proposal:
cache keeps entries indexed by app/form name, then ETag
probably only keep max of n ETags per app/form name, per LRU
incoming request processing
request to persistence layer
URL w/ app/form name
Orbeon-For-Document-Id or Orbeon-Form-Definition-Version header (vary)
list of ETags: all ETags in cache for given incoming app/form name
must keep ref during request to persistence layer to all cache entries for app/form, so they don't go away
response is EITHER 200 + document + ETag
create static state
cache static state by form/app name then etag
OR 304 + one of the ETags which matched (should be only 1)
refresh cache entry for app/name/etag
persistence implemenatation
gets app/form/version headers
determines version when needed
checks incoming ETags if any
responds with 200 + document + Etag or 304 + Etag
ETag could be if of document in db (would fail to update if somebody update form def in place, but nobody should!)
Q: should cache hold digest, or static state itself (and keep separate static state cache like now)?
A: TODO
Q: how to handle instance/schema/XInclude dependencies
we create the algorithm above within e.g. oxf:form-runner-persistence-client
produces key = ETag, validity = 0
oxf:xforms-to-xhtml stays connected to its input pipeline as it is now
if we use pipeline/custom URL generator, should be equivalent to the above
Next steps:
first implementation of oxf:form-runner-persistence-client
make sure transformation pipeline is pure
document
what persistence implementors must do
either use ETags (specify what the requirements are for unicity)
changed the title [-]Long delay between request and XForms processing[/-][+]Improve caching to reduce delay between request and XForms processing[/+]on Dec 7, 2017
Looking at the load times of a large form (~3000 controls), I notice that XSLT processing takes a very long time (~7 out of ~9 seconds). However, this XSLT processing is only there because we eventually compute a hash of the unrolled form definition. Once we obtain it, we retrieve the compiled form directly from the static state cache.
So we get back to the idea that we should not perform a full form read/preprocessing step, but instead do a conditional GET.
key to SimpleOutputCacheKey(getProcessorClass, outputName, key)
where key is actually set to a digest of the output result
On the other hand, CacheableTransformerOutputImpl "assumes that an output simply depends on all the inputs plus optional local information". The local information is enabled with supportsLocalKeyValidity. In this case, getLocalKey/getLocalValidity are called if needed. This is used by:
ImageServer
XSLTTransformer
DigestTransformerOutputImpl, used by:
DigestedProcessor, only used by PageFlowControllerProcessor
RequestGenerator
ScopeGenerator
There must be a reason we do this?
For ScopeGenerator, we obtain and produce the same key/validity we had set with ScopeSerializer when available.
For ReguestGenerator, we use getFilledOutState to do the following:
get digest of the output from the state
state.key = new InternalCacheKey(getProcessor(pipelineContext), "requestHash", NumberUtils.toHexString(state.digest))
get OutputCacheKey from input keys and local key
read/write from/to ObjectCache the validity based on that local key
if (state.validity ==null) {
state.validity =DigestTransformerOutputImpl.DEFAULT_VALIDITY// HACK so we don't recurse at the next linevaloutputCacheKey= getKeyImpl(pipelineContext)
if (outputCacheKey !=null) {
valcache=ObjectCache.instance
valdigestValidity= cache.findValid(outputCacheKey, DigestTransformerOutputImpl.DEFAULT_VALIDITY).asInstanceOf[DigestValidity]
if (digestValidity !=null&& util.Arrays.equals(state.digest, digestValidity.digest))
state.validity = digestValidity.lastModified
else {
valcurrentValidity=System.currentTimeMillis: java.lang.Long
cache.add(outputCacheKey, DigestTransformerOutputImpl.DEFAULT_VALIDITY, DigestValidity(state.digest, currentValidity))
state.validity = currentValidity
}
} else
state.validity =null// HACK restore
}
So the question is whether we need to use the same logic for our output keys.
Activity
ebruchez commentedon Jan 16, 2014
How things work now:
detail-model.xpland viewhtml-view.xplread-form.xploxf:url-generatorfr-form-definitionfor Summary pageunroll-form.xplcomponents.xslepilogue.xplxforms-epilogue.xploxf:xforms-to-xhtml⇒ where static state caching takes placeCaching at the XForms level:
XFormsToSomethingStage1CacheableState(URIReferences)Stage2CacheableState(staticStateDigest+template)XFormsStaticStatestored into separateXFormsStaticStateCache, indexed bystaticStateDigestHere is what we expect:
XFormsToSomethingactual dependenciesOrbeon-For-Document-IdorOrbeon-Form-Definition-VersiondocumentURL parameter.ETagThe mechanism must also work with the form version number headers:
VaryheaderETagandIf-None-MatchETagcould beform-versioncan be provided as URL parameter to FR pageform-versionis ignored and overridden by version returned by persistence layer viaorbeon-form-definition-version(or1if not found)ETagLast-Modifiedand noEtagversioningcapability?High-level algorithm proposal:
Orbeon-For-Document-IdorOrbeon-Form-Definition-Versionheader (vary)oxf:form-runner-persistence-clientoxf:xforms-to-xhtmlstays connected to its input pipeline as it is nowNext steps:
oxf:form-runner-persistence-clientdocumentURL parameter be removed?ebruchez commentedon Jun 20, 2017
Following #3272, it would be really good to improve form loading. Requirements:
The ideas in the previous comments still appear sound. In addition, we might try to get rid of the pipeline entirely.
[-]Long delay between request and XForms processing[/-][+]Improve caching to reduce delay between request and XForms processing[/+]ebruchez commentedon Jan 10, 2024
+1 following discussion today.
ebruchez commentedon Jan 9, 2026
Looking at the load times of a large form (~3000 controls), I notice that XSLT processing takes a very long time (~7 out of ~9 seconds). However, this XSLT processing is only there because we eventually compute a hash of the unrolled form definition. Once we obtain it, we retrieve the compiled form directly from the static state cache.
So we get back to the idea that we should not perform a full form read/preprocessing step, but instead do a conditional
GET.+1 from customer
ebruchez commentedon Jan 9, 2026
Implementation question: where would we hook up the code? If we want to do it entirely in Scala, what would it take?
GETCurrently, this is hooked up in PFC:
Or, could we "simply" get
read-form.xplto return better cache key/validity, and maybe just rewrite that?ebruchez commentedon Jan 10, 2026
For reference, dependencies found for Summary page in
XFormsProcessorBase:Landing:
Form Builder
/newebruchez commentedon Jan 10, 2026
The "annotated-document" input of
XFormsProcessorBasealways getsnullfor both input key and validity, unsurprisingly, but verified here.ebruchez commentedon Jan 12, 2026
If we rely on the pipeline key/validity combo, here is how things should work for the form definition retrieval:
Long? API isObject/AnyRef)MemoryCacheImpl.lowerOrEqual(), we specifically check onLongSteps:
fr:form-reader(orfr:read-form) processorGETif ETag is availableGET(if not supported yet)ETagfor published form definitionread-form.xpl45 remaining items
Following #1239: don't use `fr-form-definition` request attribute
Following #1239: indent some XPL files
For #1239: fix compilation
For #1239: indent
For #1239: remove unused definition
ebruchez commentedon Mar 23, 2026
Check last 3 tasks above.
ebruchez commentedon Apr 11, 2026
Trying to wrap my mind around the output digest question. Outputs:
ReadFormProcessor:paramsoutputFormRunnerConfigProcessor:form-runner-configoutputIn both cases, we set:
0LSimpleOutputCacheKey(getProcessorClass, outputName, key)keyis actually set to a digest of the output resultOn the other hand,
CacheableTransformerOutputImpl"assumes that an output simply depends on all the inputs plus optional local information". The local information is enabled withsupportsLocalKeyValidity. In this case,getLocalKey/getLocalValidityare called if needed. This is used by:ImageServerXSLTTransformerDigestTransformerOutputImpl, used by:DigestedProcessor, only used byPageFlowControllerProcessorRequestGeneratorScopeGeneratorThere must be a reason we do this?
For
ScopeGenerator, we obtain and produce the same key/validity we had set withScopeSerializerwhen available.For
ReguestGenerator, we usegetFilledOutStateto do the following:state.key = new InternalCacheKey(getProcessor(pipelineContext), "requestHash", NumberUtils.toHexString(state.digest))OutputCacheKeyfrom input keys and local keyObjectCachethe validity based on that local keySo the question is whether we need to use the same logic for our output keys.
ebruchez commentedon Apr 13, 2026
After discussion:
0Lfor those outputsFor #1239: settled on `0L` output validity
Following #1239: create simple case class
ebruchez commentedon Apr 15, 2026
Some tests with a large form (~3000 static controls) and small form (Bookshelf):