@convex-dev/workpool 0.2.0 → 0.2.1

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.
Files changed (87) hide show
  1. package/README.md +81 -3
  2. package/dist/commonjs/client/index.d.ts +30 -5
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +27 -2
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/complete.d.ts.map +1 -1
  7. package/dist/commonjs/component/complete.js +9 -7
  8. package/dist/commonjs/component/complete.js.map +1 -1
  9. package/dist/commonjs/component/kick.d.ts +3 -2
  10. package/dist/commonjs/component/kick.d.ts.map +1 -1
  11. package/dist/commonjs/component/kick.js +12 -9
  12. package/dist/commonjs/component/kick.js.map +1 -1
  13. package/dist/commonjs/component/lib.d.ts +3 -3
  14. package/dist/commonjs/component/lib.d.ts.map +1 -1
  15. package/dist/commonjs/component/lib.js +25 -19
  16. package/dist/commonjs/component/lib.js.map +1 -1
  17. package/dist/commonjs/component/logging.d.ts +3 -2
  18. package/dist/commonjs/component/logging.d.ts.map +1 -1
  19. package/dist/commonjs/component/logging.js +34 -15
  20. package/dist/commonjs/component/logging.js.map +1 -1
  21. package/dist/commonjs/component/loop.js +10 -10
  22. package/dist/commonjs/component/loop.js.map +1 -1
  23. package/dist/commonjs/component/recovery.d.ts +29 -0
  24. package/dist/commonjs/component/recovery.d.ts.map +1 -1
  25. package/dist/commonjs/component/recovery.js +69 -66
  26. package/dist/commonjs/component/recovery.js.map +1 -1
  27. package/dist/commonjs/component/schema.d.ts +11 -11
  28. package/dist/commonjs/component/shared.d.ts +4 -4
  29. package/dist/commonjs/component/shared.d.ts.map +1 -1
  30. package/dist/commonjs/component/shared.js +2 -2
  31. package/dist/commonjs/component/shared.js.map +1 -1
  32. package/dist/commonjs/component/stats.d.ts +20 -21
  33. package/dist/commonjs/component/stats.d.ts.map +1 -1
  34. package/dist/commonjs/component/stats.js +86 -38
  35. package/dist/commonjs/component/stats.js.map +1 -1
  36. package/dist/commonjs/component/worker.d.ts +2 -2
  37. package/dist/esm/client/index.d.ts +30 -5
  38. package/dist/esm/client/index.d.ts.map +1 -1
  39. package/dist/esm/client/index.js +27 -2
  40. package/dist/esm/client/index.js.map +1 -1
  41. package/dist/esm/component/complete.d.ts.map +1 -1
  42. package/dist/esm/component/complete.js +9 -7
  43. package/dist/esm/component/complete.js.map +1 -1
  44. package/dist/esm/component/kick.d.ts +3 -2
  45. package/dist/esm/component/kick.d.ts.map +1 -1
  46. package/dist/esm/component/kick.js +12 -9
  47. package/dist/esm/component/kick.js.map +1 -1
  48. package/dist/esm/component/lib.d.ts +3 -3
  49. package/dist/esm/component/lib.d.ts.map +1 -1
  50. package/dist/esm/component/lib.js +25 -19
  51. package/dist/esm/component/lib.js.map +1 -1
  52. package/dist/esm/component/logging.d.ts +3 -2
  53. package/dist/esm/component/logging.d.ts.map +1 -1
  54. package/dist/esm/component/logging.js +34 -15
  55. package/dist/esm/component/logging.js.map +1 -1
  56. package/dist/esm/component/loop.js +10 -10
  57. package/dist/esm/component/loop.js.map +1 -1
  58. package/dist/esm/component/recovery.d.ts +29 -0
  59. package/dist/esm/component/recovery.d.ts.map +1 -1
  60. package/dist/esm/component/recovery.js +69 -66
  61. package/dist/esm/component/recovery.js.map +1 -1
  62. package/dist/esm/component/schema.d.ts +11 -11
  63. package/dist/esm/component/shared.d.ts +4 -4
  64. package/dist/esm/component/shared.d.ts.map +1 -1
  65. package/dist/esm/component/shared.js +2 -2
  66. package/dist/esm/component/shared.js.map +1 -1
  67. package/dist/esm/component/stats.d.ts +20 -21
  68. package/dist/esm/component/stats.d.ts.map +1 -1
  69. package/dist/esm/component/stats.js +86 -38
  70. package/dist/esm/component/stats.js.map +1 -1
  71. package/dist/esm/component/worker.d.ts +2 -2
  72. package/package.json +6 -7
  73. package/src/client/index.ts +64 -35
  74. package/src/component/_generated/api.d.ts +6 -6
  75. package/src/component/complete.ts +18 -7
  76. package/src/component/kick.test.ts +17 -7
  77. package/src/component/kick.ts +14 -11
  78. package/src/component/lib.ts +33 -26
  79. package/src/component/logging.test.ts +16 -0
  80. package/src/component/logging.ts +45 -23
  81. package/src/component/loop.test.ts +12 -12
  82. package/src/component/loop.ts +11 -11
  83. package/src/component/recovery.test.ts +6 -11
  84. package/src/component/recovery.ts +77 -69
  85. package/src/component/shared.ts +2 -2
  86. package/src/component/stats.test.ts +345 -0
  87. package/src/component/stats.ts +111 -41
