GitLab Orbit sizing
- Tier: Premium, Ultimate
- Offering: GitLab Self-Managed
Size GitLab Orbit next to your GitLab reference architecture. Find the size of your reference architecture, then add the GitLab Orbit components for that size to your environment.
GitLab Orbit load depends more on the groups that you index, your Gitaly nodes, and your PostgreSQL change rate than on user count. After you choose a size, read Adjust for your workload.
The values are starting points, and they can be more than your workload needs. Monitor the signals in Monitor and scale, then decrease the values.
Choose a size
Use the size that matches your GitLab reference architecture. For fewer than 2,000 users, use the 2,000 users size.
| Size | GitLab reference architecture | Cloud Native Hybrid size |
|---|---|---|
| 2,000 users | Up to 40 RPS or 2,000 users | None |
| 3,000 users | Up to 60 RPS or 3,000 users | None |
| 5,000 users | Up to 100 RPS or 5,000 users | Small |
| 10,000 users | Up to 200 RPS or 10,000 users | Medium |
| 25,000 users | Up to 500 RPS or 25,000 users | Large |
| 50,000 users | Up to 1000 RPS or 50,000 users | Extra large |
Components
| Component | Runs on | Purpose |
|---|---|---|
| Webserver | Kubernetes | Serves GitLab Orbit queries. |
| SDLC indexer | Kubernetes | Builds the graph from the GitLab data in the ClickHouse data lake. |
| Code indexer | Kubernetes | Builds the code graph from repository archives that it fetches through the GitLab internal API. |
| Dispatcher | Kubernetes | Schedules indexing tasks. Use the default resources of the chart. |
| Health check | Kubernetes | Serves component health to the webserver, and the queue depth that autoscaling reads. Turn it on with healthCheck.enabled: true. Use the default resources of the chart. |
| NATS | Kubernetes | Holds the stream of PostgreSQL changes and the indexing tasks. |
| Siphon | Kubernetes | Reads PostgreSQL changes and writes them to NATS and ClickHouse. |
| ClickHouse | ClickHouse Cloud or VMs | Stores the data lake and the graph. |
Kubernetes
Node pools
Put the GitLab Orbit pods on their own node pool, and NATS and Siphon on a second node pool. The maximum node counts leave room for the extra pods that a chart upgrade starts.
| Size | GitLab Orbit node pool | Node disk | NATS and Siphon node pool |
|---|---|---|---|
| 2,000 users | 1 to 3 nodes, 8 vCPU, 32 GB memory | 200 GB | 1 to 2 nodes, 4 vCPU, 16 GB memory |
| 3,000 users | 1 to 6 nodes, 8 vCPU, 32 GB memory | 200 GB | 1 to 2 nodes, 4 vCPU, 16 GB memory |
| 5,000 users | 2 to 5 nodes, 16 vCPU, 64 GB memory | 300 GB | 1 to 2 nodes, 4 vCPU, 16 GB memory |
| 10,000 users | 2 to 6 nodes, 16 vCPU, 64 GB memory | 400 GB | 1 to 2 nodes, 4 vCPU, 16 GB memory |
| 25,000 users | 3 to 10 nodes, 16 vCPU, 64 GB memory | 500 GB | 3 to 4 nodes, 4 vCPU, 16 GB memory |
| 50,000 users | 3 to 10 nodes, 32 vCPU, 128 GB memory | 600 GB | 3 to 4 nodes, 8 vCPU, 32 GB memory |
| Node size | GCP example | AWS example | Azure example |
|---|---|---|---|
| 4 vCPU, 16 GB memory | n2-standard-4 | m6i.xlarge | Standard_D4s_v5 |
| 8 vCPU, 32 GB memory | n2-standard-8 | m6i.2xlarge | Standard_D8s_v5 |
| 16 vCPU, 64 GB memory | n2-standard-16 | m6i.4xlarge | Standard_D16s_v5 |
| 32 vCPU, 128 GB memory | n2-standard-32 | m6i.8xlarge | Standard_D32s_v5 |
Machine type examples are for illustration. Other machine types that meet the requirements are supported, including Arm types. Do not use burstable instance types, because their CPU performance is not consistent.
Pods
The following tables give the pod count, and the request and the limit for one pod. The indexers use most of their memory limit during backfill, so their memory request equals the limit. GitLab Orbit sets CPU limits on the code indexer, because the code indexer sets its indexing lanes from them.
Webserver:
| Size | Pods | Request | Limit |
|---|---|---|---|
| 2,000 users | 1 | 250m vCPU, 1 GiB | 2 vCPU, 4 GiB |
| 3,000 users | 2 | 250m vCPU, 1 GiB | 2 vCPU, 4 GiB |
| 5,000 users | 2 | 500m vCPU, 2 GiB | 2 vCPU, 6 GiB |
| 10,000 users | 2 | 500m vCPU, 2 GiB | 2 vCPU, 6 GiB |
| 25,000 users | 3 | 1 vCPU, 4 GiB | 4 vCPU, 8 GiB |
| 50,000 users | 3 | 1 vCPU, 4 GiB | 4 vCPU, 8 GiB |
SDLC indexer:
| Size | Pods | Request | Limit | Workers per pod |
|---|---|---|---|---|
| 2,000 users | 1 | 250m vCPU, 16 GiB | 2 vCPU, 16 GiB | 4 |
| 3,000 users | 1 | 250m vCPU, 16 GiB | 2 vCPU, 16 GiB | 4 |
| 5,000 users | 2 | 500m vCPU, 24 GiB | 4 vCPU, 24 GiB | 8 |
| 10,000 users | 2 | 500m vCPU, 24 GiB | 4 vCPU, 24 GiB | 8 |
| 25,000 users | 3 | 1 vCPU, 32 GiB | 8 vCPU, 32 GiB | 12 |
| 50,000 users | 3 | 1 vCPU, 32 GiB | 8 vCPU, 32 GiB | 16 |
Code indexer:
| Size | Pods | Request | Limit | Indexing lanes per pod | Ephemeral disk (request, limit) | /tmp size | Archive slots per Gitaly storage |
|---|---|---|---|---|---|---|---|
| 2,000 users | 1 | 2 vCPU, 10 GiB | 4 vCPU, 10 GiB | 2 | 20 GiB, 30 GiB | 26 GiB | 1 |
| 3,000 users | 1 to 3 | 3 vCPU, 16 GiB | 6 vCPU, 16 GiB | 3 | 20 GiB, 30 GiB | 26 GiB | 1 |
| 5,000 users | 1 to 3 | 4 vCPU, 20 GiB | 8 vCPU, 20 GiB | 4 | 30 GiB, 60 GiB | 54 GiB | 2 |
| 10,000 users | 1 to 3 | 6 vCPU, 26 GiB | 12 vCPU, 26 GiB | 5 | 30 GiB, 60 GiB | 54 GiB | 4 |
| 25,000 users | 2 to 3 | 6 vCPU, 34 GiB | 12 vCPU, 34 GiB | 6 | 50 GiB, 65 GiB | 59 GiB | 8 |
| 50,000 users | 2 to 3 | 8 vCPU, 40 GiB | 16 vCPU, 40 GiB | 8 | 65 GiB, 65 GiB | 59 GiB | 16 |
Each indexing lane needs about 2 vCPU and 5 GiB of memory. Keep the CPU limit below the usable vCPU of one node.
Configure the pools
The chart defaults do not match these tables.
To configure the pools, set the values in orbit-values.yaml, then upgrade the chart.
For example, for 10,000 users:
webserver:
replicas: 2
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: "2"
memory: 6Gi
indexer:
pools:
sdlc:
replicas: 2
resources:
requests:
cpu: 500m
memory: 24Gi
limits:
cpu: "4"
memory: 24Gi
engine:
max_concurrent_workers: 8
concurrency_groups:
sdlc: 8
code:
replicas: 3
tmpSizeLimit: 54Gi
resources:
requests:
cpu: "6"
memory: 26Gi
ephemeral-storage: 30Gi
limits:
cpu: "12"
memory: 26Gi
ephemeral-storage: 60Gi
engine:
handlers:
code-indexing-task:
pipeline:
archive_storage_budget: 4
archive_tenant_budget: 4- For the SDLC indexer, set
max_concurrent_workersandconcurrency_groups.sdlcto the same value. - Keep
tmpSizeLimitbelow the ephemeral storage limit, because logs and the container layer use the same disk. - Without autoscaling, set the code indexer
replicasto the maximum of the pod range. - Before you turn on indexing for groups, apply the rules in Limit the archive load on Gitaly to
archive_storage_budgetandarchive_tenant_budget. - To put the pods on the node pool, set
nodeSelectorandtolerationsfor the webserver, the dispatcher, the health check, and each indexer pool.
Autoscale the code indexer
Prerequisites:
- The KEDA operator runs in the cluster.
To autoscale the code indexer on the count of pending code indexing tasks:
Add these values to
orbit-values.yaml:healthCheck: enabled: true indexer: pools: code: keda: enabled: true minReplicaCount: <code_min_pods> maxReplicaCount: <code_max_pods>Replace the following values:
<code_min_pods>: the minimum of the code indexer pod range in Pods.<code_max_pods>: the maximum of the code indexer pod range. The chart default is 4.
Upgrade the chart.
ClickHouse
The sizes are for the ClickHouse instance that holds the GitLab Orbit data lake and graph. If GitLab Orbit shares the ClickHouse instance of GitLab, use these sizes for the shared instance. They are larger than the sizes for GitLab ClickHouse features, because the data lake holds a copy of the GitLab database.
| Size | Deployment | ClickHouse nodes | ClickHouse Keeper nodes |
|---|---|---|---|
| 2,000 users | ClickHouse Cloud or 1 node | 1, 8 vCPU, 32 GB memory | None |
| 3,000 users | ClickHouse Cloud or 1 node | 1, 8 vCPU, 32 GB memory | None |
| 5,000 users | ClickHouse Cloud or 1 node | 1, 16 vCPU, 64 GB memory | None |
| 10,000 users | ClickHouse Cloud or 1 node, with optional high availability | 1, 16 vCPU, 64 GB memory | 3, 2 vCPU, 8 GB memory, for high availability |
| 25,000 users | ClickHouse Cloud, 1 node, or high availability | 1, 32 vCPU, 128 GB memory, or 3, 16 vCPU, 64 GB memory | 3, 2 vCPU, 8 GB memory, for high availability |
| 50,000 users | ClickHouse Cloud or high availability | 3, 16 vCPU, 64 GB memory | 3, 2 vCPU, 8 GB memory |
| Node size | GCP example | AWS example | Azure example |
|---|---|---|---|
| 2 vCPU, 8 GB memory | c4a-standard-2 | m8g.large | Standard_D2ps_v6 |
| 8 vCPU, 32 GB memory | c4a-standard-8 | m8g.2xlarge | Standard_D8ps_v6 |
| 16 vCPU, 64 GB memory | c4a-standard-16 | m8g.4xlarge | Standard_D16ps_v6 |
| 32 vCPU, 128 GB memory | c4a-standard-32 | m8g.8xlarge | Standard_D32ps_v6 |
The examples are Arm machine types. x86 machine types of the same size are also supported.
For the high availability setup and its load balancer, see High availability. On ClickHouse Cloud, choose a service with at least the memory of one node in the table.
If you run ClickHouse on VMs:
Give each node this much disk:
disk = 2 x (GitLab PostgreSQL database size + 1 MiB x indexed projects)The factor of 2 keeps free space for merges.
Set the memory and external sort settings in Memory and disk. Without them, large sorts fail instead of spilling to disk. The example there is for 32 GiB of memory. For other sizes, set
max_memory_usageto about 62% of the memory, and the two external limits to 25%. On a 64 GiB node, use 42949672960 formax_memory_usage, and 17179869184 for the external limits.Set a TTL on the system log tables, as in System log retention.
NATS
| Size | Replicas | Request per pod | Limit per pod | Minimum volume per replica |
|---|---|---|---|---|
| 2,000 users | 1 | 250m vCPU, 512 MiB | 1 vCPU, 1 GiB | 20 GiB |
| 3,000 users | 1 | 250m vCPU, 512 MiB | 1 vCPU, 1 GiB | 30 GiB |
| 5,000 users | 1 | 500m vCPU, 1 GiB | 2 vCPU, 2 GiB | 100 GiB |
| 10,000 users | 1 | 500m vCPU, 1 GiB | 2 vCPU, 2 GiB | 200 GiB |
| 25,000 users | 3 | 1 vCPU, 2 GiB | 2 vCPU, 4 GiB | 400 GiB |
| 50,000 users | 3 | 1 vCPU, 2 GiB | 4 vCPU, 8 GiB | 500 GiB |
For fewer than 25,000 users, run NATS as one node.
One node has no redundancy.
If you lose its volume, Siphon needs a fresh snapshot, and GitLab Orbit can stop with Error: ontology archive vNN is missing.
For 25,000 users or more, run NATS as a 3-node JetStream cluster. For the steps, see Configure a NATS cluster.
Calculate the volume with this formula. Use the larger of the result and the table value.
volume = retention_seconds x peak_changes_per_second x 300 bytesretention_secondsis the Siphonmax_age_seconds. The example in Install Siphon keeps 15 days, which is 1296000 seconds.peak_changes_per_secondis the PostgreSQL change rate at peak. To measure it, run this query twice during peak, then divide the difference by the seconds between the two runs:SELECT sum(n_tup_ins + n_tup_upd + n_tup_del) FROM pg_stat_user_tables;
Configure a NATS cluster
Prerequisites:
- Three nodes in the NATS and Siphon node pool.
To run NATS as a 3-node JetStream cluster:
Add the cluster settings to the NATS chart values, then upgrade the NATS release:
config: cluster: enabled: true replicas: 3Set
nats_config.replicas: 3insiphon-values.yaml, as in Install Siphon, then upgrade the Siphon release.
The cluster protects the Siphon stream only.
GitLab Orbit creates its own streams and key-value buckets, including the schema archive catalog, with one replica.
If you lose the node that holds them, GitLab Orbit can stop with Error: ontology archive vNN is missing.
Siphon
Each Siphon deployment runs one pod.
| Size | Memory request | Memory limit |
|---|---|---|
| 2,000 users | 256 MiB | 2 GiB |
| 3,000 users | 256 MiB | 2 GiB |
| 5,000 users | 512 MiB | 4 GiB |
| 10,000 users | 1 GiB | 6 GiB |
| 25,000 users | 1 GiB | 8 GiB |
| 50,000 users | 2 GiB | 10 GiB |
For CPU, give each pod a request of 250m vCPU and a limit of 2 vCPU.
PostgreSQL disk for the replication slot
Siphon reads changes through a PostgreSQL logical replication slot. While Siphon is stopped, the slot holds write-ahead log (WAL) on the GitLab PostgreSQL disk.
Keep enough free disk for the longest Siphon outage that you accept, and at least 24 hours:
free disk = WAL bytes per hour x outage hoursTo measure the WAL rate:
Run this query, and note the result:
SELECT pg_current_wal_lsn();One hour later, run the same query again.
Calculate the WAL bytes for the hour:
SELECT pg_wal_lsn_diff('<second_lsn>', '<first_lsn>');Replace the following values:
<first_lsn>: the result of the first query.<second_lsn>: the result of the second query.
To protect the disk, you can set max_slot_wal_keep_size in PostgreSQL to less than the free disk.
When a slot exceeds this limit, PostgreSQL invalidates it, and Siphon needs a fresh snapshot.
max_slot_wal_keep_size applies to every replication slot on the server, including the slots of standby replicas and GitLab Geo.
Set it larger than the lag that your standby replicas can reach.
For more information, see Verify replication.
Adjust for your workload
The first code backfill puts the most load on GitLab Orbit and on Gitaly. Estimate its length, and limit its load on Gitaly before you turn on indexing for groups.
Estimate the backfill time
To estimate the time for the first code backfill, use this formula:
parallel_archives = min(archive slots x Gitaly storages, code indexer pods x indexing lanes per pod)
backfill_hours = indexed projects x 30 seconds / parallel_archives / 3600For example, 100,000 projects with 4 parallel archives take about 9 days. The formula assumes that the repositories are spread evenly across the storages. If one storage holds most of them, that storage sets the pace. Large repositories take longer.
The smaller term in parallel_archives sets the pace:
- If archive slots x Gitaly storages is smaller, the archive slots set the pace. Add slots only as the rules in Limit the archive load on Gitaly allow. More code indexer pods do not help.
- If code indexer pods x indexing lanes per pod is smaller, add code indexer pods.
Limit the archive load on Gitaly
Gitaly builds an archive of every repository for the code backfill. The load goes to the Gitaly node that holds the repositories.
Before you turn on indexing for groups:
- Turn on the GitLab rate limits.
- Balance your repositories across your Gitaly nodes as evenly as you can.
- Find the vCPU count of the Gitaly node that holds the most repositories. For Gitaly Cluster (Praefect), use the smallest node in the cluster.
- Calculate the archive slots: 1 for each 4 vCPU of that node, up to 16. If the node has more than one storage, divide by the number of storages on the node.
- Set
archive_storage_budgetandarchive_tenant_budgetto the smaller of this value and the archive slots in Pods, then upgrade the chart.
archive_storage_budget limits the archive downloads in flight for each Gitaly storage, across all code indexer pods.
archive_tenant_budget is the same limit for projects whose Gitaly storage GitLab Orbit cannot find.
During the backfill:
- Turn on indexing for a few groups at a time.
- Monitor the CPU of the Gitaly node with the most repositories.
If its CPU is more than 70%, or its 1-minute load average is more than its vCPU count, pause code indexing until the load drops.
To pause it, set
indexer.pools.code.replicas: 0and upgrade the chart, or with autoscaling, set theautoscaling.keda.sh/paused-replicas: "0"annotation on the code indexerScaledObject. - Monitor the p95 of
grpc_server_handling_secondson Gitaly forGetArchive, which is the backfill load.PackObjectsHookWithSidechannel,PostUploadPackWithSidechannel, andFindCommitshow the effect on your users. Gitaly exports these histograms only when you setgrpc_latency_bucketsin its Prometheus configuration.
Monitor and scale
After the first backfill, monitor these signals and adjust the values. For the metrics and dashboards, see Monitor GitLab Orbit.
| Signal | Action |
|---|---|
| OOM kills on any GitLab Orbit pod | Increase the memory request and limit of that pod. |
| CPU throttling on the code or SDLC indexer | Increase the CPU limit, or add pods. |
code_pending in the health check grows and does not decrease | Use the smaller term in Estimate the backfill time to choose between archive slots and pods. |
gkg_indexer_sdlc_watermark_lag_seconds grows | Increase the SDLC indexer CPU limit or workers. |
| Gitaly node CPU is more than 70% during backfill | Decrease the archive slots, or pause code indexing. |
| NATS volume is more than 80% full | Increase the volume, or decrease the Siphon max_age_seconds. |
| ClickHouse disk is more than 50% full | Increase the disk, so merges have space. |
| Free disk on PostgreSQL is less than one day of WAL | Find why Siphon is behind, or increase the disk. |
| Usage is far below the request for 30 days | Decrease the request. |