Triggers - Best Practices
Effective Trigger Implementation
Triggers look simple in demos and difficult in production for one reason: they run on your write path unless you design them very deliberately.
The first design decision is always timing:
- Pre-commit trigger: same transaction, can roll back the originating write.
- Post-commit trigger: separate transaction, cannot roll back the originating write.
The examples in this article assume the llamaverse (v2.0+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse — see the llamaverse article for full setup instructions.
Timing Model
| Concern | Pre-commit | Post-commit |
|---|---|---|
| Transaction boundary | Same as source write | Separate transaction |
| Failure effect | Can fail and roll back source write | Source write remains committed |
| Best fit | Validation, invariants, mandatory side effects | Audit fan-out, notifications, asynchronous enrichment |
| Write-path latency impact | Direct | Indirect |
A good trigger design starts by deciding whether failure must block the original write.
Pre-commit Validation Trigger
Use pre-commit for strict invariants that must hold at commit time.
xquery version "1.0-ml";
import module namespace trgr = "http://marklogic.com/xdmp/triggers"
at "/MarkLogic/triggers.xqy";
(: Pre-commit trigger for incoming llama validation. :)
trgr:create-trigger(
"validate-incoming-llamas",
"Reject invalid incoming llama profiles",
trgr:trigger-data-event(
trgr:directory-scope("/cleverllamas/llamaverse/work/incoming/", "infinity"),
trgr:document-content("create"),
trgr:pre-commit()
),
trgr:trigger-module(
xdmp:database("Modules"),
"/triggers/",
"validate-llama-create.xqy"
),
fn:true(),
xdmp:default-permissions(),
fn:false(),
"normal"
)
xquery version "1.0-ml";
(: Trigger action template for pre-commit validation. :)
declare namespace trgr = "http://marklogic.com/xdmp/triggers";
declare variable $trgr:uri as xs:string external;
declare function local:assert-required-fields($doc as node()) {
if (empty($doc/name) or empty($doc/breed))
then fn:error(xs:QName("MISSING-REQUIRED-FIELD"), "name and breed are required")
else ()
};
let $doc := fn:doc($trgr:uri)
return local:assert-required-fields($doc)
Runtime note (local validation):
- Re-running trigger creation can return TRGR-TNEXISTS when the trigger already exists.
- This is expected during repeatable article validation.
- For clean reruns, drop the trigger first or use idempotent setup wrappers.
If this action throws, the source write fails, which is exactly what you want for validation rules.
Post-commit Side-Effect Trigger
Use post-commit for side effects where eventual completion is acceptable.
xquery version "1.0-ml";
import module namespace trgr = "http://marklogic.com/xdmp/triggers"
at "/MarkLogic/triggers.xqy";
(: Post-commit trigger for asynchronous audit fan-out. :)
trgr:create-trigger(
"audit-llama-updates",
"Write audit document after updates",
trgr:trigger-data-event(
trgr:directory-scope("/cleverllamas/llamaverse/raw/wild-llamas/llamas/", "infinity"),
trgr:document-content("modify"),
trgr:post-commit()
),
trgr:trigger-module(
xdmp:database("Modules"),
"/triggers/",
"audit-llama-update.xqy"
),
fn:true(),
xdmp:default-permissions(),
fn:false(),
"normal"
)
xquery version "1.0-ml";
declare namespace trgr = "http://marklogic.com/xdmp/triggers";
declare variable $trgr:uri as xs:string external;
(: Post-commit side-effect template. Failures here do not roll back source write. :)
xdmp:document-insert(
"/cleverllamas/llamaverse/audit/" || xdmp:random() || ".json",
object-node {
"sourceUri": $trgr:uri,
"event": "llama-document-updated",
"capturedAt": fn:current-dateTime()
},
(xdmp:permission("cleverllamas-llamaverse-reader", "read")),
("audit", "trigger-generated")
)
Runtime note (local validation):
- Re-running trigger creation can return TRGR-TNEXISTS when the trigger already exists.
- This is expected during repeatable article validation.
- For clean reruns, drop the trigger first or use idempotent setup wrappers.
If this action fails, the original document update is still committed. Build retry and monitoring around that fact.
Security and Privilege Model
Normal data triggers run in the context of the user whose update caused the event. Do not assume implicit elevated rights. If elevated work is required, isolate it in carefully reviewed amped functions with minimal privilege scope.
Troubleshooting with Tracing
Tracing is one of the most useful ways to troubleshoot triggers because trigger failures often happen off the happy path and can be difficult to reproduce from the application layer alone.
When a trigger misbehaves, you usually need to answer questions such as:
- Did the trigger fire at all?
- Which URI or event matched?
- Which branch of the action module ran?
- Did the failure happen before or after a write or security assertion?
Named trace events are well suited to that job. MarkLogic's xdmp:trace() signals a trace event, and the event is logged when tracing for that event is enabled. xdmp:trace-enabled() lets you guard more detailed debug output so you only pay for it when you are actively diagnosing a problem. That matters with MarkLogic's execution model because trigger code runs on real transaction and write paths, not in a harmless debug sandbox, so unnecessary debug work can affect latency, log volume, and operational clarity.
For production-safe trigger support, prefer a small set of stable trace event names in trigger action modules. That gives operations a way to turn on targeted diagnostics for one trigger path without flooding the logs with unrelated noise.
Tracing is especially valuable for post-commit triggers because the source write may succeed even when the follow-on action fails. In that case, trace output is often the fastest way to reconstruct what happened inside the separate trigger transaction.
Avoiding Trigger Recursion
A common operational bug is self-firing trigger chains where the action writes content in a scope watched by the same trigger. Prevent this by:
- Writing action output to separate URI spaces or collections.
- Adding explicit guards in the action module.
- Using narrow trigger scopes.
Performance Guidance
- Keep trigger action modules small and deterministic.
- Avoid expensive network calls in pre-commit actions.
- Prefer post-commit for heavier enrichment.
- Keep high-volume event scopes narrow.
- Measure write latency before and after trigger activation.
Practical Checklist
1. Choose pre-commit only for true invariants.
2. Keep trigger action modules small and deterministic.
3. Avoid heavy network calls in pre-commit actions.
4. Guard against trigger recursion explicitly.
5. Log and monitor trigger failures with clear URIs.
6. Test with real security roles, not only admin users.
Also verify that each trigger action module has a minimal tracing strategy:
- One stable trace event name per trigger family or action path.
- Entry/exit trace points around the action's important decision boundaries.
- Enough URI or event-context detail to diagnose failures without dumping excessive document content.
Common Mistakes
| Mistake | Consequence | Better approach |
|---|---|---|
| Using pre-commit for optional side effects | Slower writes and unnecessary rollbacks | Use post-commit for optional work |
| Running heavy logic in trigger actions | Throughput degradation | Keep trigger actions thin |
| Ignoring security context | Permission errors in production | Test with real application roles |
| Not handling recursion | Cascading writes and instability | Separate scopes and guard logic |
| Missing trigger tracing and failure observability | Silent data-quality drift and slow incident diagnosis | Add targeted trace events, alerting, and operational dashboards |
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- Timing Model
- Pre-commit Validation Trigger
- Post-commit Side-Effect Trigger
- Security and Privilege Model
- Troubleshooting with Tracing
- Avoiding Trigger Recursion
- Performance Guidance
- Practical Checklist
- Common Mistakes