← Back to list

Stop Failing Batch Jobs: The Complete Guide to OutSystems 11 Timers

Learn how to configure, schedule, monitor, and fail-proof your OutSystems Timers with a step-by-step cleanup job built for enterprise…

Darshan Prajapati · 2026-08-24 18:43 · 0 claps · 13.6 min read
#outsystems #low-code #low-code-development #web-development #outsystems11
Open on Medium ↗
Wiki topics: 🌐 · Web Development 📐 · Mathematics

Stop Failing Batch Jobs: The Complete Guide to OutSystems 11 Timers

Learn how to configure, schedule, monitor, and fail-proof your OutSystems Timers with a step-by-step cleanup job built for enterprise scale.

Imagine ordering food for a massive party of 500 guests.

If one waiter tries to carry all 500 plates out of the kitchen at the exact same time, two things will happen: the waiter will collapse under the weight, every plate will smash on the floor, and the guests will be left waiting forever with an empty table.

In software development, this is exactly what happens when you try to process thousands of records directly from a screen button. A user clicks “Generate Reports” or “Send Emails,” the screen freezes with a loading spinner, the server runs out of time, and the entire system crashes — often leaving half-finished work behind.

This is where an OutSystems Timer steps in like a dedicated night-shift kitchen team.

Instead of forcing the front-desk waiter to handle everything while customers watch, the button simply drops an order ticket into the kitchen. The user immediately gets a message saying, “Your request is being processed,” and they can continue using the application without any delay.

Behind the scenes, the Timer goes to work during quiet hours:

It carries manageable trays: Instead of grabbing 500 plates at once, it takes a sensible tray of 50 items.

It serves them safely: It processes the batch, marks each item as finished, and permanently saves the progress so nothing gets lost if an accident happens.

It takes smart breaks: If the task is enormous, it finishes its current batch, logs out, and immediately calls itself back for a fresh shift to avoid getting exhausted or hitting system time limits.

By letting the Timer work quietly in the background, the screen stays fast, the database stays healthy, and thousands of tasks get completed automatically while everyone is asleep.

What Is a Timer in OutSystems O11?

A Timer is an automated background worker. It executes a Server Action (backend logic) independently of user sessions, running quietly on the server without anyone needing to click a button or keep a screen open.

In OutSystems 11, Timers are managed in the Logic tab of Service Studio. You create a Timer, attach a Server Action to it, and define a Schedule (such as daily at 02:00 AM or every 30 minutes). You can also run a Timer on demand from anywhere in your logic using its built-in Wake<TimerName> action.

Think of a Timer like a reliable night-shift worker with an alarm clock: you assign the task, set the schedule, and OutSystems takes care of running the job, retrying on network drops, and logging every run in Service Center.

What Timers Are Good For

  • Nightly Cleanups: Purging expired sessions, temporary tokens, or stale logs to keep database tables lean.
  • Scheduled Notifications: Dispatching daily digest emails, approval reminders, or push notifications at specific hours.
  • Data Synchronization: Fetching updates from external REST APIs or third-party databases during off-peak hours.
  • Pre-Aggregating Metrics: Calculating heavy dashboard totals and summary metrics overnight so analytics screens load instantly during business hours.
  • Historical Archiving: Moving inactive or historical data from core operational tables to archive storage.

What Timers Are NOT Good For

  • Instant, Synchronous User Feedback: Timers run asynchronously in the background. If a screen needs an immediate response (e.g., verifying a password or calculating cart totals), use a standard Server Action.
  • Sub-Second Execution: Timers have a small scheduling and thread-initialization overhead. They are built for reliability, not millisecond latency.
  • High-Concurrency Record Queues: Standard Timers run on limited background threads (defaulting to 3 concurrent timers per front-end server). For processing tens of thousands of individual events in parallel, Light BPT (Business Process Technology) is the better architecture (scaling up to 20 concurrent threads).

