@cloudbitmaps/gcs 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/dist/backend.d.ts +29 -3
- package/dist/download-retry.d.ts +8 -2
- package/dist/gcs-errors.d.ts +6 -0
- package/dist/index.js +287 -61
- package/dist/index.js.map +4 -4
- package/dist/read-once.d.ts +16 -1
- package/dist/read-timeout.d.ts +28 -0
- package/dist/registry.d.ts +45 -5
- package/dist/storage.d.ts +32 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@ It builds its own client from Application Default Credentials. Any other key is
|
|
|
42
42
|
| `client` | your own `Storage` client. Pass it as `client`; `storage` is refused, since in `CloudRoaring` that word means the backend. Build it with `retryOptions: { autoRetry: false }` (see below) |
|
|
43
43
|
| `projectId`, `apiEndpoint` | build a client for you, such as one for fake-gcs-server; refused beside `client` |
|
|
44
44
|
| `simpleUploadThresholdBytes`, `maxObjectBytes` | the size up to which an object is one simple request (default 8 MiB) and the largest object (default 5 TiB, GCS's maximum) |
|
|
45
|
+
| `readTimeoutMs` | cut off a read that has run this long, in milliseconds (see below); `0`, the default, sets no timeout |
|
|
46
|
+
| `conditionalDelete` | whether the registry removes a deleted row with an object delete under `ifGenerationMatch`, rather than leaving a tombstone every full listing reads. Off by default, on the public endpoint too: whether real GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server ignores it, so CI cannot show it. Set `true` to remove rows for good. Needs delete permission on the registry prefix |
|
|
45
47
|
|
|
46
48
|
## Before production
|
|
47
49
|
|
|
@@ -62,8 +64,38 @@ It builds its own client from Application Default Credentials. Any other key is
|
|
|
62
64
|
resumable uploads on that client, which the library does not retry; the client `GcsStorage` builds keeps them.
|
|
63
65
|
- **A client `timeout` does not bound a download on `@google-cloud/storage` 8.x**, so a read whose server stalls waits
|
|
64
66
|
for it. Measured against a local server that never answers: still pending after 12 s with `timeout: 2000`.
|
|
67
|
+
**`readTimeoutMs` does, and it is off unless you set it.** It bounds each read as a whole: a generation's tail (with
|
|
68
|
+
the metadata read it falls back on for an empty object), a range of it, or a registry row. One deadline covers every
|
|
69
|
+
attempt the driver makes and the backoff between them, from the call into the driver (a credential fetch and any
|
|
70
|
+
wait for a socket count) to the end of the body. It counts time the process spends busy too: Node runs a due timer
|
|
71
|
+
before it reads a socket, so a synchronous stretch longer than the timeout fails the reads in flight even when their
|
|
72
|
+
responses have arrived. When it passes, the read throws `TransientError` naming the read and the timeout, and no
|
|
73
|
+
further attempt starts; the store's read retry runs it again, so with `readTimeoutMs: 2_000` a read that stalls on
|
|
74
|
+
every attempt fails after about 8.35 s (4 × 2 s plus up to 0.35 s of backoff; measured: 8.1 to 8.2 s). Uploads,
|
|
75
|
+
deletes and listings are not timed.
|
|
76
|
+
- **A read that times out before its server answers leaves its connection open.** The SDK cannot cancel a request
|
|
77
|
+
whose response has not begun, so that request holds its connection until the server answers or closes it, and until
|
|
78
|
+
then keeps a Node process from exiting: one per driver read, up to four per call through the store's retry, so an
|
|
79
|
+
outage at N reads a second adds about 4N held connections each second. On 8.x a read cut off after its response
|
|
80
|
+
began closes its own; on 7.x (checked on 7.22.0) it stays open too. A 404 whose error body arrives after the
|
|
81
|
+
deadline is reported as a `TransientError`, not `NotFoundError`.
|
|
82
|
+
- **Downloads go out on Node's global agent, so one the driver cuts off or refuses resets nothing else.** When a
|
|
83
|
+
download fails part-way, the SDK destroys the HTTP agent it went out on, and its own agent is one keep-alive pool the
|
|
84
|
+
whole process shares, so every other request in flight would be reset with it, uploads included. The SDK never
|
|
85
|
+
destroys the global agent. Measured on 8.1.0 with no timeout: a body cut off part-way reset a 64-byte upload and a
|
|
86
|
+
registry write on a second backend on the SDK's agent, and on the global agent all of them completed. The cost: the
|
|
87
|
+
global agent closes a connection idle for 5 seconds (the SDK's own pool does not), so a read after a longer pause
|
|
88
|
+
opens a new connection, with its TCP and TLS handshake, and downloads share that agent with any other `http` or
|
|
89
|
+
`https` request in the process. Raising `https.globalAgent.options.timeout` keeps idle connections longer.
|
|
65
90
|
- **A transient failure of a write throws `TransientError`, and the write may or may not have landed.** Re-run the
|
|
66
|
-
call, or check `store.generations(ref)`.
|
|
91
|
+
call, or check `store.generations(ref)`. The driver sends each registry write once; a load that gets no answer reads
|
|
92
|
+
the row and sends a fresh compare-and-swap from it, at most three times. A generation's object of up to
|
|
93
|
+
`simpleUploadThresholdBytes` is sent again, up to three more times, only after a `429` or `503`; every object carries
|
|
94
|
+
a random id in its metadata, so a first send that landed is told from another writer's object. A load whose row writes
|
|
95
|
+
all go unanswered throws `TransientError` and keeps its object. The waits before an object's re-sends use real
|
|
96
|
+
timers: the store's injected clock does not reach them, and `GcsStorage` takes no clock, so a test that throttles
|
|
97
|
+
one object write three times waits about 3.5 s at most (the publish's waits before a fresh row write do use the
|
|
98
|
+
store's clock).
|
|
67
99
|
|
|
68
100
|
The [production checklist](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md) covers each item, and the ones every backend shares: backups of the data and the registry, and a schedule for your loads.
|
|
69
101
|
|
package/dist/backend.d.ts
CHANGED
|
@@ -18,8 +18,9 @@ export interface GcsStorageOptions {
|
|
|
18
18
|
readonly prefix?: string;
|
|
19
19
|
/**
|
|
20
20
|
* A constructed `@google-cloud/storage` client. One is built from the ambient credentials when absent. Its retry
|
|
21
|
-
* options apply to every request except the single-request conditional writes, which
|
|
22
|
-
*
|
|
21
|
+
* options apply to every request except the single-request conditional writes, which the driver sends without
|
|
22
|
+
* that retry whatever they say: a registry row once, and a generation's object again only after a throttle, under
|
|
23
|
+
* its own backoff. In `@google-cloud/storage` 7.x and 8.x a download the SDK retries after a 408, 429, 500, 502, 503 or 504 can crash the process
|
|
23
24
|
* with `ERR_STREAM_UNABLE_TO_PIPE`, so build it with `retryOptions: { autoRetry: false }`; the driver retries
|
|
24
25
|
* downloads itself. That also turns off the SDK's retries of listings, metadata reads and resumable uploads on
|
|
25
26
|
* this client, which the store does not retry. The client built here needs none of this: only its downloads are sent once.
|
|
@@ -36,13 +37,38 @@ export interface GcsStorageOptions {
|
|
|
36
37
|
readonly simpleUploadThresholdBytes?: number;
|
|
37
38
|
/** Injected clock for the registry's `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
38
39
|
readonly now?: () => number;
|
|
40
|
+
/**
|
|
41
|
+
* Cut off a read that has run this long, in milliseconds. `0`, the default, sets no timeout. A client's own `timeout`
|
|
42
|
+
* does not bound a download on `@google-cloud/storage` 8.x (the SDK hands it to an HTTP client that has no such
|
|
43
|
+
* option), so this is what does.
|
|
44
|
+
*
|
|
45
|
+
* It bounds each read as a whole — a generation's tail (with the metadata read it falls back on for an empty
|
|
46
|
+
* object), a range of it, a registry row — with one deadline across every attempt the driver makes and the backoff
|
|
47
|
+
* between them. The clock starts at the call into the driver, so a credential fetch and any wait for a socket count
|
|
48
|
+
* (Node's agents set no socket limit unless your process sets one), and runs until the whole body has arrived. It
|
|
49
|
+
* counts time the process spends busy too: Node runs a due timer before it reads a socket, so a synchronous stretch
|
|
50
|
+
* longer than the timeout fails the reads in flight even when their responses have arrived. When it passes, the read
|
|
51
|
+
* throws `TransientError` naming the read and the timeout and no further attempt starts, for the store's read retry
|
|
52
|
+
* to run again: about 8.35 s in all at `2_000` with the default retry policy. Uploads, deletes, listings and the
|
|
53
|
+
* conditional writes are not timed. The SDK cannot cancel a request whose response has not begun, so a read timed
|
|
54
|
+
* out before its server answers leaves that connection open until the server answers or closes it: one per read, up
|
|
55
|
+
* to four per call through the store's retry. A non-negative safe integer no larger than 2,147,483,647.
|
|
56
|
+
*/
|
|
57
|
+
readonly readTimeoutMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the registry removes a deleted row for good, by an object delete sent with `ifGenerationMatch`, rather
|
|
60
|
+
* than leaving a tombstone a full listing reads forever. Defaults to `false`, on the public endpoint too: whether real
|
|
61
|
+
* GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server
|
|
62
|
+
* accepts the precondition and ignores it, so CI cannot show it. Set `true` to remove rows for good.
|
|
63
|
+
*/
|
|
64
|
+
readonly conditionalDelete?: boolean;
|
|
39
65
|
}
|
|
40
66
|
/**
|
|
41
67
|
* The keys `new GcsStorage(options)` takes. Any other is refused by name rather than ignored: an ignored client key
|
|
42
68
|
* — `storage`, as the lower-level storage driver calls it — falls back to ambient credentials and the **public** endpoint, and
|
|
43
69
|
* for a client pointed at an emulator that is production traffic from a wiring typo.
|
|
44
70
|
*/
|
|
45
|
-
export declare const GCS_STORAGE_OPTION_KEYS: readonly ["bucket", "prefix", "client", "projectId", "apiEndpoint", "maxObjectBytes", "simpleUploadThresholdBytes", "now"];
|
|
71
|
+
export declare const GCS_STORAGE_OPTION_KEYS: readonly ["bucket", "prefix", "client", "projectId", "apiEndpoint", "maxObjectBytes", "simpleUploadThresholdBytes", "now", "readTimeoutMs", "conditionalDelete"];
|
|
46
72
|
export declare class GcsStorage implements StorageBackend {
|
|
47
73
|
/** Cross-bundle brand, stamped non-enumerably in the constructor so a spread cannot carry it. */
|
|
48
74
|
readonly [STORAGE_BACKEND]: true;
|
package/dist/download-retry.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type Deadline } from './read-timeout.js';
|
|
2
|
+
/** Under a deadline, a retry is not sent with less than this left of it. */
|
|
3
|
+
export declare const MIN_RETRY_MS = 10;
|
|
4
|
+
/**
|
|
5
|
+
* Call `download`, and again after each retryable fault, up to {@link RETRIES} more times, within `deadline` when there
|
|
6
|
+
* is one; rethrow the last error, or the deadline's once it has passed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function retryDownload<T>(download: () => Promise<T>, deadline?: Deadline): Promise<T>;
|
package/dist/gcs-errors.d.ts
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
* returns **412 Precondition Failed**. Maps to `WriteConflictError` (caller OCC), never a blind retry.
|
|
13
13
|
*/
|
|
14
14
|
export declare function isPreconditionFailed(err: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* GCS asked the client to slow down: `429` (`rateLimitExceeded`) or `503` (`backendError`), which the SDK raises as an
|
|
17
|
+
* `ApiError` with the status as a numeric `code`. GCS does not document that a throttled request was not applied, so
|
|
18
|
+
* an upload sent again after one must still tell a landed first send apart.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isThrottle(err: unknown): boolean;
|
|
15
21
|
/** The object / generation does not exist (GCS returns 404). */
|
|
16
22
|
export declare function isNotFound(err: unknown): boolean;
|
|
17
23
|
/** A range request started past EOF (HTTP 416 Requested Range Not Satisfiable). */
|