@convex-dev/sharded-counter 0.1.2 → 0.1.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.
Files changed (38) hide show
  1. package/README.md +133 -4
  2. package/dist/commonjs/client/index.d.ts +81 -11
  3. package/dist/commonjs/client/index.d.ts.map +1 -1
  4. package/dist/commonjs/client/index.js +87 -2
  5. package/dist/commonjs/client/index.js.map +1 -1
  6. package/dist/commonjs/component/_generated/api.d.ts.map +1 -1
  7. package/dist/commonjs/component/_generated/api.js +0 -2
  8. package/dist/commonjs/component/_generated/api.js.map +1 -1
  9. package/dist/commonjs/component/_generated/server.d.ts.map +1 -1
  10. package/dist/commonjs/component/_generated/server.js +0 -2
  11. package/dist/commonjs/component/_generated/server.js.map +1 -1
  12. package/dist/commonjs/component/public.d.ts +9 -0
  13. package/dist/commonjs/component/public.d.ts.map +1 -1
  14. package/dist/commonjs/component/public.js +60 -0
  15. package/dist/commonjs/component/public.js.map +1 -1
  16. package/dist/esm/client/index.d.ts +81 -11
  17. package/dist/esm/client/index.d.ts.map +1 -1
  18. package/dist/esm/client/index.js +87 -2
  19. package/dist/esm/client/index.js.map +1 -1
  20. package/dist/esm/component/_generated/api.d.ts.map +1 -1
  21. package/dist/esm/component/_generated/api.js +0 -2
  22. package/dist/esm/component/_generated/api.js.map +1 -1
  23. package/dist/esm/component/_generated/server.d.ts.map +1 -1
  24. package/dist/esm/component/_generated/server.js +0 -2
  25. package/dist/esm/component/_generated/server.js.map +1 -1
  26. package/dist/esm/component/public.d.ts +9 -0
  27. package/dist/esm/component/public.d.ts.map +1 -1
  28. package/dist/esm/component/public.js +60 -0
  29. package/dist/esm/component/public.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/client/index.ts +136 -8
  32. package/src/component/_generated/api.d.ts +12 -4
  33. package/src/component/_generated/api.js +0 -4
  34. package/src/component/_generated/dataModel.d.ts +0 -4
  35. package/src/component/_generated/server.d.ts +0 -4
  36. package/src/component/_generated/server.js +0 -4
  37. package/src/component/counter.test.ts +11 -1
  38. package/src/component/public.ts +62 -0
package/README.md CHANGED
@@ -10,7 +10,9 @@ This component adds counters to Convex. It acts as a key-value store from
10
10
  string to number, with sharding to increase throughput when updating values.
11
11
 
12
12
  Since it's built on Convex, everything is automatically consistent, reactive,
13
- and cached.
13
+ and cached. Since it's built with [Components](https://convex.dev/components),
14
+ the operations are isolated and increment/decrement are atomic even if run in
15
+ parallel.
14
16
 
15
17
  For example, if you want to display
