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

[Paywalls V2] Prefetch low res images #4658

Merged
merged 6 commits into from
Jan 15, 2025
Merged

Conversation

joshdholtz
Copy link
Member

Motivation

Prefetch low res images of all paywalls v2 paywalls

Description

Collects all the images in v2 paywalls for warming up cache

  • Traverses the stack tree to get all of the low res images
  • Collects all the images from the localizations

Copy link
Member

@JayShortway JayShortway left a comment

Choose a reason for hiding this comment

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

Good one!

#if PAYWALL_COMPONENTS

private var allImagesInPaywallsV2: Set<URL> {
// Attempting to warm up all offerings for Paywalls V2.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Attempting to warm up all offerings for Paywalls V2.
// Attempting to warm up all images for Paywalls V2.

Copy link
Member Author

@joshdholtz joshdholtz Jan 14, 2025

Choose a reason for hiding this comment

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

Oh, this is actually not a typo but I could clarify...

Paywalls V1 only warmed up image for the default/current offering 😬 And I totally don't love that. But it had to be done because we didn't have:

  1. Low res images
  2. Explicit draft/published state

But with paywalls V2, we can load "all low res images for all offerings" (so I'll change the comment to that to be more clear)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, great! Thanks for clarifying!

}

// swiftlint:disable:next cyclomatic_complexity
private func collectAllImageURLs(in stack: PaywallComponent.StackComponent) -> [URL] {
Copy link
Member

Choose a reason for hiding this comment

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

Traversing the tree of components is not the most performant, especially since we're already doing that to build the paywall. But I guess we need to do it in a separate traversal, because if we do it at the same time as building the paywall it's too late? (It wouldn't be prefetch anymore ha.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeaahhhh, I figured this could be improved in the future at some point if we wanted 🤷‍♂️ The thing this does differently than paywalls is that it doesn't load the entire view models and very localization stuff. Its pretty specific in how/what its traversing.

But tbh... precaching a bunch of images is LESS performant than traversing this free but its leading to better performance of the paywall 😛 🥴

Copy link
Member

Choose a reason for hiding this comment

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

That's true, good perspective!

Comment on lines +23 to +32
for (_, localeValues) in self.componentsLocalizations {
for (_, value) in localeValues {
switch value {
case .string:
break
case .image(let image):
imageUrls += image.imageUrls
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This made me think: should all images be in componentsLocalizations, including the "base" images?

Copy link
Member Author

Choose a reason for hiding this comment

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

I had a reason against putting the base image in localization at one point and it made sense so I'm going to say... no. But now I need to remember that good argument that I had 😛

But TBH, I did think the same thing as you while writing this code how that would be easier but... we'd have to do the same thing for icons anyway 🤷‍♂️

@joshdholtz joshdholtz force-pushed the paywalls-v2/prefetch-images branch from 55c8b91 to 4e723db Compare January 14, 2025 12:15
@nguyenhuy
Copy link
Contributor

Nice attempt to prefetch images!

If you want (and have perf metrics in place to measure), you can push even further than this. For example, your backend knows which images it's about to include in the response body. So it can collect them all and put them in a seperate list at the top level of the JSON. This way, the client won't need to traverse the model tree to collect them.

Backend can even have a custom HTTP header just for this list. The client can then just grab the header and kick off the image requests. No need to wait for the body to be parsed or even fully transmitted (since headers are sent over first).

And this optimization works on both iOS and Android (and possibly web though I can't recall).

@joshdholtz
Copy link
Member Author

joshdholtz commented Jan 14, 2025

Nice attempt to prefetch images!

If you want (and have perf metrics in place to measure), you can push even further than this. For example, your backend knows which images it's about to include in the response body. So it can collect them all and put them in a seperate list at the top level of the JSON. This way, the client won't need to traverse the model tree to collect them.

Backend can even have a custom HTTP header just for this list. The client can then just grab the header and kick off the image requests. No need to wait for the body to be parsed or even fully transmitted (since headers are sent over first).

And this optimization works on both iOS and Android (and possibly web though I can't recall).

@nguyenhuy Hey, thanks for this! 😊

I did think of something similar a while back but definitely did not act on it 😅 But since you brought it up, it might actually be something we look into! I'm not sure if we have the capacity/priority to also make the backend change for this right now but... I think that's something we can add in soon ™️ and then just replace this logic.

What do you think? cc: @JayShortway @tonidero

This would be nice because less duplicated logic and we can control the prefetch logic a bit more on the backend. I think it would have to go to in the body of the offerings response right now (not the header) because of the way our caching works 🤷‍♂️

@JayShortway
Copy link
Member

@joshdholtz @nguyenhuy I kinda love it! If we use a top-level dictionary, the image components in the tree can reference the keys from that dictionary. That way we can save a bit on bandwidth, as the JSON can already get quite big.

@tonidero
Copy link
Contributor

I like it! It should indeed simplify the logic in the SDKs. I'm not sure if it would save much bandwidth unless we have a bunch of repeated images (which might be the case) but still, it should simplify the parsing. I guess it might increase the complexity in the backend a bit, depending on how the images are stored? Not sure about this part... But well, I'm all for it!

@nguyenhuy
Copy link
Contributor

nguyenhuy commented Jan 14, 2025

we can control the prefetch logic a bit more on the backend.

Exactly. Backend can control which images and how many of them clients should prefetch (only main ones for example) and which resolution(s).

the image components in the tree can reference the keys from that dictionary

Yeap, that works too if network speed/consumption is a concern.

@tonidero
Copy link
Contributor

A different but related question, I guess this will also need to support prefetching localized images if any? If we do it in the SDK, I guess that's more complexity that it needs to handle, that can be dynamically handled by the backend

@joshdholtz
Copy link
Member Author

A different but related question, I guess this will also need to support prefetching localized images if any? If we do it in the SDK, I guess that's more complexity that it needs to handle, that can be dynamically handled by the backend

Backend has support for locale filtering (we just aren't using it yet int he SDK) so it once the SDK support that, it would just really do looking at any localized images 🤷‍♂️ But yeah, doing it on backend does still decrease that complexity in the SDK by quite a bit 😇

Will make some time to do that in the upcoming week 💪

@joshdholtz joshdholtz force-pushed the paywalls-v2/prefetch-images branch from 4e723db to ee492f3 Compare January 14, 2025 22:40
Copy link

emerge-tools bot commented Jan 14, 2025

1 build increased size

Name Version Download Change Install Change Approval
Paywalls
com.revenuecat.PaywallsTester
1.0 (1) 11.7 MB ⬆️ 28.3 kB (0.24%) 43.2 MB ⬆️ 104.1 kB (0.24%) N/A

Paywalls 1.0 (1)
com.revenuecat.PaywallsTester

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 104.1 kB (0.24%)
Total download size change: ⬆️ 28.3 kB (0.24%)

Largest size changes

Item Install Size Change
DYLD.String Table ⬆️ 20.9 kB
Code Signature ⬆️ 2.9 kB
Other ⬆️ 80.3 kB
View Treemap

Image of diff


🛸 Powered by Emerge Tools

Comment trigger: Size diff threshold of 100.00kB exceeded

@joshdholtz joshdholtz merged commit c008e63 into main Jan 15, 2025
10 checks passed
@joshdholtz joshdholtz deleted the paywalls-v2/prefetch-images branch January 15, 2025 01:37
This was referenced Jan 15, 2025
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.

4 participants