diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-19 17:18:50 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-12-19 17:18:50 +0000 |
| commit | d3c5cf71620d7a767506889239fe3747be4d3876 (patch) | |
| tree | 661102263b1ed526f4a026e550d1426a6195644a /docs/explanation | |
| parent | 64a9df3046e21d896f143c2fbbbefabdf86048f9 (diff) | |
docs: sync add pagination updates
Diffstat (limited to 'docs/explanation')
| -rw-r--r-- | docs/explanation/grasp-02-proactive-sync.md | 106 |
1 files changed, 85 insertions, 21 deletions
diff --git a/docs/explanation/grasp-02-proactive-sync.md b/docs/explanation/grasp-02-proactive-sync.md index 0607610..64fa096 100644 --- a/docs/explanation/grasp-02-proactive-sync.md +++ b/docs/explanation/grasp-02-proactive-sync.md | |||
| @@ -106,16 +106,30 @@ pub enum SyncMethod { | |||
| 106 | /// Key: relay URL | 106 | /// Key: relay URL |
| 107 | pub type PendingSyncIndex = Arc<RwLock<HashMap<String, Vec<PendingBatch>>>>; | 107 | pub type PendingSyncIndex = Arc<RwLock<HashMap<String, Vec<PendingBatch>>>>; |
| 108 | 108 | ||
| 109 | /// Pagination state for a subscription in non-Negentropy historic sync | ||
| 110 | #[derive(Debug, Clone)] | ||
| 111 | pub struct PaginationState { | ||
| 112 | /// Number of events received for this subscription | ||
| 113 | pub event_count: usize, | ||
| 114 | /// Smallest created_at timestamp seen (for pagination with `until`) | ||
| 115 | pub min_created_at: Option<Timestamp>, | ||
| 116 | /// Original filter to reconstruct for next page | ||
| 117 | pub original_filter: Filter, | ||
| 118 | } | ||
| 119 | |||
| 109 | pub struct PendingBatch { | 120 | pub struct PendingBatch { |
| 110 | /// Unique ID for this batch - for debugging/logging | 121 | /// Unique ID for this batch - for debugging/logging |
| 111 | pub batch_id: u64, | 122 | pub batch_id: u64, |
| 112 | /// The items this batch is syncing | 123 | /// The items this batch is syncing |
| 113 | pub items: PendingItems, | 124 | pub items: PendingItems, |
| 114 | /// Subscription IDs that must ALL receive EOSE before confirming (for ReqEose) | 125 | /// Subscription IDs that must ALL receive EOSE before confirming (for ReqEose) |
| 115 | /// Empty for Negentropy sync method until missing event ids identified | 126 | /// Empty for Negentropy sync method |
| 116 | pub outstanding_subs: HashSet<SubscriptionId>, | 127 | pub outstanding_subs: HashSet<SubscriptionId>, |
| 117 | /// The sync method used for this batch | 128 | /// The sync method used for this batch |
| 118 | pub sync_method: SyncMethod, | 129 | pub sync_method: SyncMethod, |
| 130 | /// Pagination tracking for REQ+EOSE subscriptions (empty for Negentropy) | ||
| 131 | /// Maps subscription ID to its pagination state | ||
| 132 | pub pagination_state: HashMap<SubscriptionId, PaginationState>, | ||
| 119 | } | 133 | } |
| 120 | 134 | ||
| 121 | #[derive(Debug, Clone, Default)] | 135 | #[derive(Debug, Clone, Default)] |
| @@ -125,6 +139,17 @@ pub struct PendingItems { | |||
| 125 | } | 139 | } |
| 126 | ``` | 140 | ``` |
| 127 | 141 | ||
| 142 | **Pagination for REQ+EOSE Historic Sync:** | ||
| 143 | |||
| 144 | When a relay doesn't support NIP-77 Negentropy, historic sync falls back to traditional REQ+EOSE. To handle large result sets efficiently: | ||
| 145 | |||
| 146 | - **`PaginationState`** tracks per-subscription pagination progress | ||
| 147 | - `event_count`: Number of events received so far | ||
| 148 | - `min_created_at`: Smallest timestamp seen, used to set `until` for next page | ||
| 149 | - `original_filter`: Base filter to reconstruct with updated `until` parameter | ||
| 150 | - **Automatic pagination**: When EOSE is received, if enough events were received to suggest more may exist, the system automatically issues a follow-up request with `until` set to `min_created_at` | ||
| 151 | - **Completion**: Pagination continues until an EOSE is received with fewer events than expected, indicating the end of results | ||
| 152 | |||
| 128 | --- | 153 | --- |
| 129 | 154 | ||
| 130 | ## Connection Lifecycle | 155 | ## Connection Lifecycle |
| @@ -155,13 +180,13 @@ stateDiagram-v2 | |||
| 155 | 180 | ||
| 156 | ### Connection Flow Methods | 181 | ### Connection Flow Methods |
| 157 | 182 | ||
| 158 | | Method | Purpose | When Called | Actions | | 183 | | Method | Purpose | When Called | Actions | |
| 159 | |--------|---------|-------------|---------| | 184 | | ------------------------------- | ------------------------- | ------------------------------- | --------------------------------------------------------------- | |
| 160 | | `register_relay()` | Initialize relay tracking | Discovery via RepoSyncIndex | Creates RelayConnection, stores in HashMap, returns immediately | | 185 | | `register_relay()` | Initialize relay tracking | Discovery via RepoSyncIndex | Creates RelayConnection, stores in HashMap, returns immediately | |
| 161 | | `try_connect_relay()` | Attempt connection | Periodic retry (500ms) | Calls connect_and_subscribe, sends notification on success | | 186 | | `try_connect_relay()` | Attempt connection | Periodic retry (500ms) | Calls connect_and_subscribe, sends notification on success | |
| 162 | | `handle_connect_or_reconnect()` | Setup after connection | ConnectNotification received | Spawns event loop, updates state, decides sync strategy | | 187 | | `handle_connect_or_reconnect()` | Setup after connection | ConnectNotification received | Spawns event loop, updates state, decides sync strategy | |
| 163 | | `handle_disconnect()` | Cleanup after disconnect | DisconnectNotification received | Updates state, clears pending, KEEPS RelayConnection | | 188 | | `handle_disconnect()` | Cleanup after disconnect | DisconnectNotification received | Updates state, clears pending, KEEPS RelayConnection | |
| 164 | | `retry_disconnected_relays()` | Periodic reconnection | Every 500ms | For each ready relay: try_connect_relay() | | 189 | | `retry_disconnected_relays()` | Periodic reconnection | Every 500ms | For each ready relay: try_connect_relay() | |
| 165 | 190 | ||
| 166 | ### Event Loop Lifecycle | 191 | ### Event Loop Lifecycle |
| 167 | 192 | ||
| @@ -179,6 +204,7 @@ flowchart LR | |||
| 179 | ``` | 204 | ``` |
| 180 | 205 | ||
| 181 | **Why respawn is required**: | 206 | **Why respawn is required**: |
| 207 | |||
| 182 | - `run_event_loop()` breaks on RelayStatus::Disconnected | 208 | - `run_event_loop()` breaks on RelayStatus::Disconnected |
| 183 | - The spawned task completely exits | 209 | - The spawned task completely exits |
| 184 | - Cannot resume terminated task - must spawn fresh | 210 | - Cannot resume terminated task - must spawn fresh |
| @@ -477,22 +503,11 @@ async fn historic_sync( | |||
| 477 | 503 | ||
| 478 | Dispatches to: | 504 | Dispatches to: |
| 479 | 505 | ||
| 480 | - `historic_sync_negentropy()` - NIP-77 parallel sync (if supported) | 506 | - `historic_sync_negentropy()` - NIP-77 parallel sync (if supported) - no pagination needed |
| 481 | - `historic_sync_legacy()` - REQ+EOSE fallback | 507 | - `historic_sync_legacy()` - REQ+EOSE fallback with automatic pagination for large result sets |
| 482 | 508 | ||
| 483 | ### Building Blocks | 509 | ### Building Blocks |
| 484 | 510 | ||
| 485 | #### `reconstruct_filters()` - Rebuild from Confirmed State | ||
| 486 | |||
| 487 | ```rust | ||
| 488 | /// Reconstruct filters from RelaySyncIndex (confirmed state ONLY) | ||
| 489 | /// | ||
| 490 | /// Returns raw Vec<Filter> for L1+L2+L3. | ||
| 491 | /// Used by: quick_reconnect, consolidate | ||
| 492 | /// Does NOT include pending items - those flow through AddFilters path. | ||
| 493 | async fn reconstruct_filters(&self, relay_url: &str) -> Vec<Filter> | ||
| 494 | ``` | ||
| 495 | |||
| 496 | #### `sync_computed_filters()` - Handle New AddFilters | 511 | #### `sync_computed_filters()` - Handle New AddFilters |
| 497 | 512 | ||
| 498 | ```rust | 513 | ```rust |
| @@ -695,6 +710,55 @@ If negentropy fails (relay doesn't support NIP-77, network error, etc.): | |||
| 695 | 710 | ||
| 696 | --- | 711 | --- |
| 697 | 712 | ||
| 713 | ## REQ+EOSE Pagination | ||
| 714 | |||
| 715 | When a relay doesn't support NIP-77 Negentropy, historic sync uses traditional REQ+EOSE with automatic pagination to handle large result sets efficiently. | ||
| 716 | |||
| 717 | ### How Pagination Works | ||
| 718 | |||
| 719 | 1. **Initial Request**: Send REQ with filters (may include `since` parameter) | ||
| 720 | 2. **Track Events**: As events arrive, [`PaginationState`](src/sync/mod.rs:165) tracks: | ||
| 721 | - `event_count`: Number of events received | ||
| 722 | - `min_created_at`: Smallest timestamp seen (oldest event) | ||
| 723 | - `original_filter`: Base filter for reconstruction | ||
| 724 | 3. **EOSE Detection**: When EOSE arrives, check if pagination is needed | ||
| 725 | 4. **Next Page**: If enough events were received (suggesting more exist): | ||
| 726 | - Create new filter with `until: min_created_at` | ||
| 727 | - Issue another REQ for events older than the oldest seen | ||
| 728 | - Reuse same subscription ID | ||
| 729 | 5. **Completion**: Repeat until EOSE arrives with fewer events, indicating end of results | ||
| 730 | |||
| 731 | ### Pagination State Lifecycle | ||
| 732 | |||
| 733 | ```mermaid | ||
| 734 | flowchart TB | ||
| 735 | REQ[Send REQ with filters] --> TRACK[Initialize PaginationState] | ||
| 736 | TRACK --> EVENT[Receive EVENT] | ||
| 737 | EVENT --> UPDATE[Update event_count and min_created_at] | ||
| 738 | UPDATE --> MORE{More events?} | ||
| 739 | MORE --> |yes| EVENT | ||
| 740 | MORE --> |no| EOSE[Receive EOSE] | ||
| 741 | EOSE --> CHECK{event_count suggests more pages?} | ||
| 742 | CHECK --> |yes| NEXT[Create filter with until=min_created_at] | ||
| 743 | NEXT --> REQ2[Send next page REQ] | ||
| 744 | REQ2 --> RESET[Reset event_count, keep min_created_at] | ||
| 745 | RESET --> EVENT | ||
| 746 | CHECK --> |no| DONE[Batch complete, confirm items] | ||
| 747 | ``` | ||
| 748 | |||
| 749 | ### Pagination vs Negentropy | ||
| 750 | |||
| 751 | | Aspect | Negentropy Sync | REQ+EOSE Pagination | | ||
| 752 | | ------------------- | ---------------------------- | --------------------------------------------------------- | | ||
| 753 | | **Efficiency** | High (set reconciliation) | Lower (sequential pages) | | ||
| 754 | | **Bandwidth** | Minimal (only missing items) | Higher (all matching events transferred) | | ||
| 755 | | **Relay support** | Requires NIP-77 | Universal (standard Nostr) | | ||
| 756 | | **State tracking** | None needed | [`PaginationState`](src/sync/mod.rs:165) per subscription | | ||
| 757 | | **Completion time** | Typically faster | Slower for large sets | | ||
| 758 | | **Use cases** | Full sync, large event sets | Fallback, small gaps with `since` | | ||
| 759 | |||
| 760 | --- | ||
| 761 | |||
| 698 | ## State Flow Summary | 762 | ## State Flow Summary |
| 699 | 763 | ||
| 700 | ```mermaid | 764 | ```mermaid |