Skip to content
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

Propose batch saveEntityRecords action #27241

Closed
wants to merge 3 commits into from

Conversation

adamziel
Copy link
Contributor

@adamziel adamziel commented Nov 24, 2020

Description

Batch requests are now supported in WP core and already used in edit-widgets Gutenberg package. They could enhance any code issuing a burst of requests. Consider multi-entity saving:

entitiesToSave.forEach( ( { kind, name, key } ) => {
saveEditedEntityRecord( kind, name, key );
} );

The code above issues one request per entity record. Wouldn't it be nice if it would issue just one request and save multiple entity records at once? Many pieces of that puzzle are already in their place, and this PR would take us one step closer by exposing the following API:

__experimentalBatchSaveEntityRecords( entitiesToSave );

From here, the last missing piece would be enabling batch requests in all the API endpoints used in the site editor.

Notes

  • batch-processing API could use a bit of polish. I intentionally didn't fully clean it up as there is an active discussion about the future of state management in Gutenberg. It may take multiple directions, but it seems like in any case we'll be moving towards embracing async/await instead of using generators, controls, yields, and alike. Once that happens, the cleanup here should be easier by an order of magnitude.
  • Once the API stabilizes batch-processing should become a separate package. For now, let's keep it experimental and private.

How has this been tested?

  1. Confirm all the tests pass.
  2. Confirm the widgets editor keeps saving changes in the same way as before this PR.

Types of changes

New feature (non-breaking change which adds functionality)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@github-actions
Copy link

github-actions bot commented Nov 24, 2020

Size Change: -9.19 kB (0%)

Total Size: 1.19 MB

Filename Size Change
build/a11y/index.js 1.14 kB -2 B (0%)
build/annotations/index.js 3.8 kB +1 B
build/api-fetch/index.js 3.42 kB -3 B (0%)
build/blob/index.js 665 B +1 B
build/block-directory/index.js 8.71 kB -4 B (0%)
build/block-editor/index.js 128 kB -5.47 kB (4%)
build/block-editor/style-rtl.css 11.2 kB -156 B (1%)
build/block-editor/style.css 11.2 kB -153 B (1%)
build/block-library/editor-rtl.css 8.86 kB -102 B (1%)
build/block-library/editor.css 8.87 kB -99 B (1%)
build/block-library/index.js 148 kB +17 B (0%)
build/block-library/style-rtl.css 8.27 kB +39 B (0%)
build/block-library/style.css 8.27 kB +40 B (0%)
build/block-library/theme-rtl.css 789 B -3 B (0%)
build/block-library/theme.css 790 B -3 B (0%)
build/block-serialization-default-parser/index.js 1.88 kB +6 B (0%)
build/blocks/index.js 48.1 kB +12 B (0%)
build/components/index.js 172 kB +37 B (0%)
build/compose/index.js 9.95 kB -5 B (0%)
build/core-data/index.js 15.2 kB +404 B (2%)
build/data-controls/index.js 828 B +1 B
build/data/index.js 8.97 kB +171 B (1%)
build/date/index.js 11.2 kB -5 B (0%)
build/dom/index.js 4.95 kB +1 B
build/edit-navigation/index.js 11.1 kB +27 B (0%)
build/edit-post/index.js 306 kB -13 B (0%)
build/edit-post/style-rtl.css 6.42 kB -38 B (0%)
build/edit-post/style.css 6.4 kB -38 B (0%)
build/edit-site/index.js 24.1 kB +488 B (2%)
build/edit-site/style-rtl.css 4.06 kB +192 B (4%)
build/edit-site/style.css 4.06 kB +191 B (4%)
build/edit-widgets/index.js 22.8 kB -3.49 kB (15%) 👏
build/editor/index.js 43.3 kB -30 B (0%)
build/format-library/index.js 6.86 kB -3 B (0%)
build/hooks/index.js 2.27 kB -1 B
build/i18n/index.js 3.56 kB -1 B
build/keyboard-shortcuts/index.js 2.54 kB -3 B (0%)
build/list-reusable-blocks/index.js 3.11 kB +8 B (0%)
build/media-utils/index.js 5.31 kB -9 B (0%)
build/notices/index.js 1.82 kB +2 B (0%)
build/nux/index.js 3.42 kB +2 B (0%)
build/plugins/index.js 2.56 kB +3 B (0%)
build/priority-queue/index.js 791 B +1 B
build/redux-routine/index.js 2.84 kB +2 B (0%)
build/reusable-blocks/index.js 2.92 kB -3 B (0%)
build/rich-text/index.js 13.4 kB +10 B (0%)
build/server-side-render/index.js 2.77 kB +2 B (0%)
build/shortcode/index.js 1.69 kB -2 B (0%)
build/token-list/index.js 1.27 kB +1 B
build/url/index.js 2.84 kB -1.22 kB (42%) 🎉
build/viewport/index.js 1.86 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/autop/index.js 2.84 kB 0 B
build/block-directory/style-rtl.css 943 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-serialization-spec-parser/index.js 3.06 kB 0 B
build/components/style-rtl.css 15.3 kB 0 B
build/components/style.css 15.3 kB 0 B
build/deprecated/index.js 768 B 0 B
build/dom-ready/index.js 571 B 0 B
build/edit-navigation/style-rtl.css 881 B 0 B
build/edit-navigation/style.css 885 B 0 B
build/edit-widgets/style-rtl.css 3.13 kB 0 B
build/edit-widgets/style.css 3.13 kB 0 B
build/editor/editor-styles-rtl.css 476 B 0 B
build/editor/editor-styles.css 478 B 0 B
build/editor/style-rtl.css 3.85 kB 0 B
build/editor/style.css 3.85 kB 0 B
build/element/index.js 4.62 kB 0 B
build/escape-html/index.js 735 B 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/html-entities/index.js 623 B 0 B
build/is-shallow-equal/index.js 698 B 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/primitives/index.js 1.43 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.22 kB 0 B

