-
Couldn't load subscription status.
- Fork 10
Sync bucket priorities #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
30b47ee
1a182d5
af6db34
1a77a2b
23f770c
628f97b
8293a51
60c6e14
362d1e0
829d534
d07b32e
162780c
6cb53ab
a8a3e9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,48 +3,164 @@ | |
| description: "In some scenarios, you may want to sync tables using different priorities. For example, you may want to sync a subset of all tables first to log a user in as fast as possible, then sync the remaining tables in the background." | ||
| --- | ||
|
|
||
| <Info> | ||
| Note that this strategy is specifically to prioritize data on initial sync, and cannot be used for incremental sync after that. | ||
| </Info> | ||
| # Overview | ||
|
|
||
| ## Overview | ||
| PowerSync supports defining [Sync Bucket](/usage/sync-rules/organize-data-into-buckets) Priorities, which allows you to control the sync order for different data sets. This is particularly useful when certain data should be available sooner than others. | ||
|
|
||
| The general approach is as follows: | ||
| # Why Use Sync Bucket Priorities? | ||
|
|
||
| 1. Define how many priority types you want - typically only two are needed: "high priority" and "the rest" | ||
| PowerSync's standard sync protocol ensures that: | ||
| - The local data view is only updated when a fully consistent checkpoint is available. | ||
| - All pending local changes must be uploaded, acknowledged, and synced back before new data is applied. | ||
|
|
||
| 2. Create a sync bucket for each priority type | ||
| While this guarantees consistency, it can lead to delays, especially for large datasets or continuous client-side updates. Sync Bucket Priorities provide a way to speed up syncing of high-priority data while still maintaining overall integrity. | ||
|
|
||
| 3. Use [client parameters](/usage/sync-rules/advanced-topics/client-parameters) to control which priorities you want the client to sync | ||
| # How It Works | ||
|
|
||
| ## Example | ||
| Each sync bucket is assigned a priority value between 0 and 3, where: | ||
|
|
||
| Suppose we have two tables: `lists` and `todos` (as per the standard todolist demo app [schema](/integration-guides/supabase-+-powersync#create-the-demo-database-schema)). Further, suppose we want the sync priority to behave as follows: | ||
| - 0 is the highest priority and has special behavior (detailed below). | ||
| - 3 is the default and lowest priority. | ||
| - Lower numbers indicate higher priority. | ||
|
|
||
| 1. First, sync all the user's lists, enabling us to render the initial screen in the app | ||
| Buckets with higher priorities sync first, and lower-priority buckets sync later. It's worth noting that if you only use a single priority, there is no difference between priorities 1-3. The difference only comes in if you use multiple different priorities. | ||
|
|
||
| 2. Then, sync the user's todos | ||
| # Syntax and Configuration | ||
|
|
||
| Below are the sync rules that will enable this: | ||
| Priorities can be defined for a bucket using the `priority` YAML key, or with the `_priority` attribute inside parameter queries: | ||
|
|
||
| ```yaml | ||
| bucket_definitions: | ||
| # always sync high priority tables (first), in this case the user's lists | ||
| high_priority: | ||
| parameters: select id as list_id from lists where owner_id = token_parameters.user_id | ||
| data: | ||
| - select * from lists where id = bucket.list_id | ||
| # sync any remaining tables, in this case todo items | ||
| remaining: | ||
| parameters: select id as list_id from lists where owner_id = token_parameters.user_id and (request.parameters() ->> 'remaining_tables' = true) | ||
| # Using the `priority` YAML key | ||
| user_data: | ||
| priority: 1 | ||
| parameters: SELECT request.user_id() as id where...; | ||
| data: | ||
| # ... | ||
|
|
||
| # Using the `_priority` attribute | ||
| project_data: | ||
| parameters: select id as project_id, 2 as _priority from projects where ...; # This approach is useful when you have multiple parameter queries with different priorities. | ||
| data: | ||
| - select * from todos where list_id = bucket.list_id | ||
| # ... | ||
| ``` | ||
|
|
||
| Note: | ||
| - If multiple parameter queries specify different priorities for the same bucket, the highest priority (lowest number) is used. | ||
|
||
| - Priorities must be static and cannot depend on row values within a parameter query. | ||
|
|
||
| # Example: Syncing Lists Before Todos | ||
|
|
||
| Consider a scenario where you want to display lists immediately while loading todos in the background. This approach allows users to view and interact with lists right away without waiting for todos to sync. Here's how to configure sync priorities in your Sync Rules to achieve this: | ||
|
|
||
| ```yaml | ||
| bucket_definitions: | ||
| user_lists: | ||
| # Sync the user's lists with a higher priority | ||
| priority: 1 | ||
| parameters: select id as list_id from lists where user_id = request.user_id() | ||
| data: | ||
| - select * from lists where id = bucket.list_id | ||
|
|
||
| user_todos: | ||
| # Sync the user's todos with a lower priority | ||
| priority: 3 | ||
| parameters: select id as todo_id from todos where list_id in (select id from lists where user_id = request.user_id()) | ||
| data: | ||
| - select * from todos where list_id = bucket.todo_id | ||
| ``` | ||
| It is recommended to set Client Parameters in the [Diagnostics App](https://github.com/powersync-ja/powersync-js/tree/main/tools/diagnostics-app) to verify functionality at this point: | ||
| In this configuration: | ||
| The `lists` bucket has the default priority of 1, meaning it syncs first. | ||
|
|
||
| The `todos` bucket is assigned a priority of 2, meaning it may sync only after the lists have been synced. | ||
|
|
||
|
|
||
| # Behavioral Considerations | ||
|
|
||
| - **Interruption for Higher Priority Data**: Syncing lower-priority buckets _may_ be interrupted if new data for higher-priority buckets arrives. | ||
| - **Local Changes & Consistency**: If local writes fail due to validation or permission issues, they are only reverted after _all_ buckets sync. | ||
| - **Deleted Data**: Deleted data may only be removed after _all_ buckets have synced. Future updates may improve this behavior. | ||
| - **Data Ordering**: Data in lower-priority buckets will never appear before higher-priority data. | ||
|
|
||
| ## Special Case: Priority 0 | ||
|
|
||
| Priority 0 buckets sync regardless of pending uploads. | ||
|
|
||
| <Frame> | ||
| <img src="/images/usage/use-case-prioritized.png" /> | ||
| </Frame> | ||
| For example, in a collaborative document editing app (e.g., using Yjs), each change is stored as a separate row. Since out-of-order updates don’t affect document integrity, Priority 0 can ensure immediate availability of updates. | ||
|
|
||
| Caution: If misused, Priority 0 may cause flickering or inconsistencies, as updates could arrive out of order. | ||
|
|
||
| # Consistency Considerations | ||
|
|
||
| PowerSync's full consistency guarantees only apply once all buckets have completed syncing. | ||
|
|
||
| When higher-priority buckets are synced, all inserts and updates within the buckets for the specific priority will be consistent. However, deletes are only applied when the full sync completes, so you may still have some stale data within those buckets. | ||
|
|
||
| Consider the following example: | ||
|
|
||
| Example: Todo List Syncing | ||
benitav marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Imagine a task management app where users create lists and todos. Some users have millions of todos. To improve first-load speed: | ||
|
|
||
| - Lists are assigned Priority 1, syncing first to allow UI rendering. | ||
| - Todos are assigned Priority 2, loading in the background. | ||
|
|
||
| Now, if another user adds new todos, it’s possible for the list count (synced at Priority 1) to temporarily not match the actual todos (synced at Priority 2). If real-time accuracy is required, both lists and todos should use the same priority. | ||
|
|
||
| # Client-Side Considerations | ||
|
|
||
| PowerSync's client SDKs provide APIs to allow applications to track sync status at different priority levels. Developers can leverage these to ensure critical data is available before proceeding with UI updates or background processing. This includes: | ||
|
|
||
| 1. `waitForFirstSync(priority: int)`. When passing the optional `priority` parameter to this method, it will wait for specific priority level to complete syncing. | ||
| 2. `SyncStatus.priorityStatusEntries()` A list containing sync information for each priority that was seen by the PowerSync Service. | ||
| 3. `SyncStatus.statusForPriority(priority: int)` This method takes a fixed priority and returns the sync state for that priority by looking it up in `priorityStatusEntries`. | ||
|
|
||
| ## Example | ||
| Using the above we can render a lists component only once the user's lists (with priority 1) have completed syncing, else display a message indicating that the sync is still in progress: | ||
|
|
||
| ```dart | ||
| // Define the priority level of the lists bucket | ||
| static final _listsPriority = BucketPriority(1); | ||
| @override | ||
| Widget build(BuildContext context) { | ||
| // Use FutureBuilder to wait for the first sync of the specified priority to complete | ||
| return FutureBuilder( | ||
| future: db.waitForFirstSync(priority: _listsPriority), | ||
| builder: (context, snapshot) { | ||
| if (snapshot.connectionState == ConnectionState.done) { | ||
| // Use StreamBuilder to render the lists once the sync completes | ||
| return StreamBuilder( | ||
| stream: TodoList.watchListsWithStats(), | ||
| builder: (context, snapshot) { | ||
| if (snapshot.data case final todoLists?) { | ||
| return ListView( | ||
| padding: const EdgeInsets.symmetric(vertical: 8.0), | ||
| children: todoLists.map((list) { | ||
| return ListItemWidget(list: list); | ||
| }).toList(), | ||
| ); | ||
| } else { | ||
| return const CircularProgressIndicator(); | ||
| } | ||
| }, | ||
| ); | ||
| } else { | ||
| return const Text('Busy with sync...'); | ||
| } | ||
| }, | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| If everything checks out, you can then proceed to implement the client parameter switching accordingly in your app. | ||
| Example implementations of prioritized sync are also available in the following apps: | ||
| - Flutter: [Supabase To-Do List](https://github.com/powersync-ja/powersync.dart/tree/main/demos/supabase-todolist) | ||
| - React Native: Coming soon. | ||
| - JavaScript/Web: Coming soon. | ||
| - Kotlin Multiplatform: | ||
| - [Supabase To-Do List (KMP)](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/supabase-todolist/shared/src/commonMain/kotlin/com/powersync/demos/App.kt#L46) | ||
| - [Supabase To-Do List (Android)](https://github.com/powersync-ja/powersync-kotlin/blob/main/demos/android-supabase-todolist/app/src/main/java/com/powersync/androidexample/screens/HomeScreen.kt#L69) | ||
| - Swift: Coming soon. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't have to do that now, but maybe it's helpful to also expand the "Organize Data Into Buckets" page to mention that buckets can have metadata attached to them (like the
priorityYAML key or the_priorityparameter of a parameter query).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great catch, thanks - I'll work on that next 👍