Skip to content

Commit c407d0b

Browse files
author
Patryk Lesiewicz
authored
Merge pull request #83 from FirebasePrivate/patryk/counter-docs
Improve documentation
2 parents 1d595dd + a1fcf6c commit c407d0b

File tree

4 files changed

+90
-11
lines changed

4 files changed

+90
-11
lines changed
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Final step
2+
3+
To finish the installation please set up a cloud scheduler job to call the controller function every minute. You can do it by running the following gcloud command.
4+
5+
```
6+
gcloud scheduler jobs create http firestore-sharded-counter-controller --schedule="* * * * *" --uri=${function:controller.url} --project=${param:PROJECT_ID}
7+
```

firestore-sharded-counter/README.md

+41-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
1+
# firestore-sharded-counter
2+
3+
**DESCRIPTION**: Auto-scalable counters for your app.
4+
5+
6+
**FEATURES**:
7+
8+
- Zero configuration, one mod for all your app needs
9+
- Automatically scales from 0 updates/sec to at least 10k updates/sec
10+
- Efficient for very low traffic counters
11+
- Handles gracefully bursts of up to thousands updates/sec
12+
- Can support thousands of updates/sec per counter and millions of counters in an app
13+
- Works well offline and provides latency compensation
14+
- Counter updates are immediately visible locally even though the main counter is eventually updated
15+
16+
17+
**DETAILS**: This mod allows you to increment any fields in your documents at arbitrary rate.
18+
19+
Client SDK, instead of incrementing the field directly, increments their own shard in `_counter_shards_` subcollection. A background task is periodically aggregating these shards and eventually rolling them up to the main counters.
20+
21+
There are three cloud functions that orchestrate shard aggregations:
22+
1. A `worker` function is responsible for monitoring and aggregating a range of shards. There may be 0 or hundreds of workers running concurrently to scale up to large workloads
23+
2. A `controller` function runs every minute and monitors the health of the workers. It can scale up and down the number of workers as needed and recover a worker on failure.
24+
3. A `onWrite` function triggers every time a shard is written and runs one-time aggregation. This improves latency for low workloads where no workers is running. To improve efficiency there's only one instance of this function running at any given time (`maxInstances` is set to 1).
25+
126
# Installation
2-
`firebase mods:install . --project=<my-project-id>`
27+
```
28+
firebase mods:install . --project=<my-project-id>
29+
30+
Please check the post-install message for the final step to set up your mod.
31+
```
332

433
# Web SDK
534
```
@@ -11,21 +40,26 @@
1140
</head>
1241
<body>
1342
<script>
14-
// Initialize Firebase
43+
// Initialize Firebase.
1544
var config = {};
1645
firebase.initializeApp(config);
1746
var db = firebase.firestore();
1847
19-
// Initialize counter
48+
// Initialize the sharded counter.
2049
var views = new sharded.Counter(db.doc("pages/hello-world"), "stats.views");
2150
22-
// Increment the counter
23-
views.incrementBy(firebase.firestore.FieldValue.increment(3));
51+
// This will increment a field "stats.views" of the "pages/hello-world" document by 3.
52+
views.incrementBy(3);
2453
2554
// Listen to locally consistent values
2655
views.onSnapshot((snap) => {
27-
console.log("Visits: " + snap.data());
56+
console.log("Locally consistent view of visits: " + snap.data());
2857
});
58+
59+
// Alternatively if you don't mind counter delays, you can listen to the document directly.
60+
db.doc("pages/hello-world").onSnapshot((snap) => {
61+
console.log("Eventually consistent view of visits: " + snap.get("stats.views"));
62+
})
2963
</script>
3064
</body>
3165
</html>
@@ -36,4 +70,4 @@
3670
cd functions/
3771
npm install
3872
npm run build
39-
```
73+
```

firestore-sharded-counter/clients/web/dist/sharded-counter.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

firestore-sharded-counter/clients/web/src/index.ts

+40-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export class Counter {
2121
private shards: { [key: string]: number } = {};
2222
private notifyPromise: Promise<void> = null;
2323

24+
/**
25+
* Constructs a sharded counter object that references to a field
26+
* in a document that is a counter.
27+
*
28+
* @param doc A reference to a document with a counter field.
29+
* @param field A path to a counter field in the above document.
30+
*/
2431
constructor(
2532
private doc: firebase.firestore.DocumentReference,
2633
private field: string
@@ -37,6 +44,12 @@ export class Counter {
3744
this.shards[shardsRef.doc("\t\t\t\t" + this.shardId.substr(0, 1)).path] = 0;
3845
}
3946

47+
/**
48+
* Get latency compensated view of the counter.
49+
*
50+
* All local increments will be reflected in the counter even if the main
51+
* counter hasn't been updated yet.
52+
*/
4053
public async get(options?: firebase.firestore.GetOptions): Promise<number> {
4154
const valuePromises = Object.keys(this.shards).map(async (path) => {
4255
const shard = await this.db.doc(path).get(options);
@@ -46,6 +59,12 @@ export class Counter {
4659
return values.reduce((a,b) => a + b, 0);
4760
}
4861

62+
/**
63+
* Listen to latency compensated view of the counter.
64+
*
65+
* All local increments to this counter will be immediately visible in the
66+
* snapshot.
67+
*/
4968
public onSnapshot(observable: ((next: CounterSnapshot) => void)) {
5069
Object.keys(this.shards).forEach((path) => {
5170
this.db
@@ -65,19 +84,38 @@ export class Counter {
6584
});
6685
}
6786

87+
/**
88+
* Increment the counter by a given value.
89+
*
90+
* e.g.
91+
* const counter = new sharded.Counter(db.doc("path/document"), "counter");
92+
* counter.incrementBy(1);
93+
*/
6894
public incrementBy(
69-
val: firebase.firestore.FieldValue
95+
val: number
7096
): Promise<void> {
97+
// @ts-ignore
98+
const increment: any = firebase.firestore.FieldValue.increment(val);
7199
const update: { [key: string]: any } = this.field
72100
.split(".")
73101
.reverse()
74-
.reduce((value, name) => ({ [name]: value }), <any>val);
102+
.reduce((value, name) => ({ [name]: value }), increment);
75103
return this.doc
76104
.collection(SHARD_COLLECTION_ID)
77105
.doc(this.shardId)
78106
.set(update, { merge: true });
79107
}
80108

109+
/**
110+
* Access the assigned shard directly. Useful to update multiple counters
111+
* at the same time, batches or transactions.
112+
*
113+
* e.g.
114+
* const counter = new sharded.Counter(db.doc("path/counter"), "");
115+
* const shardRef = counter.shard();
116+
* shardRef.set({"counter1", firestore.FieldValue.Increment(1),
117+
* "counter2", firestore.FieldValue.Increment(1));
118+
*/
81119
public shard(): firebase.firestore.DocumentReference {
82120
return this.doc.collection(SHARD_COLLECTION_ID).doc(this.shardId);
83121
}

0 commit comments

Comments
 (0)