compressed-size-action

@TimothyBJacobs
Copy link
Member

I think we still need to preflight the batch endpoint to get the max items value.

@adamziel
Copy link
Contributor Author

You got it @TimothyBJacobs! Added in 774c3df.

@adamziel
Copy link
Contributor Author

adamziel commented Dec 2, 2020

cc @ellatrix @gziolo @youknowriad I'd love to give more polish to this PR (like docstrings), but it would be good to hear your preliminary feedback about the direction first :-)

*/
export function* __experimentalBatchSaveEntityRecords( spec ) {
for ( const { kind, type, key, record } of spec ) {
if ( key ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check seems weird, why sometimes we have a key and other times a record

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're editing an existing record then there is a key. If we're creating a new record then there is no key.

}
}
yield __unstableAwaitPromise(
new Promise( ( resolve ) => setTimeout( resolve, 10 ) )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a random timeout here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is that it addresses an issue similar to what I outlined in #27173 (comment). Rungen hands execution back to __experimentalBatchSaveEntityRecords which calls processBatch before addToBatch has been called.

Probably we should replace this setTimeout with something akin to await waitUntilNItemsAreEnqueued( spec.length ).


// Ensure batch-processing store is available for dispatching
setTimeout( () => {
dispatch( 'core/__experimental-batch-processing' ).registerProcessor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is global and applies to all registries, it shouldn't be the case. I think the issues comes from the "registerProcessor" API. Why do we need to register a processor at all and why are these tracked in the store.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, picking this work up from @adamziel and not sure what you mean here @youknowriad! What you would replace registering a processor with?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamziel wrote some docs on @wordpress/batch-processing here:

https://github.com/WordPress/gutenberg/pull/27241/files#diff-15ed4b1ffcab0ae3c62807b5d98ed60ea36bfacd841f965cdac183d69d37ce66

My understanding is that the processor concept makes it so that @wordpress/batch-processing is a generic way to batch async work and not something that's reliant on /v1/batch.

I'm not against removing this abstraction and instead calling apiFetch( '/v1/batch' ) directly from saveEntityRecords. In general I think it's better to hold off on adding abstractions until we're sure that we need it. Is this what you mean @youknowriad?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @noisysocks my comment was not clear it's not the abstraction that bothers me but more the shape of the API (relying on a store and strings).

For example, an API like that would be more appealing to me:

// Create the "processor"
const processor = createProcessor( inputs => doSomethingWithInputs );

// Create the "batch" to be kept  in core-data if it's something that need to persist between action calls
// or just use  a local variable if the batching happens within the same function like desired here `__experimentalBatchSaveEntityRecords`
const batch = createBatch( processor );

batch.add( input )
batch.add( input2 )

const result = batch.flush()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmm! Very good. I'll have a play.

@noisysocks noisysocks assigned noisysocks and unassigned adamziel Jan 11, 2021
@noisysocks
Copy link
Member

Taking this over from @adamziel as it will help with fixing #27173 properly.

@adamziel
Copy link
Contributor Author

adamziel commented Feb 4, 2021

@adamziel adamziel closed this Feb 4, 2021
@youknowriad youknowriad deleted the add/__experimentalBatchSaveEntityRecords branch February 8, 2021 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] API fetch /packages/api-fetch [Package] Core data /packages/core-data [Type] Experimental Experimental feature or API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants