@farukada/aws-langgraph-dynamodb-ts 0.7.0 → 0.9.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 +52 -9
- package/dist/checkpointer/actions/delete-thread.d.ts +3 -4
- package/dist/checkpointer/actions/delete-thread.d.ts.map +1 -1
- package/dist/checkpointer/actions/delete-thread.js +21 -41
- package/dist/checkpointer/actions/delete-thread.js.map +1 -1
- package/dist/checkpointer/actions/list.d.ts.map +1 -1
- package/dist/checkpointer/actions/list.js +38 -4
- package/dist/checkpointer/actions/list.js.map +1 -1
- package/dist/checkpointer/actions/put-writes.d.ts.map +1 -1
- package/dist/checkpointer/actions/put-writes.js +15 -24
- package/dist/checkpointer/actions/put-writes.js.map +1 -1
- package/dist/checkpointer/internal/item-reader.d.ts +45 -0
- package/dist/checkpointer/internal/item-reader.d.ts.map +1 -1
- package/dist/checkpointer/internal/item-reader.js +63 -1
- package/dist/checkpointer/internal/item-reader.js.map +1 -1
- package/dist/checkpointer/internal/item-writer.d.ts +0 -8
- package/dist/checkpointer/internal/item-writer.d.ts.map +1 -1
- package/dist/checkpointer/internal/item-writer.js +16 -21
- package/dist/checkpointer/internal/item-writer.js.map +1 -1
- package/dist/checkpointer/internal/keys.d.ts +19 -3
- package/dist/checkpointer/internal/keys.d.ts.map +1 -1
- package/dist/checkpointer/internal/keys.js +41 -6
- package/dist/checkpointer/internal/keys.js.map +1 -1
- package/dist/checkpointer/internal/special-write-cas.d.ts +30 -0
- package/dist/checkpointer/internal/special-write-cas.d.ts.map +1 -0
- package/dist/checkpointer/internal/special-write-cas.js +104 -0
- package/dist/checkpointer/internal/special-write-cas.js.map +1 -0
- package/dist/checkpointer/internal/special-write-cleanup.d.ts +18 -12
- package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +1 -1
- package/dist/checkpointer/internal/special-write-cleanup.js +31 -86
- package/dist/checkpointer/internal/special-write-cleanup.js.map +1 -1
- package/dist/checkpointer/internal/special-write-verify.d.ts +54 -0
- package/dist/checkpointer/internal/special-write-verify.d.ts.map +1 -0
- package/dist/checkpointer/internal/special-write-verify.js +65 -0
- package/dist/checkpointer/internal/special-write-verify.js.map +1 -0
- package/dist/checkpointer/internal/validation.d.ts +8 -4
- package/dist/checkpointer/internal/validation.d.ts.map +1 -1
- package/dist/checkpointer/internal/validation.js +12 -10
- package/dist/checkpointer/internal/validation.js.map +1 -1
- package/dist/checkpointer/internal/write-guard.d.ts +13 -0
- package/dist/checkpointer/internal/write-guard.d.ts.map +1 -0
- package/dist/checkpointer/internal/write-guard.js +39 -0
- package/dist/checkpointer/internal/write-guard.js.map +1 -0
- package/dist/checkpointer/internal/write-index.d.ts +37 -0
- package/dist/checkpointer/internal/write-index.d.ts.map +1 -0
- package/dist/checkpointer/internal/write-index.js +42 -0
- package/dist/checkpointer/internal/write-index.js.map +1 -0
- package/dist/checkpointer/types.d.ts +8 -0
- package/dist/checkpointer/types.d.ts.map +1 -1
- package/dist/history/actions/add-messages.d.ts.map +1 -1
- package/dist/history/actions/add-messages.js +24 -4
- package/dist/history/actions/add-messages.js.map +1 -1
- package/dist/history/actions/clear.d.ts +4 -6
- package/dist/history/actions/clear.d.ts.map +1 -1
- package/dist/history/actions/clear.js +18 -33
- package/dist/history/actions/clear.js.map +1 -1
- package/dist/history/actions/get-messages.d.ts +2 -1
- package/dist/history/actions/get-messages.d.ts.map +1 -1
- package/dist/history/actions/get-messages.js +28 -2
- package/dist/history/actions/get-messages.js.map +1 -1
- package/dist/history/actions/list-sessions.d.ts +2 -1
- package/dist/history/actions/list-sessions.d.ts.map +1 -1
- package/dist/history/actions/list-sessions.js +21 -2
- package/dist/history/actions/list-sessions.js.map +1 -1
- package/dist/history/actions/reconcile-count.d.ts.map +1 -1
- package/dist/history/actions/reconcile-count.js +2 -2
- package/dist/history/actions/reconcile-count.js.map +1 -1
- package/dist/history/internal/append-saga.d.ts.map +1 -1
- package/dist/history/internal/append-saga.js +2 -78
- package/dist/history/internal/append-saga.js.map +1 -1
- package/dist/history/internal/compensation.d.ts +21 -0
- package/dist/history/internal/compensation.d.ts.map +1 -0
- package/dist/history/internal/compensation.js +84 -0
- package/dist/history/internal/compensation.js.map +1 -0
- package/dist/history/internal/keys.d.ts +9 -1
- package/dist/history/internal/keys.d.ts.map +1 -1
- package/dist/history/internal/keys.js +21 -4
- package/dist/history/internal/keys.js.map +1 -1
- package/dist/history/internal/message-chunker.d.ts.map +1 -1
- package/dist/history/internal/message-chunker.js +13 -5
- package/dist/history/internal/message-chunker.js.map +1 -1
- package/dist/history/internal/session-count.d.ts +21 -0
- package/dist/history/internal/session-count.d.ts.map +1 -1
- package/dist/history/internal/session-count.js +51 -0
- package/dist/history/internal/session-count.js.map +1 -1
- package/dist/history/internal/session-title.d.ts +20 -0
- package/dist/history/internal/session-title.d.ts.map +1 -0
- package/dist/history/internal/session-title.js +44 -0
- package/dist/history/internal/session-title.js.map +1 -0
- package/dist/history/internal/setup.d.ts +2 -1
- package/dist/history/internal/setup.d.ts.map +1 -1
- package/dist/history/internal/setup.js +1 -0
- package/dist/history/internal/setup.js.map +1 -1
- package/dist/history/internal/title-generator.d.ts +8 -2
- package/dist/history/internal/title-generator.d.ts.map +1 -1
- package/dist/history/internal/title-generator.js +12 -3
- package/dist/history/internal/title-generator.js.map +1 -1
- package/dist/history/internal/validation.d.ts +9 -0
- package/dist/history/internal/validation.d.ts.map +1 -0
- package/dist/history/internal/validation.js +16 -0
- package/dist/history/internal/validation.js.map +1 -0
- package/dist/history/types.d.ts +12 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/codec/s3/offloader.d.ts +7 -1
- package/dist/shared/codec/s3/offloader.d.ts.map +1 -1
- package/dist/shared/codec/s3/offloader.js +14 -1
- package/dist/shared/codec/s3/offloader.js.map +1 -1
- package/dist/shared/constants.d.ts +15 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +16 -1
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/dynamodb/conditional-put.d.ts +51 -0
- package/dist/shared/dynamodb/conditional-put.d.ts.map +1 -0
- package/dist/shared/dynamodb/conditional-put.js +59 -0
- package/dist/shared/dynamodb/conditional-put.js.map +1 -0
- package/dist/shared/dynamodb/partition-delete.d.ts +33 -0
- package/dist/shared/dynamodb/partition-delete.d.ts.map +1 -0
- package/dist/shared/dynamodb/partition-delete.js +70 -0
- package/dist/shared/dynamodb/partition-delete.js.map +1 -0
- package/dist/shared/dynamodb/retry-classifier.d.ts.map +1 -1
- package/dist/shared/dynamodb/retry-classifier.js +9 -1
- package/dist/shared/dynamodb/retry-classifier.js.map +1 -1
- package/dist/shared/logging/redaction-walk.d.ts +23 -0
- package/dist/shared/logging/redaction-walk.d.ts.map +1 -0
- package/dist/shared/logging/redaction-walk.js +92 -0
- package/dist/shared/logging/redaction-walk.js.map +1 -0
- package/dist/shared/logging/redaction.d.ts +26 -17
- package/dist/shared/logging/redaction.d.ts.map +1 -1
- package/dist/shared/logging/redaction.js +24 -48
- package/dist/shared/logging/redaction.js.map +1 -1
- package/dist/shared/logging/secret-patterns.d.ts +74 -0
- package/dist/shared/logging/secret-patterns.d.ts.map +1 -0
- package/dist/shared/logging/secret-patterns.js +107 -0
- package/dist/shared/logging/secret-patterns.js.map +1 -0
- package/dist/shared/validation/primitives.d.ts +9 -1
- package/dist/shared/validation/primitives.d.ts.map +1 -1
- package/dist/shared/validation/primitives.js +13 -0
- package/dist/shared/validation/primitives.js.map +1 -1
- package/dist/store/actions/get.d.ts.map +1 -1
- package/dist/store/actions/get.js +15 -1
- package/dist/store/actions/get.js.map +1 -1
- package/dist/store/actions/list-namespaces.d.ts.map +1 -1
- package/dist/store/actions/list-namespaces.js +1 -0
- package/dist/store/actions/list-namespaces.js.map +1 -1
- package/dist/store/actions/put.d.ts.map +1 -1
- package/dist/store/actions/put.js +24 -18
- package/dist/store/actions/put.js.map +1 -1
- package/dist/store/actions/search.d.ts.map +1 -1
- package/dist/store/actions/search.js +4 -35
- package/dist/store/actions/search.js.map +1 -1
- package/dist/store/internal/backend-search.d.ts +5 -0
- package/dist/store/internal/backend-search.d.ts.map +1 -0
- package/dist/store/internal/backend-search.js +68 -0
- package/dist/store/internal/backend-search.js.map +1 -0
- package/dist/store/internal/filter.d.ts.map +1 -1
- package/dist/store/internal/filter.js +46 -5
- package/dist/store/internal/filter.js.map +1 -1
- package/dist/store/internal/index-reconcile.d.ts.map +1 -1
- package/dist/store/internal/index-reconcile.js +41 -5
- package/dist/store/internal/index-reconcile.js.map +1 -1
- package/dist/store/internal/item-mapper.d.ts +1 -0
- package/dist/store/internal/item-mapper.d.ts.map +1 -1
- package/dist/store/internal/item-mapper.js +2 -0
- package/dist/store/internal/item-mapper.js.map +1 -1
- package/dist/store/internal/keys.d.ts +1 -1
- package/dist/store/internal/keys.d.ts.map +1 -1
- package/dist/store/internal/keys.js +8 -2
- package/dist/store/internal/keys.js.map +1 -1
- package/dist/store/internal/overwrite-swap.d.ts +33 -0
- package/dist/store/internal/overwrite-swap.d.ts.map +1 -0
- package/dist/store/internal/overwrite-swap.js +62 -0
- package/dist/store/internal/overwrite-swap.js.map +1 -0
- package/dist/store/internal/persist.d.ts +22 -6
- package/dist/store/internal/persist.d.ts.map +1 -1
- package/dist/store/internal/persist.js +41 -20
- package/dist/store/internal/persist.js.map +1 -1
- package/dist/store/internal/read-existing.d.ts +19 -0
- package/dist/store/internal/read-existing.d.ts.map +1 -0
- package/dist/store/internal/read-existing.js +29 -0
- package/dist/store/internal/read-existing.js.map +1 -0
- package/dist/store/internal/score-direction.d.ts +32 -0
- package/dist/store/internal/score-direction.d.ts.map +1 -0
- package/dist/store/internal/score-direction.js +39 -0
- package/dist/store/internal/score-direction.js.map +1 -0
- package/dist/store/internal/search-filter.d.ts +4 -0
- package/dist/store/internal/search-filter.d.ts.map +1 -0
- package/dist/store/internal/search-filter.js +11 -0
- package/dist/store/internal/search-filter.js.map +1 -0
- package/dist/store/internal/setup.d.ts +13 -1
- package/dist/store/internal/setup.d.ts.map +1 -1
- package/dist/store/internal/setup.js +57 -1
- package/dist/store/internal/setup.js.map +1 -1
- package/dist/store/internal/validation.d.ts +8 -2
- package/dist/store/internal/validation.d.ts.map +1 -1
- package/dist/store/internal/validation.js +15 -6
- package/dist/store/internal/validation.js.map +1 -1
- package/dist/store/internal/write-verify.d.ts +31 -6
- package/dist/store/internal/write-verify.d.ts.map +1 -1
- package/dist/store/internal/write-verify.js +48 -8
- package/dist/store/internal/write-verify.js.map +1 -1
- package/dist/store/types.d.ts +16 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/vector-backend.d.ts +17 -0
- package/dist/store/vector-backend.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.codecDeps = codecDeps;
|
|
4
|
-
exports.resolveWriteIndex = resolveWriteIndex;
|
|
5
4
|
exports.buildCheckpointItems = buildCheckpointItems;
|
|
6
5
|
exports.buildWriteItems = buildWriteItems;
|
|
7
|
-
const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
|
|
8
6
|
const codec_1 = require("../../shared/codec/codec");
|
|
9
7
|
const keys_1 = require("./keys");
|
|
8
|
+
const write_index_1 = require("./write-index");
|
|
10
9
|
/** Map a context to the codec collaborators. */
|
|
11
10
|
function codecDeps(context) {
|
|
12
11
|
return { serde: context.serde, compression: context.compression, offloader: context.offloader };
|
|
@@ -16,16 +15,6 @@ function withTtl(item, ttlTimestamp) {
|
|
|
16
15
|
item.ttl = ttlTimestamp;
|
|
17
16
|
return item;
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Resolve a write's DynamoDB sort-key index: WRITES_IDX_MAP's slot for a
|
|
21
|
-
* known special channel, else its position. `Object.hasOwn` guards against
|
|
22
|
-
* WRITES_IDX_MAP's own `Object.prototype` chain — a channel literally named
|
|
23
|
-
* `constructor`/`toString`/etc. must fall through to `positional`, not
|
|
24
|
-
* resolve to an inherited function reference.
|
|
25
|
-
*/
|
|
26
|
-
function resolveWriteIndex(channel, positional) {
|
|
27
|
-
return Object.hasOwn(langgraph_checkpoint_1.WRITES_IDX_MAP, channel) ? langgraph_checkpoint_1.WRITES_IDX_MAP[channel] : positional;
|
|
28
|
-
}
|
|
29
18
|
/** Encode a checkpoint + metadata into its META and PAYLOAD items. */
|
|
30
19
|
async function buildCheckpointItems(context, threadId, checkpointNs, checkpoint, metadata, parentCheckpointId, ttlTimestamp) {
|
|
31
20
|
const deps = codecDeps(context);
|
|
@@ -70,24 +59,30 @@ async function buildWriteItems(context, threadId, checkpointNs, checkpointId, ta
|
|
|
70
59
|
const deps = codecDeps(context);
|
|
71
60
|
const pk = (0, keys_1.partitionKey)(threadId);
|
|
72
61
|
const items = [];
|
|
73
|
-
for (
|
|
74
|
-
const [channel, value] = writes[positional];
|
|
62
|
+
for (const { channel, value, index, occurrence } of (0, write_index_1.resolveWriteIndices)(writes)) {
|
|
75
63
|
/**
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
64
|
+
* `channel` is part of the key as well as the index: two channels can
|
|
65
|
+
* share an index (each channel's first occurrence is 0), so without it
|
|
66
|
+
* their uploads would collide on one S3 object within a single call.
|
|
79
67
|
*/
|
|
80
|
-
const index = resolveWriteIndex(channel, positional);
|
|
81
|
-
const baseKeyParts = [threadId, checkpointNs, checkpointId, taskId, `write-${index}`];
|
|
82
68
|
const descriptor = await (0, codec_1.encodePayload)(value, deps, {
|
|
83
|
-
keyParts: [
|
|
69
|
+
keyParts: [threadId, checkpointNs, checkpointId, taskId, `write-${index}`, channel, nonce],
|
|
84
70
|
});
|
|
85
71
|
const item = {
|
|
86
72
|
PK: pk,
|
|
87
|
-
SK: (0, keys_1.writeSortKey)(checkpointNs, checkpointId, taskId, index),
|
|
73
|
+
SK: (0, keys_1.writeSortKey)(checkpointNs, checkpointId, taskId, index, channel),
|
|
88
74
|
taskId,
|
|
89
75
|
index,
|
|
90
76
|
channel,
|
|
77
|
+
/**
|
|
78
|
+
* Shared by every row this call writes. Positions shift when a retried
|
|
79
|
+
* task's write mix changes, so a channel an earlier call already
|
|
80
|
+
* committed can land at a second index and be replayed twice; the group
|
|
81
|
+
* is what lets the read side tell that apart from a channel a single
|
|
82
|
+
* call legitimately wrote more than once.
|
|
83
|
+
*/
|
|
84
|
+
writeGroup: nonce,
|
|
85
|
+
occurrence,
|
|
91
86
|
value: descriptor,
|
|
92
87
|
};
|
|
93
88
|
items.push(withTtl(item, ttlTimestamp));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-writer.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/item-writer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"item-writer.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/item-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Reserved separator joining sort-key segments; forbidden inside any segment. */
|
|
2
2
|
export declare const SORT_KEY_SEPARATOR = "#";
|
|
3
|
-
/** Partition key for a thread: the thread id
|
|
3
|
+
/** Partition key for a thread: the adapter tag plus the thread id. */
|
|
4
4
|
export declare function partitionKey(threadId: string): string;
|
|
5
5
|
/** Sort key for a checkpoint's lightweight metadata item. */
|
|
6
6
|
export declare function metaSortKey(checkpointNs: string, checkpointId: string): string;
|
|
@@ -8,8 +8,24 @@ export declare function metaSortKey(checkpointNs: string, checkpointId: string):
|
|
|
8
8
|
export declare function metaSortKeyPrefix(checkpointNs: string): string;
|
|
9
9
|
/** Sort key for a checkpoint's heavy payload item. */
|
|
10
10
|
export declare function payloadSortKey(checkpointNs: string, checkpointId: string): string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Sort key for a single pending write. The trailing `channel` segment is what
|
|
13
|
+
* keeps two *different* channels from ever occupying one row: without it, a
|
|
14
|
+
* retried task whose write mix changed could compute an index another
|
|
15
|
+
* channel already holds, and the first-write-wins guard — which cannot tell a
|
|
16
|
+
* genuine retry from an unrelated write — would silently discard it. The
|
|
17
|
+
* channel is appended verbatim as the final segment, so two sort keys collide
|
|
18
|
+
* only when their channels are byte-identical; `writeSortKeyPrefix` stops at
|
|
19
|
+
* the checkpoint id, ahead of this segment, so `begins_with` reads are
|
|
20
|
+
* unaffected.
|
|
21
|
+
*/
|
|
22
|
+
export declare function writeSortKey(checkpointNs: string, checkpointId: string, taskId: string, index: number, channel: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* True when `sortKey` is one this adapter writes. A partition query carries no
|
|
25
|
+
* sort-key condition, so a partition-wide delete uses this to leave any row it
|
|
26
|
+
* does not own in place rather than deleting the whole partition blindly.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isCheckpointerSortKey(sortKey: string): boolean;
|
|
13
29
|
/** `begins_with` prefix selecting every WRITE item for one checkpoint. */
|
|
14
30
|
export declare function writeSortKeyPrefix(checkpointNs: string, checkpointId: string): string;
|
|
15
31
|
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/keys.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/keys.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,MAAM,CAAC;AA8BtC,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM,CAaR;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAI9D;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAErF"}
|
|
@@ -6,7 +6,9 @@ exports.metaSortKey = metaSortKey;
|
|
|
6
6
|
exports.metaSortKeyPrefix = metaSortKeyPrefix;
|
|
7
7
|
exports.payloadSortKey = payloadSortKey;
|
|
8
8
|
exports.writeSortKey = writeSortKey;
|
|
9
|
+
exports.isCheckpointerSortKey = isCheckpointerSortKey;
|
|
9
10
|
exports.writeSortKeyPrefix = writeSortKeyPrefix;
|
|
11
|
+
const errors_1 = require("../../shared/errors/errors");
|
|
10
12
|
/** Reserved separator joining sort-key segments; forbidden inside any segment. */
|
|
11
13
|
exports.SORT_KEY_SEPARATOR = '#';
|
|
12
14
|
/** Fixed digit width for the WRITE index so sort keys order numerically. */
|
|
@@ -24,9 +26,19 @@ var CheckpointItemKind;
|
|
|
24
26
|
CheckpointItemKind["PAYLOAD"] = "PAYLOAD";
|
|
25
27
|
CheckpointItemKind["WRITE"] = "WRITE";
|
|
26
28
|
})(CheckpointItemKind || (CheckpointItemKind = {}));
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Adapter tag prefixed to every checkpointer partition key. Without it a
|
|
31
|
+
* `thread_id` reused as a `sessionId` or a store namespace root — an ordinary
|
|
32
|
+
* design choice — put all three adapters' rows in one partition on a table
|
|
33
|
+
* shared via `DynamoDBFactory.createAll()`, where a partition-wide delete
|
|
34
|
+
* reached another adapter's data and composed sort keys could collide
|
|
35
|
+
* byte-for-byte. The three tags differ in their first character, so the key
|
|
36
|
+
* spaces are disjoint by construction.
|
|
37
|
+
*/
|
|
38
|
+
const ADAPTER_PARTITION_PREFIX = `CHKPT${exports.SORT_KEY_SEPARATOR}`;
|
|
39
|
+
/** Partition key for a thread: the adapter tag plus the thread id. */
|
|
28
40
|
function partitionKey(threadId) {
|
|
29
|
-
return threadId
|
|
41
|
+
return `${ADAPTER_PARTITION_PREFIX}${threadId}`;
|
|
30
42
|
}
|
|
31
43
|
/** Sort key for a checkpoint's lightweight metadata item. */
|
|
32
44
|
function metaSortKey(checkpointNs, checkpointId) {
|
|
@@ -40,10 +52,33 @@ function metaSortKeyPrefix(checkpointNs) {
|
|
|
40
52
|
function payloadSortKey(checkpointNs, checkpointId) {
|
|
41
53
|
return `${CheckpointItemKind.PAYLOAD}${exports.SORT_KEY_SEPARATOR}${checkpointNs}${exports.SORT_KEY_SEPARATOR}${checkpointId}`;
|
|
42
54
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Sort key for a single pending write. The trailing `channel` segment is what
|
|
57
|
+
* keeps two *different* channels from ever occupying one row: without it, a
|
|
58
|
+
* retried task whose write mix changed could compute an index another
|
|
59
|
+
* channel already holds, and the first-write-wins guard — which cannot tell a
|
|
60
|
+
* genuine retry from an unrelated write — would silently discard it. The
|
|
61
|
+
* channel is appended verbatim as the final segment, so two sort keys collide
|
|
62
|
+
* only when their channels are byte-identical; `writeSortKeyPrefix` stops at
|
|
63
|
+
* the checkpoint id, ahead of this segment, so `begins_with` reads are
|
|
64
|
+
* unaffected.
|
|
65
|
+
*/
|
|
66
|
+
function writeSortKey(checkpointNs, checkpointId, taskId, index, channel) {
|
|
67
|
+
const offsetIndex = index + WRITE_INDEX_OFFSET;
|
|
68
|
+
if (offsetIndex < 0 || offsetIndex.toString().length > WRITE_INDEX_PAD_WIDTH) {
|
|
69
|
+
throw new errors_1.ValidationError(`write index ${index} is outside the range encodable at offset ${WRITE_INDEX_OFFSET} ` +
|
|
70
|
+
`with ${WRITE_INDEX_PAD_WIDTH} digits`, 'index');
|
|
71
|
+
}
|
|
72
|
+
const paddedIndex = offsetIndex.toString().padStart(WRITE_INDEX_PAD_WIDTH, '0');
|
|
73
|
+
return [CheckpointItemKind.WRITE, checkpointNs, checkpointId, taskId, paddedIndex, channel].join(exports.SORT_KEY_SEPARATOR);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* True when `sortKey` is one this adapter writes. A partition query carries no
|
|
77
|
+
* sort-key condition, so a partition-wide delete uses this to leave any row it
|
|
78
|
+
* does not own in place rather than deleting the whole partition blindly.
|
|
79
|
+
*/
|
|
80
|
+
function isCheckpointerSortKey(sortKey) {
|
|
81
|
+
return Object.values(CheckpointItemKind).some((kind) => sortKey.startsWith(`${kind}${exports.SORT_KEY_SEPARATOR}`));
|
|
47
82
|
}
|
|
48
83
|
/** `begins_with` prefix selecting every WRITE item for one checkpoint. */
|
|
49
84
|
function writeSortKeyPrefix(checkpointNs, checkpointId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/keys.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/keys.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,uDAA6D;AAE7D,kFAAkF;AACrE,QAAA,kBAAkB,GAAG,GAAG,CAAC;AAEtC,4EAA4E;AAC5E,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC;;;;GAIG;AACH,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,6EAA6E;AAC7E,IAAK,kBAIJ;AAJD,WAAK,kBAAkB;IACrB,mCAAa,CAAA;IACb,yCAAmB,CAAA;IACnB,qCAAe,CAAA;AACjB,CAAC,EAJI,kBAAkB,KAAlB,kBAAkB,QAItB;AAED;;;;;;;;GAQG;AACH,MAAM,wBAAwB,GAAG,QAAQ,QAAA,kBAAkB,EAAE,CAAC;AAE9D,sEAAsE;AACtE,sBAA6B,QAAgB;IAC3C,OAAO,GAAG,wBAAwB,GAAG,QAAQ,EAAE,CAAC;AAClD,CAAC;AAED,6DAA6D;AAC7D,qBAA4B,YAAoB,EAAE,YAAoB;IACpE,OAAO,GAAG,kBAAkB,CAAC,IAAI,GAAG,QAAA,kBAAkB,GAAG,YAAY,GAAG,QAAA,kBAAkB,GAAG,YAAY,EAAE,CAAC;AAC9G,CAAC;AAED,gFAAgF;AAChF,2BAAkC,YAAoB;IACpD,OAAO,GAAG,kBAAkB,CAAC,IAAI,GAAG,QAAA,kBAAkB,GAAG,YAAY,GAAG,QAAA,kBAAkB,EAAE,CAAC;AAC/F,CAAC;AAED,sDAAsD;AACtD,wBAA+B,YAAoB,EAAE,YAAoB;IACvE,OAAO,GAAG,kBAAkB,CAAC,OAAO,GAAG,QAAA,kBAAkB,GAAG,YAAY,GAAG,QAAA,kBAAkB,GAAG,YAAY,EAAE,CAAC;AACjH,CAAC;AAED;;;;;;;;;;GAUG;AACH,sBACE,YAAoB,EACpB,YAAoB,EACpB,MAAc,EACd,KAAa,EACb,OAAe;IAEf,MAAM,WAAW,GAAG,KAAK,GAAG,kBAAkB,CAAC;IAC/C,IAAI,WAAW,GAAG,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QAC7E,MAAM,IAAI,wBAAe,CACvB,eAAe,KAAK,6CAA6C,kBAAkB,GAAG;YACpF,QAAQ,qBAAqB,SAAS,EACxC,OAAO,CACR,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,WAAW,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;IAChF,OAAO,CAAC,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,CAC9F,QAAA,kBAAkB,CACnB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,+BAAsC,OAAe;IACnD,OAAO,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CACrD,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,QAAA,kBAAkB,EAAE,CAAC,CACnD,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,4BAAmC,YAAoB,EAAE,YAAoB;IAC3E,OAAO,GAAG,kBAAkB,CAAC,KAAK,GAAG,QAAA,kBAAkB,GAAG,YAAY,GAAG,QAAA,kBAAkB,GAAG,YAAY,GAAG,QAAA,kBAAkB,EAAE,CAAC;AACpI,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CheckpointWriteItem } from '../types';
|
|
2
|
+
import type { CheckpointerContext } from './setup';
|
|
3
|
+
import { type SpecialWriteOutcome } from './special-write-verify';
|
|
4
|
+
/**
|
|
5
|
+
* Overwrite one special row, pinned to the `writeGroup` this call observed, and
|
|
6
|
+
* report the descriptor it superseded.
|
|
7
|
+
*
|
|
8
|
+
* Overwriting is correct for special channels — every reference implementation
|
|
9
|
+
* does it — but two concurrent calls to the same channel both read the same
|
|
10
|
+
* previous descriptor and both delete it, orphaning the loser's upload. Pinning
|
|
11
|
+
* the observed `writeGroup` and re-reading on rejection makes each call
|
|
12
|
+
* supersede exactly one payload.
|
|
13
|
+
*
|
|
14
|
+
* `BatchWriteItem` cannot carry conditions, which is why this path issues
|
|
15
|
+
* individual puts; a call holds at most one row per special channel, so that is
|
|
16
|
+
* four writes at worst.
|
|
17
|
+
*
|
|
18
|
+
* The compare-and-swap runs only when an offloader is configured — matching
|
|
19
|
+
* `store/internal/persist.ts` — because without one there is no S3 object to
|
|
20
|
+
* orphan, so a plain unconditional put stays correct and costs no extra
|
|
21
|
+
* ConsistentRead or write capacity. That shortcut is also why the surviving
|
|
22
|
+
* `catch` may report `committed: false` without verifying: with no offloader
|
|
23
|
+
* there is no object for the caller to delete on the strength of it.
|
|
24
|
+
*
|
|
25
|
+
* Never rejects: the caller runs this concurrently with the regular writes
|
|
26
|
+
* under `Promise.all`, whose own cleanup depends on every branch resolving
|
|
27
|
+
* rather than short-circuiting.
|
|
28
|
+
*/
|
|
29
|
+
export declare function writeSpecialItem(context: CheckpointerContext, item: CheckpointWriteItem): Promise<SpecialWriteOutcome>;
|
|
30
|
+
//# sourceMappingURL=special-write-cas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special-write-cas.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cas.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,wBAAwB,CAAC;AAsEhC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAoB9B"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeSpecialItem = writeSpecialItem;
|
|
4
|
+
const conditional_put_1 = require("../../shared/dynamodb/conditional-put");
|
|
5
|
+
const retry_1 = require("../../shared/dynamodb/retry");
|
|
6
|
+
const special_write_verify_1 = require("./special-write-verify");
|
|
7
|
+
/**
|
|
8
|
+
* Retry a conditional put up to {@link OVERWRITE_CAS_MAX_ATTEMPTS} times,
|
|
9
|
+
* re-reading the row each time a racer's write invalidates the pinned
|
|
10
|
+
* `writeGroup`. Extracted from {@link writeSpecialItem} to keep both
|
|
11
|
+
* functions under the repo's block-nesting limit.
|
|
12
|
+
*
|
|
13
|
+
* A rejection is not proof a competitor won: `withDynamoDBRetry` retries
|
|
14
|
+
* transient errors, so an attempt can commit server-side, its response can be
|
|
15
|
+
* lost, and the retried put can hit the row it just wrote and fail the same
|
|
16
|
+
* guard — indistinguishable from a competitor's win by the rejection alone.
|
|
17
|
+
* Each attempt's pinned observation is captured in `attempted` before the
|
|
18
|
+
* put, so that when {@link verifyAfterFailure} finds the row already holding
|
|
19
|
+
* *this item's own* `writeGroup`, the outcome reports having superseded
|
|
20
|
+
* whatever `attempted` held — never the item's own just-committed payload,
|
|
21
|
+
* which would strand the live row pointing at a deleted object.
|
|
22
|
+
*
|
|
23
|
+
* Only a rejection whose re-read proves some *other* writer holds the row is
|
|
24
|
+
* retried; every other failure is already settled by the verification.
|
|
25
|
+
*/
|
|
26
|
+
async function attemptCasWrites(context, item, initial) {
|
|
27
|
+
let observed = initial;
|
|
28
|
+
for (let attempt = 1; attempt <= conditional_put_1.OVERWRITE_CAS_MAX_ATTEMPTS; attempt++) {
|
|
29
|
+
const attempted = observed;
|
|
30
|
+
try {
|
|
31
|
+
await (0, retry_1.withDynamoDBRetry)(() => context.client.put({
|
|
32
|
+
TableName: context.tableName,
|
|
33
|
+
Item: item,
|
|
34
|
+
...(0, conditional_put_1.revisionGuard)(special_write_verify_1.SPECIAL_REVISION_ATTRIBUTE, attempted),
|
|
35
|
+
}));
|
|
36
|
+
return { done: true, outcome: { committed: true, superseded: attempted.value } };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const verified = await (0, special_write_verify_1.verifyAfterFailure)(context, item, attempted, error);
|
|
40
|
+
if (!verified.observed || !(0, conditional_put_1.isConditionalCheckFailed)(error)) {
|
|
41
|
+
return { done: true, outcome: verified.outcome };
|
|
42
|
+
}
|
|
43
|
+
observed = verified.observed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { done: false, observed };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Overwrite the row unconditionally once the compare-and-swap budget is spent,
|
|
50
|
+
* verifying rather than assuming if that put fails too.
|
|
51
|
+
*/
|
|
52
|
+
async function overwriteUnconditionally(context, item, observed) {
|
|
53
|
+
try {
|
|
54
|
+
await (0, retry_1.withDynamoDBRetry)(() => context.client.put({ TableName: context.tableName, Item: item }));
|
|
55
|
+
return { committed: true, superseded: observed.value };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return (await (0, special_write_verify_1.verifyAfterFailure)(context, item, observed, error)).outcome;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Overwrite one special row, pinned to the `writeGroup` this call observed, and
|
|
63
|
+
* report the descriptor it superseded.
|
|
64
|
+
*
|
|
65
|
+
* Overwriting is correct for special channels — every reference implementation
|
|
66
|
+
* does it — but two concurrent calls to the same channel both read the same
|
|
67
|
+
* previous descriptor and both delete it, orphaning the loser's upload. Pinning
|
|
68
|
+
* the observed `writeGroup` and re-reading on rejection makes each call
|
|
69
|
+
* supersede exactly one payload.
|
|
70
|
+
*
|
|
71
|
+
* `BatchWriteItem` cannot carry conditions, which is why this path issues
|
|
72
|
+
* individual puts; a call holds at most one row per special channel, so that is
|
|
73
|
+
* four writes at worst.
|
|
74
|
+
*
|
|
75
|
+
* The compare-and-swap runs only when an offloader is configured — matching
|
|
76
|
+
* `store/internal/persist.ts` — because without one there is no S3 object to
|
|
77
|
+
* orphan, so a plain unconditional put stays correct and costs no extra
|
|
78
|
+
* ConsistentRead or write capacity. That shortcut is also why the surviving
|
|
79
|
+
* `catch` may report `committed: false` without verifying: with no offloader
|
|
80
|
+
* there is no object for the caller to delete on the strength of it.
|
|
81
|
+
*
|
|
82
|
+
* Never rejects: the caller runs this concurrently with the regular writes
|
|
83
|
+
* under `Promise.all`, whose own cleanup depends on every branch resolving
|
|
84
|
+
* rather than short-circuiting.
|
|
85
|
+
*/
|
|
86
|
+
async function writeSpecialItem(context, item) {
|
|
87
|
+
try {
|
|
88
|
+
if (!context.offloader) {
|
|
89
|
+
await (0, retry_1.withDynamoDBRetry)(() => context.client.put({ TableName: context.tableName, Item: item }));
|
|
90
|
+
return { committed: true };
|
|
91
|
+
}
|
|
92
|
+
const initial = await (0, special_write_verify_1.readSpecialRow)(context, item);
|
|
93
|
+
const attempt = await attemptCasWrites(context, item, initial);
|
|
94
|
+
if (attempt.done)
|
|
95
|
+
return attempt.outcome;
|
|
96
|
+
context.logger.warn('putWrites: special-write compare-and-swap exhausted; overwriting unconditionally, which ' +
|
|
97
|
+
'can orphan one S3 object under a concurrent call (reclaimed by ensureS3LifecycleRule)', { sortKey: item.SK, channel: item.channel, attempts: conditional_put_1.OVERWRITE_CAS_MAX_ATTEMPTS });
|
|
98
|
+
return await overwriteUnconditionally(context, item, attempt.observed);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return { committed: false, error: error };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=special-write-cas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special-write-cas.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cas.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import type { CheckpointWriteItem } from '../types';
|
|
2
2
|
import type { CheckpointerContext } from './setup';
|
|
3
3
|
/**
|
|
4
|
-
* Write special (negative-index) items
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
4
|
+
* Write special (negative-index) items, then clean up the correct side of each.
|
|
5
|
+
*
|
|
6
|
+
* Overwrite is correct here, matching every reference checkpointer. Each item
|
|
7
|
+
* is written with a compare-and-swap on its row's `writeGroup` (see
|
|
8
|
+
* {@link writeSpecialItem}) so a concurrent call to the same special channel
|
|
9
|
+
* cannot make both callers delete the same superseded object and orphan one
|
|
10
|
+
* upload. A committed item cleans up the payload it actually superseded; an
|
|
11
|
+
* item confirmed never to have committed cleans up its own new upload.
|
|
12
|
+
*
|
|
13
|
+
* "Confirmed" is load-bearing, and {@link writeSpecialItem} is what earns it:
|
|
14
|
+
* an ambiguous failure is verified against the row and reported as committed
|
|
15
|
+
* unless the read proves otherwise. Deleting on *unknown* would strand a live
|
|
16
|
+
* row pointing at a deleted object; leaking one object instead is recoverable.
|
|
17
|
+
*
|
|
18
|
+
* Never rejects — a failure is reported via the return value, because the
|
|
19
|
+
* caller runs this concurrently with `writeRegularItems` under `Promise.all`,
|
|
20
|
+
* whose own cleanup depends on every branch resolving rather than
|
|
21
|
+
* short-circuiting.
|
|
16
22
|
*/
|
|
17
23
|
export declare function writeSpecialItemsWithCleanup(context: CheckpointerContext, items: CheckpointWriteItem[]): Promise<Error | undefined>;
|
|
18
24
|
//# sourceMappingURL=special-write-cleanup.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"special-write-cleanup.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cleanup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"special-write-cleanup.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cleanup.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAgBnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,mBAAmB,EAAE,GAC3B,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAmB5B"}
|
|
@@ -3,100 +3,45 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.writeSpecialItemsWithCleanup = writeSpecialItemsWithCleanup;
|
|
4
4
|
const descriptor_keys_1 = require("../../shared/codec/descriptor-keys");
|
|
5
5
|
const orphans_1 = require("../../shared/codec/s3/orphans");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Read each special item's stored descriptor before it's overwritten —
|
|
10
|
-
* mirrors store/actions/put.ts's readExisting. Keyed by SK (unique per
|
|
11
|
-
* special channel per call).
|
|
12
|
-
*/
|
|
13
|
-
async function readPreviousDescriptors(context, items) {
|
|
14
|
-
const previous = new Map();
|
|
15
|
-
for (const item of items) {
|
|
16
|
-
const result = await (0, retry_1.withDynamoDBRetry)(() => context.client.get({
|
|
17
|
-
TableName: context.tableName,
|
|
18
|
-
Key: { PK: item.PK, SK: item.SK },
|
|
19
|
-
ConsistentRead: true,
|
|
20
|
-
ProjectionExpression: '#v',
|
|
21
|
-
ExpressionAttributeNames: { '#v': 'value' },
|
|
22
|
-
}));
|
|
23
|
-
previous.set(item.SK, result.Item?.value);
|
|
24
|
-
}
|
|
25
|
-
return previous;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Split `items` by outcome after a failed batch write (always a
|
|
29
|
-
* BatchWriteAllIncompleteError — batchWriteAll's only throw, and every one of
|
|
30
|
-
* its own failedChunks is in turn always a BatchWriteIncompleteError with an
|
|
31
|
-
* accurate `unprocessed`, whichever of drainUnprocessedWrites's exit paths
|
|
32
|
-
* produced it — see drain-unprocessed.ts): never-committed items are safe to
|
|
33
|
-
* clean up their own upload, every other item its previous one. Uses `.name`,
|
|
34
|
-
* not `instanceof` (banned repo-wide, see base-error.ts).
|
|
35
|
-
*/
|
|
36
|
-
function splitSpecialOutcome(items, error) {
|
|
37
|
-
const neverCommittedSks = new Set();
|
|
38
|
-
for (const failedChunk of error.failedChunks) {
|
|
39
|
-
for (const request of failedChunk.unprocessed) {
|
|
40
|
-
/** Always a PutRequest echoing this submission — special writes never send DeleteRequests. */
|
|
41
|
-
const { SK } = request.PutRequest.Item;
|
|
42
|
-
neverCommittedSks.add(SK);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
committed: items.filter((item) => !neverCommittedSks.has(item.SK)),
|
|
47
|
-
neverCommitted: items.filter((item) => neverCommittedSks.has(item.SK)),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Best-effort delete S3 objects backing `descriptors`, if an offloader is configured. */
|
|
6
|
+
const special_write_cas_1 = require("./special-write-cas");
|
|
7
|
+
/** Best-effort delete the S3 objects backing `descriptors`, if offloading is on. */
|
|
51
8
|
async function deleteDescriptors(context, descriptors, label) {
|
|
52
9
|
if (!context.offloader)
|
|
53
10
|
return;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const descriptors = items
|
|
59
|
-
.map((item) => previous.get(item.SK))
|
|
60
|
-
.filter((descriptor) => descriptor !== undefined);
|
|
61
|
-
await deleteDescriptors(context, descriptors, 'putWrites.special.previous');
|
|
11
|
+
const present = descriptors.filter((d) => d !== undefined);
|
|
12
|
+
if (present.length === 0)
|
|
13
|
+
return;
|
|
14
|
+
await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(present), label, context.logger);
|
|
62
15
|
}
|
|
63
16
|
/**
|
|
64
|
-
* Write special (negative-index) items
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
17
|
+
* Write special (negative-index) items, then clean up the correct side of each.
|
|
18
|
+
*
|
|
19
|
+
* Overwrite is correct here, matching every reference checkpointer. Each item
|
|
20
|
+
* is written with a compare-and-swap on its row's `writeGroup` (see
|
|
21
|
+
* {@link writeSpecialItem}) so a concurrent call to the same special channel
|
|
22
|
+
* cannot make both callers delete the same superseded object and orphan one
|
|
23
|
+
* upload. A committed item cleans up the payload it actually superseded; an
|
|
24
|
+
* item confirmed never to have committed cleans up its own new upload.
|
|
25
|
+
*
|
|
26
|
+
* "Confirmed" is load-bearing, and {@link writeSpecialItem} is what earns it:
|
|
27
|
+
* an ambiguous failure is verified against the row and reported as committed
|
|
28
|
+
* unless the read proves otherwise. Deleting on *unknown* would strand a live
|
|
29
|
+
* row pointing at a deleted object; leaking one object instead is recoverable.
|
|
30
|
+
*
|
|
31
|
+
* Never rejects — a failure is reported via the return value, because the
|
|
32
|
+
* caller runs this concurrently with `writeRegularItems` under `Promise.all`,
|
|
33
|
+
* whose own cleanup depends on every branch resolving rather than
|
|
34
|
+
* short-circuiting.
|
|
76
35
|
*/
|
|
77
36
|
async function writeSpecialItemsWithCleanup(context, items) {
|
|
78
37
|
if (items.length === 0)
|
|
79
38
|
return undefined;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, items.map((item) => ({ PutRequest: { Item: item } })));
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
const { committed, neverCommitted } = splitSpecialOutcome(items, error);
|
|
95
|
-
await deleteDescriptors(context, neverCommitted.map((item) => item.value), 'putWrites.special.newUpload');
|
|
96
|
-
await cleanUpPrevious(context, committed, previous);
|
|
97
|
-
return error;
|
|
98
|
-
}
|
|
99
|
-
await cleanUpPrevious(context, items, previous);
|
|
100
|
-
return undefined;
|
|
39
|
+
const outcomes = await Promise.all(items.map(async (item) => [
|
|
40
|
+
item,
|
|
41
|
+
await (0, special_write_cas_1.writeSpecialItem)(context, item),
|
|
42
|
+
]));
|
|
43
|
+
await deleteDescriptors(context, outcomes.filter(([, o]) => o.committed).map(([, o]) => o.superseded), 'putWrites.special.previous');
|
|
44
|
+
await deleteDescriptors(context, outcomes.filter(([, o]) => !o.committed).map(([item]) => item.value), 'putWrites.special.newUpload');
|
|
45
|
+
return outcomes.find(([, o]) => o.error)?.[1].error;
|
|
101
46
|
}
|
|
102
47
|
//# sourceMappingURL=special-write-cleanup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"special-write-cleanup.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cleanup.ts"],"names":[],"mappings":";;;AACA,wEAAmE;AACnE,2DAAiE;
|
|
1
|
+
{"version":3,"file":"special-write-cleanup.js","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-cleanup.ts"],"names":[],"mappings":";;;AACA,wEAAmE;AACnE,2DAAiE;AAGjE,2DAAuD;AAGvD,oFAAoF;AACpF,KAAK,UAAU,iBAAiB,CAC9B,OAA4B,EAC5B,WAA8C,EAC9C,KAAa;IAEb,IAAI,CAAC,OAAO,CAAC,SAAS;QAAE,OAAO;IAC/B,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAA0B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IACnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,MAAM,IAAA,0BAAgB,EAAC,OAAO,CAAC,SAAS,EAAE,IAAA,+BAAa,EAAC,OAAO,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACI,KAAK,uCACV,OAA4B,EAC5B,KAA4B;IAE5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAChC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAuD,EAAE,CAAC;QAC7E,IAAI;QACJ,MAAM,IAAA,oCAAgB,EAAC,OAAO,EAAE,IAAI,CAAC;KACtC,CAAC,CACH,CAAC;IACF,MAAM,iBAAiB,CACrB,OAAO,EACP,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EACpE,4BAA4B,CAC7B,CAAC;IACF,MAAM,iBAAiB,CACrB,OAAO,EACP,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EACpE,6BAA6B,CAC9B,CAAC;IACF,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PayloadDescriptor } from '../../shared/codec/codec';
|
|
2
|
+
import type { CheckpointWriteItem } from '../types';
|
|
3
|
+
import type { CheckpointerContext } from './setup';
|
|
4
|
+
/**
|
|
5
|
+
* A special row already carries a per-call ULID in `writeGroup`, so it needs no
|
|
6
|
+
* extra revision attribute to compare and swap on.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SPECIAL_REVISION_ATTRIBUTE = "writeGroup";
|
|
9
|
+
/** What a special item's row held before this call tried to overwrite it. */
|
|
10
|
+
export interface SpecialRowState {
|
|
11
|
+
exists: boolean;
|
|
12
|
+
value?: PayloadDescriptor;
|
|
13
|
+
revision?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Outcome of one special item's conditional write. Never thrown, always returned. */
|
|
16
|
+
export interface SpecialWriteOutcome {
|
|
17
|
+
committed: boolean;
|
|
18
|
+
superseded?: PayloadDescriptor;
|
|
19
|
+
error?: Error;
|
|
20
|
+
}
|
|
21
|
+
/** What a post-failure verification read established about the attempt. */
|
|
22
|
+
export interface VerifiedFailure {
|
|
23
|
+
outcome: SpecialWriteOutcome;
|
|
24
|
+
/** Present only when the row was read and holds some other writer's group. */
|
|
25
|
+
observed?: SpecialRowState;
|
|
26
|
+
}
|
|
27
|
+
/** Read a special row's current descriptor and the writeGroup guarding it. */
|
|
28
|
+
export declare function readSpecialRow(context: CheckpointerContext, item: CheckpointWriteItem): Promise<SpecialRowState>;
|
|
29
|
+
/**
|
|
30
|
+
* Read the row back after a put failed, and report what that failure actually
|
|
31
|
+
* did — never assuming it did nothing.
|
|
32
|
+
*
|
|
33
|
+
* No rejection is proof of a non-commit. `withDynamoDBRetry` re-issues a put
|
|
34
|
+
* whose response was lost, and those re-issues can time out at the transport
|
|
35
|
+
* without ever reaching DynamoDB, so the budget is spent on a
|
|
36
|
+
* `RetryExhaustedError` and never on a `ConditionalCheckFailedException` — yet
|
|
37
|
+
* the row is committed. Reporting that as a confirmed non-commit let
|
|
38
|
+
* `writeSpecialItemsWithCleanup` delete the S3 object the live row points at,
|
|
39
|
+
* so every later `getTuple()` on that checkpoint failed with `NoSuchKey`,
|
|
40
|
+
* permanently.
|
|
41
|
+
*
|
|
42
|
+
* Three answers are possible:
|
|
43
|
+
* - the row holds this item's own `writeGroup`: the write landed, and the
|
|
44
|
+
* descriptor this attempt pinned is the dead one.
|
|
45
|
+
* - the row holds some other group: the write is confirmed not to be what is
|
|
46
|
+
* live, so this item's own upload is the dead one. `observed` is returned so
|
|
47
|
+
* a rejected compare-and-swap can re-pin and try again.
|
|
48
|
+
* - the read itself fails: nothing is confirmed, so the outcome still reports a
|
|
49
|
+
* commit and keeps the originating error. That leaks one S3 object at worst
|
|
50
|
+
* (reclaimed by `ensureS3LifecycleRule`) where the alternative strands a live
|
|
51
|
+
* row — the same trade `store/internal/persist.ts` makes.
|
|
52
|
+
*/
|
|
53
|
+
export declare function verifyAfterFailure(context: CheckpointerContext, item: CheckpointWriteItem, attempted: SpecialRowState, error: Error): Promise<VerifiedFailure>;
|
|
54
|
+
//# sourceMappingURL=special-write-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special-write-verify.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/internal/special-write-verify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,eAAe,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,8EAA8E;AAC9E,wBAAsB,cAAc,CAClC,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,eAAe,CAAC,CAgB1B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,mBAAmB,EAC5B,IAAI,EAAE,mBAAmB,EACzB,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,eAAe,CAAC,CAU1B"}
|