> For the complete documentation index, see [llms.txt](https://ayakaleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ayakaleaf-pro.ayaka.space/on-premises/zh-cn/wei-hu/horizontal-scaling.md).

# 水平扩展

从 3.5.6 版本开始，Server Pro 支持水平扩展。

本文档列出了技术要求，并提供了在多个节点上运行 Server Pro 的指南。

{% hint style="danger" %}
从 Server CE/Server Pro 开始 `5.0.3` 环境变量已从以下名称更名为 `SHARELATEX_*` 到 `OVERLEAF_*`.

如果你使用的是 `4.x` 版本（或更早版本），请确保变量前缀相应更新（例如： `SHARELATEX_SITE_URL` 而不是 `OVERLEAF_SITE_URL`)
{% endhint %}

设置水平扩展需要付出大量工作。我们建议在达到一定规模时再考虑水平扩展， **仅** 例如，一个包含 1,000 名总用户的 Server Pro 安装，已使用一台配备两个 4 核处理器和 32GB 系统内存的服务器成功部署。请参阅 [硬件要求](/on-premises/zh-cn/ru-men/requirements/hardware-requirements.md) 文档中的建议。

带有水平扩展的 Server Pro 部署涉及一组外部组件，例如负载均衡器和兼容 S3 的存储后端。

我们可以帮助排查 Server Pro 容器中的错误，这些错误可能由配置错误导致，并可根据本文档提供一般性建议。遗憾的是，我们无法协助配置第三方应用程序/系统。

对与你的硬件/软件相关的技术问题进行排查，以确保外部组件可用，这不在我们的支持条款范围内。

### 要求

#### 外部中央数据存储

Server Pro 中的数据存储可以分为四个数据存储区：

