mirror of
https://github.com/neondatabase/neon.git
synced 2026-08-12 09:19:39 +00:00
3d1b08496a
## Problem Found typos while reading the docs ## Summary of changes Fixed the typos found
237 lines
8.0 KiB
Markdown
237 lines
8.0 KiB
Markdown
# Supporting custom user Extensions (Dynamic Extension Loading)
|
||
Created 2023-05-03
|
||
|
||
## Motivation
|
||
|
||
There are many extensions in the PostgreSQL ecosystem, and not all extensions
|
||
are of a quality that we can confidently support them. Additionally, our
|
||
current extension inclusion mechanism has several problems because we build all
|
||
extensions into the primary Compute image: We build the extensions every time
|
||
we build the compute image regardless of whether we actually need to rebuild
|
||
the image, and the inclusion of these extensions in the image adds a hard
|
||
dependency on all supported extensions - thus increasing the image size, and
|
||
with it the time it takes to download that image - increasing first start
|
||
latency.
|
||
|
||
This RFC proposes a dynamic loading mechanism that solves most of these
|
||
problems.
|
||
|
||
## Summary
|
||
|
||
`compute_ctl` is made responsible for loading extensions on-demand into
|
||
the container's file system for dynamically loaded extensions, and will also
|
||
make sure that the extensions in `shared_preload_libraries` are downloaded
|
||
before the compute node starts.
|
||
|
||
## Components
|
||
|
||
compute_ctl, PostgreSQL, neon (extension), Compute Host Node, Extension Store
|
||
|
||
## Requirements
|
||
|
||
Compute nodes with no extra extensions should not be negatively impacted by
|
||
the existence of support for many extensions.
|
||
|
||
Installing an extension into PostgreSQL should be easy.
|
||
|
||
Non-preloaded extensions shouldn't impact startup latency.
|
||
|
||
Uninstalled extensions shouldn't impact query latency.
|
||
|
||
A small latency penalty for dynamically loaded extensions is acceptable in
|
||
the first seconds of compute startup, but not in steady-state operations.
|
||
|
||
## Proposed implementation
|
||
|
||
### On-demand, JIT-loading of extensions
|
||
|
||
Before postgres starts we download
|
||
- control files for all extensions available to that compute node;
|
||
- all `shared_preload_libraries`;
|
||
|
||
After postgres is running, `compute_ctl` listens for requests to load files.
|
||
When PostgreSQL requests a file, `compute_ctl` downloads it.
|
||
|
||
PostgreSQL requests files in the following cases:
|
||
- When loading a preload library set in `local_preload_libraries`
|
||
- When explicitly loading a library with `LOAD`
|
||
- When creating extension with `CREATE EXTENSION` (download sql scripts, (optional) extension data files and (optional) library files)))
|
||
|
||
|
||
#### Summary
|
||
|
||
Pros:
|
||
- Startup is only as slow as it takes to load all (shared_)preload_libraries
|
||
- Supports BYO Extension
|
||
|
||
Cons:
|
||
- O(sizeof(extensions)) IO requirement for loading all extensions.
|
||
|
||
### Alternative solutions
|
||
|
||
1. Allow users to add their extensions to the base image
|
||
|
||
Pros:
|
||
- Easy to deploy
|
||
|
||
Cons:
|
||
- Doesn't scale - first start size is dependent on image size;
|
||
- All extensions are shared across all users: It doesn't allow users to
|
||
bring their own restrictive-licensed extensions
|
||
|
||
2. Bring Your Own compute image
|
||
|
||
Pros:
|
||
- Still easy to deploy
|
||
- User can bring own patched version of PostgreSQL
|
||
|
||
Cons:
|
||
- First start latency is O(sizeof(extensions image))
|
||
- Warm instance pool for skipping pod schedule latency is not feasible with
|
||
O(n) custom images
|
||
- Support channels are difficult to manage
|
||
|
||
3. Download all user extensions in bulk on compute start
|
||
|
||
Pros:
|
||
- Easy to deploy
|
||
- No startup latency issues for "clean" users.
|
||
- Warm instance pool for skipping pod schedule latency is possible
|
||
|
||
Cons:
|
||
- Downloading all extensions in advance takes a lot of time, thus startup
|
||
latency issues
|
||
|
||
4. Store user's extensions in persistent storage
|
||
|
||
Pros:
|
||
- Easy to deploy
|
||
- No startup latency issues
|
||
- Warm instance pool for skipping pod schedule latency is possible
|
||
|
||
Cons:
|
||
- EC2 instances have only limited number of attachments shared between EBS
|
||
volumes, direct-attached NVMe drives, and ENIs.
|
||
- Compute instance migration isn't trivially solved for EBS mounts (e.g.
|
||
the device is unavailable whilst moving the mount between instances).
|
||
- EBS can only mount on one instance at a time (except the expensive IO2
|
||
device type).
|
||
|
||
5. Store user's extensions in network drive
|
||
|
||
Pros:
|
||
- Easy to deploy
|
||
- Few startup latency issues
|
||
- Warm instance pool for skipping pod schedule latency is possible
|
||
|
||
Cons:
|
||
- We'd need networked drives, and a lot of them, which would store many
|
||
duplicate extensions.
|
||
- **UNCHECKED:** Compute instance migration may not work nicely with
|
||
networked IOs
|
||
|
||
|
||
### Idea extensions
|
||
|
||
The extension store does not have to be S3 directly, but could be a Node-local
|
||
caching service on top of S3. This would reduce the load on the network for
|
||
popular extensions.
|
||
|
||
## Extension Storage implementation
|
||
|
||
The layout of the S3 bucket is as follows:
|
||
```
|
||
5615610098 // this is an extension build number
|
||
├── v14
|
||
│ ├── extensions
|
||
│ │ ├── anon.tar.zst
|
||
│ │ └── embedding.tar.zst
|
||
│ └── ext_index.json
|
||
└── v15
|
||
├── extensions
|
||
│ ├── anon.tar.zst
|
||
│ └── embedding.tar.zst
|
||
└── ext_index.json
|
||
5615261079
|
||
├── v14
|
||
│ ├── extensions
|
||
│ │ └── anon.tar.zst
|
||
│ └── ext_index.json
|
||
└── v15
|
||
├── extensions
|
||
│ └── anon.tar.zst
|
||
└── ext_index.json
|
||
5623261088
|
||
├── v14
|
||
│ ├── extensions
|
||
│ │ └── embedding.tar.zst
|
||
│ └── ext_index.json
|
||
└── v15
|
||
├── extensions
|
||
│ └── embedding.tar.zst
|
||
└── ext_index.json
|
||
```
|
||
|
||
Note that build number cannot be part of prefix because we might need extensions
|
||
from other build numbers.
|
||
|
||
`ext_index.json` stores the control files and location of extension archives.
|
||
It also stores a list of public extensions and a library_index
|
||
|
||
We don't need to duplicate `extension.tar.zst`` files.
|
||
We only need to upload a new one if it is updated.
|
||
(Although currently we just upload every time anyways, hopefully will change
|
||
this sometime)
|
||
|
||
*access* is controlled by spec
|
||
|
||
More specifically, here is an example ext_index.json
|
||
```
|
||
{
|
||
"public_extensions": [
|
||
"anon",
|
||
"pg_buffercache"
|
||
],
|
||
"library_index": {
|
||
"anon": "anon",
|
||
"pg_buffercache": "pg_buffercache"
|
||
// for more complex extensions like postgis
|
||
// we might have something like:
|
||
// address_standardizer: postgis
|
||
// postgis_tiger: postgis
|
||
},
|
||
"extension_data": {
|
||
"pg_buffercache": {
|
||
"control_data": {
|
||
"pg_buffercache.control": "# pg_buffercache extension \ncomment = 'examine the shared buffer cache' \ndefault_version = '1.3' \nmodule_pathname = '$libdir/pg_buffercache' \nrelocatable = true \ntrusted=true"
|
||
},
|
||
"archive_path": "5670669815/v14/extensions/pg_buffercache.tar.zst"
|
||
},
|
||
"anon": {
|
||
"control_data": {
|
||
"anon.control": "# PostgreSQL Anonymizer (anon) extension \ncomment = 'Data anonymization tools' \ndefault_version = '1.1.0' \ndirectory='extension/anon' \nrelocatable = false \nrequires = 'pgcrypto' \nsuperuser = false \nmodule_pathname = '$libdir/anon' \ntrusted = true \n"
|
||
},
|
||
"archive_path": "5670669815/v14/extensions/anon.tar.zst"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### How to add new extension to the Extension Storage?
|
||
|
||
Simply upload build artifacts to the S3 bucket.
|
||
Implement a CI step for that. Splitting it from compute-node-image build.
|
||
|
||
### How do we deal with extension versions and updates?
|
||
|
||
Currently, we rebuild extensions on every compute-node-image build and store them in the <build-version> prefix.
|
||
This is needed to ensure that `/share` and `/lib` files are in sync.
|
||
|
||
For extension updates, we rely on the PostgreSQL extension versioning mechanism (sql update scripts) and extension authors to not break backwards compatibility within one major version of PostgreSQL.
|
||
|
||
### Alternatives
|
||
|
||
For extensions written on trusted languages we can also adopt
|
||
`dbdev` PostgreSQL Package Manager based on `pg_tle` by Supabase.
|
||
This will increase the amount supported extensions and decrease the amount of work required to support them.
|