Rule of Thumb: Use standard Server Actions for immediate screen responses, Light BPT for high-volume event queues, and Timers for scheduled maintenance, nightly syncs, and chunked batch routines.

How Timers Work Internally

Before configuring background jobs, understanding what happens under the hood will save you hours of troubleshooting in Service Center.

  • The Scheduler Service: A background Windows service on each Front-End server polls the database every 20–30 seconds, looking for Timers whose scheduled time has arrived or that were triggered via Wake<TimerName>.
  • Isolated Headless Session: Timers run in a separate system session with no user context. Session variables take default values, and GetUserId() returns NullIdentifier().
  • Thread Pool Limits: By default, each Front-End server allocates only 3 concurrent Timer threads. If all 3 are busy, additional jobs wait in a queue until a thread frees up.
  • How Wake Works: Calling Wake<TimerName> doesn’t execute the logic instantly. It updates the Timer’s Next_Run database timestamp to the current time, queuing it for the Scheduler’s next polling cycle.

The 20-Minute Default Timeout

Every Timer has a default Timeout in Minutes set to 20 minutes.

If your Timer’s Server Action runs past this threshold, the OutSystems Scheduler Service terminates the request, registers an error in the Service Center logs, and marks the run as failed.

While you can increase this timeout in Service Studio, extending it is an anti-pattern. If a job takes longer than 20 minutes:

  • The Retry Trap: OutSystems treats the timeout as an unexpected crash and automatically retries the Timer up to 3 times — often causing duplicate emails, corrupted calculations, or repeated API calls.
  • Resource Locking: Long-running transactions hold database locks, slowing down active end-users on the frontend.
  • Key Rule: Never increase the timeout to solve long processing times. Instead, process data in small chunks (batching) so the job finishes well before the time limit.

Concurrency: The 3-Thread Limit

Unlike Light Processes (BPT), which can scale up to 20 parallel threads, Timers have strict execution limits:

  • 3 Concurrent Threads: By default, the Scheduler Service runs a maximum of 3 Timers simultaneously per Front-End server.
  • Queueing Delays: If you schedule 10 Timers to run at midnight, 3 will start immediately while the other 7 wait in a queue.
  • Stagger Your Schedules: Space out your nightly maintenance jobs (e.g., 01:00, 01:30, 02:00) instead of firing them all at the same instant to prevent thread bottlenecks.

Step 1: Creating the Timer in Service Studio

Let’s build a practical example: a background job that purges expired user sessions every night to keep the database lean and performant.

How to Set It Up:

  • Open your module in Service Studio.
  • Navigate to the Processes tab in the top-right panel (the gear/flow icon).
  • Right-click the Timers folder and select Add Timer.

  • Name the Timer: Timer_CleanupExpiredSessions.
  • In the Timer’s property pane at the bottom right:

1. Schedule: Set your execution window (e.g., Daily at 02:00). 2. Timeout in Minutes: Leave as default (20) or adjust to a safe threshold. 3. Action: Click the dropdown and select (New Server Action). Service Studio will automatically scaffold the backend logic action bound to this Timer.

Step 2: Creating the Timer Server Action

The Timer executes a dedicated Server Action. This is where all your background processing logic lives. Because background workers run autonomously, two strict rules apply:

  • No Input Parameters: Timers cannot accept inputs at runtime. If your logic requires dynamic thresholds or configurations, read them from a Site Property or a database Entity.
  • No Output Parameters: Timers do not return values to a caller. Any processing results, metrics, or error summaries should be committed directly to database logs.

Setting Up the Action Flow:

  1. Go to the Logic tab → Server Actions.

  2. Create or open the Server Action linked to your Timer: Timer_CleanupExpiredSessions.

  3. Set Public to No (Timer logic should remain encapsulated within its core module).

The Logic Architecture Inside:

Why Process in Batches?

This is the single most critical architectural pattern in OutSystems background processing.

When dealing with production data, writing a single unbounded query is a recipe for system crashes:

