What Is a Bucket?
A bucket is a group of rows that PowerSync syncs together as one unit. When you define a Sync Stream, PowerSync splits the matching rows into buckets so that each client can sync the exact subset of data it needs. You do not create or name buckets yourself. PowerSync creates them for you based on your stream queries. For background on why buckets exist and how they make sync efficient, see Bucket System. This page focuses on understanding how many buckets your configuration creates and how to keep that number under control.The Example App
Every example on this page uses the same app. It has organizations, projects, tasks, assets, and documents. Here is how the tables relate:- A user belongs to one or more organizations. The
org_membershiptable links auser_idto anorg_id. - Each project belongs to one organization through
org_id. Each project also has a plain textregioncolumn such asus-east. - Each task belongs to one project through
project_id. - Projects and assets link through the
project_assetstable. - Each document has an
owner_id, ashared_withuser, and anorg_id. - The
categoriestable is shared reference data. It has no owner and no links to the other tables, so it does not appear in the diagram above.
How Sync Streams Create Buckets
The Bucket Rule
The rule is simple. A stream creates one bucket for each unique value of its filter. Take this stream:owner_id = auth.user_id(). Each user has one user ID, so each user gets one bucket. The value that separates one bucket from the next is called the bucket’s parameter.
The filter value can come from several places. Each unique value still becomes its own bucket:
Buckets Are Counted Per Stream
PowerSync does not share buckets between streams. Two streams that select the same data still create separate buckets. Each stream adds its own buckets to the user’s total. To combine data that shares a filter, put it in one stream with multiple queries instead of several streams.Reading a Bucket Name
Bucket names appear in your instance logs. A bucket name has three parts. For example:5#my_documents|0["ef718ff3..."].
5#is the storage version.my_documents|0is the stream name with an internal index.["ef718ff3..."]is the parameter value for the bucket.
The exact bucket name format can change between releases. Use it to read logs. Do not depend on it in your application code.
Counting Buckets by Query Pattern
Each stream below creates a set number of buckets per user. Work through them one at a time. The pattern you use decides the count.No Parameters: One Global Bucket
A Direct Auth Filter: One Bucket Per User
A JWT Array: One Bucket Per Value
["org-a", "org-b", "org-c"]. PowerSync makes one bucket for each value in the list.
Count: one bucket per org ID in the token.
A Subscription Parameter: One Bucket Per Subscription
A Subquery: One Bucket Per Result Row
user_orgs filter returns the user’s org IDs. The query keys on org_id, which is a column on projects. PowerSync makes one bucket for each org the user belongs to. All projects in the same org share one bucket.
Count: one bucket per org the user belongs to.
A JOIN Through Another Table: One Bucket Per Joined Row
org_id. They only have project_id. A bucket’s key must be a column on the table you sync (see The Partition Key Must Exist on the Row). So PowerSync keys these buckets on project_id, not org_id. It makes one bucket for each project.
Compare this to the subquery above. That stream syncs projects, which has org_id, so it keys on org. This stream syncs tasks, which only has project_id, so it keys on project. The table you sync decides the key.
Count: one bucket per project.
A Many-to-Many JOIN: One Bucket Per Row You Select
project_assets. But you select from assets, and the join keys on assets.id. So PowerSync makes one bucket per asset, not one per project. A user with 2,000 assets gets 2,000 buckets.
Count: one bucket per asset.
The subquery, JOIN, and many-to-many examples above each run a parameter lookup to find the values to key on. The rows a lookup returns count toward a second limit, the parameter query results limit, which is separate from the bucket limit. For example, if a user belongs to 1,200 orgs, the
org_projects lookup returns 1,200 rows, which is over the default limit of 1,000, and the sync fails. See Limits to learn how the two limits differ.How Bucket Counts Combine
Real streams often combine more than one filter. These examples show how the counts stack up, so you can spot the patterns that grow fastest.Two Filters Multiply
Write a stream with two filters. This one syncs the user’s projects, but only in a region the client picks:Chained Queries Add Each Level
Now chain two levels. This stream syncs the user’s orgs, their projects, and their tasks:- The
orgsquery keys on org. The user has 2 orgs, so it makes 2 buckets. - The
projectsquery keys on project. The user has 6 projects, so it makes 6 buckets. - The
tasksquery also keys on project. It uses the same 6 projects, so it shares those buckets and adds 0.
orgs query keys on org, so its buckets do not merge with the project buckets. Each new level adds more buckets. With 10 orgs and 50 projects each, this stream makes 10 + 500 = 510 buckets.
Multiple Queries in One Stream Share Buckets
Queries in the same stream that filter the same way share one bucket per value. Put related tables in one stream to keep the count low:Global CTEs Count Per Stream
A global CTE lets you write filtering logic once and use it in many streams. It does not share buckets between those streams. Each stream runs the CTE on its own and makes its own buckets.user_orgs CTE. But they are separate streams, so they do not share buckets. Say the user has 3 orgs. The my_orgs stream makes 3 buckets. The org_projects stream makes 3 more. That is 6 buckets, not 3.
Use a global CTE to keep your config readable. To share buckets, put the queries in one stream, as shown above.
OR Conditions Expand
AnOR in a filter splits into parts. Take this stream:
OR. PowerSync splits it into two parts: documents you own, and documents shared with you. Each part makes its own bucket. So this stream makes 2 buckets per user, not 1.
PowerSync rewrites A AND (B OR C) into (A AND B) OR (A AND C). A shared part like A runs in each part. If A is a subquery or a parameter lookup, its rows count again in each part. Keep OR out of filters that use subqueries or parameters where you can.
Limits
PowerSync limits how much a single user can sync. There are two limits. Both default to 1,000. Exceeding either fails the sync with aPSYNC_S2305 error, though each has its own message (shown below). They measure different things, so you need to understand both.
Buckets Per Connection
This limit counts the unique buckets a user syncs. PowerSync removes duplicate buckets before it counts. Global buckets count toward this limit. When a user exceeds it, the log shows:Parameter Query Results
Some filters need a lookup to find the values to key on. A subquery, a CTE, or a JWT array each produces a list of values, such as the org IDs a user belongs to. This is a parameter lookup. The values it returns are parameter query results. This limit counts those result rows. It counts them for one user, across every stream that user subscribes to, added together. It counts them before PowerSync removes duplicates. Global buckets use no lookup, so they do not count toward this limit. The limit applies to the values that define buckets, not to the number of data rows synced. A separate, much higher limit controls how many data rows a client can sync (see Performance and Limits). So a limit of 1,000 means up to 1,000 bucket-defining values per user. When a user exceeds it, the log shows:Why the Two Counts Differ
The two limits measure different things, so they are usually different numbers. A stream can produce a small set of unique buckets from a large number of parameter rows. In that case you reach the parameter limit first. A config with many global buckets can reach the bucket limit first. This is why a checkpoint log can readbuckets: 7 | param_results: 6. One global bucket adds to the bucket count but not to the parameter count.
In legacy Sync Rules, these two limits were effectively one number, because each parameter-query result became one bucket. In Sync Streams they can diverge.
Total Buckets vs Buckets Per User
The 1,000 limit applies to each individual user, not to your whole instance. Your PowerSync Service can track millions of buckets in total, as long as each user syncs fewer than the limit. A large total bucket count is not a problem on its own.Related Pages
- Reducing Bucket Count diagnoses and fixes a high bucket count.
- Writing Queries covers the query syntax that determines your bucket count.
- Common Table Expressions (CTEs) covers shared filtering logic.
- Using Parameters covers auth, subscription, and connection parameters.
- Examples, Patterns & Demos covers common stream patterns.
- Bucket System covers how buckets work internally.
- Troubleshooting covers common PowerSync issues and diagnostics.
- Performance and Limits lists the Service limits.