Uploaded image for project: 'Moodle'
  1. Moodle
  2. MDL-82652

Support bulk REST API calls

XMLWordPrintable

    • Icon: Improvement Improvement
    • Resolution: Unresolved
    • Icon: Minor Minor
    • None
    • 4.5
    • General
    • MOODLE_405_STABLE
    • MDL-82652-main
    • Hide

      Test preparation

      1. Make sure developer mode is turned off. If you're using MDK, run "mdk run undev"
      2. Open your browser to Moodle
      3. Open the browser developer tools ot the "Network" tab
      4. Ensure that the "Disable cache" option is NOT ticked
      5. Open your Moodle config.php
      6. Near the bottom of the file, add:

        $CFG->cachetemplates = true;
        

      7. Ensure that $CFG->templaterev is not explicitly set anywhere in the file
      8. Ensure that $CFG->batch_fetch_requests is not explicitly set anywhere in the file

      Test 1: Unbatched loaders

      1. Navigate to your user Dashboard
      2. Run {{admin/cli/purge_caches.php}:

        php admin/cli/purge_caches.php
        

      3. From the page Navigation click on the "Dashboard" link (that is, do not just hit the Refresh button)
        1. Confirm that the page loads correctly
      4. In the Network tab of the developer tools, filter for core/strings
        1. Confirm that there are a number of requests for strings
        2. Confirm that there all returned a valid 200 response code
      5. Click in to inspect any one of these
        1. Confirm that the "Response Headers" section has an "Expires" header
        2. Confirm that the "Response Headers" section has a "Cache-Control" header
        3. Confirm that the "Expires" header is about 3 months in the future (guesstimate is fine)
        4. Confirm that the URL includes cachekey:[some number] in it
      6. In the Network tab of the developer tools, filter for core/templates
        1. Confirm that there are a number of requests for templates
        2. Confirm that there all returned a valid 200 response code
      7. Click in to inspect any one of these
        1. Confirm that the "Response Headers" section has an "Expires" header
        2. Confirm that the "Response Headers" section has a "Cache-Control" header
        3. Confirm that the "Expires" header is about 3 months in the future (guesstimate is fine)
      8. Click on the "Dashboard" link in the page navigation again (do not just hit the refresh button)
        1. Confirm that the templates were requested again
        2. Confirm that the templates were all returned from disk cache (May appear differently in different browsers. On Chrome the Status Code would show "200 OK (from disk cache)")
        3. Confirm that the URL includes cachekey:[some number] in it

      Test 2: Using the batcher

      1. Open your Moodle config.php
      2. Update the configuration to set:

        $CFG->cachetemplates = false;
        

      3. Purge caches again:

        php admin/cli/purge_caches.php
        

      4. From the page Navigation click on the "Dashboard" link again
        1. Confirm that the page loads correctly
      5. In the Network tab of the developer tools, filter for r.php
        1. Confirm that there are some requests for $batch
      6. Click in to inspect any one of these
        1. Confirm that the "Response Headers" section does not have an "Expires" header
        2. Confirm that the "Response Headers" section does not have a "Cache-Control" header
      7. There may also be a request for a string and/or template, if so:
        1. Confirm that the "Response Headers" section does not have an "Expires" header
        2. Confirm that the "Response Headers" section does not have a "Cache-Control" header
        3. Confirm that the URL does not include cachekey:[some number] in it
      8. Click on the "Dashboard" link in the page navigation again
        1. Confirm that the templates were requested again

      Test 3: Disabling the batcher

      1. Open your Moodle config.php
      2. Update the configuration to set:

        $CFG->batch_fetch_requests = false;
        

      3. Purge caches again:
      4. From the page Navigation click on the "Dashboard" link again
        1. Confirm that the page loads correctly
      5. In the Network tab of the developer tools, filter for r.php
        1. Confirm that there are no requests for $batch
      Show
      Test preparation Make sure developer mode is turned off. If you're using MDK, run " mdk run undev " Open your browser to Moodle Open the browser developer tools ot the "Network" tab Ensure that the "Disable cache" option is NOT ticked Open your Moodle config.php Near the bottom of the file, add: $CFG->cachetemplates = true; Ensure that $CFG->templaterev is not explicitly set anywhere in the file Ensure that $CFG->batch_fetch_requests is not explicitly set anywhere in the file Test 1: Unbatched loaders Navigate to your user Dashboard Run {{admin/cli/purge_caches.php}: php admin/cli/purge_caches.php From the page Navigation click on the "Dashboard" link (that is, do not just hit the Refresh button) Confirm that the page loads correctly In the Network tab of the developer tools, filter for core/strings Confirm that there are a number of requests for strings Confirm that there all returned a valid 200 response code Click in to inspect any one of these Confirm that the "Response Headers" section has an "Expires" header Confirm that the "Response Headers" section has a "Cache-Control" header Confirm that the "Expires" header is about 3 months in the future (guesstimate is fine) Confirm that the URL includes cachekey: [some number] in it In the Network tab of the developer tools, filter for core/templates Confirm that there are a number of requests for templates Confirm that there all returned a valid 200 response code Click in to inspect any one of these Confirm that the "Response Headers" section has an "Expires" header Confirm that the "Response Headers" section has a "Cache-Control" header Confirm that the "Expires" header is about 3 months in the future (guesstimate is fine) Click on the "Dashboard" link in the page navigation again (do not just hit the refresh button) Confirm that the templates were requested again Confirm that the templates were all returned from disk cache (May appear differently in different browsers. On Chrome the Status Code would show " 200 OK (from disk cache) ") Confirm that the URL includes cachekey: [some number] in it Test 2: Using the batcher Open your Moodle config.php Update the configuration to set: $CFG->cachetemplates = false; Purge caches again: php admin/cli/purge_caches.php From the page Navigation click on the "Dashboard" link again Confirm that the page loads correctly In the Network tab of the developer tools, filter for r.php Confirm that there are some requests for $batch Click in to inspect any one of these Confirm that the "Response Headers" section does not have an "Expires" header Confirm that the "Response Headers" section does not have a "Cache-Control" header There may also be a request for a string and/or template, if so: Confirm that the "Response Headers" section does not have an "Expires" header Confirm that the "Response Headers" section does not have a "Cache-Control" header Confirm that the URL does not include cachekey: [some number] in it Click on the "Dashboard" link in the page navigation again Confirm that the templates were requested again Test 3: Disabling the batcher Open your Moodle config.php Update the configuration to set: $CFG->batch_fetch_requests = false; Purge caches again: From the page Navigation click on the "Dashboard" link again Confirm that the page loads correctly In the Network tab of the developer tools, filter for r.php Confirm that there are no requests for $batch
    • Show
      Fails against automated checks. Checked MDL-82652 using repository: https://github.com/andrewnicols/moodle.git main (21 errors / 40 warnings) [branch: MDL-82652-main | CI Job ] overview (0/0) , phplint (0/0) , phpcs (15/0) , js (0/39) , css (0/0) , phpdoc (4/0) , commit (2/1) , savepoint (0/0) , thirdparty (0/0) , externalbackup (0/0) , grunt (0/0) , shifter (0/0) , mustache (0/0) , gherkin (0/0) , Should these errors be fixed? Built on: Tue Feb 11 03:40:00 AM UTC 2025
    • Team Dragons 2025 Sprint 1.2, Team Dragons 2025 Sprint 1.3

      REST does not, on the whole, suppor the notion of bulk actions.

      Endpoints are typically in the format:

      • GET /resourcetype[s] -> Get all instances
      • POST /resourcetype -> Create a new instance
      • GET /resourcetype/:instanceid -> Get an instance
      • DELETE /resourcetype/:instanceid -> Delete that instance
      • PATCH /resourcetype/:instanceid -> Update part of that instance
      • PUT /resourcetype/:instanceid -> Update that instance

      None of these allow for bulk actions (except for the GET all) and the RESTful design does not accomodate for these.

      There are a number of approaches that can be taken to this:

      • Use of a batch endpoint
      • Use of many batch endpoints
      • Accepting parameters to the base endpoint without any instanceid

      Use of a batch endpoint

      This is a technique that both Microsoft and Google employ.

      The idea here is that a new, generic, endpoint is created at a root level, for example in the case of sharepoint it may be:

      https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/make-batch-requests-with-the-rest-apis

      This endpoint accepts a POST method and a request body with MIME type of multipart/mixed. The body contains a number of requests, each in its own part, and each defining the relevant HTTP verb and REST url and any parameters and/or body.

      Example content may look like this:

      POST https://fabrikam.sharepoint.com/_api/$batch HTTP/1.1
      Authorization: Bearer <access token omitted>
      Content-Type: multipart/mixed; boundary=batch_e3b6819b-13c3-43bb-85b2-24b14122fed1
      Host: fabrikam.sharepoint.com
      Content-Length: 527
      Expect: 100-continue
       
      --batch_e3b6819b-13c3-43bb-85b2-24b14122fed1
      Content-Type: application/http
      Content-Transfer-Encoding: binary
       
      GET https://fabrikam.sharepoint.com/_api/Web/lists/getbytitle('Composed%20Looks')/items?$select=Title HTTP/1.1
       
      --batch_e3b6819b-13c3-43bb-85b2-24b14122fed1
      Content-Type: application/http
      Content-Transfer-Encoding: binary
       
      GET https://fabrikam.sharepoint.com/_api/Web/lists/getbytitle('User%20Information%20List')/items?$select=Title HTTP/1.1
       
      --batch_e3b6819b-13c3-43bb-85b2-24b14122fed1--
      

      Any part of the REST URL on the same REST baseUrl may be used.

      Requests are returned in the same order as the request.
      Requests are non-transactional.
      Any failing request will not impede any subsequent request.

      Pros and Cons

      • Not limited to a single endpoint
      • Allows for many requests to be performed in a single request
      • Depending on how we implement it this could have some caching issues (not sure)
      • More complex implementation
      • Implementation only done once for all web services
      • Requires a matching server/client implementation (easy)
      • Commonly used in other products
      • Requests are actually still dealt with individually, and not as a whole
      • Which means that each, for example, delete request is still processed individaully (e.g. DELETE FROM foo WHERE id = x vs a list of ids).

      Sources

      Multiple batch endpoints

      Another approach to this problem is for each resource type to have its own batch endpoints, separate to the standard endpoints.

      This is an approach taken by Zendesk and others.

      Rather than having a single batch endpoint taking multipart data with one part per action, for example:

      • https:// {subdomain}.zendesk.com/api/v2/organizations/update_many.json
        * https://{subdomain}

        .zendesk.com/api/v2//update_many.json

      In the case of zendesk, they queue an asynchronous job and return the jobid only.
      The job status can be queried separately. This is an optional thing - we could implement this kind of queue but it is not a requirement.

      The zendesk appraoch allows for different bulk actions on the same resource to be queued.

      Pros and cons

      • Each resource must write its own batch handler
      • Queue handling can get messy if using async jobs
      • Still individual CRUD operatons per action
      • Simpler than the MS/Google batch approach in some ways
      • Ideally has a matching client/server implementation to do properly, but is not strictly required

      Sources

      Individual endpoints

      Another appraoch would be have the generic resource type accept a request body for bulk actions.

      For example:

      • POST /resourcetype
      • DELETE /resourcetype

      This is supported from OpenAPI 3.1 onwards (which we are using) but is currently marked as discouraged. There is more information on the GH ticket that approved this workflow as to the main reason for it being discouraged, notably:

      • RFC7231 does not define explicit semantics for request bodies on DELETE
      • Client-side caching can be thrown by this (normally the DELETE of a specific resource will invalidate this in a cache)
      • lots of conjecture that something not explicitly defined is something that should generally be avoided

      This can be done by specifying data in the query parameter, or header parameters, but these have their own drawbacks, including URI length

      Pros and cons

      • Not well supported
      • Generally discouraged
      • Allows for bulk deletion (fewer DB queries etc)

      Sources

      Summary

      Generally speaking there is no good approach to this issue. Every approach has its drawbacks.

      Personally I'm inclined to suggest that the single batch approach with a batching client gives the most flexibility, but is also hardest to implement well.

      The simplest to implement is the individual bulk endpoints but these have the most repitition too.

      Sources

      1. https://stackoverflow.com/questions/21863326/delete-multiple-records-using-rest
      2. https://www.codementor.io/blog/batch-endpoints-6olbjay1hd
      3. https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/make-batch-requests-with-the-rest-apis
      4. https://developerblog.zendesk.com/from-100-requests-to-1-introducing-our-new-bulk-and-batch-apis-a5bb294e2132
      5. https://developers.shopware.com/developers-guide/rest-api/examples/batch/
      6. https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE
      7. https://learn.microsoft.com/en-us/sharepoint/dev/sp-add-ins/make-batch-requests-with-the-rest-apis#code-samples
      8. https://developers.google.com/drive/api/guides/performance#batch-requests
      9. https://itnext.io/mass-delete-via-http-rest-how-do-you-do-it-1bff0f5eb72d
      10. https://stackoverflow.com/questions/54939681/swagger-openapi-spec-3-0-delete-operation
      11. https://github.com/OAI/OpenAPI-Specification/issues/1801

            dobedobedoh Andrew Lyons
            dobedobedoh Andrew Lyons
            Meirza Meirza
            Jun Pataleta Jun Pataleta
            Votes:
            1 Vote for this issue
            Watchers:
            10 Start watching this issue

              Created:
              Updated:

                Estimated:
                Original Estimate - Not Specified
                Not Specified
                Remaining:
                Remaining Estimate - 0 minutes
                0m
                Logged:
                Time Spent - 5 weeks, 3 hours, 59 minutes
                5w 3h 59m

                  Error rendering 'clockify-timesheets-time-tracking-reports:timer-sidebar'. Please contact your Jira administrators.