Skip to content

Commit 1f94d4b

Browse files
fix: sync diagnostics row count (#879)
Co-authored-by: Ralf Kistner <ralf@journeyapps.com>
1 parent f237294 commit 1f94d4b

3 files changed

Lines changed: 67 additions & 22 deletions

File tree

.changeset/slimy-jars-compete.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/diagnostics-app': patch
3+
---
4+
5+
Fixed incorrect table row count when a source table is present in multiple buckets.

tools/diagnostics-app/src/app/views/sync-diagnostics.tsx

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { NavigationPage } from '@/components/navigation/NavigationPage';
2-
import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription';
3-
import { StreamsTable } from '@/components/widgets/StreamsTable';
4-
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
5-
import { getTokenUserId, decodeTokenPayload } from '@/library/powersync/TokenConnector';
6-
import React, { useState } from 'react';
7-
import { useQuery as useTanstackQuery, useQueryClient } from '@tanstack/react-query';
2+
import { Alert, AlertDescription } from '@/components/ui/alert';
83
import { Button } from '@/components/ui/button';
9-
import { Spinner } from '@/components/ui/spinner';
104
import { Card, CardContent } from '@/components/ui/card';
11-
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
125
import { DataTable, DataTableColumn } from '@/components/ui/data-table';
13-
import { Alert, AlertDescription } from '@/components/ui/alert';
6+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
7+
import { Spinner } from '@/components/ui/spinner';
8+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
9+
import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription';
10+
import { StreamsTable } from '@/components/widgets/StreamsTable';
1411
import { formatBytes } from '@/lib/utils';
15-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
16-
import { ChevronDown, ChevronUp, Info, Eye } from 'lucide-react';
12+
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
13+
import { decodeTokenPayload, getTokenUserId } from '@/library/powersync/TokenConnector';
14+
import { useQueryClient, useQuery as useTanstackQuery } from '@tanstack/react-query';
15+
import { ChevronDown, ChevronUp, Eye, Info } from 'lucide-react';
16+
import React, { useState } from 'react';
1717

1818
const BUCKETS_QUERY = `
1919
WITH
@@ -51,8 +51,22 @@ FROM local_bucket_data local
5151
LEFT JOIN ps_buckets ON ps_buckets.name = local.id
5252
LEFT JOIN oplog_stats stats ON stats.bucket_id = ps_buckets.id`;
5353

54-
const TABLES_QUERY = `
55-
SELECT row_type as name, count() as count, sum(length(data)) as size FROM ps_oplog GROUP BY row_type
54+
/**
55+
* Groups ps_oplog entries by row_type (table/view name) to get the list
56+
* of tables and their total data sizes.
57+
* - row_count: number of unique row_id values per row_type (actual row count, robust across multiple buckets/keys)
58+
* - synced_count: This is not quite number of ops since they're de-duplicated per key already, but it counts the number of times the same row is synced via different buckets or different keys.
59+
*/
60+
const TABLES_SIZE_QUERY = /* sql */ `
61+
SELECT
62+
row_type as name,
63+
count(distinct row_id) as count,
64+
count() as synced_count,
65+
sum(length (data)) as size
66+
FROM
67+
ps_oplog
68+
GROUP BY
69+
row_type
5670
`;
5771

5872
const BUCKETS_QUERY_FAST = `
@@ -88,12 +102,12 @@ async function fetchSyncStats(): Promise<SyncStats> {
88102

89103
if (synced_at != null && !sync?.syncStatus.dataFlowStatus.downloading) {
90104
const bucketRows = await db.getAll(BUCKETS_QUERY);
91-
const tableRows = await db.getAll(TABLES_QUERY);
105+
const tableRows = await db.getAll(TABLES_SIZE_QUERY);
92106
return { bucketRows, tableRows, lastSyncedAt };
93107
}
94108
if (synced_at != null) {
95109
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
96-
const tableRows = await db.getAll(TABLES_QUERY);
110+
const tableRows = await db.getAll(TABLES_SIZE_QUERY);
97111
return { bucketRows, tableRows, lastSyncedAt };
98112
}
99113
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
@@ -218,7 +232,21 @@ export default function SyncDiagnosticsPage() {
218232

219233
const tablesColumns: DataTableColumn<any>[] = [
220234
{ field: 'name', headerName: 'Name', flex: 2 },
221-
{ field: 'count', headerName: 'Row Count', flex: 1, type: 'number' },
235+
{
236+
field: 'count',
237+
headerName: 'Row Count',
238+
flex: 1,
239+
type: 'number',
240+
tooltip: 'Number of unique rows synced to this database.'
241+
},
242+
{
243+
field: 'synced_count',
244+
headerName: 'Synced Count',
245+
flex: 1,
246+
type: 'number',
247+
hideOnMobile: true,
248+
tooltip: 'Total number of rows synced via different buckets or different replication keys.'
249+
},
222250
{
223251
field: 'size',
224252
headerName: 'Data Size',
@@ -385,9 +413,9 @@ export default function SyncDiagnosticsPage() {
385413
<Info className="h-4 w-4" />
386414
<AlertDescription>
387415
Total operations ({totals.total_operations.toLocaleString()}) significantly exceeds total rows (
388-
{totals.row_count.toLocaleString()}). This indicates bucket history has accumulated which negatively
389-
affects sync times for new clients. Performing a Compact or Defragment operation on your instance,
390-
could improve this.{' '}
416+
{totals.row_count.toLocaleString()}). This indicates bucket history has accumulated which negatively
417+
affects sync times for new clients. Performing a Compact or Defragment operation on your instance, could
418+
improve this.{' '}
391419
<a
392420
href="https://docs.powersync.com/maintenance-ops/compacting-buckets"
393421
target="_blank"
@@ -491,8 +519,7 @@ function TruncatedTablesList({ tables }: { tables: string }) {
491519
</>
492520
) : (
493521
<>
494-
<ChevronDown className="h-3 w-3" />
495-
+{hiddenCount} more
522+
<ChevronDown className="h-3 w-3" />+{hiddenCount} more
496523
</>
497524
)}
498525
</button>

tools/diagnostics-app/src/components/ui/data-table.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ import {
1717
ArrowUpDown,
1818
ArrowUp,
1919
ArrowDown,
20-
Search
20+
Search,
21+
Info
2122
} from 'lucide-react';
2223
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
2324
import { Button } from '@/components/ui/button';
2425
import { Input } from '@/components/ui/input';
2526
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
27+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
2628
import { cn } from '@/lib/utils';
2729

2830
export interface DataTableColumn<T> {
2931
field: keyof T | string;
3032
headerName: string;
33+
tooltip?: string;
3134
flex?: number;
3235
type?: 'text' | 'number' | 'boolean' | 'dateTime';
3336
valueFormatter?: (params: { value: any; row: T }) => string;
@@ -72,6 +75,16 @@ function toColumnDefs<T extends { id: string | number }>(columns: DataTableColum
7275
)}
7376
onClick={column.getToggleSortingHandler()}>
7477
<span className="truncate">{col.headerName}</span>
78+
{col.tooltip && (
79+
<TooltipProvider delayDuration={200}>
80+
<Tooltip>
81+
<TooltipTrigger asChild>
82+
<Info className="h-3.5 w-3.5 shrink-0 opacity-50" />
83+
</TooltipTrigger>
84+
<TooltipContent className="max-w-xs">{col.tooltip}</TooltipContent>
85+
</Tooltip>
86+
</TooltipProvider>
87+
)}
7588
{sorted === 'asc' ? (
7689
<ArrowUp className="h-4 w-4 shrink-0" />
7790
) : sorted === 'desc' ? (

0 commit comments

Comments
 (0)