This guide covers strategies for making Figranium tasks faster, more efficient, and more reliable at scale.
Understanding Execution Time
A typical task execution involves:
- Browser launch: ~1–3 seconds (includes Playwright startup and stealth plugin initialization)
- Navigation: ~0.5–5 seconds depending on the page
- Actions: Variable (clicks, types, waits)
- Extraction: ~0.1–1 second
The biggest time sinks are usually navigation (waiting for pages to load) and explicit waits. Target these first.
Reduce Unnecessary Waits
Replace fixed waits with conditional waits
Instead of waiting a fixed time:
{ "type": "click", "selector": "#submit" },
{ "type": "wait", "value": "3000" }
Wait for the result element to appear:
{ "type": "click", "selector": "#submit" },
{ "type": "wait_selector", "selector": ".success-message" }
This completes as soon as the element appears — often much faster than the fixed wait.
Use realistic minimum waits
For stealth purposes, some wait time is necessary. But 500ms is usually sufficient to appear human. Avoid blanket 3000ms waits unless the site specifically requires them.
Broad selectors require more DOM traversal:
/* Slow: searches entire document for deeply nested elements */
.container div span.price
/* Faster: scoped to a closer parent */
.product-card .price
Prefer:
- ID selectors (
#element-id) — O(1) lookup by the browser
- Attribute selectors (
[data-id="123"]) — indexed in modern browsers
- Short class chains over deep descendant paths
Minimize Page Navigations
Each navigate action waits for the page to fully load. If you can extract all data from a single page (e.g., using JavaScript to process an API response already in the DOM), do so.
Use in-page API calls instead of navigating:
// Instead of navigating to /api/products, fetch it from inside the current page
const resp = await fetch("/api/products?page=1&limit=100");
const data = await resp.json();
return data.items;
This is dramatically faster than navigating to each product page individually.
Foreach loop optimization
Figranium automatically optimizes foreach loops based on which loop variables you use. If your loop body does not reference {$loop.html}, the engine skips fetching the inner HTML of each element, which significantly reduces serialization overhead.
This means simple text-based loops are fast by default:
// Optimized automatically — loop.html is not referenced, so HTML is not fetched
{ "type": "foreach", "selector": ".product" },
{ "type": "javascript", "value": "return loop.text" },
{ "type": "end" }
If your loop body uses {$loop.html}, the full HTML is still available as expected — no changes are needed.
For loops that only need text content, avoid referencing {$loop.html} to benefit from the automatic optimization.
Large result previews
The table view in the results drawer previews up to 200 rows of extracted data (CSV, JSON arrays, or nested arrays). This keeps the UI responsive even when your extraction returns tens of thousands of rows. The full dataset is still available through Export and Copy Full — only the in-editor preview is capped.
If you are working with very large extractions and notice slow rendering, switch from the Table tab to Raw for a lightweight text view.
For maximum performance when extracting structured data, collect all data in one JavaScript block rather than iterating with foreach + individual JavaScript calls:
// Slower: foreach + javascript per item
{ "type": "foreach", "selector": ".product" },
{ "type": "javascript", "value": "return loop.text" },
{ "type": "end" }
// Faster: single JavaScript block extracting everything
{
"type": "javascript",
"value": "return Array.from(document.querySelectorAll('.product')).map(el => ({ name: el.querySelector('.name')?.innerText, price: el.querySelector('.price')?.innerText }))"
}
Stateless vs Stateful Execution
- Stateful (default): Skips login if cookies are still valid. Faster for repeated runs of tasks requiring authentication.
- Stateless: Slightly slower per run because it authenticates from scratch. Use only when isolation is required.
For frequently scheduled tasks requiring login, use stateful mode and set up a “Refresh Cookies” sub-task on a schedule (e.g., every 12 hours) to keep the session alive.
Proxies add latency. Minimize proxy-related slowdowns:
- Use geographically nearby proxies when possible.
- Use residential proxies only when required for stealth. For internal or unprotected sites, skip the proxy entirely.
- Set a proxy default rather than rotating on every request if consistency matters more than IP diversity.
Test proxy latency before deploying at scale:
curl --proxy "http://user:pass@proxy-host:port" -o /dev/null -s -w "%{time_total}\n" https://example.com
Resource Management
Execution History
Figranium stores execution records in data/executions.json (or in PostgreSQL). Over time, this file can grow large and slow down the dashboard.
- Set up a periodic cleanup: Settings > Data > Clear Execution History.
- Use the REST API to automate cleanup:
POST /api/executions/clear
Screenshot and Recording Cleanup
Recordings (video files) can be large. Regularly purge them:
- Settings > Data > Clear Screenshots & Recordings
- Or via API:
POST /api/clear-screenshots
PostgreSQL for Scale
For high-volume deployments with many concurrent tasks or large execution histories, switch from file-based storage to PostgreSQL:
DB_TYPE=postgres
DB_POSTGRESDB_HOST=your-db-host
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_USER=figranium
DB_POSTGRESDB_PASSWORD=your-password
DB_POSTGRESDB_DATABASE=figranium
PostgreSQL provides better concurrency, atomic writes, and faster queries for large datasets. See Database Support.
Task Design for Reliability
Performance and reliability go hand-in-hand. A task that fails and restarts wastes more time than one that completes slightly slower.
- Build in error handling: Wrap optional interactions in
on_error/end blocks. See Error Handling.
- Use
wait_selector over fixed waits: More reliable and faster.
- Test on slow network: Use browser DevTools throttling to verify your selectors and waits work under slow conditions before deploying.
- Keep tasks focused: Large monolithic tasks are harder to debug. Break them into smaller, modular workflows. See Modular Workflows.
Docker Resource Allocation
For production deployments, allocate adequate resources in docker-compose.yml:
services:
figranium:
image: ghcr.io/figranium/figranium:latest
shm_size: '1g' # Shared memory for Chromium
deploy:
resources:
limits:
memory: 2g # Minimum 1g, 2g+ recommended
cpus: '2'
Chromium is memory-hungry. Running it with less than 512MB of RAM will cause frequent crashes.
Headless mode (default) is significantly faster than headful mode because it skips rendering the browser UI. Only use headful mode for:
- Debugging tasks during development
- Sites that specifically require a visible browser to bypass detection
Use the Headful Browser as a development tool, then run production tasks headless.