Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/lucky-beers-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics-app': patch
---

Improved query parameter warnings and limits
105 changes: 97 additions & 8 deletions tools/diagnostics-app/src/app/views/sync-diagnostics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { Spinner } from '@/components/ui/spinner';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription';
import { StreamsTable } from '@/components/widgets/StreamsTable';
import { formatBytes } from '@/lib/utils';
import { cn, formatBytes } from '@/lib/utils';
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { decodeTokenPayload, getTokenUserId } from '@/library/powersync/TokenConnector';
import { useQueryClient, useQuery as useTanstackQuery } from '@tanstack/react-query';
import { ChevronDown, ChevronUp, Eye, Info } from 'lucide-react';
Expand Down Expand Up @@ -90,6 +91,17 @@ const syncDiagnosticsKeys = {
/** When total_operations exceeds row_count by this factor, we show a warning that bucket history has accumulated and compacting may help. This is an abritrary threshold and indicates significant history buildup.*/
const BUCKET_HISTORY_THRESHOLD = 3;

/**
* Default server-side limit for both the "too many buckets" and "too many parameter query results"
* limits (both reported as error PSYNC_S2305). These are two distinct limits that often coincide
* but can differ:
* - Duplicate parameter query results count individually toward the parameter result limit,
* but are de-duplicated in the bucket count.
* - Non-partitioning queries count 1 toward the bucket limit but not toward the parameter result limit.
* The default is 1000 but can be configured on the server.
*/
const DEFAULT_SYNC_LIMIT = 1000;

interface SyncStats {
bucketRows: any[] | null;
tableRows: any[] | null;
Expand Down Expand Up @@ -222,6 +234,7 @@ export default function SyncDiagnosticsPage() {

const totals = {
buckets: rows.length,
parameterized_buckets: rows.filter((row) => !row.name.endsWith('[]')).length,
row_count: rows.reduce((total, row) => total + row.row_count, 0),
downloaded_operations: rows.reduce((total, row) => total + row.downloaded_operations, 0),
total_operations: rows.reduce((total, row) => total + row.total_operations, 0),
Expand Down Expand Up @@ -272,7 +285,27 @@ export default function SyncDiagnosticsPage() {
<Table>
<TableHeader>
<TableRow>
<TableHead>Buckets</TableHead>
<TableHead>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-flex items-center gap-1 cursor-default">
Buckets
<Info className="h-3 w-3 text-muted-foreground" />
</span>
</TooltipTrigger>
<TooltipContent className="max-w-92">
Two separate limits apply <strong>bucket count</strong> and{' '}
<strong>parameter query results</strong> (both PSYNC_S2305, default{' '}
{DEFAULT_SYNC_LIMIT.toLocaleString()} each):
<div className="mt-2">
Global buckets count only toward bucket count. Parameterized buckets count toward both, but
are de-duplicated client-side (server's parameter result count may be higher).
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableHead>
<TableHead className="text-right">Total Rows</TableHead>
<TableHead className="text-right">Downloaded Ops</TableHead>
<TableHead className="text-right">Total Ops</TableHead>
Expand All @@ -284,7 +317,22 @@ export default function SyncDiagnosticsPage() {
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">{totals.buckets}</TableCell>
<TableCell className="font-medium">
<span
className={cn(
totals.buckets >= 900 ? 'text-destructive' : totals.buckets >= 800 ? 'text-amber-600' : ''
)}
>
{totals.buckets.toLocaleString()}
</span>
<span className="text-muted-foreground text-xs ml-1">
/ {DEFAULT_SYNC_LIMIT.toLocaleString()} (default)
</span>
<div className="text-muted-foreground text-xs font-normal mt-0.5">
{totals.parameterized_buckets.toLocaleString()} parameterized,{' '}
{(totals.buckets - totals.parameterized_buckets).toLocaleString()} global
</div>
</TableCell>
<TableCell className="text-right">{totals.row_count.toLocaleString()}</TableCell>
<TableCell className="text-right">{totals.downloaded_operations.toLocaleString()}</TableCell>
<TableCell className="text-right">{totals.total_operations.toLocaleString()}</TableCell>
Expand All @@ -300,7 +348,22 @@ export default function SyncDiagnosticsPage() {
<div className="md:hidden p-4 grid grid-cols-2 gap-3 text-sm">
<div>
<div className="text-muted-foreground">Buckets</div>
<div className="font-medium">{totals.buckets}</div>
<div className="font-medium">
<span
className={cn(
totals.buckets >= 900 ? 'text-destructive' : totals.buckets >= 800 ? 'text-amber-600' : ''
)}
>
{totals.buckets.toLocaleString()}
</span>
<span className="text-muted-foreground text-xs ml-1">
/ {DEFAULT_SYNC_LIMIT.toLocaleString()} (default)
</span>
<div className="text-muted-foreground text-xs font-normal mt-0.5">
{totals.parameterized_buckets.toLocaleString()} parameterized,{' '}
{(totals.buckets - totals.parameterized_buckets).toLocaleString()} global
</div>
</div>
</div>
<div>
<div className="text-muted-foreground">Total Rows</div>
Expand Down Expand Up @@ -358,7 +421,8 @@ export default function SyncDiagnosticsPage() {
variant="ghost"
size="sm"
className="h-7 gap-1.5 text-muted-foreground"
onClick={() => setShowTokenDialog(true)}>
onClick={() => setShowTokenDialog(true)}
>
<Eye className="h-3.5 w-3.5" />
View Token
</Button>
Expand Down Expand Up @@ -401,7 +465,8 @@ export default function SyncDiagnosticsPage() {
variant="outline"
onClick={() => {
clearData();
}}>
}}
>
Clear & Redownload
</Button>
<span className="text-sm text-muted-foreground">
Expand All @@ -420,12 +485,35 @@ export default function SyncDiagnosticsPage() {
href="https://docs.powersync.com/maintenance-ops/compacting-buckets"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground">
className="underline hover:text-foreground"
>
Learn about compacting
</a>
</AlertDescription>
</Alert>
)}
{totals.buckets >= 800 && (
<Alert className={cn('mt-4', totals.buckets >= 900 ? 'border-destructive/50' : 'border-amber-500/50')}>
<Info className={cn('h-4 w-4', totals.buckets >= 900 ? 'text-destructive' : 'text-amber-600')} />
<AlertDescription>
<span className={cn('font-medium', totals.buckets >= 900 ? 'text-destructive' : 'text-amber-600')}>
{totals.buckets >= 900 ? 'Critical: ' : 'Warning: '}
</span>
{totals.buckets.toLocaleString()} of {DEFAULT_SYNC_LIMIT.toLocaleString()} buckets used (PSYNC_S2305,
default limit). {totals.parameterized_buckets.toLocaleString()} are parameterized - at least that many
parameter query results on the server. Review your sync rules to reduce buckets and parameter query
Comment thread
joshuabrink marked this conversation as resolved.
Outdated
results for this user.{' '}
<a
href="https://docs.powersync.com/sync/rules/parameter-queries"
Comment thread
joshuabrink marked this conversation as resolved.
Outdated
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Learn about parameter queries
</a>
</AlertDescription>
</Alert>
)}
</div>

<div className="space-y-4">
Expand Down Expand Up @@ -511,7 +599,8 @@ function TruncatedTablesList({ tables }: { tables: string }) {
e.stopPropagation();
setExpanded(!expanded);
}}
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors">
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
{expanded ? (
<>
<ChevronUp className="h-3 w-3" />
Expand Down
Loading