Skip to content

Commit

Permalink
Misc docs improvements (#32605)
Browse files Browse the repository at this point in the history
* Add a bit more clarity about Convex in the tutorial
* Add some best practices to the Actions doc
* Other small improvements

GitOrigin-RevId: ed01687302fb47be3eccfa886ff28abdf0314ca1
  • Loading branch information
ikhare authored and Convex, Inc. committed Dec 23, 2024
1 parent 7f84105 commit a0204c3
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
sidebar_label: "Indexes and Query Performance"
title: "Introduction to Indexes and Query Performance"
sidebar_position: 100
todo: Suggesting n00bs read all this first is bad, remove from parent
---

How do I ensure my Convex
Expand Down
13 changes: 12 additions & 1 deletion docs/functions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@ There are three types of functions:
database and are automatically cached and subscribable (realtime, reactive).
- [Mutations](/functions/mutation-functions.mdx) write data to the database and
run as a transaction.
- [Actions](/functions/actions.mdx) can call Open AI, Stripe, Twilio, or any
- [Actions](/functions/actions.mdx) can call OpenAI, Stripe, Twilio, or any
other service or API you need to make your app work.

You can also build [HTTP actions](/docs/functions/http-actions.mdx) when you
want to call your functions from a webhook or a custom client.

Here's an overview of the three different types of Convex functions and what
they can do:

| | Queries | Mutations | Actions |
| -------------------------- | ------- | --------- | ------- |
| Database access | Yes | Yes | No |
| Transactional | Yes | Yes | No |
| Cached | Yes | No | No |
| Real-time Updates | Yes | No | No |
| External API Calls (fetch) | No | No | Yes |
44 changes: 44 additions & 0 deletions docs/functions/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,47 @@ Make sure to await all promises created within an action. Async tasks still
running when the function returns might or might not complete. In addition,
since the Node.js execution environment might be reused between action calls,
dangling promises might result in errors in subsequent action invocations.

## Best practices

### `await ctx.runAction` should only be used for crossing JS runtimes

**Why?** `await ctx.runAction` incurs to overhead of another Convex server
function. It counts as an extra function call, it allocates it's own system
resources, and while you're awaiting this call the parent action call is frozen
holding all it's resources. If you pile enough of these calls on top of each
other, your app may slow down significantly.

**Fix:** The reason this api exists is to let you run code in the
[Node.js environment](functions/runtimes). If you want to call an action from
another action that's in the same runtime, which is the normal case, the best
way to do this is to pull the code you want to call into a TypeScript
[helper function](/docs/production/best-practices/best-practices.mdx#use-helper-functions-to-write-shared-code)
and call the helper instead.

### Avoid `await ctx.runMutation` / `await ctx.runQuery`

```ts
//
const foo = await ctx.runQuery(...)
const bar = await ctx.runQuery(...)

//
const fooAndBar = await ctx.runQuery(...)
```

**Why?** Multiple runQuery / runMutations execute in separate transactions and
aren’t guaranteed to be consistent with each other (e.g. foo and bar could read
the same document and return two different results), while a single runQuery /
runMutation will always be consistent. Additionally, you’re paying for multiple
function calls when you don’t have to.

**Fix:** Make a new internal query / mutation that does both things. Refactoring
the code for the two functions into helpers will make it easy to create a new
internal function that does both things while still keeping around the original
functions. Potentially try and refactor your action code to “batch” all the
database access.

Caveats: Separate runQuery / runMutation calls are valid when intentionally
trying to process more data than fits in a single transaction (e.g. running a
migration, doing a live aggregate).
3 changes: 1 addition & 2 deletions docs/search/search.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ In the example above, the expression `search("body", "hello hi")` would
internally be split into `"hi"` and `"hello"` and matched against words in your
document (ignoring case and punctuation).

The behavior of search incorporates
[fuzzy and prefix matching rules](#search-behavior).
The behavior of search incorporates [prefix matching rules](#search-behavior).

### Equality expressions

Expand Down
14 changes: 8 additions & 6 deletions docs/tutorial/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,22 @@ Go ahead, now play with your app!
Queries and mutations are the only ways to interact with the database and the
scheduler enables building sophisticated workflows with actions in between.

Actions are just an escape hatch. It's the reality of dealing with the messy
outside world that has little to no guarantees.
[Actions](/docs/functions/actions.mdx) are normal serverless functions like AWS
Lambda and Google Cloud Run. They help model flows like calling AI APIs and
using the Vector Store. They serve as an escape hatch. They deal with the
reality of the messy outside world with few guarantees.

Actions are not part of the sync engine. To talk to the database they have to
talk through query and mutation functions. This restriction lets Convex enforce
transactional guarantees in the database and keep the sync engine fast and
nimble.

The best way to structure your application for scale is to minimize the work
that happens in an action to only the part that needs the
that happens in an action. Only the part that needs the
[non-determinism](https://en.wikipedia.org/wiki/Deterministic_algorithm), like
making the external `fetch` call. In an ideal scenario most actions are about as
simple as the action in this tutorial. This is the most scalable architecture in
Convex, enabling the highest throughput.
making the external `fetch` call should use them. Keeping them as small as
possible is the most scalable way to build Convex apps, enabling the highest
throughput.

The scheduler allows your app to keep most of its important logic in queries and
mutations and structure your code as workflows in and out of actions.
Expand Down
19 changes: 13 additions & 6 deletions docs/tutorial/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ During setup, you'll see that Convex uses your GitHub account for
authentication. Sign into Convex with GitHub and then accept the default project
setup prompts.

This will create your backend and a folder called `convex/` in your project,
where you'll write your backend code.
This will **automatically create your backend** and a folder called `convex/` in
your project, where you'll write your backend code.

**Make sure you keep this command (`npm run dev`) running in the background
throughout this tutorial.** It's running both the dev web server for the
Expand All @@ -79,9 +79,10 @@ of how Convex works.
</div>

**Database.** The Convex database is a document-relational database, which means
you have tables with documents in them. All documents have an id that can be
used to create relations between documents. You interact with the database
through query and mutation functions that are written entirely in TypeScript.
you have tables with JSON like documents in them. All documents have an
auto-generated `_id` that can be used to create relations between documents. You
interact with the database through mutation and query functions that are written
entirely in TypeScript.

**Mutation functions.** Mutations are TypeScript functions that update the
database. All mutation functions in Convex run as a database transaction. So
Expand All @@ -92,7 +93,8 @@ the database. As we'll see in a bit, you subscribe to them from your frontend to
keep your app automatically up to date.

Your frontend registers to listen to query updates through the **client
library**.
library**. The client libraries talk to Convex via WebSockets for fast realtime
updates.

The **sync engine** reruns query functions when any input to the function
changes, including any changes to the documents in the database that the query
Expand Down Expand Up @@ -210,6 +212,11 @@ In Convex, [schemas](/docs/database/schemas.mdx) are optional. Eventually,
you'll want to enforce the structure of your tables, but for the purposes of the
tutorial we'll skip this.

In the dashboard you can also go to the
[logs screen](https://dashboard.convex.dev/deployment/logs) and see every call
to the mutation as you ran with the log line we added earlier. The logs screen
is a critical part of debugging your backend in development.

You've successfully created a `mutation` function, which is also a database
transaction, and connected it to your UI.

Expand Down

0 comments on commit a0204c3

Please sign in to comment.