Counters and Other Features



Counters and Indexing

Indexing Counters can speed-up finding them and the documents that contain them.

  • Indexing Counter Values Dynamic indexes (aka auto-indexes) cannot index counter values. To index counter values, create a static index that inherits from AbstractCountersIndexCreationTask (see here).

  • Indexing Counter Names
    Re-indexing due to Counter-name modification is normally rare enough to pause no performance issues.
    To index a document's Counters by name, use CounterNamesFor.


Counters and Queries

Create queries using code, or send the server raw queries for execution.

  • Either way, you can query Counters by name but not by value.
    This is because queries are generally based on indexes, and Counter values are not indexed.
  • Counter values can be projected from query results, as demonstrated in the following examples.
    This way a client can get Counter values from a query without downloading whole documents.

  • Use Session.Query to code queries yourself.

    • Returned Counter Value: Accumulated
      A Counter's value is returned as a single sum, with no specification of the Counter's value on each node.
      const session = store.openSession();
      const query = await session.query({ collection: "Products" })
          .selectFields("ProductLikes");
      const queryResults = await query.all();
      
      for (let counterValue in queryResults) {
          console.log("ProductLikes: " + counterValue);
      }
  • Use RawQuery to send the server raw RQL expressions for execution.

    • You can use the counter method.
      Returned Counter Value: Accumulated

    • You can use the counterRaw method.
      Returned Counter Value: Distributed
      A Counter's value is returned as a series of values, each maintained by one of the nodes.

      • It is not expected of you to use this in your application.
        Applications normally use the Counter's overall value, and very rarely refer to the value each node gives it.

    counter and counterRaw samples:

    //Various RQL expressions sent to the server using counter()
    //Returned Counter value is accumulated
    const rawQuery1 = await session.advanced
        .rawQuery("from products as p select counter(p, \"ProductLikes\")")
        .all();
    
    const rawQuery2 = await session.advanced
        .rawQuery("from products select counter(\"ProductLikes\") as ProductLikesCount")
        .all();
    
    const rawQuery3 = await session.advanced
        .rawQuery("from products where PricePerUnit > 50 select Name, counter(\"ProductLikes\")")
        .all();
    //An RQL expression sent to the server using counterRaw()
    //Returned Counter value is distributed
    const query = await session.advanced
        .rawQuery("from users as u select counterRaw(u, \"downloads\")")
        .all();

Counters and Revisions

A document revision stores all the document's Counters' names and values.

  • Stored Counter Values: Accumulated
    A revision stores a Counter's value as a single sum, with no specification of the Counter's value on each node.

  • Revisions-creation can be initiated by Counter-name modification.

    • When the Revisions feature is enabled, the creation or deletion of a Counter initiates the creation of a new document revision.
    • Counter value modifications do not cause the creation of new revisions.

Counters and Smuggler

Smuggler is a DocumentStore property, that can be used to export chosen database items to an external file or to import database items from an existing file into the database.

  • Transferred Counter Value: Distributed
    Smuggler transfers the entire series of values that the different nodes maintain for a Counter.
  • To make Smuggler handle Counters, include DatabaseItemType.Counters in OperateOnTypes's list of entities to import or export.
    export type DatabaseItemType =
        "None"
        | "Documents"
        | "RevisionDocuments"
        | "Indexes"
        | "Identities"
        | "Tombstones"
        | "LegacyAttachments"
        | "Conflicts"
        | "CompareExchange"
        | "LegacyDocumentDeletions"
        | "LegacyAttachmentDeletions"
        | "DatabaseRecord"
        | "Unknown"
        | "Attachments"
        | "CounterGroups"
        | "Subscriptions"
        | "CompareExchangeTombstones"
        | "TimeSeries";

Counters and Changes API

Changes API is a Push service, that can inform you of various changes on the Server, including changes in Counter values.
You can target all Counters, or specify the ones you wish to follow.

  • Pushed Counter Value: Accumulated
    Changes API methods return a Counter's value as a single sum, without specifying its value on each node.
  • The service is initiated by Counter Value Modification.

Counters and Ongoing Tasks:

