@gscdump/lakehouse 2.0.3 → 2.0.4
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 +9 -2
- package/dist/catalog.d.mts +35 -5
- package/dist/catalog.mjs +19 -3
- package/dist/maintenance.d.mts +2 -2
- package/dist/maintenance.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Node.js 22 or newer is required.
|
|
|
29
29
|
```ts
|
|
30
30
|
import { connectIcebergCatalog, listIcebergTables } from '@gscdump/lakehouse'
|
|
31
31
|
import { encodeJsonBigintSafe } from '@gscdump/lakehouse/bigint'
|
|
32
|
-
import { isCommitRateLimited } from '@gscdump/lakehouse/maintenance'
|
|
32
|
+
import { isCommitRateLimited, isCommitTransient } from '@gscdump/lakehouse/maintenance'
|
|
33
33
|
|
|
34
34
|
const connection = await connectIcebergCatalog(config)
|
|
35
35
|
const tables = await listIcebergTables(connection)
|
|
@@ -39,11 +39,18 @@ try {
|
|
|
39
39
|
}
|
|
40
40
|
catch (error) {
|
|
41
41
|
if (isCommitRateLimited(error)) {
|
|
42
|
-
//
|
|
42
|
+
// 429 specifically: decorrelate concurrent writers (defer off-slot).
|
|
43
|
+
}
|
|
44
|
+
else if (isCommitTransient(error)) {
|
|
45
|
+
// 429 or an R2 5xx blip: worth another attempt at all.
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
`isCommitRateLimited` stays 429-only; `isCommitServerError` covers transient
|
|
51
|
+
R2 5xx responses; `isCommitTransient` is the union and is what the package's
|
|
52
|
+
own append-retry loop uses.
|
|
53
|
+
|
|
47
54
|
The dataset registry is the normal authoring boundary. Raw Icebird table
|
|
48
55
|
creation and append primitives are intentionally excluded from the package
|
|
49
56
|
root so producers cannot silently bypass registered dataset definitions.
|
package/dist/catalog.d.mts
CHANGED
|
@@ -142,13 +142,43 @@ type IcebergAppendArgs = Parameters<typeof icebergAppend>[0];
|
|
|
142
142
|
/**
|
|
143
143
|
* True when `err` is an R2 Data Catalog commit rate-limit response
|
|
144
144
|
* (`429 too many commits to this table`).
|
|
145
|
+
*
|
|
146
|
+
* Deliberately 429-ONLY. Callers use this to decide whether to decorrelate
|
|
147
|
+
* concurrent committers (defer the whole unit of work off-slot), which is the
|
|
148
|
+
* wrong response to a one-off server blip — see {@link isCommitServerError}.
|
|
145
149
|
*/
|
|
146
150
|
declare function isCommitRateLimited(err: unknown): boolean;
|
|
147
151
|
/**
|
|
148
|
-
* `
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
+
* True when `err` is a transient server-side 5xx from R2 — a bare
|
|
153
|
+
* `500 Internal Server Error` on a data/metadata object PUT, or a 502/503/504
|
|
154
|
+
* from the catalog REST API.
|
|
155
|
+
*
|
|
156
|
+
* These carry no S3 error code and no body; they are single-request blips that
|
|
157
|
+
* clear on the next call. Distinct from {@link isCommitRateLimited} so a caller
|
|
158
|
+
* that must decorrelate writers on a 429 does not also do so on a server error.
|
|
159
|
+
*/
|
|
160
|
+
declare function isCommitServerError(err: unknown): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* The retry predicate: is `err` worth another append attempt at all?
|
|
163
|
+
*
|
|
164
|
+
* Union of {@link isCommitRateLimited} and {@link isCommitServerError}.
|
|
165
|
+
* Everything else — 4xx, catalog conflicts icebird already exhausted its own
|
|
166
|
+
* 412/409 retries on, schema/validation failures — is permanent and must
|
|
167
|
+
* propagate on the first attempt.
|
|
168
|
+
*/
|
|
169
|
+
declare function isCommitTransient(err: unknown): boolean;
|
|
170
|
+
/**
|
|
171
|
+
* `icebergAppend` wrapped with retry on transient commit failures
|
|
172
|
+
* ({@link isCommitTransient}: 429 rate-limits and R2 5xx blips), using
|
|
173
|
+
* full-jitter exponential back-off, plus a landed-check idempotency guard so a
|
|
174
|
+
* failure whose commit actually landed is never re-applied (see
|
|
175
|
+
* `deriveAppendId`/`appendAlreadyLanded`).
|
|
176
|
+
*
|
|
177
|
+
* Retrying is safe because `icebergAppend` writes data + manifest files BEFORE
|
|
178
|
+
* the atomic catalog pointer swap, so a 5xx during the upload phase aborts
|
|
179
|
+
* before anything is referenced by a snapshot; the retry writes fresh files and
|
|
180
|
+
* commits once. A 5xx on the pointer swap itself is covered by the
|
|
181
|
+
* landed-check.
|
|
152
182
|
*/
|
|
153
183
|
declare function icebergAppendRetrying(args: IcebergAppendArgs, options?: CommitRetryOptions): Promise<void>;
|
|
154
184
|
/** A data file in the current snapshot's manifest, scoped to one partition. */
|
|
@@ -210,4 +240,4 @@ declare function invalidateSnapshotRef(cache: CatalogCache, namespace: string, t
|
|
|
210
240
|
* the def's own spec + identity/dims values.
|
|
211
241
|
*/
|
|
212
242
|
declare function resolveIcebergDataFiles(conn: IcebergConnection, opts: ResolveIcebergDataFilesOptions): Promise<IcebergListedDataFile[]>;
|
|
213
|
-
export { CommitRetryOptions, ConnectIcebergOptions, IcebergAppendArgs, IcebergCatalogConfig, IcebergConnection, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, QueryProfiler, ResolveIcebergDataFilesOptions, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
|
|
243
|
+
export { CommitRetryOptions, ConnectIcebergOptions, IcebergAppendArgs, IcebergCatalogConfig, IcebergConnection, IcebergListedDataFile, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableOpResult, QueryProfiler, ResolveIcebergDataFilesOptions, catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, isCommitServerError, isCommitTransient, listIcebergTables, resolveIcebergDataFiles };
|
package/dist/catalog.mjs
CHANGED
|
@@ -129,6 +129,22 @@ function isCommitRateLimited(err) {
|
|
|
129
129
|
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
130
130
|
return msg.includes("429") || msg.includes("too many commits") || msg.includes("rate limit");
|
|
131
131
|
}
|
|
132
|
+
const TRANSIENT_SERVER_STATUSES = /* @__PURE__ */ new Set([
|
|
133
|
+
500,
|
|
134
|
+
502,
|
|
135
|
+
503,
|
|
136
|
+
504
|
|
137
|
+
]);
|
|
138
|
+
const TRANSIENT_SERVER_MESSAGE_RE = /\b(?:500 internal server error|502 bad gateway|503 service unavailable|504 gateway time-?out)\b/i;
|
|
139
|
+
function isCommitServerError(err) {
|
|
140
|
+
const status = err && typeof err === "object" ? err.status : void 0;
|
|
141
|
+
if (typeof status === "number") return TRANSIENT_SERVER_STATUSES.has(status);
|
|
142
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143
|
+
return TRANSIENT_SERVER_MESSAGE_RE.test(msg);
|
|
144
|
+
}
|
|
145
|
+
function isCommitTransient(err) {
|
|
146
|
+
return isCommitRateLimited(err) || isCommitServerError(err);
|
|
147
|
+
}
|
|
132
148
|
function defaultCommitSleep(ms) {
|
|
133
149
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
134
150
|
}
|
|
@@ -151,7 +167,7 @@ async function icebergAppendRetrying(args, options = {}) {
|
|
|
151
167
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
152
168
|
const err = await icebergAppend(stampedArgs).then(() => void 0, (e) => e);
|
|
153
169
|
if (err === void 0) return;
|
|
154
|
-
if (!
|
|
170
|
+
if (!isCommitTransient(err)) throw err;
|
|
155
171
|
if (await appendAlreadyLanded(args, appendId)) return;
|
|
156
172
|
if (attempt === maxAttempts - 1) throw err;
|
|
157
173
|
const ceiling = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
|
|
@@ -181,7 +197,7 @@ async function icebergAppendBatchesRetrying(args, options) {
|
|
|
181
197
|
batches: batchFactory()
|
|
182
198
|
}).then(() => void 0, (e) => e);
|
|
183
199
|
if (err === void 0) return true;
|
|
184
|
-
if (!
|
|
200
|
+
if (!isCommitTransient(err)) throw err;
|
|
185
201
|
if (await appendAlreadyLanded(args, appendId)) return true;
|
|
186
202
|
if (attempt === maxAttempts - 1) throw err;
|
|
187
203
|
const ceiling = Math.min(maxDelayMs, baseDelayMs * 2 ** attempt);
|
|
@@ -348,4 +364,4 @@ async function resolveIcebergDataFiles(conn, opts) {
|
|
|
348
364
|
}
|
|
349
365
|
return out;
|
|
350
366
|
}
|
|
351
|
-
export { catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendBatchesRetrying, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, listIcebergTables, resolveIcebergDataFiles };
|
|
367
|
+
export { catalogCacheScope, connectIcebergCatalog, dropIcebergTables, ensureIcebergNamespace, icebergAppendBatchesRetrying, icebergAppendRetrying, invalidateSnapshotRef, isCommitRateLimited, isCommitServerError, isCommitTransient, listIcebergTables, resolveIcebergDataFiles };
|
package/dist/maintenance.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { isCommitRateLimited } from "./catalog.mjs";
|
|
1
|
+
import { isCommitRateLimited, isCommitServerError, isCommitTransient } from "./catalog.mjs";
|
|
2
2
|
import { SweepListPage, SweepListedObject, SweepStorageClient, SweepUncommittedOrphansOptions, SweepUncommittedOrphansResult, sweepUncommittedOrphans } from "./orphan-sweep.mjs";
|
|
3
|
-
export { type SweepListPage, type SweepListedObject, type SweepStorageClient, type SweepUncommittedOrphansOptions, type SweepUncommittedOrphansResult, isCommitRateLimited, sweepUncommittedOrphans };
|
|
3
|
+
export { type SweepListPage, type SweepListedObject, type SweepStorageClient, type SweepUncommittedOrphansOptions, type SweepUncommittedOrphansResult, isCommitRateLimited, isCommitServerError, isCommitTransient, sweepUncommittedOrphans };
|
package/dist/maintenance.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { isCommitRateLimited } from "./catalog.mjs";
|
|
1
|
+
import { isCommitRateLimited, isCommitServerError, isCommitTransient } from "./catalog.mjs";
|
|
2
2
|
import { sweepUncommittedOrphans } from "./orphan-sweep.mjs";
|
|
3
|
-
export { isCommitRateLimited, sweepUncommittedOrphans };
|
|
3
|
+
export { isCommitRateLimited, isCommitServerError, isCommitTransient, sweepUncommittedOrphans };
|
package/package.json
CHANGED