Skip to content

Conversation

@AAwouters
Copy link
Contributor

@AAwouters AAwouters commented Mar 14, 2025

References

Description

Prefetch & store REST responses for configured endpoints/urls to reduce request chaining and associated overhead.

How it works

On (Angular) server start, and periodically, a request is sent to every configured endpoint/url.
The responses & headers are stored in the config for later use.

During initialization of either the server or the browser a findByHref call is done for all configured endpoints.
The DspaceRestService will retrieve the responses from the config instead of making actual requests to the REST server during these calls.
By using the findByHref method the responses are then added to the cache.
On the browser side the stored responses are then cleared from the config so future requests are actually sent to the REST server.

Gains

The biggest gains with these changes should be with CSR on slow connections.
The images below show that CSR initial load is almost halved.

(The 'pointless gap' mentioned on the image will be tackled with issue #4074)
20250220-112824
20250220-112801

Instructions for Reviewers

In config.prod.yml (or a different configuration file used for your local prod environment) configure the following values:

prefetch:
  urls:
    - /api
    - /api/statistics
    - /api/discover
    - /api/discover/search
    - /api/config/properties/google.analytics.key
    - /api/config/properties/registration.verification.enabled
    - /api/config/properties/websvc.opensearch.enable
    - /api/config/properties/websvc.opensearch.svccontext
    - /api/discover/browses?size=9999
    - /api/core/sites
    # - {other frequently accessed REST urls you might want to add} 
  # refreshInterval: 60000 # Uncomment & change to refresh more/less often.

Then start the Angular server in prod mode and use DSpace as you normally would.

For the best effect, the configured urls should be endpoints that Angular will retrieve on every load or very often.

Points of discussion

Prefetch interval

At the moment, the default interval to refresh the stored responses is 60 seconds. Is this appropriate?

Storage of bootstrapped responses

The bootstrapped responses at this moment are stored in the config.json file for simplicity. Should this be moved to a separate JSON file?

Checklist

  • My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
  • My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
  • My PR passes ESLint validation using npm run lint
  • My PR doesn't introduce circular dependencies (verified via npm run check-circ-deps)
  • My PR includes TypeDoc comments for all new (or modified) public methods and classes. It also includes TypeDoc for large or complex private methods.
  • My PR passes all specs/tests and includes new/updated specs or tests based on the Code Testing Guide.
  • My PR aligns with Accessibility guidelines if it makes changes to the user interface.
  • My PR uses i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
  • If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
  • If my PR fixes an issue ticket, I've linked them together.

ybnd and others added 22 commits February 14, 2025 00:11
DSpace Angular's production server can modify the configuration it serves to CSR-only clients through YAML or environment variables.
However, these files can remain cached in the browser and leave users with out-of-date configuration until the TTL runs out or the user does a "hard refresh".
On build time this sort of problem is solved by saving the content hash as part of the path of each file (JS, CSS, ...)

We introduce HashedFileMapping to bridge the same gap for dynamic content generated _after_ the server is built:
- Files added to this mapping on the server are copied to a hashed path
- A copy is injected into index.html, where clients can read it out and resolve the hashed paths
- If a given path is not found in the mapping, the client will fall back to the original version (to prevent errors in development mode)

With this mechanism we can ensure updates to config.json and similar files take immediate effect without losing the performance benefit of client-side caching.
The JSON mapping needs to be declared as a data block, otherwise browers may complain (nothing seems to really break though, except for Cypress)
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#any_other_value
….6_CLEAN' into w2p-124369_Bootstrap-HAL-endpoint-maps
…est-responses-9.0

# Conflicts:
#	config/config.example.yml
#	package.json
#	server.ts
#	src/app/app.module.ts
#	src/app/core/auth/auth.interceptor.spec.ts
#	src/app/core/browse/browse-definition-data.service.ts
#	src/app/core/cache/builders/remote-data-build.service.ts
#	src/app/core/cache/object-cache.service.ts
#	src/app/core/core.module.ts
#	src/app/core/data/base-response-parsing.service.ts
#	src/app/core/data/base/create-data.ts
#	src/app/core/data/browse-response-parsing.service.spec.ts
#	src/app/core/data/default-change-analyzer.service.ts
#	src/app/core/data/dspace-rest-response-parsing.service.ts
#	src/app/core/data/endpoint-map-response-parsing.service.ts
#	src/app/core/data/request.effects.ts
#	src/app/core/dspace-rest/dspace-rest.service.spec.ts
#	src/app/core/dspace-rest/dspace-rest.service.ts
#	src/app/core/forward-client-ip/forward-client-ip.interceptor.spec.ts
#	src/app/core/locale/locale.interceptor.spec.ts
#	src/app/core/log/log.interceptor.spec.ts
#	src/app/core/shared/flat-browse-definition.model.ts
#	src/app/core/shared/hierarchical-browse-definition.model.ts
#	src/app/core/shared/value-list-browse-definition.model.ts
#	src/app/core/xsrf/xsrf.interceptor.spec.ts
#	src/app/init.service.ts
#	src/app/shared/mocks/dspace-rest/endpoint-mocking-rest.service.ts
#	src/app/shared/theme-support/theme.service.spec.ts
#	src/app/shared/theme-support/theme.service.ts
#	src/app/statistics/statistics.module.ts
#	src/config/app-config.interface.ts
#	src/config/config.server.ts
#	src/config/default-app-config.ts
#	src/environments/environment.test.ts
#	src/main.browser.ts
#	src/modules/app/browser-init.service.ts
#	src/modules/app/server-init.service.ts
#	yarn.lock
@github-actions
Copy link

Hi @AAwouters,
Conflicts have been detected against the base branch.
Please resolve these conflicts as soon as you can. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reduce request chaining and associated overhead

2 participants