Skip to main content

Database events

Structure

The structure of a database change event depends on the model and the kind of operation that was performed. You can learn more in the API reference.

The examples below are based on this User model:

model User {
id Int @id @default(autoincrement())
name String?
email String @unique
}

All events have the following fields in common:

  • id: Unique identifier
  • modelName: The model on which this was performed, e.g. User or Post
  • action: The kind of event that was performed, either of the following: create, update, delete

Depending on the kind of event, there may be additional fields in the event objects received via Prisma Pulse. See the following sections for a few sample events.

Create events

Here is an example of an event object you may receive when a new record is created:

{
action: 'create',
created: { id: 3, email: 'jane@prisma.io', name: 'Jane Doe' },
id: '0/2A5A590',
modelName: 'User'
}

Update events

Here is an example of an event object you may receive when a record is updated:

{
action: 'update',
after: { id: 2, email: 'jane@prisma.io', name: 'Jane Doe' },
before: null,
id: '0/2A5A248',
modelName: 'User'
}

If you want the before field to carry the values of the record before it was updated, you need to set REPLICA IDENTITY to FULL as explained here. In that case, the event object may look as follows:

{
action: 'update',
after: { id: 2, email: 'jane@prisma.io', name: 'Jane Doe' },
before: { id: 2, email: 'jane@prisma.io', name: 'Jane' },
id: '0/2A5A248',
modelName: 'User'
}

Delete events

Here is an example of an event object you may receive when a record is deleted:

 {
action: 'delete',
deleted: { id: 1 },
id: '0/2A5A398',
modelName: 'User'
}

If you want the deleted field to carry the values of the record that was deleted, you need to set REPLICA IDENTITY to FULL as explained here. Otherwise it will only carry the id value of the record. In that case, the event object may look as follows:

 {
action: 'delete',
deleted: { id: 21, email: 'jane@prisma.io', name: 'Jane Doe' },
id: '0/2A5A398',
modelName: 'User'
}

Delivery semantics

This section gives an overview of the event delivery semantics of Prisma Pulse.

What are event delivery semantics?

Event delivery semantics describe the guarantees an event producer can provide about the delivery of events in an event-driven architecture.

There generally are three kinds of delivery guarantees:

  • At most once: The event is delivered either once or not at all.
  • At least once: The event is delivered one or more times, ensuring it never goes undelivered.
  • Exactly once: The event is delivered exactly once, avoiding duplicates.

Event delivery semantics in Prisma Pulse

Here is a summary of the event delivery semantics in Prisma Pulse:

stream()subscribe()
Requires event persistenceYesNo
Delivery guaranteesAt least onceAt most once
Event orderSame order as events were producedMaybe different order than events were produced
Can "replay" missed eventsYesNo
warning

Note that if an events exceeds the size limit for your subscription plan, the event will be rejected and won't make it to your application..

stream()

When using stream(), Prisma Pulse provides the following delivery guarantees:

At least once delivery

When streaming database change events with stream(), database change events are guaranteed to be delivered with at least once semantics, meaning that Prisma Pulse can guarantee that any event happening in the database will be delivered one or more times.

Delivery of events in the right order

Prisma Pulse further guarantees to deliver the database change events in the order they were produced.

Exactly once delivery

While Pulse by default offers at least once semantics, it also provides the primitives for you to implement exactly once delivery guarantees yourself!

Each event produced by Prisma Pulse carries an identifier/idempotency key that you can use to deduplicate during downstream processing of the event. This is best implemented by passing the identifier along to external services that support idempotency or using concepts like upsert when working with a database.

The id field in this event payload for a User model represent the identifier/idempotency key:

{
action: 'update',
after: { id: 1, name: 'Jane', email: "doe@prisma.io" },
before: null,
id: '01HYBEER1JPSBVPG2NQADNQTA6',
modelName: 'User'
}

subscribe()

At most once delivery

When streaming database change events with .subscribe(), database change events are guaranteed to be delivered with at most once semantics, meaning that some of the database events may get lost.

No guarantees about the order of the events

subscribe() doesn't provide any guarantees about the order in which events will arrive.

Event persistence

You can configure Event persistence for Pulse in your Console project. Only with Event persistence enabled, you will be able to take advantage of at least once and right order delivery guarantees by Prisma Pulse via the stream() API.

What events are persisted?

As soon as you enable Event persistence for Pulse in your Console project, Pulse will store all database events from all tables.

In what shape are events being persisted?

Events are being persisted in the same structure that they're delivered.

How does event persistence impact pricing?

With event persistence enabled, pricing is impacted as follows:

  • Database events: The number of database events captured by Pulse
  • Events reads: The number of database events read and delivered by Pulse via .stream()
  • Event storage: The amount of disk space the stored events consume (in GiB)

See the subscription plans for more details. Pricing applies regardless of whether you use subscribe() or stream().

Resuming event streams

The stream() API offers the option to provide a name argument which makes a stream resumable:

const stream = await prisma.user.stream({
name: "all-user-events"
})

If a name is provided, Pulse tracks the delivery of events with a cursor. Only if the event is acknowledged on the receiver side, the cursor associated with that name will move.

In case a stream is unavailable for some reason, e.g. because your server was down, the receiver can't acknowledge any events. Once the stream is available again, the stream will pick up at the last cursor position and deliver any events that haven't been acknowledged in the meantime:

If the name option is omitted, no cursor will be associated with the stream and events that happen while a stream is down will not be delivered:`