* **MongoDB**

  * 大部分数据都持久化到 MongoDB 中。
  * 我们支持本地实例或外部实例，例如 [MongoDB](https://www.mongodb.com/atlas) Atlas（运行在 AWS 基础设施中的完全托管 MongoDB 服务）。<br>

  **注意：** 遗憾的是，目前对 CosmoDB/DocumentDB 这类兼容 MongoDB 的数据库没有官方支持，因为我们尚未在它们上测试过 Server Pro。虽然将 Server Pro 部署到兼容数据库上 **可能** 是可行的，但我们仅正式支持使用 MongoDB 的部署。<br>
* **Redis**

  * Redis 用于存储临时数据，例如在写入 MongoDB 之前等待提交的文档更新。
  * Redis 用于在不同服务之间传递文档更新，并通知编辑器某个项目中的状态变化。
  * Redis 用于存储用户会话。
  * 我们支持本地实例或外部实例。<br>

  **注意：** 遗憾的是，目前对 KeyDB/Valkey 这类兼容 Redis 的键值存储没有官方支持，因为我们尚未在它们上测试过 Server Pro。虽然将 Server Pro 部署到兼容存储上 **可能** 是可行的，但我们仅正式支持使用 Redis 的部署。<br>
* **项目文件和历史文件**

  * 不可编辑的项目文件存储在 MongoDB 之外。

    新的项目历史系统（Server Pro 3.5 及之后版本）也会将历史记录存储在 MongoDB 之外。
  * 对于小型单实例部署，我们支持本地文件系统（可以由本地 SSD、NFS 或 EBS 提供支持）或 [兼容 S3 的数据存储系统](/on-premises/zh-cn/pei-zhi/overleaf-toolkit/s3.md).
  * 对于水平扩展， **仅** 我们支持兼容 S3 的数据存储系统。<br>

  **重要：** NFS/Amazon EFS/Amazon EBS **不要** 支持水平扩展。有关在 Server Pro 中扩展存储的更多详情，请参阅 [硬件存储](/on-premises/zh-cn/ru-men/requirements/hardware-requirements.md#storage) 要求部分。
* **临时文件**
  * 为了获得最佳性能，LaTeX 编译需要在快速的本地磁盘上运行。编译输出无需持久化或备份。
  * 新文件上传的缓冲以及项目 zip 文件的创建也会受益于使用本地磁盘。

{% hint style="danger" %}
我们强烈建议使用本地磁盘。使用任何类型的网络磁盘（例如 NFS 或 EBS）都可能导致意外的编译错误和其他性能问题。
{% endhint %}

#### **Git-bridge**

{% hint style="info" %}
从 4.0.1 版本开始，Server Pro 中提供 Git-bridge。
{% endhint %}

git 仓库存储在本地磁盘上，没有可用的复制选项。Git-bridge 应作为 **单例**运行。为了获得最佳性能，我们建议将本地磁盘用于 git-bridge 数据。git-bridge 数据盘应定期备份。

对于带有水平扩展的数据存储，你需要：

* 一个可被所有 Server Pro 实例访问的中央 MongoDB 实例
* 一个可被所有 Server Pro 实例访问的中央 Redis 实例
* 一个用于项目和历史文件的中央兼容 S3 的存储后端
* 每个实例上的一个本地磁盘，用于临时文件
* 承载 git-bridge 容器的实例上的一个本地磁盘，用于 git-bridge 数据

#### 负载均衡器要求

* **持久路由**，例如使用 cookie

  这一要求源于以下组件：

  * Server Pro 中的实时编辑功能使用 WebSocket，并在必要时回退到 XHR 轮询。每个编辑会话在服务器端都有本地状态，某个编辑会话的请求始终需要路由到同一个 Server Pro 实例。协作功能使用 Redis [发布/订阅](https://redis.io/docs/latest/develop/interact/pubsub/) 在多个 Server Pro 实例之间共享更新。
  * LaTeX 编译会将输出和编译缓存保留在本地以获得最佳性能。向某个 Server Pro 实例发出编译请求后，随后的 PDF/日志下载请求需要路由到同一个 Server Pro 实例。
* **较长的请求超时** 以支持大型 LaTeX 文档的编译
* **WebSocket 支持** 以获得最佳性能
* **50MB 的 POST 负载大小**
* **Keep-alive 超时** 必须低于 Server Pro 的 keep-alive 超时

  Server Pro 中的 keep-alive 超时可以通过环境变量进行配置 `NGINX_KEEPALIVE_TIMEOUT`。默认值为 65 秒。

  使用默认配置时，负载均衡器中的 60 秒 keep-alive 超时可以正常工作。

  使用 `NGINX_KEEPALIVE_TIMEOUT=120`时，负载均衡器可以设为 115 秒。
* **客户端 IP**

  将请求头 `X-Forwarded-For` 设置为客户端 IP。
* 当 **终止 SSL**

  负载均衡器需要添加请求头 `X-Forwarded-Proto: https`.

<details>

<summary>HAProxy 配置示例</summary>

```
全局
  haproxy 组
  haproxy 用户

  # 详细日志记录
  log stdout format raw local0 debug

默认
  模式                    http
  选项                  httpchk HEAD /status
  http-check              expect status 200
  default-server          check

  # 详细日志记录
  log                     global
  option                  httplog

  # 如果粘性后端宕机，则重新路由到其他后端
  option                  redispatch 1
  # 这些重试针对的是 TCP 连接错误，而不是 HTTP 500 响应
  重试                 3

  # 24 小时无活动的粘性会话——编译输出会在 24 小时后删除
  cookie                  server-pro-ha insert maxidle 24h

  # 尝试连接任意后端 1 分钟，然后返回 503
  timeout queue           1m
  # 给 Server Pro 实例 15 秒启动时间
  timeout connect         15s

  # 中断来自非常慢的客户端的请求（读取请求时允许 1 分钟无活动）
  timeout client          1m

  # 允许较慢的编译——clsi 中硬编码的上限是 10 分钟
  timeout server          10m

  # 在 23 小时后断开编辑器——比昨天最后一次使用提前 1 小时
  timeout tunnel          23h

  # 注意：haproxy 中的 keepalive 行为与 Server Pro 的默认 keepalive 配置配合得很好。
  #       Haproxy 会在后台清理连接，并在需要时重新分发请求。

listen server-pro-ha-http
  bind :80
  http-request redirect scheme https unless { ssl_fc }

listen server-pro-ha-https
  bind :443 ssl crt /etc/ssl/certs/ssl-key-and-certificate-bundle.pem

  # 告诉应用程序我们位于 https 之后
  http-request set-header X-Forwarded-Proto https

  # 告诉应用程序真实的客户端 IP
  option forwardfor

  # 参见 https://hstspreload.org/#deployment-recommendations
  http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload;"

  # 将 git 流量路由到 git-bridge 的同级容器
  use-server server-pro-ha-1 if { path_beg /git/ }

  # 调试
  http-response add-header X-Served-By %s
  stats enable
  stats uri /haproxy

  server server-pro-ha-1 198.18.1.1:80 cookie server-pro-ha-1
  server server-pro-ha-2 198.18.1.2:80 cookie server-pro-ha-2
  server server-pro-ha-3 198.18.1.3:80 cookie server-pro-ha-3
```

</details>

#### Server Pro 配置

**密钥**

Server Pro 实例需要就共享密钥达成一致：

* `WEB_API_PASSWORD` （Web API 认证）
* `STAGING_PASSWORD` 和 `V1_HISTORY_PASSWORD` 相同的值（历史认证）
* `CRYPTO_RANDOM` （用于会话 cookie）
* `OT_JWT_AUTH_KEY` （历史认证）

这些密钥都需要配置为各自唯一的值，并在各实例之间共享。

如果未配置，并且用户请求被路由到不同的 Server Pro 实例，他们的请求将无法通过身份验证检查，结果要么频繁重定向到登录页，要么他们在 UI 中的操作会以意外的方式失败。

如果未配置，Server Pro 会为每个密钥使用一个基于 32 个随机字节的新随机值，来自 `/dev/urandom` （256 位随机数）。

{% code overflow="wrap" %}

```bash
# https://github.com/overleaf/overleaf/blob/45ca0f796c679103efd305ddbef28073c4a5de32/server-ce/init_scripts/00_regen_sharelatex_secrets.sh#L14
dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 -w 0 | rev | cut -b 2- | rev | tr -d '\n+/'
```

{% endcode %}

**MongoDB**

指向 `OVERLEAF_MONGO_URL` (`SHARELATEX_MONGO_URL` 用于版本 `4.x` 及更早版本）中的中央 MongoDB 实例。

**Redis**

指向 `OVERLEAF_REDIS_HOST` (`SHARELATEX_REDIS_HOST` 用于版本 `4.x` 及更早版本）和 `REDIS_HOST` 指向中央 Redis 实例。

**用于项目和历史文件的 S3 兼容存储**

请参阅关于 [S3 兼容存储](/on-premises/zh-cn/pei-zhi/overleaf-toolkit/s3.md) 的文档了解详情。

**临时文件**

到 `/var/lib/overleaf` (`/var/lib/sharelatex` 用于版本 `4.x` 及更早版本）上的默认本地 SSD 挂载应当足够。请务必将 `SANDBOXED_COMPILES_HOST_DIR` 指向主机上的挂载点。

{% hint style="danger" %}
我们强烈建议使用本地磁盘。使用任何类型的网络磁盘（例如 NFS 或 EBS）都可能导致意外的编译错误和其他性能问题。
{% endhint %}

**代理配置**

* 设置 `OVERLEAF_BEHIND_PROXY=true` (`SHARELATEX_BEHIND_PROXY` 用于版本 `4.x` 及更早版本）以获得准确的客户端 IP。
* 设置 `TRUSTED_PROXY_IPS` 为负载均衡器的 IP。（可指定多个 CIDR，使用逗号分隔）。

**Git-bridge 集成**

{% hint style="info" %}
从 4.0.1 版本开始，Server Pro 中提供 Git-bridge。
{% endhint %}

git-bridge 容器需要一个同级的 Server Pro 容器来处理传入的 git 请求。这个同级容器也可以提供普通用户流量服务。在示例配置中，第一台实例充当 git-bridge 的同级容器，但实际上任何实例都可以。

为什么我们需要指定一个 Server Pro 容器作为 git-bridge 的同级容器？Server Pro 会将历史服务的下载 URL 提供给 git-bridge。我们需要配置这些历史 URL，使其可以从 git-bridge 容器访问。

Server Pro 容器配置：

* 设置 `GIT_BRIDGE_ENABLED` 到 `'true'`
* 设置 `GIT_BRIDGE_HOST` 到 `<git-bridge 容器名称>` 例如： `git-bridge`
* 设置 `GIT_BRIDGE_PORT` 到 `8000`
* 设置 `V1_HISTORY_URL` 到 `http://<server-pro sibling container name>:3100/api`.

  注意：这只对 git-bridge 容器的同级容器是必要的。其他实例可以使用 localhost URL，这是默认值。

git-bridge 容器配置：

* 设置 `GIT_BRIDGE_API_BASE_URL` 到 `http://<server-pro sibling container name>/api/v0`，例如 `http://server-pro-ha-1/api/v0`
* 设置 `GIT_BRIDGE_OAUTH2_SERVER` 到 `http://<server-pro sibling container name>`，例如 `http://server-pro-ha-1`
* 设置 `GIT_BRIDGE_POSTBACK_BASE_URL` 到 `http://<git-bridge container name>:8000`，例如 `http://git-bridge:8000`
* 设置 `GIT_BRIDGE_ROOT_DIR` 指向通过绑定挂载的 git-bridge 数据盘，例如 `/data/git-bridge`

<details>

<summary>docker-compose.yml 配置示例</summary>

以下配置展示的是一个自包含的设置。为了让演示正常工作，你需要提供有效的 SSL 密钥/证书，并调整 `OVERLEAF_SITE_URL` (`SHARELATEX_SITE_URL` 用于版本 `4.x` 及更早版本）。对于实际部署，你必须用实际密钥替换下面标注的占位密钥。对于实际部署，你还需要将各个容器移到专用节点上，并根据本地网络环境调整 IP 地址。

```yaml
version: '2.2'

# 实际的水平扩展部署：选择自己的网络并替换配置中的 IP。
networks:
    default:
        ipam:
            config:
                # 该子网属于用于基准测试的保留子网
                # https://tools.ietf.org/html/rfc2544
                # 完整子网为 198.18.0.0/15
                # 为 lb 和 db 使用 198.18.0.0/24
                # 为 server-pro 使用 198.18.1.0/24
                # 为临时容器使用 198.18.0.128/25
                - gateway: 198.18.0.1
                  ip_range: 198.18.0.128/25
                  subnet: 198.18.0.0/23

services:
    # 实际的水平扩展部署：在另一台主机上、Docker 之外运行 haproxy。
    lb:
        image: haproxy:2.6
        container_name: lb
        user: root
        logging:
            driver: local
            options:
                max-size: 10g
                max-file: '100'
        volumes:
            - ./haproxy.conf:/usr/local/etc/haproxy/haproxy.cfg
            # $ cat certificate.pem key.pem > ssl-key-and-certificate-bundle.pem
            - /path/to/ssl-key-and-certificate-bundle.pem:/etc/ssl/certs/ssl-key-and-certificate-bundle.pem
        # "ports" 的替代方案：使用主机网络以避免 docker-proxy 开销
        network_mode: host

        # "network_mode: host" 的替代方案：使用 docker-proxy 实现网络隔离
        # ports:
        #     - "80:80"
        #     - "443:443"
        # networks:
        #     default:
        #         ipv4_address: 198.18.0.2

        # 实际的水平扩展部署：删除这些，因为它们会在其他主机上运行。
        depends_on:
            server-pro-ha-1:
                condition: service_started
            server-pro-ha-2:
                condition: service_started
            server-pro-ha-3:
                condition: service_started

    # 实际的水平扩展部署：将此容器运行在 server-pro-ha-1 旁边。
    # 适用于 Server Pro 4.0 及之后版本。
    git-bridge：
        restart: always
        # 该标签应与 `server-pro-ha-1` 容器标签匹配。
        image: quay.io/sharelatex/git-bridge:4.0.1
        volumes:
            # 实际的水平扩展部署：将 /data/git-bridge 指向专用的本地 SSD。
            - ~/git_bridge_data:/data/git-bridge
        container_name: git-bridge
        environment:
            GIT_BRIDGE_API_BASE_URL: "http://server-pro-ha-1/api/v0"
            GIT_BRIDGE_OAUTH2_SERVER: "http://server-pro-ha-1"
            GIT_BRIDGE_POSTBACK_BASE_URL: "http://198.18.0.6:8000"
            GIT_BRIDGE_ROOT_DIR: "/data/git-bridge"
        user: root
        command: ["/server-pro-start.sh"]

        # 实际的水平扩展部署：在主机 198.18.0.6 上运行并暴露端口
        # ports:
        #     - "8000:8000"
        networks:
            default:
                ipv4_address: 198.18.0.6

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    server-pro-ha-1: &server-pro-ha-config
        restart: always
        image: quay.io/sharelatex/sharelatex-pro:4.0.1
        container_name: server-pro-ha-1
        hostname: server-pro-ha-1
        depends_on:
            # 实际的水平扩展部署：保留此条目。
            git-bridge：
                condition: service_started

            # 实际的水平扩展部署：删除下面这些，因为它们会在其他主机上运行。
            mongo:
                condition: service_healthy
            redis:
                condition: service_started
            minio:
                condition: service_started
            mongo_replica_set_setup:
                condition: service_completed_successfully
            minio_setup:
                condition: service_completed_successfully
        stop_grace_period: 60s
        volumes:
            - /tmp/scratch-disk1:/var/lib/sharelatex
            - /var/run/docker.sock:/var/run/docker.sock
        environment: &server-pro-ha-environment
            # 实际的水平扩展部署：提供你自己的域名/应用名称。
            OVERLEAF_SITE_URL: 'https://overleaf.example.com'
            OVERLEAF_APP_NAME: Server Pro Horizontal Scaling Demo

            OVERLEAF_MONGO_URL: mongodb://198.18.0.3/sharelatex
            OVERLEAF_REDIS_HOST: 198.18.0.4
            REDIS_HOST: 198.18.0.4

            ENABLED_LINKED_FILE_TYPES: 'project_file,project_output_file'
            EMAIL_CONFIRMATION_DISABLED: 'true'

            SANDBOXED_COMPILES: 'true'
            SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true'
            SANDBOXED_COMPILES_HOST_DIR: '/tmp/scratch-disk1/data/compiles'

            # S3
            # 实际的水平扩展部署：选择安全的凭据。
            OVERLEAF_FILESTORE_BACKEND: s3
            OVERLEAF_FILESTORE_USER_FILES_BUCKET_NAME: overleaf-user-files
            OVERLEAF_FILESTORE_TEMPLATE_FILES_BUCKET_NAME: overleaf-template-files
            OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID: OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID
            OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY: OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY
            OVERLEAF_FILESTORE_S3_ENDPOINT: http://198.18.0.5:9000
            OVERLEAF_FILESTORE_S3_PATH_STYLE: 'true'
            OVERLEAF_FILESTORE_S3_REGION: ''

            OVERLEAF_HISTORY_BACKEND: "s3"
            OVERLEAF_HISTORY_PROJECT_BLOBS_BUCKET: "overleaf-project-blobs"
            OVERLEAF_HISTORY_CHUNKS_BUCKET: "overleaf-chunks"
            OVERLEAF_HISTORY_S3_ACCESS_KEY_ID: "OVERLEAF_HISTORY_S3_ACCESS_KEY_ID"
            OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY: "OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY"
            OVERLEAF_HISTORY_S3_ENDPOINT: http://198.18.0.5:9000
            OVERLEAF_HISTORY_S3_PATH_STYLE: 'true'
            OVERLEAF_HISTORY_S3_REGION: ''
            # /S3

            # git-bridge
            GIT_BRIDGE_ENABLED: 'true'
            GIT_BRIDGE_HOST: 198.18.0.6
            GIT_BRIDGE_PORT: 8000
            # 仅在 git-bridge 的同级实例上需要
            V1_HISTORY_URL: "http://server-pro-ha-1:3100/api"
            # /git-bridge

            # 水平扩展
            # 实际的水平扩展部署：选择安全的凭据。
            WEB_API_PASSWORD: WEB_API_PASSWORD
            STAGING_PASSWORD: V1_HISTORY_PASSWORD
            V1_HISTORY_PASSWORD: V1_HISTORY_PASSWORD
            CRYPTO_RANDOM: CRYPTO_RANDOM
            OT_JWT_AUTH_KEY: OT_JWT_AUTH_KEY
            OVERLEAF_BEHIND_PROXY: 'true'
            # 实际的水平扩展配置：负载均衡器的 IP
            TRUSTED_PROXY_IPS: 198.18.0.1,198.18.0.2
            # /水平扩展

        # 实际的水平扩展配置：在主机 198.18.1.1 上运行并暴露端口
        # ports:
        #     - "80:80"
        networks:
            default:
                ipv4_address: 198.18.1.1

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    server-pro-ha-2:
        <<: *server-pro-ha-config
        hostname: server-pro-ha-2
        container_name: server-pro-ha-2
        volumes:
            - /tmp/scratch-disk2:/var/lib/sharelatex
            - /var/run/docker.sock:/var/run/docker.sock
        environment:
            <<: *server-pro-ha-environment
            SANDBOXED_COMPILES_HOST_DIR: '/tmp/scratch-disk2/data/compiles'
            V1_HISTORY_URL: "http://localhost:3100/api"

        # 实际的水平扩展配置：在主机 198.18.1.2 上运行并暴露端口
        # ports:
        #     - "80:80"
        networks:
            default:
                ipv4_address: 198.18.1.2

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    server-pro-ha-3:
        <<: *server-pro-ha-config
        hostname: server-pro-ha-3
        container_name: server-pro-ha-3
        volumes:
            - /tmp/scratch-disk3:/var/lib/sharelatex
            - /var/run/docker.sock:/var/run/docker.sock
        environment:
            <<: *server-pro-ha-environment
            SANDBOXED_COMPILES_HOST_DIR: '/tmp/scratch-disk3/data/compiles'
            V1_HISTORY_URL: "http://localhost:3100/api"

        # 实际的水平扩展配置：在主机 198.18.1.3 上运行并暴露端口
        # ports:
        #     - "80:80"
        networks:
            default:
                ipv4_address: 198.18.1.3

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    minio:
        image: minio/minio:RELEASE.2023-05-18T00-05-36Z
        container_name: minio
        command: server /data
        volumes:
            # 实际的水平扩展配置：使用多个磁盘运行 minio，参见 minio 文档。
            - ~/minio_data:/data
        environment:
            # 实际的水平扩展部署：选择安全的凭据。
            MINIO_ROOT_USER: MINIO_ROOT_USER
            MINIO_ROOT_PASSWORD: MINIO_ROOT_PASSWORD

        # 实际的水平扩展配置：在主机 198.18.0.5 上运行并暴露端口
        # ports:
        #     - "9000:9000"
        networks:
            default:
                ipv4_address: 198.18.0.5

    # 实际的水平扩展配置：在一台单独的主机上运行此设置一次。
    minio_setup:
        depends_on:
            - minio
        image: minio/mc:RELEASE.2023-05-18T16-59-00Z
        entrypoint: sh
        命令：
            - '-c'
            # 实际的水平扩展部署：选择安全的凭据。
            - |
                mc alias set s3 http://198.18.0.5:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
                || sleep 10 && \
                mc alias set s3 http://198.18.0.5:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
                || sleep 10 && \
                mc alias set s3 http://198.18.0.5:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD \
                || sleep 10 && \
                mc alias set s3 http://198.18.0.5:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD

                mc mb --ignore-existing s3/overleaf-user-files
                mc mb --ignore-existing s3/overleaf-template-files
                mc admin user add s3 \\
                  OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID \\
                  OVERLEAF_FILESTORE_S3_SECRET_ACCESS_KEY

                mc mb --ignore-existing s3/overleaf-project-blobs
                mc mb --ignore-existing s3/overleaf-chunks
                mc admin user add s3 \\
                  OVERLEAF_HISTORY_S3_ACCESS_KEY_ID \\
                  OVERLEAF_HISTORY_S3_SECRET_ACCESS_KEY

                echo '
                  {
                    "Version": "2012-10-17",
                    "Statement": [
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:ListBucket"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-user-files"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:PutObject",
                          "s3:GetObject",
                          "s3:DeleteObject"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-user-files/*"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:ListBucket"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-template-files"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:PutObject",
                          "s3:GetObject",
                          "s3:DeleteObject"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-template-files/*"
                      }
                    ]
                  }' > policy-filestore.json

                echo '
                  {
                    "Version": "2012-10-17",
                    "Statement": [
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:ListBucket"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-project-blobs"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:PutObject",
                          "s3:GetObject",
                          "s3:DeleteObject"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-project-blobs/*"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:ListBucket"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-chunks"
                      },
                      {
                        "Effect": "Allow",
                        "Action": [
                          "s3:PutObject",
                          "s3:GetObject",
                          "s3:DeleteObject"
                        ],
                        "Resource": "arn:aws:s3:::overleaf-chunks/*"
                      }
                    ]
                  }' > policy-history.json

                # 将上一节中策略的内容放入 policy-filestore.json
                # 提醒：相应地替换桶名称。
                mc admin policy create s3 overleaf-filestore policy-filestore.json
                mc admin policy attach s3 overleaf-filestore \\
                  --user=OVERLEAF_FILESTORE_S3_ACCESS_KEY_ID || true

                mc admin policy create s3 overleaf-history policy-history.json
                mc admin policy attach s3 overleaf-history \\
                  --user=OVERLEAF_HISTORY_S3_ACCESS_KEY_ID || true

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    mongo:
        restart: always
        image: mongo:4.4
        container_name: mongo
        command: "--replSet overleaf"
        expose:
            - 27017
        volumes:
            - ~/mongo_data:/data/db
        healthcheck:
            test: echo 'db.stats().ok' | mongo localhost:27017/test --quiet
            interval: 10s
            timeout: 10s
            retries: 5

        # 实际的水平扩展配置：在主机 198.18.0.3 上运行并暴露端口
        # ports:
        #     - "27017:27017"
        networks:
            default:
                ipv4_address: 198.18.0.3

    mongo_replica_set_setup:
        image: mongo:4.4
        entrypoint: sh
        depends_on:
            mongo:
                condition: service_healthy
        命令：
            - '-c'
            - |
                mongo 198.18.0.3 --eval "rs.initiate({ _id: \"overleaf\", members: [ { _id: 0, host: \"198.18.0.3:27017\" } ] })"

    # 实际的水平扩展部署：将此容器运行在另一台主机上。
    redis:
        restart: always
        image: redis:6.2
        container_name: redis
        expose:
            - 6379
        volumes:
            - ~/redis_data:/data

        # 实际的水平扩展配置：在主机 198.18.0.4 上运行并暴露端口
        # ports:
        #     - "6379:6379"
        networks:
            default:
                ipv4_address: 198.18.0.4
```

</details>

#### 硬件

我们建议参与水平扩展的所有 Server Pro 实例使用相同的硬件规格。

关于 [硬件规格](/on-premises/zh-cn/ru-men/requirements/hardware-requirements.md) 的一般建议适用于 Server Pro 实例。

#### 升级 Server Pro

作为升级过程的一部分，Server Pro 会自动运行数据库迁移。这些迁移 **不要** 设计为可从多个实例并行运行。

这些迁移需要在实际 Web 应用启动之前完成。你可以查看日志中是否有 `迁移完成` 的条目，或者等待应用开始接受流量。

升级过程如下：

1. 安排维护窗口
2. 停止所有 Server Pro 实例
3. 执行一致性备份，如同在 [文档](/on-premises/zh-cn/wei-hu/data-and-backups.md#performing-a-consistent-backup)
4. 使用新版本启动一个单独的 Server Pro 实例
5. 验证新实例是否按预期工作
6. 使用新版本启动其他实例


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ayakaleaf-pro.ayaka.space/on-premises/zh-cn/wei-hu/horizontal-scaling.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