16
18
  [one million checkboxes](https://en.wikipedia.org/wiki/One_Million_Checkboxes)
@@ -95,8 +97,10 @@ Once you have a `ShardedCounter`, there are a few methods you can use to update
95
97
  the counter for a key in a mutation or action.
96
98
 
97
99
  ```ts
98
- await counter.add(ctx, "checkboxes"); // increment
99
- await counter.add(ctx, "checkboxes", -5); // decrement by 5
100
+ await counter.add(ctx, "checkboxes", 5); // increment by 5
101
+ await counter.inc(ctx, "checkboxes"); // increment by 1
102
+ await counter.subtract(ctx, "checkboxes", 5); // decrement by 5
103
+ await counter.dec(ctx, "checkboxes"); // decrement by 1
100
104
 
101
105
  const numCheckboxes = counter.for("checkboxes");
102
106
  await numCheckboxes.inc(ctx); // increment
@@ -158,9 +162,118 @@ const friendCounts = new ShardedCounter<Record<Id<"users">, number>>(
158
162
  );
159
163
 
160
164
  // Decrement a user's friend count by 1
161
- await friendsCount.add(ctx, userId, -1);
165
+ await friendsCount.dec(ctx, userId);
166
+ ```
167
+
168
+ ## Reduce contention on reads
169
+
170
+ Reading the count with `counter.count(ctx, "checkboxes")` reads from all shards
171
+ to get an accurate count. This takes a read dependency on all shard documents.
172
+
173
+ - In a query subscription, that means any change to the counter causes the query
174
+ to rerun.
175
+ - In a mutation, that means any modification to the counter causes an
176
+ [OCC](https://docs.convex.dev/error#1) conflict.
177
+
178
+ You can reduce contention by estimating the count: read from a smaller number
179
+ of shards and extrapolate based on the total number of shards.
180
+
181
+ ```ts
182
+ const estimatedCheckboxCount = await counter.estimateCount(ctx, "checkboxes");
183
+ ```
184
+
185
+ By default, this reads from a single random shard and multiplies by the total
186
+ number of shards to form an estimate. You can improve the estimate by reading
187
+ from more shards, at the cost of more contention:
188
+
189
+ ```ts
190
+ const betterEstimatedCheckboxCount = await counter.estimateCount(ctx, "checkboxes", 3);
162
191
  ```
163
192
 
193
+ If the counter was accumulated from many
194
+ small `counter.inc` and `counter.dec` calls, then they should be uniformly
195
+ distributed across the shards, so estimated counts will be accurate.
196
+
197
+ In some cases the counter will not be evenly distributed:
198
+
199
+ - If the counter was accumulated from few operations
200
+ - If some operations were `counter.add`s or `counter.subtract`s with large
201
+ values, because each operation only changes a single shard
202
+ - If the number of shards changed
203
+
204
+ In these cases, the count might not be evenly distributed across the shards.
205
+ To repair such cases, you can call:
206
+
207
+ ```ts
208
+ await counter.rebalance(ctx, "checkboxes");
209
+ ```
210
+
211
+ Which will even out the count across shards.
212
+
213
+ You may change the number of shards for a key, by changing the second argument
214
+ to the `ShardedCounter` constructor. If you decrease the number of shards,
215
+ you will be left with extra shards that won't be written to but are still
216
+ read when computing `count`.
217
+ In this case, you should call `counter.rebalance` to delete
218
+ the extraneous shards.
219
+
220
+ NOTE: `counter.rebalance` reads and writes all shards, so it could cause
221
+ more OCCs, and it's recommended you call it sparingly, from the Convex dashboard
222
+ or from an infrequent cron.
223
+
224
+ NOTE: counts are floats, and floating point arithmetic isn't infinitely
225
+ precise. Even if you always add and subtract integers, you may get a fractional
226
+ counts, especially if you use `estimateCount` or `rebalance`.
227
+ Values distributed across shards may be added in different combinations, and
228
+ [floating point arithmetic isn't associative](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html).
229
+ You can use `Math.round` to ensure your final count is an integer, if
230
+ desired.
231
+
232
+ ## Counting documents in a table
233
+
234
+ Often you want to use a sharded counter to track how many documents are in a
235
+ table.
236
+
237
+ > If you want more than just a count, take a look at the
238
+ > [Aggregate component](https://www.npmjs.com/package/@convex-dev/aggregate).
239
+
240
+ There are three ways to go about keeping a count in sync with a table:
241
+
242
+ 1. Be careful to always update the aggregate in any mutation that inserts or
243
+ deletes from the table.
244
+ 2. \[Recommended\] Place all writes to a table in separate TypeScript functions,
245
+ and always call these functions from mutations instead of writing to the db
246
+ directly. This method is recommended, because it encapsulates the logic for
247
+ updating a table, while still keeping all operations explicit. For example,
248
+
249
+ ```ts
250
+ // Example of a mutation that calls `insertUser`.
251
+ export const insertPair = mutation(async (ctx) => {
252
+ ...
253
+ await insertUser(ctx, user1);
254
+ await insertUser(ctx, user2);
255
+ });
256
+
257
+ // All inserts to the "users" table go through this function.
258
+ async function insertUser(ctx, user) {
259
+ await ctx.db.insert("users", user);
260
+ await counter.inc(ctx, "users");
261
+ }
262
+ ```
263
+
264
+ 3. Register a [Trigger]((https://www.npmjs.com/package/convex-helpers#triggers)),
265
+ which automatically runs code when a mutation changes the
266
+ data in a table.
267
+
268
+ ```ts
269
+ // Triggers hook up writes to the table to the ShardedCounter.
270
+ const triggers = new Triggers<DataModel>();
271
+ triggers.register("mytable", counter.trigger("mycounter"));
272
+ export const mutation = customMutation(rawMutation, customCtx(triggers.wrapDB));
273
+ ```
274
+
275
+ The [insertUserWithTrigger](example/convex/example.ts) mutation uses a trigger.
276
+
164
277
  ## Backfilling an existing count
165
278
 
166
279
  If you want to count items like documents in a table, you may already have
@@ -174,6 +287,22 @@ the value and update the counter, if there aren't in-flight requests.
174
287
  The tricky part is handling requests while doing the calculation: making sure to
175
288
  merge active updates to counts with old values that you want to backfill.
176
289
 
290
+ ### Simple backfill: if table is append-only
291
+
292
+ See example code at the bottom of
293
+ [example/convex/example.ts](example/convex/example.ts).
294
+
295
+ Walkthrough of steps:
296
+
297
+ 1. Change live writes to update the counter. In the example, you would be
298
+ changing `insertUserBeforeBackfill` to be implemented as
299
+ `insertUserAfterBackfill`.
300
+ 2. Write a backfill that counts documents that were created before the code from
301
+ (1) deployed. In the example, this would be `backfillOldUsers`.
302
+ 3. Run `backfillOldUsers` from the dashboard.
303
+
304
+ ### Complex backfill: if documents may be deleted
305
+
177
306
  See example code at the bottom of
178
307
  [example/convex/example.ts](example/convex/example.ts).
179
308
 
@@ -1,12 +1,13 @@
1
- import { Expand, FunctionReference, GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server";
1
+ import { DocumentByName, Expand, FunctionReference, GenericDataModel, GenericMutationCtx, GenericQueryCtx, TableNamesInDataModel } from "convex/server";
2
2
  import { GenericId } from "convex/values";
3
3
  import { api } from "../component/_generated/api";
4
- export declare class ShardedCounter<Shards extends Record<string, number>> {
5
- component: UseApi<typeof api>;
6
- options?: {
7
- shards?: Shards | undefined;
8
- defaultShards?: number | undefined;
9
- } | undefined;
4
+ /**
5
+ * A sharded counter is a map from string -> counter, where each counter can
6
+ * be incremented or decremented atomically.
7
+ */
8
+ export declare class ShardedCounter<ShardsKey extends string> {
9
+ private component;
10
+ private options?;
10
11
  /**
11
12
  * A sharded counter is a map from string -> counter, where each counter can
12
13
  * be incremented or decremented.
@@ -23,7 +24,7 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
23
24
  * keys not in `options.shards`.
24
25
  */
25
26
  constructor(component: UseApi<typeof api>, options?: {
26
- shards?: Shards | undefined;
27
+ shards?: Record<ShardsKey, number> | undefined;
27
28
  defaultShards?: number | undefined;
28
29
  } | undefined);
29
30
  /**
@@ -33,14 +34,51 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
33
34
  * @param name The key to update the counter for.
34
35
  * @param count The amount to increment the counter by. Defaults to 1.
35
36
  */
36
- add<Name extends string = keyof Shards & string>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
37
+ add<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
38
+ /**
39
+ * Decrease the counter for key `name` by `count`.
40
+ */
41
+ subtract<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name, count?: number): Promise<null>;
42
+ /**
43
+ * Increment the counter for key `name` by 1.
44
+ */
45
+ inc<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<null>;
46
+ /**
47
+ * Decrement the counter for key `name` by 1.
48
+ */
49
+ dec<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<null>;
37
50
  /**
38
51
  * Gets the counter for key `name`.
39
52
  *
40
53
  * NOTE: this reads from all shards. If used in a mutation, it will contend
41
54
  * with all mutations that update the counter for this key.
42
55
  */
43
- count<Name extends string = keyof Shards & string>(ctx: RunQueryCtx, name: Name): Promise<number>;
56
+ count<Name extends string = ShardsKey>(ctx: RunQueryCtx, name: Name): Promise<number>;
57
+ /**
58
+ * Redistribute counts evenly across the counter's shards.
59
+ *
60
+ * If there were more shards for this counter at some point, those shards
61
+ * will be removed.
62
+ *
63
+ * If there were fewer shards for this counter, or if the random distribution
64
+ * of counts is uneven, the counts will be redistributed evenly.
65
+ *
66
+ * This operation reads and writes all shards, so it can cause contention if
67
+ * called too often.
68
+ */
69
+ rebalance<Name extends string = ShardsKey>(ctx: RunMutationCtx, name: Name): Promise<void>;
70
+ /**
71
+ * Estimate the count of a counter by only reading from a subset of shards,
72
+ * and extrapolating the total count.
73
+ *
74
+ * After a `rebalance`, or if there were a lot of data points to yield a
75
+ * random distribution across shards, this should be a good approximation of
76
+ * the total count. If there are few data points, which are not evenly
77
+ * distributed across shards, this will be a poor approximation.
78
+ *
79
+ * Use this to reduce contention when reading the counter.
80
+ */
81
+ estimateCount<Name extends string = ShardsKey>(ctx: RunQueryCtx, name: Name, readFromShards?: number): Promise<number>;
44
82
  /**
45
83
  * Returns an object with methods to update and query the counter for key
46
84
  * `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
@@ -56,7 +94,7 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
56
94
  * });
57
95
  * ```
58
96
  */
59
- for<Name extends string = keyof Shards & string>(name: Name): {
97
+ for<Name extends string = ShardsKey>(name: Name): {
60
98
  /**
61
99
  * Add `count` to the counter.
62
100
  */
@@ -80,8 +118,40 @@ export declare class ShardedCounter<Shards extends Record<string, number>> {
80
118
  * contend with all mutations that update the counter for this key.
81
119
  */
82
120
  count: (ctx: RunQueryCtx) => Promise<number>;
121
+ /**
122
+ * Redistribute counts evenly across the counter's shards.
123
+ *
124
+ * This operation reads and writes all shards, so it can cause contention
125
+ * if called too often.
126
+ */
127
+ rebalance: (ctx: RunMutationCtx) => Promise<void>;
128
+ /**
129
+ * Estimate the counter by only reading from a subset of shards,
130
+ * and extrapolating the total count.
131
+ *
132
+ * Use this to reduce contention when reading the counter.
133
+ */
134
+ estimateCount: (ctx: RunQueryCtx, readFromShards?: number) => Promise<number>;
83
135
  };
136
+ trigger<Ctx extends RunMutationCtx, Name extends string = ShardsKey>(name: Name): Trigger<Ctx, GenericDataModel, TableNamesInDataModel<GenericDataModel>>;
137
+ private shardsForKey;
84
138
  }
139
+ export type Trigger<Ctx, DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>> = (ctx: Ctx, change: Change<DataModel, TableName>) => Promise<void>;
140
+ export type Change<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>> = {
141
+ id: GenericId<TableName>;
142
+ } & ({
143
+ operation: "insert";
144
+ oldDoc: null;
145
+ newDoc: DocumentByName<DataModel, TableName>;
146
+ } | {
147
+ operation: "update";
148
+ oldDoc: DocumentByName<DataModel, TableName>;
149
+ newDoc: DocumentByName<DataModel, TableName>;
150
+ } | {
151
+ operation: "delete";
152
+ oldDoc: DocumentByName<DataModel, TableName>;
153
+ newDoc: null;
154
+ });
85
155
  type RunQueryCtx = {
86
156
  runQuery: GenericQueryCtx<GenericDataModel>["runQuery"];
87
157
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,GAAG,EAAE,MAAM,6BAA6B,CAAC;AAElD,qBAAa,cAAc,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAiBtD,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC;IAC7B,OAAO,CAAC;;;;IAjBjB;;;;;;;;;;;;;;OAcG;gBAEM,SAAS,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC,EAC7B,OAAO,CAAC;;;iBAA6C;IAE9D;;;;;;OAMG;IACG,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACnD,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,IAAI,EACV,KAAK,GAAE,MAAU;IASnB;;;;;OAKG;IACG,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EACrD,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,IAAI;IAIZ;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI;QAEvD;;WAEG;mBACc,cAAc,UAAS,MAAM;QAE9C;;WAEG;wBACmB,cAAc,UAAS,MAAM;QAEnD;;WAEG;mBACc,cAAc;QAC/B;;WAEG;mBACc,cAAc;QAC/B;;;;;WAKG;qBACgB,WAAW;;CAGnC;AAID,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,CAAC;CACzD,CAAC;AACF,KAAK,cAAc,GAAG;IACpB,WAAW,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,aAAa,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IACrB,CAAC,SAAS,SAAS,CAAC,MAAM,EAAE,CAAC,GACzB,MAAM,GACN,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GACnB,SAAS,CAAC,CAAC,CAAC,EAAE,GACd,CAAC,SAAS,MAAM,GACd;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GACnC,CAAC,CAAC;AAEZ,MAAM,MAAM,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC;KAC9B,GAAG,IAAI,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,SAAS,iBAAiB,CACpD,MAAM,KAAK,EACX,QAAQ,EACR,MAAM,KAAK,EACX,MAAM,WAAW,EACjB,MAAM,cAAc,CACrB,GACG,iBAAiB,CACf,KAAK,EACL,UAAU,EACV,SAAS,CAAC,KAAK,CAAC,EAChB,SAAS,CAAC,WAAW,CAAC,EACtB,cAAc,CACf,GACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACrB,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,7 @@
1
+ /**
2
+ * A sharded counter is a map from string -> counter, where each counter can
3
+ * be incremented or decremented atomically.
4
+ */
1
5
  export class ShardedCounter {
2
6
  component;
3
7
  options;
@@ -28,13 +32,30 @@ export class ShardedCounter {
28
32
  * @param count The amount to increment the counter by. Defaults to 1.
29
33
  */
30
34
  async add(ctx, name, count = 1) {
31
- const shards = this.options?.shards?.[name] ?? this.options?.defaultShards;
32
35
  return ctx.runMutation(this.component.public.add, {
33
36
  name,
34
37
  count,
35
- shards,
38
+ shards: this.shardsForKey(name),
36
39
  });
37
40
  }
41
+ /**
42
+ * Decrease the counter for key `name` by `count`.
43
+ */
44
+ async subtract(ctx, name, count = 1) {
45
+ return this.add(ctx, name, -count);
46
+ }
47
+ /**
48
+ * Increment the counter for key `name` by 1.
49
+ */
50
+ async inc(ctx, name) {
51
+ return this.add(ctx, name, 1);
52
+ }
53
+ /**
54
+ * Decrement the counter for key `name` by 1.
55
+ */
56
+ async dec(ctx, name) {
57
+ return this.add(ctx, name, -1);
58
+ }
38
59
  /**
39
60
  * Gets the counter for key `name`.
40
61
  *
@@ -44,6 +65,42 @@ export class ShardedCounter {
44
65
  async count(ctx, name) {
45
66
  return ctx.runQuery(this.component.public.count, { name });
46
67
  }
68
+ /**
69
+ * Redistribute counts evenly across the counter's shards.
70
+ *
71
+ * If there were more shards for this counter at some point, those shards
72
+ * will be removed.
73
+ *
74
+ * If there were fewer shards for this counter, or if the random distribution
75
+ * of counts is uneven, the counts will be redistributed evenly.
76
+ *
77
+ * This operation reads and writes all shards, so it can cause contention if
78
+ * called too often.
79
+ */
80
+ async rebalance(ctx, name) {
81
+ await ctx.runMutation(this.component.public.rebalance, {
82
+ name,
83
+ shards: this.shardsForKey(name),
84
+ });
85
+ }
86
+ /**
87
+ * Estimate the count of a counter by only reading from a subset of shards,
88
+ * and extrapolating the total count.
89
+ *
90
+ * After a `rebalance`, or if there were a lot of data points to yield a
91
+ * random distribution across shards, this should be a good approximation of
92
+ * the total count. If there are few data points, which are not evenly
93
+ * distributed across shards, this will be a poor approximation.
94
+ *
95
+ * Use this to reduce contention when reading the counter.
96
+ */
97
+ async estimateCount(ctx, name, readFromShards = 1) {
98
+ return await ctx.runQuery(this.component.public.estimateCount, {
99
+ name,
100
+ shards: this.shardsForKey(name),
101
+ readFromShards,
102
+ });
103
+ }
47
104
  /**
48
105
  * Returns an object with methods to update and query the counter for key
49
106
  * `name`. For fixed keys, you can call `counter.for("<key>")` to get methods
@@ -84,7 +141,35 @@ export class ShardedCounter {
84
141
  * contend with all mutations that update the counter for this key.
85
142
  */
86
143
  count: async (ctx) => this.count(ctx, name),
144
+ /**
145
+ * Redistribute counts evenly across the counter's shards.
146
+ *
147
+ * This operation reads and writes all shards, so it can cause contention
148
+ * if called too often.
149
+ */
150
+ rebalance: async (ctx) => this.rebalance(ctx, name),
151
+ /**
152
+ * Estimate the counter by only reading from a subset of shards,
153
+ * and extrapolating the total count.
154
+ *
155
+ * Use this to reduce contention when reading the counter.
156
+ */
157
+ estimateCount: async (ctx, readFromShards = 1) => this.estimateCount(ctx, name, readFromShards),
158
+ };
159
+ }
160
+ trigger(name) {
161
+ return async (ctx, change) => {
162
+ if (change.operation === "insert") {
163
+ await this.inc(ctx, name);
164
+ }
165
+ else if (change.operation === "delete") {
166
+ await this.dec(ctx, name);
167
+ }
87
168
  };
88
169
  }
170
+ shardsForKey(name) {
171
+ const explicitShards = this.options?.shards?.[name];
172
+ return explicitShards ?? this.options?.defaultShards;
173
+ }
89
174
  }
90
175
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;IAiBhB;IACA;IAjBT;;;;;;;;;;;;;;OAcG;IACH,YACS,SAA6B,EAC7B,OAAqD;QADrD,cAAS,GAAT,SAAS,CAAoB;QAC7B,YAAO,GAAP,OAAO,CAA8C;IAC3D,CAAC;IACJ;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,CACP,GAAmB,EACnB,IAAU,EACV,QAAgB,CAAC;QAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;QAC3E,OAAO,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE;YAChD,IAAI;YACJ,KAAK;YACL,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IACD;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CACT,GAAgB,EAChB,IAAU;QAEV,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD;;;;;;;;;;;;;;OAcG;IACH,GAAG,CAA8C,IAAU;QACzD,OAAO;YACL;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACpD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC;YAC5B;;eAEG;YACH,QAAQ,EAAE,KAAK,EAAE,GAAmB,EAAE,QAAgB,CAAC,EAAE,EAAE,CACzD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC;YAC7B;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D;;eAEG;YACH,GAAG,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3D;;;;;eAKG;YACH,KAAK,EAAE,KAAK,EAAE,GAAgB,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;SACzD,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,iDAA0B;AAC1B,sDAA+B;AAC/B;;EAA8C"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,iDAA0B;AAC1B,sDAA+B;AAC/B;;EAA8C"}
@@ -1,4 +1,3 @@
1
- /* prettier-ignore-start */
2
1
  /* eslint-disable */
3
2
  /**
4
3
  * Generated `api` utility.
@@ -20,5 +19,4 @@ import { anyApi, componentsGeneric } from "convex/server";
20
19
  export const api = anyApi;
21
20
  export const internal = anyApi;
22
21
  export const components = componentsGeneric();
23
- /* prettier-ignore-end */
24
22
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC/B,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;AAE9C,yBAAyB"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../../src/component/_generated/api.js"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC;AAC1B,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC/B,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAuBA;;;;;;;GAOG;AACH,wEAAkC;AAElC;;;;;;;GAOG;AACH,kFAAkD;AAElD;;;;;;;GAOG;AACH,8EAAwC;AAExC;;;;;;;GAOG;AACH,wFAAwD;AAExD;;;;;;;;;;GAUG;AACH,0EAAoC;AAEpC;;;;;GAKG;AACH,oFAAoD;AAEpD;;;;;;GAMG;AACH,8MAA4C"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAqBA;;;;;;;GAOG;AACH,wEAAkC;AAElC;;;;;;;GAOG;AACH,kFAAkD;AAElD;;;;;;;GAOG;AACH,8EAAwC;AAExC;;;;;;;GAOG;AACH,wFAAwD;AAExD;;;;;;;;;;GAUG;AACH,0EAAoC;AAEpC;;;;;GAKG;AACH,oFAAoD;AAEpD;;;;;;GAMG;AACH,8MAA4C"}
@@ -1,4 +1,3 @@
1
- /* prettier-ignore-start */
2
1
  /* eslint-disable */
3
2
  /**
4
3
  * Generated utilities for implementing server-side Convex query and mutation functions.
@@ -72,5 +71,4 @@ export const internalAction = internalActionGeneric;
72
71
  * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
73
72
  */
74
73
  export const httpAction = httpActionGeneric;
75
- /* prettier-ignore-end */
76
74
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAE5C,yBAAyB"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/component/_generated/server.js"],"names":[],"mappings":"AAAA,oBAAoB;AACpB;;;;;;;GAOG;AAEH,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAEvB;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,YAAY,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,eAAe,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC"}
@@ -7,4 +7,13 @@ export declare const add: import("convex/server").RegisteredMutation<"public", {
7
7
  export declare const count: import("convex/server").RegisteredQuery<"public", {
8
8
  name: string;
9
9
  }, Promise<number>>;
10
+ export declare const rebalance: import("convex/server").RegisteredMutation<"public", {
11
+ shards?: number | undefined;
12
+ name: string;
13
+ }, Promise<void>>;
14
+ export declare const estimateCount: import("convex/server").RegisteredQuery<"public", {
15
+ shards?: number | undefined;
16
+ readFromShards?: number | undefined;
17
+ name: string;
18
+ }, Promise<number>>;
10
19
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,GAAG;;;;iBAyBd,CAAC;AAEH,eAAO,MAAM,KAAK;;mBAUhB,CAAC"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,eAAO,MAAM,GAAG;;;;iBAyBd,CAAC;AAEH,eAAO,MAAM,KAAK;;mBAUhB,CAAC;AAEH,eAAO,MAAM,SAAS;;;iBA2BpB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;mBAsBxB,CAAC"}
@@ -39,4 +39,64 @@ export const count = query({
39
39
  return counters.reduce((sum, counter) => sum + counter.value, 0);
40
40
  },
41
41
  });
42
+ export const rebalance = mutation({
43
+ args: { name: v.string(), shards: v.optional(v.number()) },
44
+ handler: async (ctx, args) => {
45
+ const counters = await ctx.db
46
+ .query("counters")
47
+ .withIndex("name", (q) => q.eq("name", args.name))
48
+ .collect();
49
+ const count = counters.reduce((sum, counter) => sum + counter.value, 0);
50
+ const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
51
+ const value = count / shardCount;
52
+ for (let i = 0; i < shardCount; i++) {
53
+ const shard = counters.find((c) => c.shard === i);
54
+ if (shard) {
55
+ await ctx.db.patch(shard._id, { value });
56
+ }
57
+ else {
58
+ await ctx.db.insert("counters", {
59
+ name: args.name,
60
+ value,
61
+ shard: i,
62
+ });
63
+ }
64
+ }
65
+ const toDelete = counters.filter((c) => c.shard >= shardCount);
66
+ for (const counter of toDelete) {
67
+ await ctx.db.delete(counter._id);
68
+ }
69
+ },
70
+ });
71
+ export const estimateCount = query({
72
+ args: {
73
+ name: v.string(),
74
+ readFromShards: v.optional(v.number()),
75
+ shards: v.optional(v.number()),
76
+ },
77
+ handler: async (ctx, args) => {
78
+ const shardCount = args.shards ?? DEFAULT_SHARD_COUNT;
79
+ const readFromShards = Math.min(Math.max(1, args.readFromShards ?? 1), shardCount);
80
+ const shards = shuffle(Array.from({ length: shardCount }, (_, i) => i)).slice(0, readFromShards);
81
+ let readCount = 0;
82
+ for (const shard of shards) {
83
+ const counter = await ctx.db
84
+ .query("counters")
85
+ .withIndex("name", (q) => q.eq("name", args.name).eq("shard", shard))
86
+ .unique();
87
+ if (counter) {
88
+ readCount += counter.value;
89
+ }
90
+ }
91
+ return (readCount * shardCount) / readFromShards;
92
+ },
93
+ });
94
+ // Fisher-Yates shuffle
95
+ function shuffle(array) {
96
+ for (let i = array.length - 1; i > 0; i--) {
97
+ const j = Math.floor(Math.random() * (i + 1));
98
+ [array[i], array[j]] = [array[j], array[i]];
99
+ }
100
+ return array;
101
+ }
42
102
  //# sourceMappingURL=public.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC;IAC1B,IAAI,EAAE;QACJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KAC/B;IACD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE;IACjB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,mBAAmB,CAAC,CAAC,CAAC;QAC/E,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,EAAE;aACzB,KAAK,CAAC,UAAU,CAAC;aACjB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;aACpE,MAAM,EAAE,CAAC;QACZ,IAAI,OAAO,EAAE;YACX,MAAM,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;gBAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;aAClC,CAAC,CAAC;SACJ;aAAM;YACL,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE;gBAC9B,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK;aACN,CAAC,CAAC;SACJ;IACH,CAAC;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE;IAC1B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,EAAE;aAC1B,KAAK,CAAC,UAAU,CAAC;aACjB,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;aACjD,OAAO,EAAE,CAAC;QACb,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"public.js","sourceRoot":"","sources":["../../../src/component/public.ts"],"names":[],"mappings":"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"}