GraphQL counter

Problem

You want to increment a counter via a GraphQL query.

Fauna provides consistent results that are globally replicated. High-volume counter updates to a single document (as shown in this solution) create contention that can slow down your queries.

We recommend alternatives such as:

  • batch your counter updates (multiple updates per query)

  • avoid counter updates faster than once or twice per second

Solution

There are several steps:

  1. Update your GraphQL schema to include:

    type Counter {
      counter: Int!
    }
    
    type Query {
      nextCounter: Counter! @resolver(name: "increment_counter")
    }

    This involves editing the GraphQL schema definition, wherever you have stored it, and then uploading the new schema in the Fauna Dashboard.

  2. Create a UDF to perform the counter increment:

    try
    {
        Value result = await client.Query(
            CreateFunction(
                Obj(
                    "name", "increment_counter",
                    "body", Query(
                        Lambda(
                            Arr(),
                            Let(
                                "counterRef",
                                Ref(Collection("Counter"), "1"),
                                "counter",
                                Get(Var("counterRef")),
                                "counterValue",
                                Select(
                                    Arr("data", "counter"),
                                    Var("counter")
                                )
                            ).In(
                                Update(
                                    Var("counterRef"),
                                    Obj(
                                        "data", Obj(
                                            "counter",
                                            Add(
                                                Var("counterValue"),
                                                1
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "increment_counter", collection = RefV(id = "functions")),ts: LongV(1626466815380000),name: StringV(increment_counter),body: QueryV(System.Collections.Generic.Dictionary`2[System.String,FaunaDB.Query.Expr]))
    result, err := client.Query(
    	f.CreateFunction(
    		f.Obj{
    			"name": "increment_counter",
    			"body": f.Query(
    				f.Lambda(
    					f.Arr{},
    					f.Let().Bind(
    						"counterRef",
    						f.Ref(f.Collection("Counter"), "1"),
    					).Bind(
    						"counter",
    						f.Get(f.Var("counterRef")),
    					).Bind(
    						"counterValue",
    						f.Select(
    							f.Arr{"data", "counter"},
    							f.Var("counter"),
    						),
    					).In(
    						f.Update(
    							f.Var("counterRef"),
    							f.Obj{
    								"data": f.Obj{
    									"counter": f.Add(
    										f.Var("counterValue"),
    										1,
    									),
    								},
    							},
    						),
    					),
    				),
    			),
    		},
    	))
    
    if err != nil {
    	fmt.Fprintln(os.Stderr, err)
    } else {
    	fmt.Println(result)
    }
    map[body:{[123 34 97 112 105 95 118 101 114 115 105 111 110 34 58 34 52 34 44 34 108 97 109 98 100 97 34 58 91 93 44 34 101 120 112 114 34 58 123 34 108 101 116 34 58 123 34 99 111 117 110 116 101 114 82 101 102 34 58 123 34 114 101 102 34 58 123 34 99 111 108 108 101 99 116 105 111 110 34 58 34 67 111 117 110 116 101 114 34 125 44 34 105 100 34 58 34 49 34 125 44 34 99 111 117 110 116 101 114 34 58 123 34 103 101 116 34 58 123 34 118 97 114 34 58 34 99 111 117 110 116 101 114 82 101 102 34 125 125 44 34 99 111 117 110 116 101 114 86 97 108 117 101 34 58 123 34 115 101 108 101 99 116 34 58 91 34 100 97 116 97 34 44 34 99 111 117 110 116 101 114 34 93 44 34 102 114 111 109 34 58 123 34 118 97 114 34 58 34 99 111 117 110 116 101 114 34 125 125 125 44 34 105 110 34 58 123 34 117 112 100 97 116 101 34 58 123 34 118 97 114 34 58 34 99 111 117 110 116 101 114 82 101 102 34 125 44 34 112 97 114 97 109 115 34 58 123 34 111 98 106 101 99 116 34 58 123 34 100 97 116 97 34 58 123 34 111 98 106 101 99 116 34 58 123 34 99 111 117 110 116 101 114 34 58 123 34 97 100 100 34 58 91 123 34 118 97 114 34 58 34 99 111 117 110 116 101 114 86 97 108 117 101 34 125 44 49 93 125 125 125 125 125 125 125 125]} name:increment_counter ref:{increment_counter 0xc0002001b0 0xc0002001b0 <nil>} ts:1626466826580000]
    System.out.println(
        client.query(
            CreateFunction(
                Obj(
                    "name", Value("increment_counter"),
                    "body", Query(
                        Lambda(
                            Arr(),
                            Let(
                                "counterRef",
                                Ref(Collection("Counter"), Value("1")),
                                "counter",
                                Get(Var("counterRef")),
                                "counterValue",
                                Select(
                                    Arr(
                                        Value("data"),
                                        Value("counter")
                                    ),
                                    Var("counter")
                                )
                            ).in(
                                Update(
                                    Var("counterRef"),
                                    Obj(
                                        "data", Obj(
                                            "counter",
                                            Add(
                                                Var("counterValue"),
                                                Value(1)
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        ).get());
    {ref: ref(id = "increment_counter", collection = ref(id = "functions")), ts: 1626466836080000, name: "increment_counter", body: QueryV({api_version=4, lambda=[], expr={let={counterRef={ref={collection=Counter}, id=1}, counter={get={var=counterRef}}, counterValue={select=[data, counter], from={var=counter}}}, in={update={var=counterRef}, params={object={data={object={counter={add=[{var=counterValue}, 1]}}}}}}}})}
    client.query(
      q.CreateFunction({
        name: 'increment_counter',
        body: q.Query(
          q.Lambda(
            [],
            q.Let(
              {
                counterRef: q.Ref(q.Collection('Counter'), '1'),
                counter: q.Get(q.Var('counterRef')),
                counterValue: q.Select(['data', 'counter'], q.Var('counter')),
              },
              q.Update(
                q.Var('counterRef'),
                {
                  data: {
                    counter: q.Add(q.Var('counterValue'), 1),
                  },
                },
              )
            )
          )
        ),
      })
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Function("increment_counter"),
      ts: 1626466852020000,
      name: 'increment_counter',
      body: Query(Lambda([], Let({"counterRef": Ref(Collection("Counter"), "1"), "counter": Get(Var("counterRef")), "counterValue": Select(["data", "counter"], Var("counter"))}, Update(Var("counterRef"), {"data": {"counter": Add(Var("counterValue"), 1)}}))))
    }
    result = client.query(
      q.create_function({
        "name": "increment_counter",
        "body": q.query(
          q.lambda_(
            [],
            q.let(
              {
                "counterRef": q.ref(q.collection("Counter"), "1"),
                "counter": q.get(q.var("counterRef")),
                "counterValue": q.select(["data", "counter"], q.var("counter"))
              },
              q.update(
                q.var("counterRef"),
                {
                  "data": {
                    "counter": q.add(q.var("counterValue"), 1)
                  }
                }
              )
            )
          )
        )
      })
    )
    print(result)
    {'ref': Ref(id=increment_counter, collection=Ref(id=functions)), 'ts': 1626466855240000, 'name': 'increment_counter', 'body': Query({'api_version': '4', 'lambda': [], 'expr': {'let': {'counterRef': {'ref': {'collection': 'Counter'}, 'id': '1'}, 'counter': {'get': {'var': 'counterRef'}}, 'counterValue': {'select': ['data', 'counter'], 'from': {'var': 'counter'}}}, 'in': {'update': {'var': 'counterRef'}, 'params': {'object': {'data': {'object': {'counter': {'add': [{'var': 'counterValue'}, 1]}}}}}}}})}
    CreateFunction({
      name: "increment_counter",
      body: Query(
        Lambda(
          [],
          Let(
            {
              counterRef: Ref(Collection("Counter"), "1"),
              counter: Get(Var("counterRef")),
              counterValue: Select(["data", "counter"], Var("counter"))
            },
            Update(
              Var("counterRef"),
              {
                data: {
                  counter: Add(Var("counterValue"), 1)
                }
              }
            )
          )
        )
      )
    })
    {
      ref: Function("increment_counter"),
      ts: 1626466930320000,
      name: 'increment_counter',
      body: Query(Lambda([], Let({"counterRef": Ref(Collection("Counter"), "1"), "counter": Get(Var("counterRef")), "counterValue": Select(["data", "counter"], Var("counter"))}, Update(Var("counterRef"), {"data": {"counter": Add(Var("counterValue"), 1)}}))))
    }
    Query metrics:
    •    bytesIn:  401

    •   bytesOut:  501

    • computeOps:    1

    •    readOps:    0

    •   writeOps:    1

    •  readBytes:   30

    • writeBytes:  593

    •  queryTime: 22ms

    •    retries:    0

  3. Create the required Counter document:

    try
    {
        Value result = await client.Query(
            Create(
                Ref(Collection("Counter"), "1"),
                Obj("data", Obj("counter", 0))
            )
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "1", collection = RefV(id = "Counter", collection = RefV(id = "collections"))),ts: LongV(1626466819370000),data: ObjectV(counter: LongV(0)))
    result, err := client.Query(
    	f.Create(
    		f.Ref(f.Collection("Counter"), "1"),
    		f.Obj{ "data": f.Obj{ "counter": 0 }},
    	),
    )
    
    if err != nil {
    	fmt.Fprintln(os.Stderr, err)
    } else {
    	fmt.Println(result)
    }
    map[data:map[counter:0] ref:{1 0xc000180e40 0xc000180e40 <nil>} ts:1626466827280000]
    System.out.println(
        client.query(
            Create(
                Ref(Collection("Counter"), Value("1")),
                Obj("data", Obj("counter", Value(0)))
            )
        ).get());
    {ref: ref(id = "1", collection = ref(id = "Counter", collection = ref(id = "collections"))), ts: 1626466842550000, data: {counter: 0}}
    client.query(
      q.Create(
        q.Ref(q.Collection('Counter'), '1'),
        { data: { counter: 0 } },
      )
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Ref(Collection("Counter"), "1"),
      ts: 1626466852320000,
      data: { counter: 0 }
    }
    result = client.query(
      q.create(
        q.ref(q.collection("Counter"), "1"),
        { "data": { "counter": 0 } }
      )
    )
    print(result)
    {'ref': Ref(id=1, collection=Ref(id=Counter, collection=Ref(id=collections))), 'ts': 1626466855610000, 'data': {'counter': 0}}
    Create(
      Ref(Collection("Counter"), "1"),
      { data: { counter: 0 } }
    )
    {
      ref: Ref(Collection("Counter"), "1"),
      ts: 1626466931300000,
      data: { counter: 0 }
    }
    Query metrics:
    •    bytesIn:  106

    •   bytesOut:  165

    • computeOps:    1

    •    readOps:    0

    •   writeOps:    1

    •  readBytes:   14

    • writeBytes:  194

    •  queryTime: 42ms

    •    retries:    0

    Here, we have set the initial counter value to 0.

  4. Run a GraphQL query to increment the counter:

    {
      nextCounter {
        counter
      }
    }

    You should see the result:

    {
      "data": {
        "nextCounter": {
          "counter": 1
        }
      }
    }
  5. You can also increment the counter in FQL queries:

    try
    {
        Value result = await client.Query(
            Call("increment_counter")
        );
        Console.WriteLine(result);
    }
    catch (Exception e)
    {
        Console.WriteLine($"ERROR: {e.Message}");
    }
    ObjectV(ref: RefV(id = "1", collection = RefV(id = "Counter", collection = RefV(id = "collections"))),ts: LongV(1626466823490000),data: ObjectV(counter: LongV(1)))
    result, err := client.Query(
    	f.Call("increment_counter"))
    
    if err != nil {
    	fmt.Fprintln(os.Stderr, err)
    } else {
    	fmt.Println(result)
    }
    map[data:map[counter:1] ref:{1 0xc0001097d0 0xc0001097d0 <nil>} ts:1626466827920000]
    System.out.println(
        client.query(
            Call(Value("increment_counter"))
        ).get());
    {ref: ref(id = "1", collection = ref(id = "Counter", collection = ref(id = "collections"))), ts: 1626466849100000, data: {counter: 1}}
    client.query(
      q.Call('increment_counter')
    )
    .then((ret) => console.log(ret))
    .catch((err) => console.error(
      'Error: [%s] %s: %s',
      err.name,
      err.message,
      err.errors()[0].description,
    ))
    {
      ref: Ref(Collection("Counter"), "1"),
      ts: 1626466852540000,
      data: { counter: 1 }
    }
    result = client.query(
      q.call("increment_counter")
    )
    print(result)
    {'ref': Ref(id=1, collection=Ref(id=Counter, collection=Ref(id=collections))), 'ts': 1626466855930000, 'data': {'counter': 1}}
    Call("increment_counter")
    {
      ref: Ref(Collection("Counter"), "1"),
      ts: 1626466932180000,
      data: { counter: 1 }
    }
    Query metrics:
    •    bytesIn:   43

    •   bytesOut:  165

    • computeOps:    1

    •    readOps:    1

    •   writeOps:    1

    •  readBytes:   58

    • writeBytes:   85

    •  queryTime: 29ms

    •    retries:    0

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!