-
Notifications
You must be signed in to change notification settings - Fork 225
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
Starting new Remote Settings API #6378
base: main
Are you sure you want to change the base?
Conversation
Added the `RemoteSettingsService` and `RemoteSettingsClient` types. These provide the new remote settings API that we're hoping to implement. The goal is to match the API of the official client more closely. The main changes are: - Downloaded data is stored locally - Getting/syncing records are split up into different operations to avoid hitting the network when getting settings values during startup. - Application-wide configuration is managed by `RemoteSettingsService`, which uses that to construct the clients. This code is still a WIP. The hope is that this outlines the general shape of the new API and the work that needs to be done.
31d7958
to
ec8a706
Compare
/// Construct a new client with the configuration passed to the constructor | ||
[Throws=RemoteSettingsError] | ||
RemoteSettingsClient make_client(string collection_name); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a nice idea to me, but it's not totally clear that we need it. How do others feel about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, most consumers won't need to to specify more than collection name.
This could be a place where we gather references to existing clients, for example in order to switch their server (stage, dev) or bucket (main, main-preview) when needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure I understand the discussion here. Would this fit with the following use case?
- iOS (or Android) code (swift/Kotlin) sets up the remote settings server to use stage + preview.
- Rust based search client calls
RemoteSettingsClient("search-config-v2").get()
.
Here I'd want the client to return the configuration according to the stage+preview server.
In other words, I would like the Rust based search client to be able to get the collection without having to worry about passing through the remote settings configuration.
I think we want the search client to be able to go directly to remote settings client as it'll avoid having to pass the configuration back and forth across the Uniffi interfaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And further clarification, I think the Remote Settings server settings should be app-global rather than per-collection, hence I don't really want the search client to have to deal with those as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out. My proposal was that the consumer app would create a RemoteSettingsService
and use that as a factory for all the clients/collections. RemoteSettingsService
stores the server config and avoids the need for a global variable.
However, I can definitely see why that would be annoying for your use-case. In order for you to create remote settings clients that used the correct server, the consumer app would need to pass the RemoteSettingsService
to you.
A global variable could simplify things, but then you have to worry about startup order. What happens if you constructed your client before the consumer app configured the server? I'm not sure if this is a big deal or not, but I like the fact that the RemoteSettingsService
makes this more explicit.
I don't have strong feelings either way, what do others think?
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6378 +/- ##
==========================================
+ Coverage 47.71% 49.91% +2.19%
==========================================
Files 162 140 -22
Lines 14164 13541 -623
==========================================
Hits 6759 6759
+ Misses 7405 6782 -623 ☔ View full report in Codecov by Sentry. |
/// Get attachment data from storage | ||
pub fn get_attachment_data(&self, _location: &str) -> Result<Option<Vec<u8>>> { | ||
// Q: should we store attachment data directly in SQLite or put it in the filesystem? | ||
todo!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally kind of interested in just storing this in SQLite. AFAIK, attachment data is not that large. I think Suggest has some of the larger attachments, our biggest is 6m, then 2m, then <1m. It doesn't seem that bad to stick this in the database and we get things like atomicity for free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, using a DB gives us several benefits when it comes to deleting attachments of obsolete collections. (pruneAttachments()
on desktop)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably that data would then have to be returned across the Uniffi interface. Are we likely to hit performance issues with serialising that data vs saving it to a file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing data across the FFI requires a couple extra copies, but so would saving/reading it to a file. I think it should be fine for the use-cases we're talking about, but should probably double-check that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great 💯 Thank you Ben 👌
} | ||
|
||
pub struct RemoteSettingsClient { | ||
client: Client, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it could be called httpClient
/// Construct a new client with the configuration passed to the constructor | ||
[Throws=RemoteSettingsError] | ||
RemoteSettingsClient make_client(string collection_name); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, most consumers won't need to to specify more than collection name.
This could be a place where we gather references to existing clients, for example in order to switch their server (stage, dev) or bucket (main, main-preview) when needed
RemoteSettingsServer server; | ||
/// Directory to store data in. Must be writable by the client. | ||
string storage_dir; | ||
/// Override bucket name ("main" by default, "preview" for preview collections) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
/// Override bucket name ("main" by default, "preview" for preview collections) | |
/// Override bucket name ("main" by default, "main-preview" for preview collections) |
/// Get attachment data from storage | ||
pub fn get_attachment_data(&self, _location: &str) -> Result<Option<Vec<u8>>> { | ||
// Q: should we store attachment data directly in SQLite or put it in the filesystem? | ||
todo!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, using a DB gives us several benefits when it comes to deleting attachments of obsolete collections. (pruneAttachments()
on desktop)
} | ||
|
||
/// Store attachment data in storage | ||
pub fn store_attachment_data(&self, _location: &str, _data: &[u8]) -> Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could maybe take a list of attachments in order to support bulk writes.
On desktop we have a situation where a consumer has to store 1300 tiny .pem
files on first sync.
But we can also always add a new method later store_attachment_bulk()
or whatever.
/// Get the current set of records. | ||
/// | ||
/// If no records have been downloaded, this will return null. Use `sync` to fetch the current | ||
/// records from the server. | ||
[Throws=RemoteSettingsError] | ||
sequence<RemoteSettingsRecord>? get(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we going to include the dumps in remote settings as well?
This would avoid the application having to load them separately, or pass them in. We can also include some of the logic that the desktop client uses to determine when the local records should be updated from the dump or not.
If so, then I'd expect this to be more If no records have been downloaded, this will load from the local dumps, if found. Otherwise this will return null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I plan to add this later on. I'm wondering if that should be a different class though, since if you know there's a local dump then get()
doesn't need to return a nullable value.
/// Sync records with the server. | ||
[Throws=RemoteSettingsError] | ||
void sync(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we going to also periodically check with the server for updates? If so, it might be worth making clear that this is only needed in the force-sync case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great question. I think the periodic syncing should be driven by the embedding application, since they have a closer integration with the OS and can create background workers, etc. However, it seems annoying to make each remote settings client consumer have to call sync()
for their client.
How about this:
- We rename this method to
force_sync()
and documentation about that. - We add a
RemoteSettingsService::sync()
method, which is what the embedding application will call. That updates the remote settings data for all the clients that it knows about (or something similar, I'm not sure exactly how this should work). This also doesn't need to be tied to theRemoteSettingsService
idea, it could just be a global function.
/// Download an attachment with the provided id to the provided path. | ||
[Throws=RemoteSettingsError] | ||
void download_attachment_to_path(string attachment_id, string path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Desktop currently has a slightly different interface. It downloads the attachment to the database (if not included in dumps locally), and then either allows loading from the dump, the database cache or to actually download it.
It also passes in the attachment record, which I believe means it can check hashes and out-of-date.
What's the advantage of downloading this to application specified file? Would it be better to have remote settings handle the file paths/cache, and then maybe tell the app where the path of the file is?
The desktop api currently returns blobs, but maybe storing the file under storage_path and returning the path within that would be better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Desktop we deprecated the file-base API, because it was tedious to purge old files etc.
If there are uniffi concerns, maybe we could have a hybrid approach, where downloaded files are tracked in a db and referenced as paths?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just remove this method for now, I don't know of a real use-case for it.
/// Get attachment data from storage | ||
pub fn get_attachment_data(&self, _location: &str) -> Result<Option<Vec<u8>>> { | ||
// Q: should we store attachment data directly in SQLite or put it in the filesystem? | ||
todo!() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably that data would then have to be returned across the Uniffi interface. Are we likely to hit performance issues with serialising that data vs saving it to a file?
Added the
RemoteSettingsService
andRemoteSettingsClient
types. These provide the new remote settings API that we're hoping to implement. The goal is to match the API of the official client more closely. The main changes are:RemoteSettingsService
, which uses that to construct the clients.This code is still a WIP. The hope is that this outlines the general shape of the new API and the work that needs to be done.
Pull Request checklist
[ci full]
to the PR title.Branch builds: add
[firefox-android: branch-name]
to the PR title.