-
Notifications
You must be signed in to change notification settings - Fork 145
feat: Add server-side search and pagination performance improvements #1668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: Add server-side search and pagination performance improvements #1668
Conversation
61f88ca to
4044e28
Compare
Fixes notes UI performance issue when loading large note databases. Changes: - Migrate from /apps/notes/notes to /apps/notes/api/v1/notes endpoint - Add exclude=content parameter to skip heavy content field on initial load - Load ALL note metadata (id, title, category, favorite, etc.) at once - Content loaded on-demand when user selects a note via existing fetchNote() - Fetch settings from /apps/notes/api/v1/settings endpoint Benefits: - 10x faster initial load time (1MB metadata vs 10MB with content) - All categories available immediately for filtering - All notes visible in sidebar immediately - Significantly reduced memory usage - Simple implementation using existing patterns Technical details: - src/NotesService.js: Updated fetchNotes() to use API v1 with exclude param - src/store/notes.js: Added notesLoadingInProgress state tracking - src/App.vue: Updated loadNotes() to handle API v1 response format The sidebar (NoteItem) only needs metadata and doesn't use content. Content is fetched on-demand when viewing a note, which is an acceptable tradeoff for much faster initial load. Signed-off-by: Chris Coutinho <chris@coutinho.io>
Replace all-or-nothing pagination with incremental loading that adds 50 notes at a time as user scrolls. This prevents UI lockup when scrolling through large note collections by limiting DOM nodes rendered at any given time. - Change showFirstNotesOnly boolean to displayedNotesCount counter - Start with 50 notes (increased from 30) - Load additional 50 notes when scrolling to end - Reset counter to 50 when changing category or search Signed-off-by: Chris Coutinho <chris@coutinho.io>
Add isLoadingMore flag to prevent vue-observe-visibility from triggering multiple times in rapid succession. Previously, when the loading indicator became visible, it would fire repeatedly before the DOM updated, causing all notes to load at once and freezing the UI. - Add isLoadingMore flag to data() - Guard onEndOfNotes with loading check - Use $nextTick to ensure proper async DOM updates - Reset flag on category/search changes Signed-off-by: Chris Coutinho <chris@coutinho.io>
…scroll The previous implementation fetched all 3,633 note metadata records at once on initial load, even though only 50 were displayed. When scrolling triggered pagination, the UI would hang while processing all notes. This change implements proper chunked loading using the existing backend chunkSize and chunkCursor API parameters: - Initial load fetches only first 50 notes - Scrolling triggers incremental fetches of 50 notes at a time - Cursor is stored in sync state to track pagination position - Notes are updated incrementally using existing store actions This prevents the UI hang and significantly improves performance for users with large note collections. Signed-off-by: Chris Coutinho <chris@coutinho.io>
Add extensive console logging to track the flow of chunked note loading: - NotesService.fetchNotes(): Log API calls, response structure, and update paths - App.loadNotes(): Log initial load flow and cursor management - NotesView.onEndOfNotes(): Log scroll trigger, cursor state, and display count updates This will help diagnose why the pagination is still hanging despite the chunked loading implementation. The logging will show: - When and with what parameters fetchNotes is called - What data structure is returned from the API - Which update path is taken (incremental vs full) - How the cursor and display count are being managed Signed-off-by: Chris Coutinho <chris@coutinho.io>
When no cursor is provided (last chunk or full refresh), the API was returning pruned notes with only the 'id' field, missing title, category, and other metadata. This caused Vue components to render "undefined" for all note titles after pagination. Fixed by using getNoteData() to return full metadata while still respecting the 'exclude' parameter. Also updated frontend to correctly read cursor from response headers instead of response body. Signed-off-by: Chris Coutinho <chris@coutinho.io>
This commit fixes several critical issues with the search functionality: 1. Periodic Refresh Conflict: Modified App.vue loadNotes() to skip refresh when search is active, preventing the 30-second refresh timer from overwriting active search results. 2. Search State Synchronization: Restored updateSearchText commit in NotesView.vue to keep client-side and server-side filters in sync. 3. Null Safety: Added comprehensive null checks to all getters in notes.js to prevent errors when clearing search or during progressive loading transitions. 4. Search Clear Behavior: Removed unnecessary clearSyncCache() call and added proper display count reset when reverting from search to normal pagination mode. Files modified: - src/App.vue: Skip periodic refresh during active search - src/components/NotesView.vue: Restore search state sync - src/store/notes.js: Add null safety to all note getters - lib/Controller/Helper.php: Server-side search implementation Signed-off-by: Chris Coutinho <chris@coutinho.io>
This commit implements server-side search functionality with full pagination support: 1. API Support: Added search parameter to NotesApiController index() endpoint, allowing client to pass search queries to filter notes by title on the server side. 2. Category Statistics: Added X-Notes-Category-Stats and X-Notes-Total-Count headers on first chunk to provide accurate counts even during pagination, enabling proper category display without loading all notes. 3. Client Search Service: Implemented searchNotes() function in NotesService.js with full chunked pagination support, debouncing, and proper state management. 4. Performance: Search operates on chunked data (50 notes at a time) with progressive loading, maintaining UI responsiveness even with large note collections. Files modified: - lib/Controller/NotesApiController.php: Add search param and stats headers - src/NotesService.js: Implement searchNotes() with pagination - src/components/NoteRich.vue: Minor adjustments for search support Signed-off-by: Chris Coutinho <chris@coutinho.io>
4044e28 to
e5c119a
Compare
|
Hi friendly ping @enjeck @JuliaKirschenheuter is there interest in this PR? |
enjeck
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this!
I tested locally and it does improve speed. I made a script to create 4000 notes:
#!/usr/bin/env zsh
set -u
NC_BASE_URL="http://nextcloud.local/index.php"
NC_USER="admin"
NC_APP_PASSWORD="admin"
NOTES_CATEGORY="${NOTES_CATEGORY:-NotesTest}"
TOTAL_NOTES="${TOTAL_NOTES:-4000}"
echo "Starting..."
# Function to create a single note
create_note() {
local idx="$1"
local title="AutoTest Note ${idx}"
local content="This is autogenerated content for note ${idx}.\nSeed: $(date +%s)-${RANDOM}"
# Use a small JSON payload; favorite=false and modified=0 omit extra work
curl --connect-timeout 5 --max-time 10 --retry 2 --retry-delay 1 -sS -u "${NC_USER}:${NC_APP_PASSWORD}" \
-H "Content-Type: application/json" \
-X POST \
-d "{\"category\":\"${NOTES_CATEGORY}\",\"title\":\"${title}\",\"content\":\"${content}\"}" \
"${NC_BASE_URL}/apps/notes/api/v1/notes" >/dev/null 2>&1 || true
}
progress_interval=100
created=0
for (( i=1; i<=TOTAL_NOTES; i++ )); do
create_note "${i}" >/dev/null
(( created++ ))
if (( created % progress_interval == 0 )); then
echo "Created ${created} notes..."
fi
done
echo "Done. Created ${TOTAL_NOTES} notes in category '${NOTES_CATEGORY}'."
Without these changes, it take about 12 seconds for the initial page load. With this, that time is halved
| // Skip refresh if in search mode - search results should not be overwritten | ||
| const searchText = store.state.app.searchText | ||
| if (searchText && searchText.trim() !== '') { | ||
| console.log('[App.loadNotes] Skipping - in search mode with query:', searchText) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the search text is cleared, we should see the list of all notes. Currently, when that happens, it's empty
| this.startRefreshTimer(config.interval.notes.refresh) | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point, this was an oversight - I will try to bring those subheaders back
Actually, I take this back. I tested again and there doesn't seem to be any consistent speedup. What sort of testing did you do on your side to show that these changes improve performance? |
|
Hi @enjeck - I tested this locally by creating 6k notes and then testing how long it took to trigger the next-page scroll capability. I didn't compare initial page load in isolation - only the time it takes to scroll through successive pages. On large numbers of notes, the initial page load would timeout without this change. I will try to gather some more profiling information and get back to you |

Summary
This PR introduces server-side search functionality and several performance improvements to the Notes app, particularly for users with large note collections. The changes focus on optimizing the initial load time and implementing efficient pagination.
Related Issues
Fixes #1226 - Notes are not loading (user has ~5k notes). HTTP 504 Gateway Timeout
Addresses #966 - Notes search is very slow via global search
Possibly fixes #1407 - On web editor, notes loading forever and impossible to show/edit
Changes
Performance Improvements
Server-Side Search
Bug Fixes
Modified Files
lib/Controller/Helper.php- Added search parameter and filtering logiclib/Controller/NotesApiController.php- Integrated search with pagination, added category stats headerssrc/App.vue- Updated note loading logicsrc/NotesService.js- Enhanced service layer for pagination and searchsrc/components/NoteRich.vue- UI adjustmentssrc/components/NotesView.vue- Scroll-based pagination handlingsrc/store/notes.js- State management for paginated notessrc/store/sync.js- Sync improvementsTesting
Tested with large note collections (1000+ notes) to ensure:
This PR was generated with the help of AI, and reviewed by a Human