Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions apps/docs/content/postgresql/how-to/connect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ A few things to know:
- **TLS is required** on `6432` (see [above](#connection-ports-and-tls)), even for internal connections.
- **HA mode.** pgBouncer pools connections to the primary (writes). Read routing across replicas on port `5433` is separate and is not pooled.

## Connection limits

`max_connections` follows the service's RAM and [workload type](/postgresql/how-to/scale#workload-types): 50 per GiB (between 20 and 500) for OLTP and WriteHeavy, 25 per GiB (between 10 and 200) for OLAP. RAM counts in [memory steps](/postgresql/how-to/scale#how-postgresql-scaling-works), so a service with 7 GB of RAM still gets the `4 GiB` limits. pgBouncer accepts `20 × (max_connections − 4)` clients, at least 100.

<table className="w-full my-1.5">
<thead>
<tr>
<th className="w-fit" rowSpan={2} style={{ minWidth: '105px' }}>Memory step</th>
<th className="w-fit" colSpan={2}><code>max_connections</code> (<code>5432</code>, <code>5433</code>)</th>
<th className="w-fit" colSpan={2}>pgBouncer clients (<code>6432</code>)</th>
</tr>
<tr>
<th className="w-fit">OLTP / WriteHeavy</th>
<th className="w-fit">OLAP</th>
<th className="w-fit">OLTP / WriteHeavy</th>
<th className="w-fit">OLAP</th>
</tr>
</thead>
<tbody>
<tr><td className="w-fit"><code>256 MiB</code></td><td className="w-fit">20</td><td className="w-fit">10</td><td className="w-fit">320</td><td className="w-fit">120</td></tr>
<tr><td className="w-fit"><code>512 MiB</code></td><td className="w-fit">25</td><td className="w-fit">12</td><td className="w-fit">420</td><td className="w-fit">160</td></tr>
<tr><td className="w-fit"><code>1 GiB</code></td><td className="w-fit">50</td><td className="w-fit">25</td><td className="w-fit">920</td><td className="w-fit">420</td></tr>
<tr><td className="w-fit"><code>2 GiB</code></td><td className="w-fit">100</td><td className="w-fit">50</td><td className="w-fit">1920</td><td className="w-fit">920</td></tr>
<tr><td className="w-fit"><code>4 GiB</code></td><td className="w-fit">200</td><td className="w-fit">100</td><td className="w-fit">3920</td><td className="w-fit">1920</td></tr>
<tr><td className="w-fit"><code>8 GiB</code></td><td className="w-fit">400</td><td className="w-fit">200</td><td className="w-fit">7920</td><td className="w-fit">3920</td></tr>
<tr><td className="w-fit"><code>16 GiB</code> and up</td><td className="w-fit">500</td><td className="w-fit">200</td><td className="w-fit">9920</td><td className="w-fit">3920</td></tr>
</tbody>
</table>

- **Not all of `max_connections` is yours.** 3 connections are reserved for superusers, and Zerops itself uses up to 4 (monitoring, health checks, backups).
- **pgBouncer's server-side pool is much smaller than its client limit.** Each user/database pair gets up to `2 × vCPU + 1` server connections (the database's CPU cores), and each database at most a third of `max_connections`. Transactions beyond that wait in a queue instead of failing.
- **The limits cannot be overridden.** If you need more connections, connect through `6432`, or raise the minimum RAM to the next memory step.
- **HA mode.** The limits apply per node. Port `5433` balances across both replicas, so reads get twice the `max_connections`. Each of the two proxies runs its own pgBouncer with the full client limit, but both pool into the same primary: with several busy databases they can still exhaust its `max_connections`.
- **Idle connections in HA mode.** The proxies close connections on `5432` and `5433` that stay idle for 60 minutes.

## Connect from services in the same project

All services in a project share a private network, so other services reach PostgreSQL directly by its hostname. There are two ways to wire it up.
Expand Down
61 changes: 59 additions & 2 deletions apps/docs/content/postgresql/how-to/scale.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ PostgreSQL services use **vertical scaling** to adjust CPU, RAM, and disk resour
:::danger Scaling can briefly interrupt the service
When scaling changes the service's resources, Zerops regenerates the PostgreSQL configuration and applies it with an automatic **reload**. If the new values require it, the service is **restarted** instead: rolling through the cluster in HA mode, a short outage in single mode.

A restart is only needed when the granted RAM crosses a memory step: `256 MiB`, `512 MiB`, `1 GiB`, `2 GiB`, `4 GiB`, then multiples of `8 GiB`. Scaling within a step reloads only; to rule out restarts entirely, keep `minRam` and `maxRam` within one step.
A restart is only needed when the granted RAM crosses a memory step: `256 MiB`, `512 MiB`, `1 GiB`, `2 GiB`, `4 GiB`, then multiples of `8 GiB`. Scaling within a step reloads only; to rule out restarts entirely, keep `minRam` and `maxRam` within one step. Crossing a step also changes the [connection limits](/postgresql/how-to/connect#connection-limits).
:::

## Scaling profiles
Expand Down Expand Up @@ -61,6 +61,63 @@ A profile name combines a **workload type** with a **tier**, e.g. `oltp-producti
</tbody>
</table>

The settings you are most likely to run into (RAM means the current [memory step](#how-postgresql-scaling-works)):

<ExpandableTable>
<table className="w-full my-1.5">
<thead>
<tr>
<th className="w-fit">Setting</th>
<th className="w-fit">OLTP</th>
<th className="w-fit">OLAP</th>
<th className="w-fit">WriteHeavy</th>
</tr>
</thead>
<tbody>
<tr>
<td className="w-fit"><code>max_connections</code></td>
<td className="w-fit" colSpan={3}>See <a href="/postgresql/how-to/connect#connection-limits">Connection limits</a></td>
</tr>
<tr>
<td className="w-fit"><code>shared_buffers</code></td>
<td className="w-fit">25% of RAM, max <code>8 GiB</code></td>
<td className="w-fit">25% of RAM, max <code>16 GiB</code></td>
<td className="w-fit">25% of RAM, max <code>8 GiB</code></td>
</tr>
<tr>
<td className="w-fit"><code>effective_cache_size</code></td>
<td className="w-fit">75% of RAM</td>
<td className="w-fit">75% of RAM</td>
<td className="w-fit">50% of RAM</td>
</tr>
<tr>
<td className="w-fit"><code>work_mem</code></td>
<td className="w-fit">RAM / (<code>max_connections</code> × 4): about <code>5 MiB</code> up to 8 GiB of RAM, <code>25 MiB</code> at 48 GiB</td>
<td className="w-fit">RAM / (<code>max_connections</code> × 2): about <code>20 MiB</code> up to 8 GiB of RAM, <code>123 MiB</code> at 48 GiB</td>
<td className="w-fit">RAM / (<code>max_connections</code> × 8), at least <code>4 MiB</code>: <code>4 MiB</code> up to 16 GiB of RAM, <code>12 MiB</code> at 48 GiB</td>
</tr>
<tr>
<td className="w-fit"><code>temp_file_limit</code></td>
<td className="w-fit" colSpan={3}>2 × RAM, max <code>48 GiB</code>. A query that needs more temporary disk space fails.</td>
</tr>
<tr>
<td className="w-fit"><code>idle_in_transaction_session_timeout</code></td>
<td className="w-fit">5 minutes</td>
<td className="w-fit">10 minutes</td>
<td className="w-fit">5 minutes</td>
</tr>
<tr>
<td className="w-fit"><code>jit</code></td>
<td className="w-fit">On from 4 GiB of RAM</td>
<td className="w-fit">On</td>
<td className="w-fit">Off</td>
</tr>
</tbody>
</table>
</ExpandableTable>

`work_mem` is sized so that every connection can run several sorts at once without exhausting RAM. If a few heavy queries need more, raise it just for them on any profile: `ALTER ROLE ... SET work_mem` for a dedicated role, or `SET LOCAL work_mem` inside a transaction.

### Available profiles

The tier part of the name sets the size of the autoscaling envelope (and, in HA, the replication topology). Which profiles you can pick depends on the deployment mode:
Expand Down Expand Up @@ -92,7 +149,7 @@ The tier part of the name sets the size of the autoscaling envelope (and, in HA,
<tr>
<td className="w-fit"><code>oltp-enterprise</code></td>
<td className="w-fit">HA only</td>
<td className="w-fit">High-throughput OLTP at scale. Highest connection limits and the most aggressive headroom.</td>
<td className="w-fit">High-throughput OLTP at scale. The largest resource envelope and the most aggressive headroom.</td>
</tr>
<tr>
<td className="w-fit"><code>olap-production</code></td>
Expand Down
5 changes: 3 additions & 2 deletions apps/docs/src/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function DropdownItem({ title, children, wrap = true }: DropdownItemProps

return (
<div className="py-0.5">
<div className="card bg-[#F2F5F7] dark:bg-medusa-bg-base shadow-card-rest dark:shadow-card-rest-dark w-full p-[5px] rounded-md">
<div className="card !overflow-visible bg-[#F2F5F7] dark:bg-medusa-bg-base shadow-card-rest dark:shadow-card-rest-dark w-full p-[5px] rounded-md">
<button
className="flex w-full p-[9px] justify-between items-center text-left bg-transparent border-none cursor-pointer"
onClick={() => setIsOpen(!isOpen)}
Expand All @@ -37,7 +37,8 @@ export function DropdownItem({ title, children, wrap = true }: DropdownItemProps
</button>
<div
ref={answerRef}
className={`overflow-hidden transition-all duration-500 ease-in-out`}
// Clipping only the vertical axis hides the collapsed content but lets an expanded code block extend sideways out of the card.
className="overflow-x-visible overflow-y-clip transition-all duration-500 ease-in-out"
style={{ height: height }}
role="region"
>
Expand Down
79 changes: 67 additions & 12 deletions apps/docs/src/css/_docusaurus.css
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,21 @@ details + p {
@apply relative mb-4 bg-medusa-bg-subtle dark:bg-medusa-bg-base hover:bg-medusa-bg-subtle-hover dark:hover:bg-medusa-bg-base-hover;
}

.col.toc-wrapper {
--ifm-col-width: 220px;
:root {
--doc-toc-width: 220px;
--code-block-actions-width: 68px;
}

@media (min-width: 1400px) {
.col.toc-wrapper {
--ifm-col-width: 300px;
:root {
--doc-toc-width: 300px;
}
}

.col.toc-wrapper {
--ifm-col-width: var(--doc-toc-width);
}

.markdown-doc-wrapper--fluid {
@apply max-w-[inherit];
}
Expand All @@ -92,19 +97,69 @@ details + p {
@apply text-medusa-code-text-subtle !pl-0;
}

/* The action buttons get a fixed-width gutter as a margin (not a percentage max-width), so it stays the same size when the block expands
and it counts towards the max-content width the expanded block sizes itself to. */
.prism-code {
@apply xs:max-w-[90%] text-code-body [&_*]:text-code-body xs:after:content-[''] xs:after:rounded xs:after:absolute;
@apply xs:after:right-0 xs:after:top-0 xs:after:w-[calc(10%+24px)] xs:after:h-full xs:after:bg-code-fade;
@apply text-code-body [&_*]:text-code-body xs:after:content-[''] xs:after:rounded xs:after:absolute;
@apply xs:after:right-0 xs:after:top-0 xs:after:h-full xs:after:bg-code-fade;
@apply xs:after:w-[calc(var(--code-block-actions-width)+24px)] xs:after:pointer-events-none;
}

/* The extra class in the selector is there to outrank the m-0 utility on the pre, which comes later in the stylesheet. */
@media (min-width: 576px) {
.theme-code-block .prism-code {
margin-right: var(--code-block-actions-width);
}
}

/* Touch devices never hover, so hiding the thumb there would leave their scroll indicator permanently invisible. */
@media (hover: hover) and (pointer: fine) {
.prism-code {
scrollbar-color: transparent transparent;
}

.theme-code-block:hover .prism-code,
.prism-code:focus-visible {
scrollbar-color: var(--ifm-scrollbar-thumb-background-color) transparent;
}
}

.prism-code:not(:hover)::-webkit-scrollbar-thumb,
.prism-code:not(:hover)::-webkit-scrollbar-track {
@apply xs:invisible;
/* A permanent scrollbar keeps the block height the same whether the code overflows (collapsed) or fits (expanded).
With the track transparent it only shows as a thumb, which is absent when there is nothing to scroll. */
.code-block-scrollable .prism-code {
overflow-x: scroll;
}

.prism-code:hover::-webkit-scrollbar-thumb,
.prism-code:hover::-webkit-scrollbar-track {
@apply xs:opacity-100;
/* Overflowing code blocks expand over the TOC column on hover. Needs a pointer that can hover and a TOC column to expand into. */
@media (hover: hover) and (pointer: fine) and (min-width: 997px) {
.row:has(> .toc-wrapper) .theme-code-block.code-block-scrollable:not(.reference-table *) {
position: relative;
width: max-content;
min-width: 100%;
max-width: 100%;
/* z-index is held until the collapse animation ends, otherwise the sticky TOC paints over the shrinking block.
The resting value has to be a number for that, because a change from auto cannot be delayed by a transition. */
z-index: 0;
transition: max-width 120ms ease-out, box-shadow 120ms ease-out, z-index 0s 120ms;
}

/* :active keeps the block open while a text selection drag leaves it. :focus-visible (not :focus-within) so a mouse click does not pin it. */
.row:has(> .toc-wrapper) .theme-code-block.code-block-scrollable:not(.reference-table *):is(:hover, :active, :has(:focus-visible)) {
/* Reaches the right edge of the TOC content: article and column paddings (32px) plus the column's auto margin (13.5px on the fixed-width
layout from 1419px up) plus the TOC column minus its own right padding (16px). Narrower viewports have a smaller margin, where the
rounded-up 30px ends inside the TOC column's right padding. */
max-width: calc(100% + var(--doc-toc-width) + 30px);
z-index: 99;
/* The delay stops blocks from popping open as they pass under the cursor while the page scrolls. */
transition: max-width 120ms ease-out 180ms, box-shadow 120ms ease-out 180ms, z-index 0s;
@apply shadow-flyout dark:shadow-flyout-dark;
}
}

@media (prefers-reduced-motion: reduce) {
.theme-code-block.code-block-scrollable {
transition-duration: 0s !important;
}
}

.prism-code {
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/theme/CodeBlock/Content/String.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export default function CodeBlockString({
as="div"
className={clsx(
blockClassName,
wordWrap.isCodeScrollable && 'code-block-scrollable',
language &&
!blockClassName.includes(`language-${language}`) &&
`language-${language}`
Expand Down
3 changes: 3 additions & 0 deletions zerops.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ zerops:
- yarn build
deployFiles:
- apps/docs/build/~
cache:
- ./node_modules/
- ./apps/docs/node_modules/
run:
base: static
initCommands:
Expand Down
Loading