diff --git a/docs/production-deployment/self-hosted-guide/temporal-nexus.mdx b/docs/production-deployment/self-hosted-guide/temporal-nexus.mdx index 95e78a2e14..88d1c519b3 100644 --- a/docs/production-deployment/self-hosted-guide/temporal-nexus.mdx +++ b/docs/production-deployment/self-hosted-guide/temporal-nexus.mdx @@ -9,6 +9,7 @@ keywords: tags: - Self hosting - enable-nexus + - multi-cluster-replication --- :::info NEW TO NEXUS? @@ -21,11 +22,6 @@ This page explains how to self-host Nexus. To learn about Nexus, see the [how Ne Nexus can be configured by setting static configuration and dynamic configuration entries. - -:::note -Nexus is supported in single-cluster setups only. See [Nexus Architecture](https://github.com/temporalio/temporal/blob/main/docs/architecture/nexus.md) for operational details. -::: - :::note Replace `$PUBLIC_URL` with a URL value that's accessible to external callers or internally within the cluster. Currently, external Nexus calls are considered experimental so it should be safe to use the address of an internal load balancer for the Frontend Service. @@ -91,3 +87,69 @@ See [how Nexus works](/nexus) for an architectural overview, then follow an SDK [.NET](/develop/dotnet/nexus) ::: + +## Multi-Cluster Replication (multi-region failover) + +:::warning EXPERIMENTAL + +Nexus across [Multi-Cluster Replication](/self-hosted-guide/multi-cluster-replication) +is experimental. + +::: + +Nexus works across +[Multi-Cluster Replication](/self-hosted-guide/multi-cluster-replication). An +asynchronous Nexus Operation started in one Cluster completes even if its Namespace +fails over, or its Cluster is lost, before the Operation finishes. + +:::note Endpoint target types + +This applies to [Worker-target Endpoints](/nexus/endpoints), where the Endpoint +routes to a target Namespace and Task Queue that a Worker polls. Endpoints can also +target an external URL (`--target-url`), which is experimental and not covered here. + +::: + +### Configuration + +1. **Set up Multi-Cluster Replication.** See + [Multi-Cluster Replication](/self-hosted-guide/multi-cluster-replication) for + connecting Clusters and creating replicated Namespaces. + +2. **Advertise a frontend HTTP address on every Cluster.** Set + `frontend.rpc.httpPort` and `clusterInformation..httpAddress` (shown in + [Enable Nexus](#enable-nexus) for one Cluster) on **each** Cluster, each with its + own address, so the other Clusters can reach it to deliver callbacks. + +3. **Register Endpoints on every Cluster.** The + [Nexus Endpoint registry](/nexus/registry) isn't replicated across Clusters. + Create the same Endpoints (same target Namespace and Task Queue) on **each** Cluster. + +4. **Cross-Cluster forwarding.** Nexus request and callback forwarding is on by + default via `system.enableNamespaceNotActiveAutoForwarding` (per-Namespace + dynamic config). + +### What to expect on failover + +For a Nexus Operation started before a Cluster failover completes on the new active +Cluster, the completion callback is delivered to the caller Namespace's current +active Cluster, re-resolved on each attempt: + +```mermaid +sequenceDiagram + box Cluster A + participant caA as Caller Namespace + participant haA as Handler Namespace + end + box Cluster B + participant caB as Caller Namespace + participant haB as Handler Namespace + end + Note over caA,haA: Cluster A active + caA->>haA: start Nexus Operation + haA-->>caA: Operation started, token returned + Note over caA,haB: 💥 Fail over both namespaces from Cluster A to B + Note over caB,haB: Cluster B active + haB->>caB: deliver completion callback + Note over caB: Nexus Operation completes +``` diff --git a/package.json b/package.json index 2751d77a28..d39a0e2da8 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "dprint": "^0.55.1", "eslint": "^9.8.0", "eslint-plugin-react": "^7.23.2", + "global-jsdom": "^29.0.0", "husky": "^9.1.7", "hyperlink": "^5.0.4" } diff --git a/scripts/lint-mermaid.mjs b/scripts/lint-mermaid.mjs index 0e68d39b9d..cbb487a511 100644 --- a/scripts/lint-mermaid.mjs +++ b/scripts/lint-mermaid.mjs @@ -1,9 +1,11 @@ +// Set up a browser-like DOM before loading mermaid. mermaid.parse() uses DOM +// APIs for some diagram syntaxes (for example a sequence diagram `box`) and +// otherwise fails headless with "window is not defined". +import 'global-jsdom/register'; + import fs from 'node:fs/promises'; import path from 'node:path'; -import DOMPurify from 'dompurify'; -import mermaidModule from 'mermaid'; - -const mermaid = mermaidModule.default ?? mermaidModule; +import mermaid from 'mermaid'; const DOCS_ROOT = path.resolve('docs'); const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']); @@ -109,20 +111,6 @@ async function lintFile(filePath) { } async function main() { - // Mermaid's parser may call DOMPurify hooks for some diagram syntaxes. - // In Node (without a browser window), dompurify can resolve to a minimal - // implementation that lacks hook APIs; provide no-op hooks so syntax-only - // parsing still works in CI. - if (typeof DOMPurify.addHook !== 'function') { - DOMPurify.addHook = () => {}; - } - if (typeof DOMPurify.removeHook !== 'function') { - DOMPurify.removeHook = () => {}; - } - if (typeof DOMPurify.sanitize !== 'function') { - DOMPurify.sanitize = (value) => value; - } - mermaid.initialize({ startOnLoad: false }); const markdownFiles = await walk(DOCS_ROOT); diff --git a/yarn.lock b/yarn.lock index 005f797302..83c7848833 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8098,6 +8098,11 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" +global-jsdom@^29.0.0: + version "29.0.0" + resolved "https://registry.yarnpkg.com/global-jsdom/-/global-jsdom-29.0.0.tgz#c7c7f51d58f8069d94563afb5d7b8cd34546d64e" + integrity sha512-S9Wl+EHDfkO8DYleqMYzf14hKfwIysEN/FvoVRdPif3uUGUlmiHs/gj1PVKXhmJ20DwUVACtdxxhNYYvvn9K7A== + globals@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"