Skip to content

Commit c4a31ca

Browse files
authored
Merge pull request #227 from powersync-ja/improve-diagnostics-app
Diagnostics app improvements
2 parents 9f35b78 + 1b2b207 commit c4a31ca

File tree

4 files changed

+121
-62
lines changed

4 files changed

+121
-62
lines changed

.changeset/happy-gifts-sort.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'diagnostics-app': minor
3+
---
4+
5+
Faster initial sync and other fixes

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

Lines changed: 100 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NavigationPage } from '@/components/navigation/NavigationPage';
2-
import { clearData, syncErrorTracker } from '@/library/powersync/ConnectionManager';
2+
import { clearData, db, syncErrorTracker } from '@/library/powersync/ConnectionManager';
33
import {
44
Box,
55
Button,
@@ -15,7 +15,6 @@ import {
1515
styled
1616
} from '@mui/material';
1717
import { DataGrid, GridColDef } from '@mui/x-data-grid';
18-
import { useQuery } from '@powersync/react';
1918
import React from 'react';
2019

2120
const BUCKETS_QUERY = `
@@ -24,9 +23,9 @@ WITH
2423
(SELECT
2524
bucket,
2625
row_type,
27-
sum(length(data)) as data_size,
26+
sum(case when op = 3 and superseded = 0 then length(data) else 0 end) as data_size,
2827
sum(length(row_type) + length(row_id) + length(bucket) + length(key) + 40) as metadata_size,
29-
count() as row_count
28+
sum(case when op = 3 and superseded = 0 then 1 else 0 end) as row_count
3029
FROM ps_oplog GROUP BY bucket, row_type),
3130
3231
oplog_stats AS
@@ -51,23 +50,65 @@ FROM local_bucket_data local
5150
LEFT JOIN oplog_stats stats ON stats.name = local.id`;
5251

5352
const TABLES_QUERY = `
54-
SELECT row_type as name, count() as count, sum(length(data)) as size FROM ps_oplog GROUP BY row_type
53+
SELECT row_type as name, count() as count, sum(length(data)) as size FROM ps_oplog WHERE superseded = 0 and op = 3 GROUP BY row_type
5554
`;
5655

57-
export default function SyncDiagnosticsPage() {
58-
const { data: bucketRows, isLoading: bucketRowsLoading } = useQuery(BUCKETS_QUERY, undefined, {
59-
rawTableNames: true,
60-
tables: ['ps_oplog', 'ps_data_local__local_bucket_data'],
61-
throttleMs: 500
62-
});
63-
const { data: tableRows, isLoading: tableRowsLoading } = useQuery(TABLES_QUERY, undefined, {
64-
rawTableNames: true,
65-
tables: ['ps_oplog', 'ps_data_local__local_bucket_data'],
66-
throttleMs: 500
67-
});
56+
const BUCKETS_QUERY_FAST = `
57+
SELECT
58+
local.id as name,
59+
'[]' as tables,
60+
0 as data_size,
61+
0 as metadata_size,
62+
0 as row_count,
63+
local.download_size,
64+
local.total_operations,
65+
local.downloading
66+
FROM local_bucket_data local`;
6867

68+
export default function SyncDiagnosticsPage() {
69+
const [bucketRows, setBucketRows] = React.useState<null | any[]>(null);
70+
const [tableRows, setTableRows] = React.useState<null | any[]>(null);
6971
const [syncError, setSyncError] = React.useState<Error | null>(syncErrorTracker.lastSyncError);
7072

73+
const bucketRowsLoading = bucketRows == null;
74+
const tableRowsLoading = tableRows == null;
75+
76+
const refreshStats = async () => {
77+
// Similar to db.currentState.hasSynced, but synchronized to the onChange events
78+
const hasSynced = await db.getOptional('SELECT 1 FROM ps_buckets WHERE last_applied_op > 0 LIMIT 1');
79+
if (hasSynced != null) {
80+
// These are potentially expensive queries - do not run during initial sync
81+
const bucketRows = await db.getAll(BUCKETS_QUERY);
82+
const tableRows = await db.getAll(TABLES_QUERY);
83+
setBucketRows(bucketRows);
84+
setTableRows(tableRows);
85+
} else {
86+
// Fast query to show progress during initial sync
87+
const bucketRows = await db.getAll(BUCKETS_QUERY_FAST);
88+
setBucketRows(bucketRows);
89+
setTableRows(null);
90+
}
91+
};
92+
93+
React.useEffect(() => {
94+
const controller = new AbortController();
95+
96+
db.onChangeWithCallback(
97+
{
98+
async onChange(event) {
99+
await refreshStats();
100+
}
101+
},
102+
{ rawTableNames: true, tables: ['ps_oplog', 'ps_buckets', 'ps_data_local__local_bucket_data'], throttleMs: 500 }
103+
);
104+
105+
refreshStats();
106+
107+
return () => {
108+
controller.abort();
109+
};
110+
}, []);
111+
71112
React.useEffect(() => {
72113
const l = syncErrorTracker.registerListener({
73114
lastErrorUpdated(error) {
@@ -111,7 +152,7 @@ export default function SyncDiagnosticsPage() {
111152
}
112153
];
113154

114-
const rows = bucketRows.map((r) => {
155+
const rows = (bucketRows ?? []).map((r) => {
115156
return {
116157
id: r.name,
117158
name: r.name,
@@ -146,7 +187,7 @@ export default function SyncDiagnosticsPage() {
146187
}
147188
];
148189

149-
const tablesRows = tableRows.map((r) => {
190+
const tablesRows = (tableRows ?? []).map((r) => {
150191
return {
151192
id: r.name,
152193
...r
@@ -181,50 +222,40 @@ export default function SyncDiagnosticsPage() {
181222
);
182223

183224
const tablesTable = (
184-
<S.QueryResultContainer>
185-
<Typography variant="h4" gutterBottom>
186-
Tables
187-
</Typography>
188-
<DataGrid
189-
autoHeight={true}
190-
rows={tablesRows}
191-
columns={tablesColumns}
192-
initialState={{
193-
pagination: {
194-
paginationModel: {
195-
pageSize: 10
196-
}
225+
<DataGrid
226+
autoHeight={true}
227+
rows={tablesRows}
228+
columns={tablesColumns}
229+
initialState={{
230+
pagination: {
231+
paginationModel: {
232+
pageSize: 10
197233
}
198-
}}
199-
pageSizeOptions={[10, 50, 100]}
200-
disableRowSelectionOnClick
201-
/>
202-
</S.QueryResultContainer>
234+
}
235+
}}
236+
pageSizeOptions={[10, 50, 100]}
237+
disableRowSelectionOnClick
238+
/>
203239
);
204240

205241
const bucketsTable = (
206-
<S.QueryResultContainer>
207-
<Typography variant="h4" gutterBottom>
208-
Buckets
209-
</Typography>
210-
<DataGrid
211-
autoHeight={true}
212-
rows={rows}
213-
columns={columns}
214-
initialState={{
215-
pagination: {
216-
paginationModel: {
217-
pageSize: 50
218-
}
219-
},
220-
sorting: {
221-
sortModel: [{ field: 'total_operations', sort: 'desc' }]
242+
<DataGrid
243+
autoHeight={true}
244+
rows={rows}
245+
columns={columns}
246+
initialState={{
247+
pagination: {
248+
paginationModel: {
249+
pageSize: 50
222250
}
223-
}}
224-
pageSizeOptions={[10, 50, 100]}
225-
disableRowSelectionOnClick
226-
/>
227-
</S.QueryResultContainer>
251+
},
252+
sorting: {
253+
sortModel: [{ field: 'total_operations', sort: 'desc' }]
254+
}
255+
}}
256+
pageSizeOptions={[10, 50, 100]}
257+
disableRowSelectionOnClick
258+
/>
228259
);
229260

230261
return (
@@ -239,8 +270,18 @@ export default function SyncDiagnosticsPage() {
239270
}}>
240271
Clear & Redownload
241272
</Button>
242-
{tableRowsLoading ? <CircularProgress /> : tablesTable}
243-
{bucketRowsLoading ? <CircularProgress /> : bucketsTable}
273+
<S.QueryResultContainer>
274+
<Typography variant="h4" gutterBottom>
275+
Tables
276+
</Typography>
277+
{tableRowsLoading ? <CircularProgress /> : tablesTable}
278+
</S.QueryResultContainer>
279+
<S.QueryResultContainer>
280+
<Typography variant="h4" gutterBottom>
281+
Buckets
282+
</Typography>
283+
{bucketRowsLoading ? <CircularProgress /> : bucketsTable}
284+
</S.QueryResultContainer>
244285
</S.MainContainer>
245286
</NavigationPage>
246287
);

tools/diagnostics-app/src/library/powersync/ConnectionManager.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
BaseListener,
33
BaseObserver,
44
PowerSyncDatabase,
5+
SyncStreamConnectionMethod,
56
WebRemote,
67
WebStreamingSyncImplementation,
78
WebStreamingSyncImplementationOptions
@@ -11,6 +12,12 @@ import { DynamicSchemaManager } from './DynamicSchemaManager';
1112
import { RecordingStorageAdapter } from './RecordingStorageAdapter';
1213
import { TokenConnector } from './TokenConnector';
1314

15+
import { Buffer } from 'buffer';
16+
17+
if (typeof self.Buffer == 'undefined') {
18+
self.Buffer = Buffer;
19+
}
20+
1421
Logger.useDefaults();
1522
Logger.setLevel(Logger.DEBUG);
1623

@@ -22,6 +29,8 @@ export const db = new PowerSyncDatabase({
2229
},
2330
schema: schemaManager.buildSchema()
2431
});
32+
db.execute('PRAGMA cache_size=-50000');
33+
2534
export const connector = new TokenConnector();
2635

2736
const remote = new WebRemote(connector);
@@ -71,7 +80,7 @@ if (connector.hasCredentials()) {
7180
}
7281

7382
export async function connect() {
74-
await sync.connect();
83+
await sync.connect({ connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET });
7584
if (!sync.syncStatus.connected) {
7685
// Disconnect but don't wait for it
7786
sync.disconnect();
@@ -87,7 +96,7 @@ export async function clearData() {
8796
await schemaManager.clear();
8897
await schemaManager.refreshSchema(db.database);
8998
if (connector.hasCredentials()) {
90-
await sync.connect();
99+
await sync.connect({ connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET });
91100
}
92101
}
93102

tools/diagnostics-app/src/library/powersync/RecordingStorageAdapter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export class RecordingStorageAdapter extends SqliteBucketStorage {
4242

4343
async syncLocalDatabase(checkpoint: Checkpoint) {
4444
const r = await super.syncLocalDatabase(checkpoint);
45-
await this.schemaManager.refreshSchema(this.rdb);
45+
// Refresh schema asynchronously, to allow us to better measure
46+
// performance of initial sync.
47+
setTimeout(() => {
48+
this.schemaManager.refreshSchema(this.rdb);
49+
}, 60);
4650
if (r.checkpointValid) {
4751
await this.rdb.execute('UPDATE local_bucket_data SET downloading = FALSE');
4852
}

0 commit comments

Comments
 (0)