Why this breaks in Production:

  • RAM Spikes: Pulling 500,000+ records into memory causes high memory utilization on the Front-End server, risking worker process crashes.
  • Hard Timeouts: Processing hundreds of thousands of records sequentially in a single transaction easily exceeds the 20-minute limit.
  • Massive Rollbacks: When OutSystems aborts the execution at the 20-minute mark, the uncommitted database transaction rolls back. Every single deletion is undone, and the entire job starts over from scratch on the next retry.

Step 3: Scheduling the Timer Once your logic is ready, define when and how often the Timer executes. In the Processes tab, click your Timer and inspect the Schedule property in the lower-right panel.

Schedule Syntax in OutSystems 11

OutSystems uses a clean, readable scheduling format instead of complex Unix cron syntax:

SCHEDULE FORMAT:

“HH:MM” → runs daily at that time “HH:MM when weekday” → runs on specific days “When Published” → runs once right after publish

EXAMPLES:

“00:00” → every day at midnight “02:30” → every day at 2:30 AM “00:00 when Monday” → every Monday at midnight “00:00 when weekday” → Monday to Friday at midnight “When Published” → runs once when you publish the module

For our cleanup job:

Schedule: 02:00 (Executes nightly during off-peak hours when database lock contention with end-users is minimal).

Staggering Schedules: Avoiding the 3-Thread Bottleneck

Because OutSystems allocates a default maximum of 3 concurrent Timer threads per Front-End server, firing multiple batch jobs simultaneously causes queueing delays and unpredictable execution order.

If you have several timers, don’t schedule them all at the same time. Remember — max 3 run simultaneously.

✅ Good scheduling — staggered: Timer_CleanupExpiredSessions → 02:00 (starts first) Timer_GenerateDailyReport → 02:30 (starts 30 min later) Timer_SendEmailDigest → 03:00 (starts after report is ready) Timer_ArchiveOldOrders → 03:30 (starts last)

❌ Bad scheduling — all at once: Timer_CleanupExpiredSessions → 00:00 Timer_GenerateDailyReport → 00:00 Timer_SendEmailDigest → 00:00 Timer_ArchiveOldOrders → 00:00

Note: Schedules defined in Service Studio act as initial defaults. You can adjust schedules dynamically per environment inside Service Center → Factory → Modules → [Your Module] → Timers without modifying code or redeploying.

Step 4 — Triggering a Timer Manually (Wake)

Sometimes you don’t want to wait for the schedule. You need to run the timer right now — from code.

Trigger a timer on demand from Server Actions using the Wake timer system action.

OutSystems auto-generates a WakeTimer_CleanupExpiredSessions system action for every timer you create. Call it from anywhere:

Example: Admin screen “Run Cleanup Now” button

Important: WakeTimer queues the timer — it doesn’t run it instantly in the same request. The Scheduler Service picks it up within seconds. Don’t wait for it to complete before showing a message to the user.

Use WakeTimer when:

  • An admin needs to force a run outside the schedule
  • A critical event requires immediate cleanup (e.g. user account deleted → clean up their data now)
  • You’re testing during development (instead of waiting for the scheduled time)
  • A dependency completes and you need to chain timers (Timer A finishes → calls WakeTimer B)

Step 5: Production-Grade Error Handling Inside Timers

Timer errors are completely silent by default. Because no user is running the screen, there are no screen popups or instant error banners. If a Timer fails, nobody knows until an administrator happens to look at Service Center — or an angry customer calls three days later.

To build bulletproof background jobs, implement exception handling at two distinct levels: Per-Record Isolation and Global Failure Catching.

Level 1: Per-Record Isolation (Child Action Pattern)

If record #42 in a 500-item batch contains corrupted data, it should not kill the remaining 458 records.

In OutSystems, the cleanest way to catch individual record errors and keep the loop running is to isolate the processing into a private child Server Action:

