|
10 | 10 | * |
11 | 11 | * The 10 newest rows are never deleted, never absent. There is no step 2. |
12 | 12 | * |
13 | | - * Post meta compaction (beta 1) does this: |
| 13 | + * Post meta compaction (beta 1–3) does this: |
14 | 14 | * 1. delete_post_meta() — removes ALL 50 updates in one call. |
15 | 15 | * 2. add_post_meta() — re-inserts the 10 newest updates one at a time. |
16 | 16 | * |
|
19 | 19 | * should still exist. |
20 | 20 | * |
21 | 21 | * Usage: |
22 | | - * npm run env:cli -- eval-file tools/local-env/scripts/collaboration-perf/DO_NOT_RELEASE_prove-data-loss.php |
| 22 | + * npm run env:cli -- eval-file tools/local-env/scripts/collaboration-perf/DO_NOT_RELEASE_prove-beta3-post_meta_data_loss.php |
23 | 23 | * |
24 | 24 | * @package WordPress |
25 | 25 | */ |
|
113 | 113 | // Step 2 of 2 (re-insert kept updates) would happen here, but the gap already occurred. |
114 | 114 |
|
115 | 115 | // ===================================================================== |
116 | | -// Gap at scale. |
| 116 | +// Compaction at scale. |
117 | 117 | // |
118 | | -// The gap is the time between delete_post_meta() and the last |
119 | | -// add_post_meta() — the window where all updates are missing. |
120 | | -// More updates to keep = more add_post_meta() calls = wider gap. |
| 118 | +// Post meta: the data loss window is the time between |
| 119 | +// delete_post_meta() and the last add_post_meta() — more updates |
| 120 | +// to keep = more add_post_meta() calls = wider window. |
121 | 121 | // |
122 | | -// Table compaction has no gap. The kept rows are never removed. |
| 122 | +// Table: rows are never absent — verify by reading immediately |
| 123 | +// after compaction at each scale. |
123 | 124 | // ===================================================================== |
124 | 125 |
|
125 | 126 | $gap_scales = array( 50, 200, 500, 1000 ); |
|
135 | 136 |
|
136 | 137 | WP_CLI::log( " Scale: {$gap_total} updates (keep {$gap_keep})" ); |
137 | 138 |
|
138 | | - // Seed post meta for this scale. |
| 139 | + // --- Post meta: measure the data loss window. --- |
| 140 | + |
139 | 141 | $gap_post_id = wp_insert_post( array( |
140 | 142 | 'post_type' => 'wp_sync_storage', |
141 | 143 | 'post_status' => 'publish', |
|
148 | 150 | ) ); |
149 | 151 | } |
150 | 152 |
|
151 | | - // The cursor: timestamp of the first update to keep. |
152 | | - $gap_cursor = 1000 + $gap_discard; |
153 | | - |
154 | | - // Read all updates before deleting (same as production code path). |
| 153 | + $gap_cursor = 1000 + $gap_discard; |
155 | 154 | $all_updates = get_post_meta( $gap_post_id, 'wp_sync_update', false ); |
156 | 155 |
|
157 | | - // Measure the full gap: delete all, then re-insert each kept update. |
158 | 156 | $gap_start = microtime( true ); |
159 | 157 | delete_post_meta( $gap_post_id, 'wp_sync_update' ); |
160 | 158 | foreach ( $all_updates as $envelope ) { |
161 | 159 | if ( is_array( $envelope ) && $envelope['timestamp'] >= $gap_cursor ) { |
162 | 160 | add_post_meta( $gap_post_id, 'wp_sync_update', $envelope ); |
163 | 161 | } |
164 | 162 | } |
165 | | - $gap_ms = ( microtime( true ) - $gap_start ) * 1000; |
| 163 | + $meta_gap_ms = ( microtime( true ) - $gap_start ) * 1000; |
| 164 | + |
| 165 | + wp_delete_post( $gap_post_id, true ); |
| 166 | + |
| 167 | + // --- Table: verify rows are never absent. --- |
| 168 | + |
| 169 | + $wpdb->query( "TRUNCATE TABLE {$wpdb->collaboration}" ); |
| 170 | + $storage = new WP_Collaboration_Table_Storage(); |
| 171 | + for ( $i = 0; $i < $gap_total; $i++ ) { |
| 172 | + $storage->add_update( $gap_room, array( 'edit' => $i ) ); |
| 173 | + } |
| 174 | + |
| 175 | + $table_cursor = (int) $wpdb->get_var( $wpdb->prepare( |
| 176 | + "SELECT id FROM {$wpdb->collaboration} WHERE room = %s ORDER BY id DESC LIMIT 1 OFFSET %d", |
| 177 | + $gap_room, |
| 178 | + $gap_keep - 1 |
| 179 | + ) ); |
| 180 | + |
| 181 | + $storage->remove_updates_before_cursor( $gap_room, $table_cursor ); |
| 182 | + |
| 183 | + $reader = new WP_Collaboration_Table_Storage(); |
| 184 | + $table_after = $reader->get_updates_after_cursor( $gap_room, 0 ); |
| 185 | + $table_visible_count = count( $table_after ); |
166 | 186 |
|
167 | 187 | $gap_results[] = array( |
168 | | - 'total' => $gap_total, |
169 | | - 'keep' => $gap_keep, |
170 | | - 'gap_ms' => $gap_ms, |
| 188 | + 'total' => $gap_total, |
| 189 | + 'keep' => $gap_keep, |
| 190 | + 'meta_gap_ms' => $meta_gap_ms, |
| 191 | + 'table_visible' => $table_visible_count, |
171 | 192 | ); |
172 | | - |
173 | | - // Cleanup this scale. |
174 | | - wp_delete_post( $gap_post_id, true ); |
175 | 193 | } |
176 | 194 |
|
177 | 195 | // ===================================================================== |
|
181 | 199 | $separator = str_repeat( '─', 60 ); |
182 | 200 |
|
183 | 201 | WP_CLI::log( '' ); |
184 | | -WP_CLI::log( WP_CLI::colorize( '%_Sync Compaction Data Integrity Test%n' ) ); |
| 202 | +WP_CLI::log( WP_CLI::colorize( '%_Compaction Data Integrity Test%n' ) ); |
185 | 203 | WP_CLI::log( 'Run: ' . gmdate( 'Y-m-d H:i:s' ) . ' UTC' ); |
186 | 204 | WP_CLI::log( '' ); |
187 | 205 | WP_CLI::log( "Compaction triggers at {$total} updates. Keeps {$keep} newest, discards {$discard} oldest." ); |
188 | 206 | WP_CLI::log( WP_CLI::colorize( "%_Expected: {$keep} newest updates remain visible after compaction.%n" ) ); |
189 | 207 |
|
190 | 208 | WP_CLI::log( '' ); |
191 | 209 | WP_CLI::log( $separator ); |
192 | | -WP_CLI::log( WP_CLI::colorize( '%_Table (proposed)%n' ) ); |
| 210 | +WP_CLI::log( WP_CLI::colorize( '%_Table (this PR)%n' ) ); |
193 | 211 | WP_CLI::log( $separator ); |
194 | 212 | WP_CLI::log( " DELETE WHERE id < cutoff — only the {$discard} oldest removed." ); |
195 | 213 | WP_CLI::log( '' ); |
|
201 | 219 |
|
202 | 220 | WP_CLI::log( '' ); |
203 | 221 | WP_CLI::log( $separator ); |
204 | | -WP_CLI::log( WP_CLI::colorize( '%_Post Meta (current beta 1)%n' ) ); |
| 222 | +WP_CLI::log( WP_CLI::colorize( '%_Post Meta (beta 1–3)%n' ) ); |
205 | 223 | WP_CLI::log( $separator ); |
206 | 224 | WP_CLI::log( " delete_post_meta() removes all {$total}, then add_post_meta() re-inserts {$keep}." ); |
207 | 225 | WP_CLI::log( '' ); |
|
213 | 231 |
|
214 | 232 | WP_CLI::log( '' ); |
215 | 233 | WP_CLI::log( $separator ); |
216 | | -WP_CLI::log( WP_CLI::colorize( '%_Post Meta gap at scale%n' ) ); |
| 234 | +WP_CLI::log( WP_CLI::colorize( '%_Compaction at scale%n' ) ); |
217 | 235 | WP_CLI::log( $separator ); |
218 | | -WP_CLI::log( ' Duration where all updates are missing:' ); |
219 | 236 | WP_CLI::log( '' ); |
220 | 237 |
|
221 | | -$scale_items = array(); |
| 238 | +$scale_fields = array( 'Updates (keep 20%)', 'Post meta data loss window', 'Table rows visible' ); |
| 239 | +$scale_items = array(); |
222 | 240 | foreach ( $gap_results as $gap ) { |
| 241 | + $table_label = $gap['table_visible'] >= $gap['keep'] |
| 242 | + ? "{$gap['table_visible']} of {$gap['keep']} — OK" |
| 243 | + : "{$gap['table_visible']} of {$gap['keep']} — UNEXPECTED"; |
223 | 244 | $scale_items[] = array( |
224 | | - 'Updates (keep 20%)' => sprintf( '%d (keep %d)', $gap['total'], $gap['keep'] ), |
225 | | - 'Gap' => sprintf( '%.1f ms', $gap['gap_ms'] ), |
| 245 | + 'Updates (keep 20%)' => sprintf( '%d (keep %d)', $gap['total'], $gap['keep'] ), |
| 246 | + 'Post meta data loss window' => sprintf( '%.1f ms', $gap['meta_gap_ms'] ), |
| 247 | + 'Table rows visible' => $table_label, |
226 | 248 | ); |
227 | 249 | } |
228 | | -WP_CLI\Utils\format_items( 'table', $scale_items, array( 'Updates (keep 20%)', 'Gap' ) ); |
229 | | - |
230 | | -WP_CLI::log( '' ); |
231 | | -WP_CLI::log( WP_CLI::colorize( ' %GTable: no gap at any scale.%n' ) ); |
| 250 | +WP_CLI\Utils\format_items( 'table', $scale_items, $scale_fields ); |
232 | 251 |
|
233 | 252 | // Cleanup. |
234 | 253 | wp_delete_post( $post_id, true ); |
|
0 commit comments