|
2 | 2 | icon: material/table-edit |
3 | 3 | --- |
4 | 4 |
|
5 | | -# UPDATE |
| 5 | +# Cross-shard UPDATE and DELETE |
| 6 | + |
| 7 | +`UPDATE` and `DELETE` statements that provide none or more than one sharding key in the `WHERE` clause are cross-shard and will be sent to all shards concurrently. |
| 8 | + |
| 9 | +For example, assuming the `users` table is sharded on the `id` column, filtering rows by any other column produces a cross-shard query: |
| 10 | + |
| 11 | +```postgresql |
| 12 | +UPDATE users SET is_admin = true WHERE email LIKE '%@pgdog.dev'; |
| 13 | +``` |
| 14 | + |
| 15 | +## Consistency |
| 16 | + |
| 17 | +Much like cross-shard [`INSERT`](insert.md) statements, any updates to multiple rows on multiple databases outside a [two-phase](../2pc.md) transaction are not guaranteed to be atomic. It's always best to send updates inside a transaction, like so: |
| 18 | + |
| 19 | +```postgresql |
| 20 | +BEGIN; |
| 21 | +UPDATE orders SET delivered_at = now() WHERE delivered_at IS NULL; |
| 22 | +DELETE FROM orders WHERE user_id = $1; |
| 23 | +COMMIT; |
| 24 | +``` |
| 25 | + |
| 26 | +Both `DELETE` and `UPDATE` statements follow the same rules and work largely the same, with the notable exception of sharding key updates. |
| 27 | + |
| 28 | +## Sharding key updates |
| 29 | + |
| 30 | +When the sharding key of a row is updated, its sharded mapping is no longer valid. Queries searching for that row will be sent to the wrong shard, causing data loss. To avoid this, PgDog supports moving the row between shards automatically. |
| 31 | + |
| 32 | +### How it works |
| 33 | + |
| 34 | +Taking the same `users` table as an example (sharded on the `id` column), a statement mutating the sharding key will, frequently, take the following form: |
| 35 | + |
| 36 | +```postgresql |
| 37 | +UPDATE users SET id = $1 WHERE id = $2; |
| 38 | +``` |
| 39 | + |
| 40 | +When a client sends such a statement, PgDog will rewrite it into three statements and execute them automatically: |
| 41 | + |
| 42 | +1. `SELECT` statement to fetch the row from the database |
| 43 | +2. `INSERT` statement to create that row on the new shard, with updated values |
| 44 | +2. `DELETE` statement to remove the now stale row from the old shard |
| 45 | + |
| 46 | +The entire exchange looks like this: |
| 47 | + |
| 48 | +```postgresql |
| 49 | +-- Old `id` value. |
| 50 | +SELECT * FROM users WHERE id = $1; |
| 51 | +
|
| 52 | + -- New `id` value, with columns retrieved by the previous query. |
| 53 | +INSERT INTO users (id, email, created_at) |
| 54 | +VALUES ($1, $2, $3); |
| 55 | +
|
| 56 | + -- Old `id` value. |
| 57 | +DELETE FROM users WHERE id = $1; |
| 58 | +``` |
| 59 | + |
| 60 | +### Transaction required |
| 61 | + |
| 62 | +Since PgDog needs to execute multiple statements across different databases, a transaction is required to maintain data consistency (as per [MVCC](https://www.postgresql.org/docs/current/mvcc.html)). PgDog expects all sharding key updates to be executed inside a transaction, like so: |
| 63 | + |
| 64 | +```postgresql |
| 65 | +BEGIN; |
| 66 | +UPDATE users SET id = $1 WHERE id = $2 RETURNING *; |
| 67 | +COMMIT; |
| 68 | +``` |
| 69 | + |
| 70 | +If the application doesn't start a transaction, PgDog will return an error and abort the request. |
| 71 | + |
| 72 | +### Configuration |
| 73 | + |
| 74 | +This feature is **disabled** by default and can be enabled with configuration in [`pgdog.toml`](../../../configuration/pgdog.toml/rewrite.md): |
| 75 | + |
| 76 | +```toml |
| 77 | +[rewrite] |
| 78 | +enabled = true |
| 79 | +shard_key = "rewrite" |
| 80 | +``` |
| 81 | + |
| 82 | +### Updating multiple rows |
| 83 | + |
| 84 | +While multi-row updates are supported, changing multiple rows' sharding keys is not. If the `UPDATE` statement `WHERE` clause returns more than one row, PgDog will abort the transaction and return an error. |
| 85 | + |
| 86 | +This check happens early in the transaction and will not create partial updates to the database. |
| 87 | + |
| 88 | +### Efficiency |
| 89 | + |
| 90 | +It's common practice for ORMs, like ActiveRecord or SQLAlchemy to mutate entire rows when saving records. Take the following example: |
| 91 | + |
| 92 | +=== "ActiveRecord" |
| 93 | + ```ruby |
| 94 | + user = User.find(1) |
| 95 | + user.email = "test@pgdog.dev" |
| 96 | + user.save |
| 97 | + ``` |
| 98 | +=== "SQL" |
| 99 | + ```postgresql |
| 100 | + UPDATE users SET |
| 101 | + id = $1, email = $2, created_at = $4, updated_at = $3 |
| 102 | + WHERE |
| 103 | + id = $1; |
| 104 | + ``` |
| 105 | + |
| 106 | +While the sharding key (`id`) is technically updated in the query, its value doesn't change. To avoid unnecessary overhead, PgDog performs multiple checks on the new row before performing the [three statement](#how-it-works) sharding key update flow: |
| 107 | + |
| 108 | +1. If the value of the sharding key parameter (`SET id = $1`) is the same as it is in the `WHERE` clause (`WHERE id = $1`), the query is routed directly to the shard without modifications. |
| 109 | +2. If the values are different, or the `WHERE` clause doesn't specify the sharding key, PgDog will check the value of the key returned by the first `SELECT` statement in the flow. If both keys map to the _same_ shard, will send the query directly to that shard, without modifications. |
| 110 | + |
| 111 | + Updating the sharding key isn't a frequent operation and both of these mechanisms ensure that the more expensive algorithm isn't triggered unnecessarily. |
0 commit comments