Inside Session_ProcessSingleItem:

  • Execute the deletion or update logic.
  • Add an AllExceptions Handler directly inside this child action.
  • In the handler branch, call LogMessage to record the failing Session.Id and the AllExceptions.ExceptionMessage, then route to an End node.
  • Because the exception is handled inside the child action, the parent For Each loop in your main Timer immediately advances to the next record.

Level 2: Global Handler (Catastrophic Crashes & Alerts)

This handler catches macro-level failures that break the entire execution — such as database connection drops, external API outages, or unexpected runtime exceptions.

The Complete Error Handling Pattern

Why This Matters: If you let an unhandled exception crash the global flow without catching it, OutSystems treats the run as a hard failure and will automatically retry the entire Timer up to 3 times. Implementing this two-tier structure ensures bad records are isolated, total failures trigger immediate developer alerts, and the job never gets stuck in an unmanaged retry loop.

Step 6: Monitoring and Diagnosing Timers in Service Center

Because background Timers run headlessly, Service Center is your primary dashboard for tracking execution health, diagnosing performance bottlenecks, and fixing failed jobs.

Navigating to Timer Monitoring 1. Log in to your environment console: https://<YOUR_ENVIRONMENT>/ServiceCenter 2. In the top navigation bar, click Monitoring → Timers. 3. Filter by your module name or browse the list of background jobs.

How to Unlock and Rerun a Stuck Timer

If an environment reboot or hard crash interrupts a running job, the Scheduler Service may continue to register the Timer as active:

  1. Navigate to Monitoring → Timers.
  2. Locate the stuck Timer.
  3. Click the Unlock link next to the status.
  4. Click Run Now to immediately trigger an on-demand background run if needed.

Analyzing Error and General Logs

When investigating a failure, check two tabs under Monitoring:

  • Errors (Monitoring → Errors): Displays unhandled system crashes, database deadlocks, foreign key violations, and hard timeout events with full stack traces.
  • General Logs (Monitoring → General): Displays your custom messages generated using the system action LogMessage.

Diagnostic Best Practice: Always pass structured, contextual strings to LogMessage (e.g., Module: “SessionMgmt”, Message: “Processed: 500 records | Batch Duration: 4.2s | Errors: 0”). When an issue arises, filtering by Module and Source gives you an instant, chronological audit trail of your background engine.

The Server Action — Full Implementation

Common Mistakes That Will Bite You

  1. No Exception Handling (The Silent Killer)
  • The Trap: One bad record on row 247 crashes the entire 500-item batch. OutSystems aborts, rolls back uncommitted changes, and retries the whole job blindly.
  • The Fix: Isolate individual record logic inside a child Server Action with its own AllExceptions handler. Let row 247 log its error and fail safely while rows 248–500 finish processing.

2. Fetching Everything at Once (Max Records = 0)

  • The Trap: Pulling 2,000,000 records into memory spikes Front-End server RAM, hits the 20-minute timeout, and crashes the worker process.
  • The Fix: Query in fixed batches (Max Records = 500). Check execution time with DiffSeconds() and call WakeTimer on itself to process remaining records across fresh sessions.

3. The Midnight Bottleneck (Zero Staggering)

  • The Trap: Scheduling 6 Timers at 00:00. OutSystems only runs 3 concurrent threads per Front-End server, forcing dependent jobs into a race condition.
  • The Fix: Stagger execution windows (e.g., 01:00, 01:30, 02:00) so upstream tasks finish before downstream dependencies wake up.

4. Silent Executions (Zero Log Footprint)

  • The Trap: Relying solely on default platform logging leaves you blind to record counts, processing speed, and subtle memory degradation.
  • The Fix: Add LogMessage at the start and end of the run with specific telemetry: “START: 02:00:01” and “DONE: 1,247 processed, 2 failed | Duration: 42s”.

