Indexes

In working with Fauna, indexes are the primary way to query your documents. Aside from querying directly for documents by Reference (or just "Ref"), most Fauna database queries use an index in some way. This section of the documentation provides an introduction to indexes and how to use them to achieve specific development goals.

The source field

Every index has one or more source Collections. Once an index is active, any query that performs a write operation in the source collection(s) and meets the index criteria (if any) causes the index to be updated. To learn more about index criteria, see Terms and Values.

The following example creates an index on a collection named People:

client.query(
  q.CreateIndex(
    {
      name: 'all_people',
      source: q.Collection('People'),
    },
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  ref: Index("all_people"),
  ts: 1641932479200000,
  active: true,
  serialized: true,
  name: 'all_people',
  source: Collection("People"),
  partitions: 8
}
Query metrics:
  •    bytesIn:    82

  •   bytesOut:   254

  • computeOps:     1

  •    readOps:     0

  •   writeOps:     8

  •  readBytes: 2,306

  • writeBytes: 1,212

  •  queryTime:  38ms

  •    retries:     0

An index can have more than one source collection. The following example demonstrates how to create an index with multiple source collections:

client.query(
  q.CreateIndex(
    {
      name: 'orders_and_products_by_name',
      source: [q.Collection('orders'), q.Collection('products')],
      terms: [{ field: ['data', 'name'] }],
    },
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  ref: Index("orders_and_products_by_name"),
  ts: 1645051208160000,
  active: false,
  serialized: true,
  name: 'orders_and_products_by_name',
  source: [ Collection("orders"), Collection("products") ],
  terms: [ { field: [ 'data', 'name' ] } ],
  partitions: 1
}
Query metrics:
  •    bytesIn:   174

  •   bytesOut:   397

  • computeOps:     1

  •    readOps:     0

  •   writeOps:     2

  •  readBytes: 2,249

  • writeBytes: 1,324

  •  queryTime:  79ms

  •    retries:     0

Indexes which cover multiple collections may be less performant than those which cover a single index. If possible, it’s a better practice to organize your collections and queries so that multi-collection indexes are not necessary.

The Documents function

The simplest way to retrieve documents from a collection using no search criteria is the Documents function. The following example uses the Documents function to return documents in the People collection. It passes a size parameter to the Paginate function to limit results to the first three entries in the collection.

client.query(
  q.Map(
    q.Paginate(q.Documents(q.Collection('People')), { size: 3 }),
    q.Lambda('personRef', q.Get(q.Var('personRef'))),
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  after: [ Ref(Collection("People"), "323794055850886656") ],
  data: [
    {
      ref: Ref(Collection("People"), "323794055847739904"),
      ts: 1645052924890000,
      data: {
        first: 'Alan',
        last: 'Perlis',
        age: 97,
        degrees: [ 'BA', 'MA', 'PhD' ],
        letter: 'A'
      }
    },
    {
      ref: Ref(Collection("People"), "323794055848788480"),
      ts: 1645052924890000,
      data: {
        first: 'Alan',
        last: 'Turing',
        age: 107,
        degrees: [ 'BA', 'MA', 'MS', 'PhD' ],
        letter: 'B'
      }
    },
    {
      ref: Ref(Collection("People"), "323794055850885632"),
      ts: 1645052924890000,
      data: {
        first: 'Grace',
        last: 'Hopper',
        age: 119,
        degrees: [ 'BA', 'MA', 'PhD' ],
        letter: 'C'
      }
    }
  ]
}
Query metrics:
  •    bytesIn:  138

  •   bytesOut:  873

  • computeOps:    1

  •    readOps:   11

  •   writeOps:    0

  •  readBytes:  848

  • writeBytes:    0

  •  queryTime: 29ms

  •    retries:    0

Terms

Including the terms field within an index allows you to specify one or more document fields to be searchable within the index. For example, consider a collection called People in which documents have fields called first_name and last_name. If you include those fields in the terms of an index on the People collection, you can run queries which search for matches within those fields.

When a document is indexed, and all of the index’s defined terms evaluate to null, no index entry is stored for the document.

See Index terms for more details.

Values

Including the values field within an index allows you to sort documents according to the contents of specific document fields, and to return specified field values to avoid the need to Get the indexed document. If you need to sort query results from a collection called People by the last_name field, you can include the last_name field within the values definition of an index. Index values can be ordered in either ascending or descending direction.

When a document is indexed, and all of the index’s defined values evaluate to null, no index entry is stored for the document.

See Values for more details.

Uniqueness

Index definition documents have an optional unique field. If unique is set to true, then for the index’s defined terms and values, each document in the index must have unique values in the fields covered by the terms and values. For example, if a field called name is included in an index’s terms, each document must have a unique name field, and if you try to add a new document with a duplicate name field, an error occurs.

It is possible to set unique to true in an index with no terms, but it is strongly discouraged due to performance considerations. All unique indexes should have defined terms.

The following example creates a unique example on the People collection, so that each document in the collection must have a unique name field.

client.query(
  q.CreateIndex(
    {
      name: 'people_by_name',
      source: q.Collection('People'),
      terms: [
        { field: ['data', 'name'] },
      ],
      unique: true,
    },
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  ref: Index("people_by_name"),
  ts: 1649187002375000,
  active: true,
  serialized: true,
  name: 'people_by_name',
  source: Collection("People"),
  terms: [ { field: [ 'data', 'name' ] } ],
  unique: true,
  partitions: 1
}
Query metrics:
  •    bytesIn:   147

  •   bytesOut:   312

  • computeOps:     1

  •    readOps:     0

  •   writeOps:     1

  •  readBytes: 2,398

  • writeBytes:   431

  •  queryTime:  54ms

  •    retries:     0

Bindings

An index binding is a way to create a computed value for an index’s terms or values definition. Many data access patterns require searching for or reporting a computed result rather than a literal value, and bindings make that operation much faster than getting results and then computing values after the fact.

Every binding is a Lambda function specified within the source field of an index. You can perform any sort of calculation within a binding function so long as it is self-contained and doesn’t produce any side effects, such as additional reads or writes. See Bindings for more details.

Pagination

If a query finds a large number of results, you probably don’t want to deal with them all in one response. The Fauna Paginate function lets you break a result set down into smaller chunks, or "pages". Pagination allows you to specify how many results per page to return, and to move through the pages of results.

Paginate defaults to 64 results, and can return up to 100,000 results.

Multi-collection indexes

Some data access patterns may involve searching across multiple collections, and that’s possible with a multi-collection index.

Temporality and indexes

Every Fauna document includes a timestamp field (ts) which identifies when the most recent document event occurred, including when the document was created, updated, or deleted.

Index entries also store document history, including previous versions of covered documents and documents events, such as updates and deletions. Storing document history permits searching at a specific point in the past (using the At function) and receiving results consistent with that time. However, usage patterns which involve frequent document history changes can lead to rapid index growth and slower performance. Be careful when indexing collections in which documents are updated frequently, and be sure to set an appropriate value for the history_days attribute on indexed collections. See Collections for more information about setting collection attributes.

Combining and comparing sets

Fauna Query Language provides several functions for comparing and combining sets from indexes, including Union, Intersection, Join, and Filter. Read more.

Collection indexes

The simplest kind of Fauna index is a collection index. A collection index has no terms or values defined and includes all documents in its source collection. Collection indexes are useful for data access patterns in which you need to retrieve all the documents in a collection with no search criteria.

Consider using the Documents function rather than a collection index. It provides the same functionality, and avoids the storage and write operations required for a collection index.

The following example uses the all_people index to return documents in the People collection. It passes a size parameter to the Paginate function to limit results to the first three entries in the index.

client.query(
  q.Map(
    q.Paginate(q.Match(q.Index('all_people')), { size: 3 }),
    q.Lambda('ref', q.Get(q.Var('ref')))
  )
)
.then((ret) => console.log(ret))
.catch((err) => console.error(
  'Error: [%s] %s: %s',
  err.name,
  err.message,
  err.errors()[0].description,
))
{
  after: [ Ref(Collection("People"), "323071623929266688") ],
  data: [
    {
      ref: Ref(Collection("People"), "323071623916683776"),
      ts: 1644363960140000,
      data: {
        first: 'Alan',
        last: 'Perlis',
        age: 97,
        degrees: [ 'BA', 'MA', 'PhD' ],
        letter: 'A'
      }
    },
    {
      ref: Ref(Collection("People"), "323071623920878080"),
      ts: 1644363960140000,
      data: {
        first: 'Alan',
        last: 'Turing',
        age: 107,
        degrees: [ 'BA', 'MA', 'MS', 'PhD' ],
        letter: 'B'
      }
    },
    {
      ref: Ref(Collection("People"), "323071623925072384"),
      ts: 1644363960140000,
      data: {
        first: 'Grace',
        last: 'Hopper',
        age: 119,
        degrees: [ 'BA', 'MA', 'PhD' ],
        letter: 'C'
      }
    }
  ]
}
Query metrics:
  •    bytesIn:  132

  •   bytesOut:  873

  • computeOps:    1

  •    readOps:   11

  •   writeOps:    0

  •  readBytes:  776

  • writeBytes:    0

  •  queryTime: 14ms

  •    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!