Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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.
84 changes: 64 additions & 20 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,10 +51,55 @@ 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.
* If multiple bucket definitions reference the same source table, there may be multiple ps_oplog entries per table row.
* For this reason we don't count ps_oplog entries to get row counts, and instead do a separate count(*) query for each table.
*/
const TABLES_SIZE_QUERY = /* sql */ `
SELECT
row_type as name,
count() as op_count,
sum(length (data)) as size
FROM
ps_oplog
GROUP BY
row_type
`;

/**
* Fetches the rows for each table, including their name, row count, and size.
* This function performs a separate count(*) query for each table.
*/
async function fetchTableRows() {
const sizeRows = await db.getAll<{ name: string; op_count: number; size: number }>(TABLES_SIZE_QUERY);

const tableRows = await Promise.all(
sizeRows.map(async (row) => {
try {
// This might fail while the dynamic schema has not yet been intialized.
const countRow = await db.get<{ count: number }>(`SELECT count(*) as count FROM ${row.name}`);
return {
name: row.name,
count: countRow.count,
size: row.size
};
} catch (ex) {
console.warn(`Failed to get row count for table ${row.name}.`, ex);
return {
name: row.name,
// Fallback to count of ops
count: row.op_count,
size: row.size
Comment thread
stevensJourney marked this conversation as resolved.
Outdated
};
}
})
);

return tableRows;
}

const BUCKETS_QUERY_FAST = `
SELECT
local.id as name,
Expand Down Expand Up @@ -88,12 +133,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 fetchTableRows();
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 fetchTableRows();
return { bucketRows, tableRows, lastSyncedAt };
}
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
Expand Down Expand Up @@ -385,9 +430,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 +536,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