5. Forcing Timers to Act Like Message Queues

  • The Trap: Using a single-threaded Timer to process 100,000 parallel high-throughput API events
  • The Fix: Use Light BPT Processes (which scale up to 20 parallel threads) for heavy event-driven record queues. Reserve Timers strictly for scheduled routines, maintenance, and chunked batch runs.

Pro Tips for Production-Ready Timers

Tip 1: Track Watermarks with a Configuration Entity

  • To process incremental deltas, store a LastRunDateTime timestamp in a dedicated configuration Entity rather than a Site Property. Filter your Aggregate with UpdatedOn >= Config.LastRunDateTime and update the timestamp upon success. (Writing to Site Properties at runtime invalidates the module cache across Front-End servers, whereas an Entity keeps execution cache-safe).

Tip 2: Use “When Published” for Instant Data Migrations

  • Setting Schedule to When Published runs the Server Action immediately after deployment—ideal for one-off data backfills or seeding lookup tables. Once the migration completes, clear the schedule in Service Studio or Service Center so routine hotfix releases don't trigger it again.

Tip 3: Build an Emergency Kill Switch

  • Create a Site Property named IsTimer_Enabled (Boolean, default True). At the start of your Timer action, check this flag:

  • If an external API goes down or database locks spike, an administrator can toggle this to False in Service Center to stop the Timer instantly without a redeployment.

Tip 4: Log Structured Telemetry for Zero-Debugger Audits

  • Format your LogMessage calls so you can diagnose issues from Service Center logs in seconds:

Quick Reference

You Now Have the Complete Blueprint

Remember the opening story — the chaos of frozen screens, dropped transactions, and late-night panic? The difference between that breakdown and a resilient enterprise system comes down to the architecture you just mastered.

Let’s review the standard blueprint for any production-ready Timer:

  • Separation of Concerns: Keep the UI fast by firing an asynchronous Wake trigger and letting the background worker handle the heavy lifting.
  • Disciplined Batching: Process data in manageable chunks (e.g., Max Records = 500) with periodic CommitTransaction calls.
  • The Self-Re-execution Loop: Monitor elapsed time with DiffSeconds() and re-wake the Timer before ever approaching the 20-minute timeout ceiling.
  • Two-Tier Exception Handling: Isolate single-record operations in a child Server Action with local error handling, while using a global handler for macro crashes.
  • Intentional Scheduling & Telemetry: Stagger your execution windows to avoid the 3-thread bottleneck, and log actionable metrics (StartTime, processed counts, duration) directly to Service Center.

Timers aren’t glamorous. End-users will never see them, and no one clicks on them directly. But behind the scenes, they are the backbone of high-performance OutSystems applications — keeping databases lean, data synchronizations reliable, and screens blazingly fast.

Build your first Timer today. Pick a simple, high-impact task: purging expired sessions, clearing stale cache records, or sending a daily reminder digest. Build it, monitor it in Service Center, and deploy it with confidence.

Final Thoughts

Mastering background Timers is what keeps your OutSystems apps fast, resilient, and enterprise-ready.

Have questions or a favorite background processing pattern? Drop a comment below — I’d love to hear how you handle batch jobs in your projects!

Let’s Connect!

  • 👏 Clap if you found this guide helpful.
  • 📌 Follow me on Medium for more practical OutSystems tips and architectural deep dives.

What OutSystems challenge should we break down next? Let me know in the comments, and stay tuned for the next deep dive!


메타데이터
post_id
1b95efce2d16
slug
stop-failing-batch-jobs-the-complete-guide-to-outsystems-11-timers-1b95efce2d16
url
https://medium.com/@darshanprajapati00786/stop-failing-batch-jobs-the-complete-guide-to-outsystems-11-timers-1b95efce2d16
canonical_url
https://medium.com/@darshanprajapati00786/stop-failing-batch-jobs-the-complete-guide-to-outsystems-11-timers-1b95efce2d16
author_url
https://medium.com/@darshanprajapati00786
status
ok
fetched_at
2026-08-25 05:24:19