Schaalbaarheid & enterprise-readiness¶
IntraVox is ontworpen om te schalen van kleine teams tot grote organisaties. Dit document beschrijft de performance-optimalisaties, resilience-patronen en enterprise-features.
Performance op schaal¶
Geteste capaciteit¶
| Metric | Ondersteund | Notes |
|---|---|---|
| Nextcloud-gebruikers | Tot 10.000 | People-widget gebruikt groep-scoped queries waar mogelijk |
| IntraVox-pagina's | Tot 5.000 | Pagina-metadata in DB geïndexeerd voor snelle lookups |
| Gelijktijdige gebruikers | Tot 1.000 | Singleflight voorkomt thundering herd op gedeelde resources |
| Externe feed-bronnen | Onbeperkt | Circuit breaker en background-refresh voorkomen cascade-failures |
Bundle-optimalisatie¶
De frontend-JavaScript-bundle is gesplitst voor snelle initial-page-loads:
| Chunk | Grootte | Loading |
|---|---|---|
intravox-main.js |
~220 KB | Direct geladen |
intravox-vendors.js |
~2.9 MB | Direct geladen (gedeelde vendor-code, browser-cached) |
| TipTap-editor | ~240 KB | Lazy-loaded alleen bij edit-modus |
| Widget-componenten | ~5–30 KB elk | Lazy-loaded per widget-type op de pagina |
Alle widget-componenten (News, People, Calendar, Feed, Links) worden via defineAsyncComponent geladen, dus pagina's zonder een widget-type downloaden de code nooit. De TipTap rich-text-editor en al z'n uitbreidingen worden dynamisch geladen bij eerste bewerking — lezers betalen die kost nooit.
Caching-strategie¶
Server-side:
| Cache | Scope | TTL | Backend |
|---|---|---|---|
| Pagina-tree | Per gebruiker + taal | 5 min | Redis/APCu (gedistribueerd) + in-process statisch |
| Feed-resultaten | Per bron + config | 15 min | Redis/APCu (gedistribueerd) |
| People-filter-resultaten | Per filter-combinatie | 1 uur | Redis/APCu (gedistribueerd) |
| Gebruikers-status | Per gebruiker | Per request | In-process |
| Bestand-content / -permissies / dir-listings | Per request | Request-lifetime | In-process |
Client-side:
| Cache | TTL | Backend |
|---|---|---|
| Pagina-lijst | 5 min | localStorage |
| Navigatie | 5 min | localStorage |
| Footer | 5 min | localStorage |
| Engagement-instellingen | 5 min | localStorage |
| Huidige pagina | 2 min stale-while-revalidate | localStorage |
HTTP-caching:
| Endpoint | Cache-Control | ETag |
|---|---|---|
| Navigatie-API | private, max-age=300, must-revalidate |
Ja |
| Footer-API | private, max-age=300, must-revalidate |
Ja |
| RSS-feed-API | public, max-age=300, must-revalidate |
Ja |
| Image-proxy | public, max-age=86400, immutable |
Nee |
Pagina-metadata-index¶
Pagina-metadata (titel, uniqueId, pad, taal, status, modification-time) is geïndexeerd in een database-tabel (intravox_page_index) voor snelle lookups. Dit vermijdt O(N) filesystem-traversals voor pagina-listing, tree-building en zoek-operaties.
De index wordt automatisch bijgewerkt bij pagina-create, -update en -delete. De Nextcloud unified-search-provider (Ctrl+K) gebruikt de index voor instant titel-gebaseerde zoekresultaten, met fallback naar full-text filesystem-search voor content-matches.
Progressive rendering¶
Pagina-tree-componenten (PageTreeSelect, PageTreeModal) gebruiken progressive rendering: alleen de eerste 50 children van elk node worden initieel gerenderd. Een "Toon meer"-knop laadt extra items in batches van 50. Hierdoor blijft de DOM klein zelfs met duizenden pagina's.
Resilience¶
Feed-widget¶
Externe feed-bronnen zijn beschermd door drie lagen:
-
Singleflight-lock — wanneer de cache vervalt, fetcht alleen de eerste request van de externe bron. Gelijktijdige requests wachten (tot 5 seconden) en lezen daarna uit de net-gevulde cache. Voorkomt thundering herd wanneer veel gebruikers tegelijk dezelfde pagina openen.
-
Circuit breaker — na 3 opeenvolgende mislukkingen voor een bron opent de circuit breaker, en volgende requests geven direct een "tijdelijk niet beschikbaar"-melding terug. De circuit reset automatisch na 5 minuten, of direct bij een succesvolle fetch.
-
Background refresh — een Nextcloud-background-job (
FeedRefreshJob) ververst geconfigureerde feed-verbindingen proactief elke 10 minuten, voor de cache verloopt. Gebruikers triggeren bijna nooit een cold fetch.
Aanvullend:
- HTTP-timeout staat op 5 seconden om PHP-worker-blocking te voorkomen
- Private IP-ranges worden geblokkeerd (SSRF-bescherming)
- Image-URLs zijn HMAC-ondertekend voor proxy-requests
- API-responses groter dan 10 MB worden geweigerd vóór parsing om OOM te voorkomen
People-widget¶
De People-widget beschermt tegen grote Nextcloud-installaties:
- Groep-scoped queries: bij groep-filter worden alleen gebruikers in die groep geladen — het efficiënte pad dat schaalt naar tienduizenden accounts
- Hard cap: het ongescoped filter-pad (geen groep-filter) is afgekapt op 5.000 gebruikers om OOM en timeouts te voorkomen
- Gedistribueerde cache: filter-resultaten worden 1 uur in Redis/APCu gecached, wat herhaalde volledige user-scans voorkomt
- Batch-status-fetching: gebruikers online/away/DND-statussen in één batch-call vooraf opgehaald in plaats van één API-call per gebruiker
News-widget¶
De News-widget-folder-scanner heeft een configureerbare collection-limit (standaard max(limit * 4, 200)) met early exit. Zodra genoeg items zijn verzameld stopt de recursieve folder-scan direct — ook na terugkeer uit recursieve subfolder-calls.
Export¶
De ZIP-export schrijft pagina's incrementeel naar disk in plaats van de hele export in memory te accumuleren. Elke pagina wordt individueel JSON-encoded en naar export.json geschreven, waardoor memory-gebruik constant blijft ongeacht het aantal pagina's.
Beveiliging¶
Rate limiting¶
Alle muterende en gebruikers-gerichte API-endpoints zijn rate-limited:
| Endpoint-categorie | Limiet | Periode |
|---|---|---|
| Pagina aanmaken / verwijderen | 10 requests | 60 sec |
| Bulk-operaties (delete, move) | 5 requests | 60 sec |
| Comments | 20 requests | 60 sec |
| Reacties | 30 requests | 60 sec |
| Feed-fetch (geauthenticeerd) | 30 requests | 60 sec |
| Feed-fetch (publieke share) | 30 requests | 60 sec |
| Analytics-tracking | 60 requests | 60 sec |
| Publieke pagina-toegang | 60 requests | 60 sec |
Content Security Policy¶
IntraVox zet een strikte CSP op alle pagina-responses:
script-src: 'self'— alleen scripts van het Nextcloud-domeinframe-src: 'self' [whitelisted video-domeinen]— alleen iframes van geconfigureerde video-services- Geen
unsafe-evalofunsafe-inline
Audit-logging¶
Administratieve operaties worden gelogd met de IntraVox Audit:-prefix voor SIEM-integratie:
- Bulk delete / move / update (inclusief pagina-ID's en gebruiker)
- Licentie-sleutel-wijzigingen
- Organisatie-instellingen-wijzigingen
- Engagement-instellingen-wijzigingen
Logs worden geschreven naar het Nextcloud-log op info-niveau en filterbaar via de IntraVox Audit:-prefix.
AVG-compliance¶
Wanneer een Nextcloud-gebruiker wordt verwijderd, ruimt IntraVox automatisch op:
- Analytics-view-records (gehashte user-ID)
- Pagina-lock-records
- Feed-tokens en LMS-OAuth-tokens (opgeslagen in Nextcloud-user-preferences)
Dit wordt afgehandeld door de UserDeletedListener die naar Nextcloud's UserDeletedEvent luistert.
Health check¶
Een publiek health-check-endpoint is beschikbaar voor monitoring en orchestration:
Widget-performance¶
Debounced watchers¶
Alle widget-componenten (News, People, Feed) debouncen hun configuratie-watchers met 300ms vertraging. Voorkomt API-call-bursts wanneer widget-settings snel worden gewijzigd in de editor.
Uitgestelde initial-fetch¶
News- en Feed-widgets gebruiken requestIdleCallback voor de initial data-fetch, en stellen de network-request uit tot de hoofdthread van de browser idle is. Verbetert waargenomen page-load-performance door layout en rendering te prioriteren.
Parallel bootstrap¶
De applicatie laadt pagina's, navigatie, footer en engagement-instellingen parallel bij mount (Promise.all). Pagina-content en lock-status worden ook parallel opgehaald, wat een sequentiële waterfall elimineert die voorheen ~100ms aan pagina-navigatie toevoegde.