@@ -1,26 +1,25 @@
1
1
  import { v } from "convex/values";
2
2
  import { Doc, Id } from "./_generated/dataModel.js";
3
- import { internalQuery, query } from "./_generated/server.js";
4
- import { DEFAULT_MAX_PARALLELISM } from "./shared.js";
5
- import { Logger } from "./logging.js";
3
+ import {
4
+ internalMutation,
5
+ internalQuery,
6
+ MutationCtx,
7
+ } from "./_generated/server.js";
8
+ import {
9
+ Config,
10
+ DEFAULT_MAX_PARALLELISM,
11
+ getCurrentSegment,
12
+ } from "./shared.js";
13
+ import { createLogger, Logger, logLevel, shouldLog } from "./logging.js";
14
+ import { internal } from "./_generated/api.js";
15
+ import schema from "./schema.js";
16
+ import { paginator } from "convex-helpers/server/pagination";
6
17
 
7
- /**
8
- * Record stats about work execution. Intended to be queried by Axiom or Datadog.
9
- */
18
+ const BACKLOG_BATCH_SIZE = 100;
10
19
 
11
20
  /**
12
- * Sample axiom dashboard query:
13
-
14
- workpool
15
- | extend parsed_message = iff(
16
- isnotnull(parse_json(trim("'", tostring(["data.message"])))),
17
- parse_json(trim("'", tostring(["data.message"]))),
18
- parse_json('{}')
19
- )
20
- | extend startLag = parsed_message["startLag"]
21
- | extend fnName = parsed_message["fnName"]
22
- | summarize avg(todouble(startLag)) by bin_auto(_time), tostring(fnName)
23
-
21
+ * Record stats about work execution. Intended to be queried by Axiom or Datadog.
22
+ * See the [README](https://github.com/get-convex/workpool) for example queries.
24
23
  */
25
24
 
26
25
  export function recordEnqueued(
@@ -65,38 +64,109 @@ export function recordCompleted(
65
64
  });
66
65
  }
67
66
 
