From 25907bd48ccc90b71dd7f68f37cc8159e9a89681 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Thu, 21 Sep 2023 10:09:34 +0200 Subject: [PATCH] feat: fallback to loading page when SSR is taking too long (#1511) --- docs/guides/nginx-startup.md | 30 ++++++++++++ nginx/Dockerfile | 3 ++ nginx/docker-entrypoint.d/40-gomplate.sh | 3 ++ nginx/docker-entrypoint.d/entrypoint.sh | 5 ++ nginx/loading-fallback.sh.tmpl | 27 +++++++++++ nginx/nginx.conf | 1 + nginx/templates/loading-fallback.conf.tmpl | 53 ++++++++++++++++++++++ nginx/templates/multi-channel.conf.tmpl | 6 +++ 8 files changed, 128 insertions(+) create mode 100644 nginx/loading-fallback.sh.tmpl create mode 100644 nginx/templates/loading-fallback.conf.tmpl diff --git a/docs/guides/nginx-startup.md b/docs/guides/nginx-startup.md index a7961ad7c9..8f63d1bec4 100644 --- a/docs/guides/nginx-startup.md +++ b/docs/guides/nginx-startup.md @@ -7,6 +7,21 @@ kb_sync_latest_only # Building and Running NGINX Docker Image +- [Building and Running NGINX Docker Image](#building-and-running-nginx-docker-image) + - [Building](#building) + - [Configuration](#configuration) + - [HTTPS or SSL](#https-or-ssl) + - [Basic Auth](#basic-auth) + - [Multi-Site](#multi-site) + - [Ignore Parameters During Caching](#ignore-parameters-during-caching) + - [Access ICM Sitemap](#access-icm-sitemap) + - [Override Identity Providers by Path](#override-identity-providers-by-path) + - [Other](#other) + - [Features](#features) + - [Cache](#cache) + - [Loading Fallback](#loading-fallback) + - [Further References](#further-references) + We provide a Docker image based on [nginx](https://nginx.org/) for the [PWA deployment](../concepts/pwa-building-blocks.md#pwa---nginx). ## Building @@ -163,6 +178,21 @@ If the cache feature is switched off, all caching for pre-rendered pages is disa The cache duration for pre-rendered pages can be customized using `CACHE_DURATION_NGINX_OK` (for successful responses) and `CACHE_DURATION_NGINX_NF` (for 404 responses). The value supplied must be in the `time` format that is supported by [nginx proxy_cache_valid](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_valid). +### Loading Fallback + +When encountering high loads on the SSR container, the nginx container can be configured to serve a loading page after a certain timeout. +This way the user will get a fast response that can be completed by the browser using the ICM REST API. +Meanwhile the nginx container will cache the page in the background after it finishes rendering.The fully rendered page will be served on the next request. + +Requests from Crawlers will bypass this mechanism and will be served directly from the SSR container with potentially longer response durations. + +To activate this feature, set the environment variable `LOADING_FALLBACK` to `"on"`. +The timeout can be configured using the environment variable `LOADING_FALLBACK_TIMEOUT` using a time format (i.e. `40s`, `15m`, ...). + +> :warning: It is not recommended to use this feature by default, as it will not solve the underlying issue. +> In most cases the SSR container should be scaled out dynamically to handle intermittent high loads. +> It might also be worth investigating default render times and do some code optimizations to get faster SSR responses. + ## Further References - [Concept - Multi-Site Handling](../concepts/multi-site-handling.md) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 9de5896b62..f98febc1c7 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -2,6 +2,7 @@ FROM nginx:1.22 RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y apache2-utils libnss3-tools COPY *.conf /etc/nginx/ +COPY loading-fallback.sh.tmpl /loading-fallback.sh.tmpl COPY features /etc/nginx/features/ COPY templates /etc/nginx/templates/ COPY docker-entrypoint.d/*.sh /docker-entrypoint.d/ @@ -21,5 +22,7 @@ ENV CACHE_DURATION_NGINX_OK=10m ENV CACHE_DURATION_NGINX_NF=60m ENV LOGFORMAT=main ENV LOG_ALL=on +ENV LOADING_FALLBACK=off +ENV LOADING_FALLBACK_TIMEOUT=10s EXPOSE 80 443 9113 diff --git a/nginx/docker-entrypoint.d/40-gomplate.sh b/nginx/docker-entrypoint.d/40-gomplate.sh index fa3c71461d..010966c538 100755 --- a/nginx/docker-entrypoint.d/40-gomplate.sh +++ b/nginx/docker-entrypoint.d/40-gomplate.sh @@ -30,3 +30,6 @@ then fi /gomplate -d "domains=$MULTI_CHANNEL_SOURCE" -d "overrideIdentityProviders=$OVERRIDE_IDENTITY_PROVIDERS_SOURCE" -d "cachingIgnoreParams=$CACHING_IGNORE_PARAMS_SOURCE" -d 'ipwhitelist=env:///BASIC_AUTH_IP_WHITELIST?type=application/yaml' --input-dir="/etc/nginx/templates" --output-map='/etc/nginx/conf.d/{{ .in | strings.ReplaceAll ".conf.tmpl" ".conf" }}' + +/gomplate -d "domains=$MULTI_CHANNEL_SOURCE" -f /loading-fallback.sh.tmpl -o /loading-fallback.sh +chmod +x /loading-fallback.sh diff --git a/nginx/docker-entrypoint.d/entrypoint.sh b/nginx/docker-entrypoint.d/entrypoint.sh index 3d8272f29a..dfa80192ce 100755 --- a/nginx/docker-entrypoint.d/entrypoint.sh +++ b/nginx/docker-entrypoint.d/entrypoint.sh @@ -18,3 +18,8 @@ if env | grep -iqE "^PROMETHEUS=(on|1|true|yes)$" then (sleep 5 && /nginx-prometheus-exporter)& fi + +if env | grep -iqE "^LOADING_FALLBACK=(on|1|true|yes)$" +then + (sleep 5 && /loading-fallback.sh)& +fi diff --git a/nginx/loading-fallback.sh.tmpl b/nginx/loading-fallback.sh.tmpl new file mode 100644 index 0000000000..2ea6696c18 --- /dev/null +++ b/nginx/loading-fallback.sh.tmpl @@ -0,0 +1,27 @@ +{{- define "clean-domain" -}} +{{ . | strings.ReplaceAll ".+" "_" | strings.ReplaceAll ".*" "_" }} +{{- end -}} +#!/bin/sh + +mkdir -p /tmp/loading + +fetch() { + target="/tmp/loading/$1/$2.html" + mkdir -p "$(dirname "$target")" + curl -s -o "$target" -H "Host: $1" "http://localhost:8080$2" +} + +while true +do +{{- range $domain, $mapping := (ds "domains") }} + {{- $cdomain := tmpl.Exec "clean-domain" $domain -}} + {{- if $mapping | test.IsKind "map" }} + fetch "{{ $cdomain }}" "/loading" + {{- else }} + {{- range $mapping }} + fetch "{{ $cdomain }}" "{{ .baseHref }}/loading" + {{- end }} + {{- end }} +{{- end }} + sleep 300 +done diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 7b18b0c093..2d0680de8b 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -72,4 +72,5 @@ http { include /etc/nginx/conf.d/features.conf; include /etc/nginx/conf.d/multi-channel.conf; + include /etc/nginx/conf.d/loading-fallback.conf; } diff --git a/nginx/templates/loading-fallback.conf.tmpl b/nginx/templates/loading-fallback.conf.tmpl new file mode 100644 index 0000000000..fb424c684f --- /dev/null +++ b/nginx/templates/loading-fallback.conf.tmpl @@ -0,0 +1,53 @@ +{{- define "clean-domain" -}} +{{ . | strings.ReplaceAll ".+" "_" | strings.ReplaceAll ".*" "_" }} +{{- end -}} + +{{ if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }} +upstream real_server { + server localhost:8080 fail_timeout=0; + keepalive 15; +} + + {{- range $domain, $mapping := (ds "domains") }} +server { + listen 80; + server_name ~^{{ $domain }}$; + proxy_set_header Host $http_host; + proxy_cache_bypass true; + + # https://mailman.nginx.org/pipermail/nginx/2007-May/001031.html + if ($is_bot) { + rewrite ^(.*)$ /bot-bypass$1; + } + location /bot-bypass { + rewrite ^/bot-bypass(.*)$ $1 break; + proxy_pass http://real_server; + } + + {{ if (has $mapping "channel") }} + location / { + proxy_pass http://real_server; + proxy_read_timeout {{ getenv "LOADING_FALLBACK_TIMEOUT" }}; + error_page 504 =200 /loading.html; + } + location = /loading.html { + root /tmp/loading/{{ tmpl.Exec "clean-domain" $domain }}; + } + {{- else -}} + {{ range $mapping }} + location {{ .baseHref }} { + proxy_pass http://real_server; + proxy_read_timeout {{ getenv "LOADING_FALLBACK_TIMEOUT" }}; + error_page 504 =200 {{ .baseHref }}/loading.html; + } + location = {{ .baseHref }}/loading.html { + root /tmp/loading/{{ tmpl.Exec "clean-domain" $domain }}; + } + {{ end }} + location / { + proxy_pass http://real_server; + } + {{ end }} + {{ end }} +} +{{ end }} diff --git a/nginx/templates/multi-channel.conf.tmpl b/nginx/templates/multi-channel.conf.tmpl index 8ad50d04a8..7d71b104fc 100644 --- a/nginx/templates/multi-channel.conf.tmpl +++ b/nginx/templates/multi-channel.conf.tmpl @@ -55,6 +55,10 @@ rewrite '^(.*)$' '$1$default_rewrite_params' break; + {{ if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }} + proxy_ignore_client_abort on; + {{ end }} + proxy_pass {{ getenv "UPSTREAM_PWA" }}; proxy_buffer_size 128k; proxy_buffers 4 256k; @@ -91,6 +95,8 @@ server { # https://ma.ttias.be/force-redirect-http-https-custom-port-nginx/ error_page 497 https://$http_host$request_uri; + {{ else if getenv "LOADING_FALLBACK" | strings.ToLower | regexp.Match "on|1|true|yes" }} + listen 8080; {{ else }} listen 80; {{- end }}