diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index af612eee13..b8e48f3013 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -223,6 +223,10 @@ export default { }, ], }, + { + name: 'Chat pricing', + link: '/docs/chat/pricing', + }, { name: 'Guides', pages: [ diff --git a/src/data/nav/liveobjects.ts b/src/data/nav/liveobjects.ts index 11e38a69e2..215ea8497d 100644 --- a/src/data/nav/liveobjects.ts +++ b/src/data/nav/liveobjects.ts @@ -55,11 +55,6 @@ export default { link: '/docs/liveobjects/concepts/synchronization', languages: ['javascript', 'swift', 'java'], }, - { - name: 'Billing', - link: '/docs/liveobjects/concepts/billing', - languages: ['javascript', 'swift', 'java'], - }, ], }, { @@ -104,6 +99,10 @@ export default { }, ], }, + { + name: 'LiveObjects pricing', + link: '/docs/liveobjects/pricing', + }, ], api: [ { diff --git a/src/data/nav/platform.ts b/src/data/nav/platform.ts index 8e1df1417d..a5070715b1 100644 --- a/src/data/nav/platform.ts +++ b/src/data/nav/platform.ts @@ -125,6 +125,10 @@ export default { link: '/docs/platform/pricing/billing', name: 'Billing', }, + { + link: '/docs/platform/pricing/message-counting', + name: 'Message counting', + }, { link: '/docs/platform/pricing/limits', name: 'Limits', diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts index 6936a8f9ab..ecf3d63a1f 100644 --- a/src/data/nav/pubsub.ts +++ b/src/data/nav/pubsub.ts @@ -356,6 +356,10 @@ export default { }, ], }, + { + name: 'Pub/Sub pricing', + link: '/docs/pub-sub/pricing', + }, { name: 'Guides', pages: [ diff --git a/src/data/nav/spaces.ts b/src/data/nav/spaces.ts index e230c99349..811b2054fb 100644 --- a/src/data/nav/spaces.ts +++ b/src/data/nav/spaces.ts @@ -57,6 +57,10 @@ export default { }, ], }, + { + name: 'Spaces pricing', + link: '/docs/spaces/pricing', + }, ], api: [ { diff --git a/src/pages/docs/chat/pricing.mdx b/src/pages/docs/chat/pricing.mdx new file mode 100644 index 0000000000..3d9c3af250 --- /dev/null +++ b/src/pages/docs/chat/pricing.mdx @@ -0,0 +1,69 @@ +--- +title: "Chat pricing" +meta_description: "Understand how Chat SDK features contribute to your message count, including messages, typing indicators, reactions, and cost optimization strategies." +meta_keywords: "chat pricing, message counting, typing indicators, reactions, cost optimization" +intro: "How Chat SDK features contribute to your message count and strategies to optimize costs." +redirect_from: + - /docs/chat/message-billing +--- + +The [Chat SDK](/docs/chat) is built on top of [Pub/Sub](/docs/pub-sub). All Chat operations generate Pub/Sub messages that follow the same counting rules. + + + +## Chat operations + +The following table shows how Chat operations contribute to your message count: + +| Operation | Messages counted | +| --- | --- | +| [Send message](/docs/chat/rooms/messages) | 1 inbound message | +| Message delivery | 1 outbound message per subscriber | +| [Update message](/docs/chat/rooms/messages#update) | 1 inbound message | +| Message update delivery | 1 outbound message per subscriber | +| [Delete message](/docs/chat/rooms/messages#delete) | 1 inbound message | +| Message deletion delivery | 1 outbound message per subscriber | +| [History](/docs/chat/rooms/history) retrieval | 1 message per retrieved message | +| [Typing indicator](/docs/chat/rooms/typing) keystroke | 1 inbound message; repeats every heartbeat interval (default 10s) | +| Typing indicator delivery | 1 outbound message per subscriber per heartbeat | +| [Typing indicator](/docs/chat/rooms/typing) stop | 1 inbound message | +| Typing stop delivery | 1 outbound message per subscriber | +| [Room reaction](/docs/chat/rooms/reactions) | 1 inbound message | +| Room reaction delivery | 1 outbound message per subscriber | +| [Message reaction](/docs/chat/rooms/message-reactions) send | 1 inbound message | +| [Message reaction](/docs/chat/rooms/message-reactions) delete | 1 inbound message | +| Message reaction summary delivery | 1 outbound message per subscriber; multiple reactions may be rolled up into a single summary | +| [Presence enter](/docs/chat/rooms/presence) | 1 inbound message | +| [Presence leave](/docs/chat/rooms/presence) | 1 inbound message | +| [Presence update](/docs/chat/rooms/presence) | 1 inbound message | +| Presence event delivery | 1 outbound message per presence subscriber | +| [Occupancy](/docs/chat/rooms/occupancy) event | 1 outbound message per subscriber (generated on membership changes, debounced up to 15s) | +| [Moderation](/docs/chat/moderation) action | 1 inbound message; triggers a message update or delete which follows standard delivery | + +## Rooms, channels, and connections + +Each Chat room maps to underlying Pub/Sub channels. Each room feature (messages, typing, reactions, presence, occupancy) uses its own channel, contributing to your [channel count](/docs/platform/pricing#channels). Ably bills each connected client for [connection minutes](/docs/platform/pricing#connections) in the same way as Pub/Sub. + +## Cost optimization + +### Reduce typing indicator frequency + +Increase the `heartbeatThrottleMs` [room option](/docs/chat/rooms#typing) to reduce typing indicator event frequency. The default is 10 seconds. Increase this value in rooms that tolerate delayed typing feedback. + +### Disable typing indicators + +Disable typing indicators entirely in rooms where they are not needed. This eliminates a significant source of messages, especially in rooms with many participants. + +### Use server-side batching + +[Server-side batching](/docs/guides/pub-sub/data-streaming#solution-server-side-batching) groups messages into single deliveries. Use this for high-throughput rooms where slight delay is acceptable. + +### Use occupancy instead of presence + +Use [occupancy](/docs/chat/rooms/occupancy) instead of [presence](/docs/chat/rooms/presence) when you only need member counts, not individual identities. This avoids the n-squared presence event fan-out. For example, 200 members joining and leaving generates approximately **80,400 messages**. See [large-scale presence sets](/docs/presence-occupancy/presence#large-presence) for details. + +### Enable server-side presence batching + +Enable [server-side batching](/docs/presence-occupancy/presence#large-presence) to group presence events. Subscribe to presence updates only on rooms where you need member-level detail. diff --git a/src/pages/docs/liveobjects/index.mdx b/src/pages/docs/liveobjects/index.mdx index 999df0e6aa..929e3aef74 100644 --- a/src/pages/docs/liveobjects/index.mdx +++ b/src/pages/docs/liveobjects/index.mdx @@ -96,7 +96,7 @@ LiveObjects [durably stores](/docs/liveobjects/storage) all objects on a channel LiveObjects usage is billed based on [Ably's standard pricing model](/docs/platform/pricing). Since LiveObjects is powered by Ably's channel messages, any interaction with LiveObjects - whether sending or receiving operations, maintaining active channels, or keeping connections open through the Realtime LiveObjects API - translates into billable usage. Storage of objects themselves does not incur additional costs; however, there is a [limit](/docs/liveobjects/storage) on the size of the channel object. -For details on how using LiveObjects contributes to your billable usage, see [Billing](/docs/liveobjects/concepts/billing). +For details on how using LiveObjects contributes to your billable usage, see [LiveObjects pricing](/docs/liveobjects/pricing). ### Realtime API diff --git a/src/pages/docs/liveobjects/concepts/billing.mdx b/src/pages/docs/liveobjects/pricing.mdx similarity index 68% rename from src/pages/docs/liveobjects/concepts/billing.mdx rename to src/pages/docs/liveobjects/pricing.mdx index a4d0420107..e8e7f9aea3 100644 --- a/src/pages/docs/liveobjects/concepts/billing.mdx +++ b/src/pages/docs/liveobjects/pricing.mdx @@ -1,11 +1,15 @@ --- -title: Billing -meta_description: "Understand how LiveObjects operations contribute to your Ably usage and billing." +title: "LiveObjects pricing" +meta_description: "Understand how LiveObjects operations contribute to your message count, including LiveMap, LiveCounter, synchronization, and REST API usage." +meta_keywords: "LiveObjects pricing, message counting, LiveMap, LiveCounter, ObjectMessages" +intro: "How LiveObjects operations contribute to your message count." +redirect_from: + - /docs/liveobjects/concepts/billing --- @@ -25,26 +29,38 @@ meta_description: "Understand how LiveObjects operations contribute to your Ably -LiveObjects operations are billed as messages. This page explains how different LiveObjects operations contribute to your Ably usage. +LiveObjects operations are billed using ObjectMessages. Each ObjectMessage follows the standard inbound/outbound counting pattern. -## Message counting +## LiveObjects operations -Each operation is sent as an [`ObjectMessage`](/docs/liveobjects/concepts/operations#properties). Ably bills for each outbound message sent from a client, and for each inbound message delivered to each client. +The following table shows how LiveObjects operations contribute to your message count: -When a client performs an operation (such as setting a value on a `LiveMap` or incrementing a `LiveCounter`), this generates an outbound message. When that operation is broadcast to other connected clients, each client receives an inbound message. +| Operation | Messages counted | +| --- | --- | +| [LiveMap](#livemap-operations) set or remove | 1 inbound message per operation | +| [LiveMap](#livemap-operations) create (shallow) | 2 inbound messages (create + assign) | +| [LiveCounter](#livecounter-operations) increment or decrement | 1 inbound message | +| [LiveCounter](#livecounter-operations) create | 2 inbound messages (create + assign) | +| ObjectMessage delivery | 1 outbound message per connected client | +| [Synchronization](#synchronization) | 1 message per object synchronized | +| [REST API](#rest-api) fetch | 1 message per object in response | +| [Batch](#livemap-operations) operation | 1 message per operation in the batch | -For example, if 5 clients are connected to a channel and one client increments a counter: +Nested object creation generates additional messages for each nested object. See the [examples below](#livemap-operations) for details. -- 1 outbound message (from the client performing the increment) -- 5 inbound messages (one delivered to each of the 5 connected clients, including the client that sent it) +## Channels and connections + +Each LiveObjects channel contributes to your [channel count](/docs/platform/pricing#channels). Ably bills [connection minutes](/docs/platform/pricing#connections) per minute of connection time for each connected client. + +Subscribing to updates does not affect the number of messages received by a client. Any client attached to a channel with the `object-subscribe` capability automatically receives all object messages for that channel. Subscribing to updates on an object adds a listener that is called whenever the client receives updates for that object. -## LiveMap operations +## LiveMap operation examples Removing a key and setting a primitive value always sends one message: @@ -100,7 +116,7 @@ await myObject.get('settings').batch((ctx) => { ``` -## LiveCounter operations +## LiveCounter operation examples Incrementing and decrementing a counter always sends one message: @@ -165,10 +181,6 @@ Similarly, if a client becomes disconnected and needs to resynchronize, it will Only [reachable](/docs/liveobjects/concepts/objects#reachability) objects are counted. Ably may send [tombstone](/docs/liveobjects/concepts/objects#tombstones) objects to the client, but these will not count towards your usage. -## Subscriptions - -Subscribing to updates does not affect the number of messages received by a client. Any client attached to a channel with the `object-subscribe` capability automatically receives all object messages for that channel. Subscribing to updates on an object adds a listener that is called whenever the client receives updates for that object. - ## REST API The [LiveObjects REST API](/docs/liveobjects/rest-api-usage) also counts messages for operations performed. diff --git a/src/pages/docs/platform/pricing/message-counting.mdx b/src/pages/docs/platform/pricing/message-counting.mdx new file mode 100644 index 0000000000..86b1642648 --- /dev/null +++ b/src/pages/docs/platform/pricing/message-counting.mdx @@ -0,0 +1,156 @@ +--- +title: "Message counting" +meta_description: "Understand which operations across all Ably products count as messages, including Pub/Sub, Chat, LiveObjects, Spaces, AI Transport, and LiveSync." +meta_keywords: "message counting, pricing, Pub/Sub messages, Chat messages, LiveObjects messages, Spaces messages, AI Transport messages, LiveSync messages" +intro: "Messages are the primary unit of consumption that Ably charges for. Understanding which operations count as messages helps you estimate and manage costs." +--- + + + +## How Ably counts messages + +As a general rule, most operations involve publishing and receiving messages. Each operation generates: + +* 1 inbound message when a client publishes or performs an operation +* 1 outbound message for each subscriber that receives it + +For example, if 1 client publishes a message to a channel with 10 subscribers, this counts as **11 messages** (1 inbound + 10 outbound). + +By default, [echo messages](/docs/pub-sub/advanced#echo) are enabled, which means the publisher also receives its own message as an outbound delivery. In the example above, echo adds 1 outbound message for a total of **12 messages**. + +Client-side [message filtering](/docs/api/realtime-sdk/channels#subscribe) does not reduce your message count. Ably counts all messages delivered to a connection, regardless of whether the client filters them. + +The tables below list all operations that count as messages. Unless noted otherwise, each operation follows this inbound/outbound pattern. `Time` and `Ping` operations are not counted. + +## Message size + +Ably calculates message size as the sum of the `name`, `clientId`, `data`, and `extras` [properties](/docs/api/realtime-sdk/messages#properties) before any compression or expansion occurs in the serialization process. This applies to all products built on Pub/Sub, including Chat. + +* `name` and `clientId` are calculated as the size in bytes of their UTF-8 representation. +* `data` is calculated as the size in bytes if it is binary, or its UTF-8 byte length if it is a string. +* `extras` is calculated as the string length of its JSON representation. + +Ably bills messages in 5KiB chunks. A message up to 5KiB counts as 1 message. A 50KiB message counts as 10 messages, and a 16KiB message counts as 4 messages. + +Each package has a bandwidth allowance based on an average message size of 5KiB. If your total bandwidth for the month exceeds this baseline, Ably charges overage per GiB. See [maximum message size](/docs/platform/pricing/limits#message) for the size limits per package type. + +## Pub/Sub operations + +The following table shows how [Pub/Sub](/docs/pub-sub) operations contribute to your message count: + +| Operation | Messages counted | +| --- | --- | +| [Publish](/docs/channels/messages#publish) | 1 inbound message | +| Message delivery | 1 outbound message per subscriber | +| [Presence enter](/docs/presence-occupancy/presence) | 1 inbound message | +| [Presence leave](/docs/presence-occupancy/presence) | 1 inbound message | +| [Presence update](/docs/presence-occupancy/presence) | 1 inbound message | +| Presence event delivery | 1 outbound message per presence subscriber | +| [Persistence](/docs/storage-history/storage) storage | 1 additional message per stored message | +| [History](/docs/storage-history/history) retrieval | 1 message per retrieved message | +| [Integration](/docs/platform/integrations) delivery | 1 outbound message per integration target | +| [Rewind](/docs/channels/options/rewind) on attach | 1 message per rewound message (up to 100) | +| [Push notification](/docs/push) delivery | 1 message per delivered notification | +| [Presence REST](/docs/presence-occupancy/presence) query | 1 message per member returned | +| [Batch presence](/docs/presence-occupancy/presence) request | 1 message per member across all queried channels | +| [Annotation](/docs/messages/annotations) publish | 1 inbound message | +| [Annotation](/docs/messages/annotations) delete | 1 inbound message | +| [Annotation summary](/docs/messages/annotations#annotation-summaries) delivery | 1 outbound message per subscriber; multiple annotations may be rolled up into a single summary | +| [Lifecycle event](/docs/metadata-stats/metadata) (`[meta]connection.lifecycle`, `[meta]channel.lifecycle`) | 1 message per event | +| [`[meta]stats:minute`](/docs/metadata-stats/metadata) event | 1 message per event | +| [`[meta]log`](/docs/metadata-stats/metadata) subscription | Not counted | + +For Pub/Sub-specific cost optimization strategies, see [Pub/Sub pricing](/docs/pub-sub/pricing). + +## Chat operations + +The [Chat SDK](/docs/chat) is built on top of [Pub/Sub](/docs/pub-sub). All Chat operations generate Pub/Sub messages that follow the same counting rules. + +| Operation | Messages counted | +| --- | --- | +| [Send message](/docs/chat/rooms/messages) | 1 inbound message | +| Message delivery | 1 outbound message per subscriber | +| [Update message](/docs/chat/rooms/messages#update) | 1 inbound message | +| Message update delivery | 1 outbound message per subscriber | +| [Delete message](/docs/chat/rooms/messages#delete) | 1 inbound message | +| Message deletion delivery | 1 outbound message per subscriber | +| [History](/docs/chat/rooms/history) retrieval | 1 message per retrieved message | +| [Typing indicator](/docs/chat/rooms/typing) keystroke | 1 inbound message; repeats every heartbeat interval (default 10s) | +| Typing indicator delivery | 1 outbound message per subscriber per heartbeat | +| [Typing indicator](/docs/chat/rooms/typing) stop | 1 inbound message | +| Typing stop delivery | 1 outbound message per subscriber | +| [Room reaction](/docs/chat/rooms/reactions) | 1 inbound message | +| Room reaction delivery | 1 outbound message per subscriber | +| [Message reaction](/docs/chat/rooms/message-reactions) send | 1 inbound message | +| [Message reaction](/docs/chat/rooms/message-reactions) delete | 1 inbound message | +| Message reaction summary delivery | 1 outbound message per subscriber; multiple reactions may be rolled up into a single summary | +| [Presence enter](/docs/chat/rooms/presence) | 1 inbound message | +| [Presence leave](/docs/chat/rooms/presence) | 1 inbound message | +| [Presence update](/docs/chat/rooms/presence) | 1 inbound message | +| Presence event delivery | 1 outbound message per presence subscriber | +| [Occupancy](/docs/chat/rooms/occupancy) event | 1 outbound message per subscriber (generated on membership changes, debounced up to 15s) | +| [Moderation](/docs/chat/moderation) action | 1 inbound message; triggers a message update or delete which follows standard delivery | + +For Chat-specific cost optimization strategies, see [Chat pricing](/docs/chat/pricing). + +## LiveObjects operations + +[LiveObjects](/docs/liveobjects) operations are billed using ObjectMessages. Each ObjectMessage follows the standard inbound/outbound counting pattern. + +| Operation | Messages counted | +| --- | --- | +| [LiveMap](/docs/liveobjects/pricing#livemap-operations) set or remove | 1 inbound message per operation | +| [LiveMap](/docs/liveobjects/pricing#livemap-operations) create (shallow) | 2 inbound messages (create + assign) | +| [LiveCounter](/docs/liveobjects/pricing#livecounter-operations) increment or decrement | 1 inbound message | +| [LiveCounter](/docs/liveobjects/pricing#livecounter-operations) create | 2 inbound messages (create + assign) | +| ObjectMessage delivery | 1 outbound message per connected client | +| [Synchronization](/docs/liveobjects/pricing#synchronization) | 1 message per object synchronized | +| [REST API](/docs/liveobjects/pricing#rest-api) fetch | 1 message per object in response | +| [Batch](/docs/liveobjects/pricing#livemap-operations) operation | 1 message per operation in the batch | + +Nested object creation generates additional messages for each nested object. See the [LiveObjects pricing](/docs/liveobjects/pricing) page for detailed examples. + +## Spaces operations + +The [Spaces SDK](/docs/spaces) is built on top of [Pub/Sub](/docs/pub-sub) channels and [presence](/docs/presence-occupancy/presence). All Spaces operations generate Pub/Sub messages that follow the same counting rules. + +| Operation | Messages counted | +| --- | --- | +| [Enter](/docs/spaces/space#enter) space | 1 inbound message | +| [Leave](/docs/spaces/space#leave) space | 1 inbound message | +| [Update profile](/docs/spaces/space#update-profile) | 1 inbound message | +| Space event delivery | 1 outbound message per subscriber | +| [Set location](/docs/spaces/locations#set) | 1 inbound message | +| Location event delivery | 1 outbound message per subscriber | +| [Set cursor position](/docs/spaces/cursors#set) | 1 inbound message per batch (default batch interval 25ms) | +| Cursor event delivery | 1 outbound message per subscriber | +| [Lock acquire](/docs/spaces/locking#acquire) | 1 inbound message | +| [Lock release](/docs/spaces/locking#release) | 1 inbound message | +| Lock event delivery | 1 outbound message per subscriber | + +Live cursors use a [separate channel](/docs/spaces/cursors#foundations) from other space features due to their high update frequency. Registering multiple subscription listeners for the same event does not increase your message count, as these are [client-side filtered events](/docs/spaces/space#subscribe). + +## AI Transport operations + +[AI Transport](/docs/ai-transport) uses standard Pub/Sub messaging. Each token or message published to Ably counts as an inbound message, and each delivery to a subscriber counts as an outbound message. + +| Operation | Messages counted | +| --- | --- | +| Token publish | 1 inbound message per published message | +| Token delivery | 1 outbound message per subscriber | +| [Persistence](/docs/storage-history/storage) storage | 1 additional message per stored message | + +The total cost depends on the [token streaming pattern](/docs/ai-transport/token-streaming#token-streaming-patterns) you choose. With [message-per-response](/docs/ai-transport/token-streaming/message-per-response), [append rollup](/docs/ai-transport/token-streaming/token-rate-limits#per-response) conflates multiple tokens into fewer inbound messages. For example, 300 tokens published individually can be conflated to approximately 100 inbound messages. See the [AI support chatbot pricing example](/docs/platform/pricing/examples/ai-chatbot) for a full breakdown. + +## LiveSync operations + +[LiveSync](/docs/livesync) publishes database changes as Pub/Sub messages. Each update follows the standard inbound/outbound counting pattern. + +| Operation | Messages counted | +| --- | --- | +| Database update publish | 1 inbound message | +| Update delivery | 1 outbound message per subscriber | + +For example, if the database connector publishes 1 update and 3 clients are subscribed, this counts as **4 messages** (1 inbound + 3 outbound). diff --git a/src/pages/docs/pub-sub/pricing.mdx b/src/pages/docs/pub-sub/pricing.mdx new file mode 100644 index 0000000000..27282326ef --- /dev/null +++ b/src/pages/docs/pub-sub/pricing.mdx @@ -0,0 +1,74 @@ +--- +title: "Pub/Sub pricing" +meta_description: "Understand how Pub/Sub operations contribute to your message count, including persistence, presence, and cost optimization strategies." +meta_keywords: "message counting, pricing, persistence, presence, cost optimization, Pub/Sub pricing" +intro: "How Pub/Sub operations contribute to your message count and strategies to optimize costs." +redirect_from: + - /docs/pub-sub/message-billing +--- + +Pub/Sub operations contribute to your message count based on publishing, delivery, and optional features like persistence and presence. + + + +## Pub/Sub operations + +The following table shows how Pub/Sub operations contribute to your message count: + +| Operation | Messages counted | +| --- | --- | +| [Publish](/docs/channels/messages#publish) | 1 inbound message | +| Message delivery | 1 outbound message per subscriber | +| [Presence enter](/docs/presence-occupancy/presence) | 1 inbound message | +| [Presence leave](/docs/presence-occupancy/presence) | 1 inbound message | +| [Presence update](/docs/presence-occupancy/presence) | 1 inbound message | +| Presence event delivery | 1 outbound message per presence subscriber | +| [Persistence](/docs/storage-history/storage) storage | 1 additional message per stored message | +| [History](/docs/storage-history/history) retrieval | 1 message per retrieved message | +| [Integration](/docs/platform/integrations) delivery | 1 outbound message per integration target | +| [Rewind](/docs/channels/options/rewind) on attach | 1 message per rewound message (up to 100) | +| [Push notification](/docs/push) delivery | 1 message per delivered notification | +| [Presence REST](/docs/presence-occupancy/presence) query | 1 message per member returned | +| [Batch presence](/docs/presence-occupancy/presence) request | 1 message per member across all queried channels | +| [Annotation](/docs/messages/annotations) publish | 1 inbound message | +| [Annotation](/docs/messages/annotations) delete | 1 inbound message | +| [Annotation summary](/docs/messages/annotations#annotation-summaries) delivery | 1 outbound message per subscriber; multiple annotations may be rolled up into a single summary | +| [Lifecycle event](/docs/metadata-stats/metadata) (`[meta]connection.lifecycle`, `[meta]channel.lifecycle`) | 1 message per event | +| [`[meta]stats:minute`](/docs/metadata-stats/metadata) event | 1 message per event | +| [`[meta]log`](/docs/metadata-stats/metadata) subscription | Not counted | + +## Channels and connection minutes + +[Channels](/docs/channels) are the unit of message distribution. Each channel you use contributes to your [channel count](/docs/platform/pricing#channels). Ably bills [connection minutes](/docs/platform/pricing#connections) per minute of connection time for each connected client. + +## Cost optimization + +### Use ephemeral messages + +Mark messages as [ephemeral](/docs/pub-sub/advanced#ephemeral) to exempt them from persistence, rewind, resume, and integrations. Use this for streaming data where history is not needed. + +### Disable self-delivery + +Set [echoMessages: false](/docs/pub-sub/advanced#echo) to prevent messages from being delivered back to the publisher. This is useful for optimistic UI patterns and server-side publishers. + +### Enable conflation + +[Conflation](/docs/guides/pub-sub/data-streaming#solution-message-conflation) delivers only the latest message per key in each time window. Use this for high-frequency updates where only the latest value matters. + +### Use server-side batching + +[Server-side batching](/docs/guides/pub-sub/data-streaming#solution-server-side-batching) groups messages into single deliveries. Use this for high-throughput channels where slight delay is acceptable. + +### Enable delta compression + +[Delta compression](/docs/channels/options/deltas) reduces payload size, lowering bandwidth costs. Use this for large, structurally similar successive messages. + +### Use occupancy instead of presence + +Use [occupancy](/docs/presence-occupancy/occupancy) instead of presence when you only need member counts, not individual identities. This avoids the n-squared presence event fan-out where each member event is delivered to every subscriber. For example, 200 members joining and leaving a channel generates approximately **80,400 messages**. See [large-scale presence sets](/docs/presence-occupancy/presence#large-presence) for the full calculation. + +### Enable server-side presence batching + +Enable [server-side batching](/docs/presence-occupancy/presence#large-presence) to group presence events and support up to 20,000 members per channel. Subscribe to presence updates only on channels where you need member-level detail. diff --git a/src/pages/docs/spaces/pricing.mdx b/src/pages/docs/spaces/pricing.mdx new file mode 100644 index 0000000000..fdabb494fa --- /dev/null +++ b/src/pages/docs/spaces/pricing.mdx @@ -0,0 +1,50 @@ +--- +title: "Spaces pricing" +meta_description: "Understand how Spaces SDK features contribute to your message count, including avatar stacks, member location, live cursors, and component locking." +meta_keywords: "Spaces pricing, message counting, live cursors, avatar stacks, component locking" +intro: "How Spaces SDK features contribute to your message count and strategies to optimize costs." +--- + +The [Spaces SDK](/docs/spaces) is built on top of [Pub/Sub](/docs/pub-sub) channels and [presence](/docs/presence-occupancy/presence). All Spaces operations generate Pub/Sub messages that follow the same counting rules. + + + +## Spaces operations + +The following table shows how Spaces operations contribute to your message count: + +| Operation | Messages counted | +| --- | --- | +| [Enter](/docs/spaces/space#enter) space | 1 inbound message | +| [Leave](/docs/spaces/space#leave) space | 1 inbound message | +| [Update profile](/docs/spaces/space#update-profile) | 1 inbound message | +| Space event delivery | 1 outbound message per subscriber | +| [Set location](/docs/spaces/locations#set) | 1 inbound message | +| Location event delivery | 1 outbound message per subscriber | +| [Set cursor position](/docs/spaces/cursors#set) | 1 inbound message per batch (default batch interval 25ms) | +| Cursor event delivery | 1 outbound message per subscriber | +| [Lock acquire](/docs/spaces/locking#acquire) | 1 inbound message | +| [Lock release](/docs/spaces/locking#release) | 1 inbound message | +| Lock event delivery | 1 outbound message per subscriber | + +Registering multiple subscription listeners for the same event does not increase your message count. These are [client-side filtered events](/docs/spaces/space#subscribe) where only a single message is published per event by Ably. + +## Channels and connections + +Each space maps to an underlying Pub/Sub [channel](/docs/channels). Live cursors use a [separate channel](/docs/spaces/cursors#foundations) from other space features due to their high update frequency. Both channels contribute to your [channel count](/docs/platform/pricing#channels). Ably bills [connection minutes](/docs/platform/pricing#connections) per minute of connection time for each connected client. + +## Cost optimization + +### Increase cursor batch interval + +Increase the [`outboundBatchInterval`](/docs/spaces/cursors#batch) to reduce the frequency of cursor position updates. The default is 25ms. Increasing this value reduces the number of messages at the cost of less smooth cursor movement. + +### Limit concurrent cursor streaming + +Ably recommends a maximum of [20 members](/docs/spaces/cursors) simultaneously streaming their cursors in a space for an optimal end-user experience. Beyond this, the volume of cursor messages increases significantly. + +### Use occupancy instead of presence for large spaces + +For spaces with many members where you only need a count of active users, consider using [occupancy](/docs/presence-occupancy/occupancy) on the underlying channel instead of tracking individual presence events.