- Firesearch overview
- Live tutorial
- Case studies
- Concepts
- Indexes
- Documents
- Searching
- Guides
- Deployment
- Security
- Access keys
- Solutions
- Cloud Functions
- Developer
- Client libs and SDKs
- API reference
- API Reference
- AccessKeyService
- GenerateKey
- AutocompleteService
- Complete
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- GeoIndexService EXP
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- Search
- IndexService
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- Search
- MetaService
- CheckIndexName
- CheckIndexPath
Case study: Pace
Pace is a minimalist project management tool that uses Firesearch to power its card search, conversation search, and name autocomplete.
This case study will focus on how Pace provides card search within the app.
Users can search for cards and use filters to narrow down the specific team, or the status of the card (in progress, done, etc.)
Indexes
It's important for customer data to remain siloed, so Pace uses an index per organisation.
When a new organisation is created, Pace uses the IndexService.CreateIndex
method to
programmatically create a dedicated index for that org.
We create multiple indexes per customer to serve different use cases. For example, Firesearch has a dedicated Autocomplete API, which we use to complete names in mentions and invite boxes.
Pace also distinguishes between card and conversation search, so we use an index for each.
Building the indexes
Whenever a new card is added or updated, Pace kicks off a background task (using a pubsub service) which
loads the updated card, and uses the IndexService.PutDoc
method to update its entry in the index.
The work is done in the background so it doesn't impact the user experience. The card search is therefore eventually consistent.
If a card is deleted, the background task will go and remove it from
the index using the IndexService.DeleteDoc
method.
Documents
Each card gets its own document in our customer's index.
Identifying documents
In Firesearch, each document has a unique ID. This allows us to update or delete them.
In Pace, we use the card ID as the document ID. Whenever a card changes, we know which document to update (since the IDs are the same).
The document IDs are prefixed with card-
so we can have other types of documents
without clashing IDs.
What goes into a document?
A typical search document looks like this:
{
"id": "card-123",
"title": "Title of the card",
"searchFields": [
{
"key": "title",
"value": "Title of the card",
"store": true
},
{
"key": "body",
"value": "This is the body of the card."
}
],
"fields": [
{
"key": "teamID",
"value": "playground-team-id"
},
{
"key": "cardStatus",
"value": "done"
},
{
"key": "isOpen",
"value": true
}
]
}
We include the title
and body
of the card in the searchFields
.
We set store: true
on the title
because we want to get it back
in the search results later.
We also include some additional fields
to drive our user experience.
These are explained below.
Pay attention to how documents are updated
Whenever source data changes (in our case, cards), we want to update the search index document to reflect the changes.
In Firesearch, you do not update individual fields within a document (like when a card's status changes), instead you have to put the entire document each time.
This is worth bearing in mind when you come to design your system.
In our case, a pubsub driven background task is perfect. Whenever the title, body or status of a card changes, we fire an event which indicates that we want the search document to be updated.
This means that our search is eventually consistent, although things happen pretty quickly so you don't ever notice that as a user.
Filtering
Search is best when the user actively or passively provides additional context about what they're looking for.
In our case, the user is likely searching for open cards (i.e. future work, up next, or in progress) by default.
We allow them to change this selection in the UI, which results in new filters
for our search query.
Let's take another look at those fields
:
"fields": [
{
"key": "teamID",
"value": "playground-team-id"
},
{
"key": "cardStatus",
"value": "done"
},
{
"key": "isOpen",
"value": true
}
]
- The
teamID
filter field allows us to scope search to an individual team - although this is optional at query time - The
cardStatus
filter field lets users search within specific statues (or by omitting this field from the query to search all cards) - The
isOpen
boolean filter lets us specify whether a card is open or not (i.e.status != "done"
)—omitting a value altogether in the query has the result of not filtering the results, so they would include both open and closed cards
Equality matching only
For performance reasons, Firesearch provides equality checking in filters rather than greater than, less than, not equal to, etc.
Instead of writing queries against a large selection of fields like you might in a document store, it is recommended to do the business logic work up-front, and store the results in the document.
For example, for product price ranges you might have a priceGroup
string field
that has one of a possible range of values depending on which group the item is in;
0-99
, 100-199
, 200-plus
, etc.
Conclusion
We covered how we use Firesearch at Pace to power all our customers' card search, conversation search, and name autocomplete.
- Multiple indexes per customer keeps data seperated and safe
- Machine learning brings the most relevant hits to the top
- Filtering allows us to provide even more targeted results, delivering a great user experience at high performance
Learn more about Pace
Pace is a minimalist project management and async-by-default tool for teams. With a heavy focus on simplicity, and getting out of the way to let teams work.
Get your free trial by visiting Pace.dev or drop us an email to hello@pace.dev to get a conversation started.
- Firesearch overview
- Live tutorial
- Case studies
- Concepts
- Indexes
- Documents
- Searching
- Guides
- Deployment
- Security
- Access keys
- Solutions
- Cloud Functions
- Developer
- Client libs and SDKs
- API reference
- API Reference
- AccessKeyService
- GenerateKey
- AutocompleteService
- Complete
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- GeoIndexService EXP
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- Search
- IndexService
- CreateIndex
- DeleteDoc
- DeleteIndex
- GetIndex
- GetIndexes
- PutDoc
- Search
- MetaService
- CheckIndexName
- CheckIndexPath