|
| 1 | +--- |
| 2 | +title: Best Practices for Compound Indexing and Query Optimization in PowerSync |
| 3 | +description: In this tutorial we will show you the Best Practices for Compound Indexing and Query Optimization using the [PowerSync Web SDK](https://github.com/powersync-ja/powersync-js). |
| 4 | +sidebarTitle: Compound Indexing & Query Performance |
| 5 | +--- |
| 6 | + |
| 7 | +## Introduction |
| 8 | + |
| 9 | +This tutorial outlines findings and recommendations based on extensive testing of compound indexes, query execution, and table performance using the [PowerSync Web SDK](https://github.com/powersync-ja/powersync-js). |
| 10 | +These best practices are designed to assist developers in optimizing their schemas and queries when working with large datasets. |
| 11 | + |
| 12 | +## Key Findings |
| 13 | + |
| 14 | +### 1. Compound Index Behavior |
| 15 | + |
| 16 | +- Compound indexes significantly improve query performance when filters align with the indexed column order. |
| 17 | +- Queries that skip leading columns in a compound index result in full table scans, negating performance benefits. |
| 18 | +- Performance is consistent with SQLite's behavior as PowerSync uses SQLite under the hood. |
| 19 | + |
| 20 | +### 2. Performance Benchmarks |
| 21 | + |
| 22 | +Using a table with 1999 columns and 50k entries: |
| 23 | + |
| 24 | +#### Queries with Leading Column Filters |
| 25 | + |
| 26 | +```sql |
| 27 | +EXPLAIN QUERY PLAN SELECT * FROM dummy_table WHERE col1 = 'val1'; |
| 28 | +``` |
| 29 | + |
| 30 | +- **Execution Time:** 256 ms |
| 31 | +- **Query Plan:** SEARCH dummy_table USING COMPOUND INDEX |
| 32 | + |
| 33 | +#### Queries with Multiple Indexed Columns |
| 34 | + |
| 35 | +```sql |
| 36 | +EXPLAIN QUERY PLAN SELECT * FROM dummy_table WHERE col1 = 'val1' AND col2 = 'val2'; |
| 37 | +``` |
| 38 | + |
| 39 | +- **Execution Time:** 206 |
| 40 | +- **Query Plan:** SEARCH dummy_table USING COMPOUND INDEX |
| 41 | + |
| 42 | +#### Queries Skipping Leading Columns |
| 43 | + |
| 44 | +```sql |
| 45 | +EXPLAIN QUERY PLAN SELECT * FROM dummy_table WHERE col2 = 'val2' AND col3 = 'val3'; |
| 46 | +``` |
| 47 | + |
| 48 | +- **Execution Time:** 556 ms |
| 49 | +- **Query Plan:** SCAN dummy_table |
| 50 | +### Performance |
| 51 | + |
| 52 | +| Query Type | Execution Time | Query Plan | |
| 53 | +|-----------------------------------|----------------|-------------------------------------| |
| 54 | +| Leading Column Filters | 253 ms | SEARCH dummy_table USING COMPOUND INDEX | |
| 55 | +| Multiple Indexed Columns | 206 ms | SEARCH dummy_table USING COMPOUND INDEX | |
| 56 | +| Skipping Leading Columns | 556 ms | SCAN dummy_table | |
| 57 | +### 3. Column Limitations |
| 58 | + |
| 59 | +- PowerSync supports a maximum of 1,999 columns in a compound index, aligning with SQLite's limitations. |
| 60 | +- Queries including non-indexed columns slightly increase execution time but remain performant under typical workloads. |
| 61 | + |
| 62 | +## Best Practices |
| 63 | + |
| 64 | +### Schema Definition |
| 65 | + |
| 66 | +Define compound indexes to match your most frequent query patterns: |
| 67 | + |
| 68 | +```javascript |
| 69 | +import { column, Table } from '@powersync/web'; |
| 70 | + |
| 71 | +const todos = new Table( |
| 72 | + { |
| 73 | + list_id: column.text, // Foreign key to lists |
| 74 | + created_at: column.text, // Creation timestamp |
| 75 | + description: column.text, // Task description |
| 76 | + }, |
| 77 | + { |
| 78 | + indexes: { |
| 79 | + list_created: ['list_id', 'created_at'], |
| 80 | + }, |
| 81 | + } |
| 82 | +); |
| 83 | +``` |
| 84 | + |
| 85 | +### Query Optimization |
| 86 | + |
| 87 | +#### Aligned Filters |
| 88 | + |
| 89 | +Ensure that queries align with the column order of compound indexes: |
| 90 | + |
| 91 | +```javascript |
| 92 | +const results = await powerSync.get( |
| 93 | + `SELECT * FROM todos WHERE list_id = ? AND created_at = ?`, |
| 94 | + ['list1', '2025-01-26'] |
| 95 | +); |
| 96 | +``` |
| 97 | + |
| 98 | +#### Skipping Leading Columns |
| 99 | + |
| 100 | +For queries skipping leading columns, define additional indexes: |
| 101 | + |
| 102 | +```javascript |
| 103 | +await powerSync.execute( |
| 104 | + `CREATE INDEX idx_description ON todos (description);` |
| 105 | +); |
| 106 | +``` |
| 107 | + |
| 108 | +### Advanced Scenarios |
| 109 | + |
| 110 | +#### Testing Column Order |
| 111 | + |
| 112 | +The order of indexed columns affects query performance: |
| 113 | + |
| 114 | +```javascript |
| 115 | +// Create compound index with a different order |
| 116 | +await powerSync.execute(`CREATE INDEX idx_order ON todos (description, list_id);`); |
| 117 | + |
| 118 | +// Query execution profiling |
| 119 | +console.time('Query with reordered index'); |
| 120 | +await powerSync.get( |
| 121 | + `SELECT * FROM todos WHERE description = ? AND list_id = ?`, |
| 122 | + ['Task 1', 'list1'] |
| 123 | +); |
| 124 | +console.timeEnd('Query with reordered index'); |
| 125 | +``` |
| 126 | + |
| 127 | +## Key Takeaways |
| 128 | + |
| 129 | +- Define compound indexes aligned with your most frequent query patterns. |
| 130 | +- Avoid skipping leading columns in compound indexes; create additional indexes if necessary. |
| 131 | + |
| 132 | +## Conclusion |
| 133 | + |
| 134 | +By following these best practices and leveraging PowerSync's schema capabilities, developers can achieve significant performance gains and ensure scalability for their applications. |
| 135 | +The findings above can be integrated directly into PowerSync's documentation to assist other developers in navigating these challenges effectively. |
0 commit comments