Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/slimy-jars-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/diagnostics-app': patch
---

Fixed incorrect table row count when a source table is present in multiple buckets.
69 changes: 48 additions & 21 deletions tools/diagnostics-app/src/app/views/sync-diagnostics.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { NavigationPage } from '@/components/navigation/NavigationPage';
import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription';
import { StreamsTable } from '@/components/widgets/StreamsTable';
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
import { getTokenUserId, decodeTokenPayload } from '@/library/powersync/TokenConnector';
import React, { useState } from 'react';
import { useQuery as useTanstackQuery, useQueryClient } from '@tanstack/react-query';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Spinner } from '@/components/ui/spinner';
import { Card, CardContent } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { DataTable, DataTableColumn } from '@/components/ui/data-table';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { ChevronDown, ChevronUp, Info, Eye } from 'lucide-react';
import { clearData, connector, db, sync, useSyncStatus } from '@/library/powersync/ConnectionManager';
import { decodeTokenPayload, getTokenUserId } from '@/library/powersync/TokenConnector';
import { useQueryClient, useQuery as useTanstackQuery } from '@tanstack/react-query';
import { ChevronDown, ChevronUp, Eye, Info } from 'lucide-react';
import React, { useState } from 'react';

const BUCKETS_QUERY = `
WITH
Expand Down Expand Up @@ -51,8 +51,22 @@ FROM local_bucket_data local
LEFT JOIN ps_buckets ON ps_buckets.name = local.id
LEFT JOIN oplog_stats stats ON stats.bucket_id = ps_buckets.id`;

const TABLES_QUERY = `
SELECT row_type as name, count() as count, sum(length(data)) as size FROM ps_oplog GROUP BY row_type
Comment thread
stevensJourney marked this conversation as resolved.
/**
* Groups ps_oplog entries by row_type (table/view name) to get the list
* of tables and their total data sizes.
* - row_count: number of unique row_id values per row_type (actual row count, robust across multiple buckets/keys)
* - 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.
*/
const TABLES_SIZE_QUERY = /* sql */ `
SELECT
row_type as name,
count(distinct row_id) as count,
count() as synced_count,
sum(length (data)) as size
FROM
ps_oplog
GROUP BY
row_type
`;

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

if (synced_at != null && !sync?.syncStatus.dataFlowStatus.downloading) {
const bucketRows = await db.getAll(BUCKETS_QUERY);
const tableRows = await db.getAll(TABLES_QUERY);
const tableRows = await db.getAll(TABLES_SIZE_QUERY);
return { bucketRows, tableRows, lastSyncedAt };
}
if (synced_at != null) {
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
const tableRows = await db.getAll(TABLES_QUERY);
const tableRows = await db.getAll(TABLES_SIZE_QUERY);
return { bucketRows, tableRows, lastSyncedAt };
}
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
Expand Down Expand Up @@ -218,7 +232,21 @@ export default function SyncDiagnosticsPage() {

const tablesColumns: DataTableColumn<any>[] = [
{ field: 'name', headerName: 'Name', flex: 2 },
{ field: 'count', headerName: 'Row Count', flex: 1, type: 'number' },
{
field: 'count',
headerName: 'Row Count',
flex: 1,
type: 'number',
tooltip: 'Number of unique rows synced to this database.'
},
{
field: 'synced_count',
headerName: 'Synced Count',
flex: 1,
type: 'number',
hideOnMobile: true,
tooltip: 'Total number of rows synced via different buckets or different replication keys.'
},
{
field: 'size',
headerName: 'Data Size',
Expand Down Expand Up @@ -385,9 +413,9 @@ export default function SyncDiagnosticsPage() {
<Info className="h-4 w-4" />
<AlertDescription>
Total operations ({totals.total_operations.toLocaleString()}) significantly exceeds total rows (
{totals.row_count.toLocaleString()}). This indicates bucket history has accumulated which negatively
affects sync times for new clients. Performing a Compact or Defragment operation on your instance,
could improve this.{' '}
{totals.row_count.toLocaleString()}). This indicates bucket history has accumulated which negatively
affects sync times for new clients. Performing a Compact or Defragment operation on your instance, could
improve this.{' '}
<a
href="https://docs.powersync.com/maintenance-ops/compacting-buckets"
target="_blank"
Expand Down Expand Up @@ -491,8 +519,7 @@ function TruncatedTablesList({ tables }: { tables: string }) {
</>
) : (
<>
<ChevronDown className="h-3 w-3" />
+{hiddenCount} more
<ChevronDown className="h-3 w-3" />+{hiddenCount} more
</>
)}
</button>
Expand Down
15 changes: 14 additions & 1 deletion tools/diagnostics-app/src/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ import {
ArrowUpDown,
ArrowUp,
ArrowDown,
Search
Search,
Info
} from 'lucide-react';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';

export interface DataTableColumn<T> {
field: keyof T | string;
headerName: string;
tooltip?: string;
flex?: number;
type?: 'text' | 'number' | 'boolean' | 'dateTime';
valueFormatter?: (params: { value: any; row: T }) => string;
Expand Down Expand Up @@ -72,6 +75,16 @@ function toColumnDefs<T extends { id: string | number }>(columns: DataTableColum
)}
onClick={column.getToggleSortingHandler()}>
<span className="truncate">{col.headerName}</span>
{col.tooltip && (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-3.5 w-3.5 shrink-0 opacity-50" />
</TooltipTrigger>
<TooltipContent className="max-w-xs">{col.tooltip}</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{sorted === 'asc' ? (
<ArrowUp className="h-4 w-4 shrink-0" />
) : sorted === 'desc' ? (
Expand Down