Skip to content

Commit e297f66

Browse files
committed
save
1 parent 386cc87 commit e297f66

9 files changed

Lines changed: 387 additions & 203 deletions

File tree

docs/features/sharding/.pages

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ nav:
33
- 'basics.md'
44
- 'query-routing.md'
55
- 'manual-routing.md'
6-
- 'cross-shard.md'
6+
- 'cross-shard-queries'
77
- 'sharding-functions.md'
8-
- 'copy.md'
98
- '...'
109
- 'resharding'
1110
- 'internals'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
nav:
2+
- 'index.md'
3+
- 'select.md'
4+
- 'insert.md'
5+
- 'update.md'
6+
- 'ddl.md'
7+
- '...'

docs/features/sharding/copy.md renamed to docs/features/sharding/cross-shard-queries/copy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
icon: material/upload
33
---
4-
# COPY command
4+
# COPY
55

66
`COPY` is a special PostgreSQL command that ingests a file directly into a specified database table. This allows for writing data faster than by using individual `INSERT` queries.
77

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
icon: material/table-cog
3+
---
4+
5+
# CREATE, ALTER, DROP
6+
7+
`CREATE`, `ALTER` and `DROP`, also known as **D**ata **D**efinition **L**anguage (DDL), are by design, cross-shard statements. When a client sends over a DDL command, PgDog will send it to all shards in parallel, ensuring the table, index, view and sequence definitions are identical across the database cluster.
8+
9+
## Atomicity
10+
11+
DDL statements should be atomic across all shards. This is to protect against a single shard failure to create a table or index, which could result in an inconsistent schema. PgDog can use [two-phase commit](2pc.md) to ensure this is the case, however that means that all DDL statements must be executed inside a transaction, for example:
12+
13+
```postgresql
14+
BEGIN;
15+
CREATE TABLE users (
16+
id BIGSERIAL PRIMARY KEY,
17+
email VARCHAR NOT NULL,
18+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
19+
);
20+
COMMIT;
21+
```
22+
23+
## Idempotency
24+
25+
Some statements, like `CREATE INDEX CONCURRENTLY`, cannot run inside transactions. To make sure these are safely executed, you have two options: use [manual routing](manual-routing.md) and execute it on each shard individually, or write idempotent schema migrations, for example:
26+
27+
```postgresql
28+
DROP INDEX IF EXISTS user_id_idx;
29+
CREATE INDEX CONCURRENTLY user_id_idx ON users USING btree(user_id);
30+
```
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
icon: material/multicast
3+
---
4+
5+
# Cross-shard queries overview
6+
7+
If a client can't or doesn't specify a sharding key in the query, PgDog will send that query to all shards in parallel, and combine the results automatically. To the client, this looks like the query was executed by a single database.
8+
9+
<center style="margin-top: 2rem;">
10+
<img src="/images/cross-shard.png" width="95%" alt="Cross-shard queries" />
11+
</center>
12+
13+
## How it works
14+
15+
PgDog understands the Postgres protocol and query language. It can connect to multiple database servers, send the query to all of them, and collect [`DataRow`](#under-the-hood) messages as they are returned by each connection.
16+
17+
Once all servers finish executing the request, PgDog processes the result, performs any requested sorting, aggregation or row disambiguation, and sends the complete result back to the client, as if all rows came from one database server.
18+
19+
Just like with [direct-to-shard](../query-routing.md) queries, each SQL command is handled differently, as documented below:
20+
21+
- [`SELECT`](select.md)
22+
- [`INSERT`](insert.md)
23+
- [`UPDATE`, `DELETE`](update.md)
24+
- [`CREATE`, `ALTER`, `DROP`](ddl.md)(and other DDL statements)
25+
- [`COPY`](copy.md)
26+
27+
28+
## Under the hood
29+
30+
PgDog implements the PostgreSQL wire protocol, which is well documented and stable. The messages sent by Postgres clients and servers contain all the necessary information about data types, column names and executed statements, which PgDog can use to present multi-database results as a single stream of data.
31+
32+
The following protocol messages are especially relevant:
33+
34+
| Message | Description |
35+
|-|-|
36+
| `DataRow` | Each `DataRow` message contains one tuple, for each row returned by the query. |
37+
| `RowDescription` | This message has the column names and data types returned by the query. |
38+
| `CommandComplete` | Indicates that the query has finished returning results. PgDog uses it to start sorting and aggregation. |
39+
40+
The protocol has two formats for encoding tuples: text and binary. Text format is equivalent to calling the `to_string()` method on native types, while binary encoding sends them in network-byte order. For example:
41+
42+
=== "Data"
43+
```postgresql
44+
SELECT 1::bigint, 2::integer, 'three'::VARCHAR;
45+
```
46+
=== "Encoding"
47+
| Data type | Text | Binary |
48+
|-|-|-|
49+
| `BIGINT` | `"1"` | `00 00 00 00 00 00 00 01` |
50+
| `INTEGER` | `"2"` | `00 00 00 02` |
51+
| `VARCHAR` | `"three"` | `three` |
52+
53+
Since PgDog needs to process rows before sending them to the client, we implemented parsing both formats for [most data types](select.md#supported-data-types).
54+
55+
56+
## Disabling cross-shard queries
57+
58+
If you don't want PgDog to route cross-shard queries, e.g., because you have a [multitenant](../../multi-tenancy.md) system with no interdependencies, cross-shard queries can be disabled with a configuration setting:
59+
60+
```toml
61+
[general]
62+
cross_shard_disabled = true
63+
```
64+
65+
If this feature is enabled, and a query doesn't have a sharding key, instead of executing the query, PgDog will return an error and abort the transaction.
66+
67+
## Read more
68+
69+
- [Sharding functions](../sharding-functions.md)
70+
- [Cross-shard `SELECT`](select.md)
71+
- [Cross-shard `INSERT`](insert.md)
72+
- [Cross-shard `UPDATE` and `DELETE`](update.md)
73+
- [DDL, e.g., `CREATE TABLE`](ddl.md)
74+
- [`COPY` command](copy.md)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
icon: material/table-plus
3+
---
4+
5+
# Cross-shard INSERT
6+
7+
If the `INSERT` statement specifies only one sharding key, it's [routed directly](../query-routing.md#insert) to one of the shards. Otherwise, it becomes a cross-shard `INSERT` statement.
8+
9+
## How it works
10+
11+
Cross-shard `INSERT` statements fall into one of three categories, each one handled differently:
12+
13+
1. `INSERT` targetting [omnisharded tables](#omnisharded-tables)
14+
2. `INSERT` targetting a [sharded table](#sharded-insert), with no sharding key specified
15+
3. `INSERT` with [multiple tuples](#multiple-tuples), each destined for a different shard
16+
17+
By design, applications using PgDog don't need to concern themselves with this and can use the database normally. However, there are some trade-offs when using cross-shard queries, documented below.
18+
19+
## Omnisharded tables
20+
21+
Queries that target [omnisharded](../omnishards.md) tables, the statement is sent to all shards concurrently. This ensures that the data is identical on all shards, for example:
22+
23+
```postgresql
24+
INSERT INTO request_logs
25+
(client_ip, request_path, response_code, created_at)
26+
VALUES
27+
($1, $2, $3, $4)
28+
```
29+
30+
A row will be created on each shard. Each shard can then use joins to fetch this data with [direct-to-shard](../query-routing.md#select) queries.
31+
32+
### Consistency
33+
34+
Unless [two-phase commit](../2pc.md) is enabled, inserts into omnisharded tables are not guaranteed to be atomic. It is possible for the statement to succeed on some of the shards and not others. If you don't want to or can't enable 2pc, consider sending cross-shard inserts inside a transaction:
35+
36+
```postgresql
37+
BEGIN;
38+
INSERT INTO request_logs
39+
(client_ip, request_path, response_code, created_at)
40+
VALUES
41+
($1, $2, $3, $4);
42+
-- You will receive an ack or an error from all shards here.
43+
COMMIT;
44+
```
45+
46+
This gives you a much higher chance of recording rows on all shards, since you will know if your statement violated some contstraint (e.g., unique index or `NOT NULL` check) before committing the transaction.
47+
48+
### Primary keys
49+
50+
It's common practice to use `BIGSERIAL` columns as primary keys. These are powered by a sequence to ensure non-recurring values are automatically generated for each new row.
51+
52+
Sharded databases can't use sequences directly because they are not aware of other shards and will produce duplicate values across databases. To circumvent this, PgDog provides a way to generate [unique integers](../unique-ids.md) in the proxy using a distributed and shard-aware algorithm.
53+
54+
To use unique IDs as primary keys (or in any other column) in omnisharded tables, you can call the `pgdog.unique_id()` function in the `VALUES` clause. For example:
55+
56+
```postgresql
57+
INSERT INTO ip_logs
58+
(id, client_ip, created_at)
59+
VALUES
60+
(pgdog.unqiue_id(), $1, now());
61+
```
62+
63+
The function is evaluated inside PgDog which places the value it returns directly into the query. This works for all queries, including prepared statements.
64+
65+
Each call to `pgdog.unique_id()` generates a unique value, so it's possible to use it multiple times inside the same query and get different numbers, for example:
66+
67+
=== "Query"
68+
```postgresql
69+
SELECT
70+
pgdog.unique_id() AS one,
71+
pgdog.unique_id() AS two;
72+
```
73+
=== "Result"
74+
```
75+
one | two
76+
-------------------+-------------------
77+
12014338348945408 | 12014338348945409
78+
(1 row)
79+
```
80+
81+
This function can be used with any tables, not just omnisharded ones, or independently of any tables at all.
82+
83+
Statements that target sharded tables but don't specify the sharding key are sent to one of the shards only. Which shard receives the statement is controlled by the round robin load balancing algorithm, ensuring all shards serve an equal number of statements.
84+
85+
Finally, if the `INSERT` statement has more than one tuple, the statement is automatically rewritten to create one row at a time, and each row is sent to the matching shard.
86+
87+
## Sharded tables
88+
89+
If the `INSERT` is creating a row in a sharded table, but the primary key is [database-generated](schema_management/primary_keys.md) _and_ used for sharding that table, the statement is sent to only one of the shards, using the round robin algorithm.
90+
91+
For example:
92+
93+
```postgresql
94+
INSERT INTO users (id, email) VALUES (DEFAULT, 'test@acme.com') RETURNING *;
95+
```
96+
97+
Instead of creating one user per shard, which would cause duplicate entries, PgDog will let the database generate a _globally_ unique primary key and place it on one of the shards only. This ensures even data distribution across the entire database cluster.
98+
99+
## Multiple tuples

0 commit comments

Comments
 (0)