68
- export function recordReport(console: Logger, state: Doc<"internalState">) {
69
- const { completed, succeeded, failed, retries, canceled } = state.report;
70
- const withoutRetries = completed - retries;
71
- console.event("report", {
72
- completed,
73
- succeeded,
74
- failed,
75
- retries,
76
- canceled,
77
- failureRate: completed ? (failed + retries) / completed : 0,
78
- permanentFailureRate: withoutRetries ? failed / withoutRetries : 0,
79
- });
67
+ export async function generateReport(
68
+ ctx: MutationCtx,
69
+ console: Logger,
70
+ state: Doc<"internalState">,
71
+ { maxParallelism, logLevel }: Config
72
+ ) {
73
+ if (!shouldLog(logLevel, "REPORT")) {
74
+ // Don't waste time if we're not going to log.
75
+ return;
76
+ }
77
+ const currentSegment = getCurrentSegment();
78
+ const pendingStart = await paginator(ctx.db, schema)
79
+ .query("pendingStart")
80
+ .withIndex("segment", (q) =>
81
+ q
82
+ .gte("segment", state.segmentCursors.incoming)
83
+ .lt("segment", currentSegment)
84
+ )
85
+ .paginate({
86
+ numItems: maxParallelism,
87
+ cursor: null,
88
+ });
89
+ if (pendingStart.isDone) {
90
+ recordReport(console, {
91
+ ...state.report,
92
+ running: state.running.length,
93
+ backlog: pendingStart.page.length,
94
+ });
95
+ } else {
96
+ await ctx.scheduler.runAfter(0, internal.stats.calculateBacklogAndReport, {
97
+ startSegment: state.segmentCursors.incoming,
98
+ endSegment: currentSegment,
99
+ cursor: pendingStart.continueCursor,
100
+ report: state.report,
101
+ running: state.running.length,
102
+ logLevel,
103
+ });
104
+ }
80
105
  }
81
106
 
82
- /**
83
- * Warning: this should not be used from a mutation, as it will cause conflicts.
84
- * Use this to debug or diagnose your queue length when it's backed up.
85
- */
86
- export const queueLength = query({
87
- args: {},
88
- returns: v.number(),
89
- handler: async (ctx) => {
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
- return (ctx.db.query("pendingStart") as any).count();
107
+ export const calculateBacklogAndReport = internalMutation({
108
+ args: {
109
+ startSegment: v.int64(),
110
+ endSegment: v.int64(),
111
+ cursor: v.string(),
112
+ report: schema.tables.internalState.validator.fields.report,
113
+ running: v.number(),
114
+ logLevel,
115
+ },
116
+ handler: async (ctx, args) => {
117
+ const pendingStart = await paginator(ctx.db, schema)
118
+ .query("pendingStart")
119
+ .withIndex("segment", (q) =>
120
+ q.gte("segment", args.startSegment).lt("segment", args.endSegment)
121
+ )
122
+ .paginate({
123
+ numItems: BACKLOG_BATCH_SIZE,
124
+ cursor: args.cursor,
125
+ });
126
+ const console = createLogger(args.logLevel);
127
+ if (pendingStart.isDone) {
128
+ recordReport(console, {
129
+ ...args.report,
130
+ running: args.running,
131
+ backlog: pendingStart.page.length,
132
+ });
133
+ } else {
134
+ await ctx.scheduler.runAfter(
135
+ 0,
136
+ internal.stats.calculateBacklogAndReport,
137
+ {
138
+ startSegment: args.startSegment,
139
+ endSegment: args.endSegment,
140
+ cursor: pendingStart.continueCursor,
141
+ report: args.report,
142
+ running: args.running,
143
+ logLevel: args.logLevel,
144
+ }
145
+ );
146
+ }
92
147
  },
93
148
  });
94
149
 
150
+ function recordReport(
151
+ console: Logger,
152
+ report: Doc<"internalState">["report"] & { running: number; backlog: number }
153
+ ) {
154
+ const { completed, failed, retries } = report;
155
+ const withoutRetries = completed - retries;
156
+ const failureRate = completed ? (failed + retries) / completed : 0;
157
+ const permanentFailureRate = withoutRetries ? failed / withoutRetries : 0;
158
+ console.event("report", {
159
+ ...report,
160
+ failureRate: Number(failureRate.toFixed(4)),
161
+ permanentFailureRate: Number(permanentFailureRate.toFixed(4)),
162
+ });
163
+ }
164
+
95
165
  /**
96
166
  * Warning: this should not be used from a mutation, as it will cause conflicts.
97
167
  * Use this while developing to see the state of the queue.
98
168
  */
99
- export const diagnostic = internalQuery({
169
+ export const diagnostics = internalQuery({
100
170
  args: {},
101
171
  returns: v.any(),
102
172
  handler: async (ctx) => {