Each ongoing task relates to Counters in its own way.

  • Counters and the Backup task
    There are two backup types: logical-backup and snapshot.
    Both types store and restore all data, including Counters.
    Both types operate as an ongoing backup routine, with a pre-set time schedule.

    • Logical Backup:
      Backed-up Counter values: Distributed
      A logical backup is a higher-level implementation of Smuggler.
      As with Smuggler, Counters are backed-up and restored including their values on all nodes.
    • Snapshot:
      A snapshot stores all data and settings as a single binary image. All components, including Counters, are restored to the exact same state they've been at during backup.
  • Counters and the External Replication task
    The ongoing external replication task replicates all data, including Counters.

    • Replicated Counter Value: Distributed
      Counters are replicated along with their values on all nodes.
    • Replication can be initiated by both Counter-name update and Counter-value modification.
  • Counters and the ETL task
    ETL is used to export data from RavenDB to an external (either Raven or SQL) database.

    • SQL ETL is not supported.
      Counters cannot be exported to an SQL database over SQL ETL.
    • RavenDB ETL is supported.
      Counters are exported over RavenDB ETL.
      • Export can be initiated by both Counter-name update and Counter-value modification.
      • Exported Counter Value: Distributed
        Counters are exported along with their values on all nodes.
      • Counters can be exported using a script.
        Default behavior: When an ETL script is not provided, Counters are exported.
  • Counters and the Data Subscriptions task
    Data Subscriptions can be initiated by document changes, including those caused by Counter Name updates.
    Documents will not be delivered in reaction to Counter Value modification.

Counters and Other Features: Summary

Use this table to find if and how various RavenDB features are triggered by Counters, and how the various features handle Counter values.

  • In the Triggered By column:
    • Document Change - Feature is triggered by a Counter Name update.
    • Countrer Value Modification - Feature is triggered by a Counter Value modification.
    • Time Schedule - Feature is invoked by a pre-set time routine.
    • No Trigger - Feature is executed manually, through the Studio or by a Client.
  • In the Counter Value column:
    • Accumulated - Counter Value is handled as a single accumulated sum.
    • Distributed - Counter Value is handled as a series of values maintained by cluster nodes.
Feature Triggered by Counter Value
Indexing Document Change doesn't handle values
LINQ Queries No trigger Accumulated
Raw Queries No trigger counter() - Accumulated
counterRaw() - Distributed
Smuggler No trigger Distributed
Backup Task Time Schedule Distributed
RavenDB ETL Task Document Change,
Countrer Value Modification
Distributed
External Replication task Document Change,
Countrer Value Modification
Distributed
Data Subscriptions Update Task Document Change
Changes API Countrer Value Modification Accumulated
Revision creation Document Change Accumulated


Including Counters

You can include Counters while loading a document.
An included Counter is retrieved in the same request as its owner-document and is held by the session, so it can be immediately retrieved when needed with no additional remote calls.

  • Including Counters when using Session.load:

    • Include a single Counter using includeCounter.
    • Include multiple Counters using includeCounters.

    includeCounter and includeCounters usage samples:

    const productPage = await session.load("products/1-C", {
        documentType: Product,
        includes: i => i.includeCounter("downloads")
            .includeCounter("ProductDislikes")
            .includeCounter("ProductDownloads")
    });
    const productPage = await session.load("orders/1-A", includeBuilder =>
        includeBuilder.includeDocuments("products/1-C")
            .includeCounters("ProductLikes", "ProductDislikes" )
    );
  • Including Counters when using Session.query:

    • Include a single Counter using includeCounter.
    • Include multiple Counters using includeCounters.

    includeCounter and includeCounters usage samples:

    const query = await session.query({ collection: "Product" })
        .include(includeBuilder =>
            includeBuilder.includeCounter("ProductLikes"));
    const query = await session.query({ collection: "Product" })
        .include(includeBuilder =>
            includeBuilder.includeCounters("ProductLikes", "ProductDownloads"));

Counters Bulk-Insert

store.bulkInsert is RavenDB's high-performance data insertion operation.
Use its countersFor interface's increment method to add or update counters with great speed.

  • Usage

    • countersFor

      const counter = session.countersFor("documentid")
      Parameters Type Description
      id string Document ID
    • increment

      const session = store.openSession();
      session.countersFor("documentid").increment("likes", 100);
      Parameters Type Description
      name string Counter Name
      delta long Default: 1L
  • Usage Flow

    • Create a store.bulkInsert instance.
    • Pass the instance's countersFor interface, the document ID
    • Call increment as many times as you like. Pass it -
      The Counter Name and Value (delta to be added).
  • Usage Sample
    In this sample, we attach a counter to all User documents.

    const query = session.query({collection:"User"})
        .whereBetween("Age", 0, 30)
    const result = await query.all();
    
    const bulkInsert = store.bulkInsert();
    for (let user = 0; user < result.length ; user++)
    {
        let userId = result[user].id;
    
        // Choose document
        let countersFor = bulkInsert.countersFor(userId);
    
        // Add or Increment a counter
        await bulkInsert.countersFor(userId).increment("downloaded", 100);
    }