Skip to content

feat: Improve the performance of saving toggles to the StorageProvider #113

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

Conversation

lsj8706
Copy link
Contributor

@lsj8706 lsj8706 commented Feb 24, 2025

About the changes

Currently, the Poller saves toggles to the StorageProvider individually through iteration, which consumes excessive resources. I Added reset(keyedValues: [String : Toggle]) to StorageProvider interface. This improvement will optimize the process by implementing batch saving of the toggle array to Storage.

Closes #112

Discussion points

In the current structure, when creating a StorageProvider that supports disk caching, read/write costs occur as many times as the number of toggles. This is because the toggle array is being iterated through and saved to Storage one by one. This PR is submitted to address this issue.

Copy link

@ElonPark ElonPark left a comment

Choose a reason for hiding this comment

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

LGTM 👍👍

@chriswk chriswk moved this from New to For later in Issues and PRs Feb 27, 2025
@gastonfournier gastonfournier moved this from For later to Investigating in Issues and PRs Feb 27, 2025
@chriswk chriswk moved this from Investigating to In Progress in Issues and PRs Feb 28, 2025
@chriswk
Copy link
Member

chriswk commented Feb 28, 2025

Hi @lsj8706 . Thank you for your contribution. Our main Swift guy is Out of office until Tuesday March 4th, so you'll have to wait until then at the earliest for a proper reply here.

@FredrikOseberg
Copy link
Contributor

@lsj8706

I see what you are trying to do here. I'm wondering if there is a way to do this without requiring a major update. As we are changing the default behavior of the StorageProvider, everyone who currently has their own implementation would have to change it.

Also, is batch a better term than reset? I was initially confused because of the terminology.

@ElonPark
Copy link

ElonPark commented Mar 5, 2025

er term than reset? I was in

Hello! Thank you for your thoughtful comments.
In my opinion, "batch" and "reset" represent different operations. The current implementation completely overwrites the existing storage, which aligns more with a "reset" operation. For it to be appropriately named "batch," I believe the implementation should merge the received parameters into the storage rather than replacing the entire storage.
That said, I'm certainly open to changing the name from "reset" to something more appropriate if there's a better term that accurately describes this operation.

@lsj8706
Copy link
Contributor Author

lsj8706 commented Mar 5, 2025

@FredrikOseberg
I understand your concerns.

However, this change is intended to resolve existing performance issues, which I believe cannot be solved with the current interface alone. Current users are incurring the cost of n write operations when storing n Toggles in Storage, and this is a critical issue that must be addressed eventually.

Although this will result in a major update, it seems better to resolve it during this opportunity when the scope of impact can be minimized as much as possible.

@FredrikOseberg
Copy link
Contributor

@FredrikOseberg I understand your concerns.

However, this change is intended to resolve existing performance issues, which I believe cannot be solved with the current interface alone. Current users are incurring the cost of n write operations when storing n Toggles in Storage, and this is a critical issue that must be addressed eventually.

Although this will result in a major update, it seems better to resolve it during this opportunity when the scope of impact can be minimized as much as possible.

With the provided DictionaryStorageProvider the current mode of operations should be fine, as the insert is entirely in memory and inserting into a dict is a O(1) operation. I can definitely see that this would be a problem for custom storage providers that are writing to disk, and I'd like to solve that problem. Do you have any benchmarks that can illustrate the performance gain on your provider with these changes?

Generally, making a major release is more work for us, because we need to provide upgrade guides and documentation around the changes that led to the major update. It also puts some mental overhead on our users because they need to understand the changes and potentially make changes. Not saying I won't make a major release for this, but we try to be careful the number of majors that we do. Out of curiosity, would it be possible to do something like:

public protocol StorageProvider {
    func set(value: Toggle?, key: String)
    @objc optional func reset(keyedValues: [String: Toggle])
    func value(key: String) -> Toggle?
    func clear()
}

if storageProvider.responds(to: #selector(StorageProvider.reset(keyedValues:))) {
            storageProvider.reset?(keyedValues: keyedValues)
        } else {
            // If reset is not supported, set each toggle individually
            for (key, value) in keyedValues {
                storageProvider.set(value: value, key: key)
            }
        }

@lsj8706
Copy link
Contributor Author

lsj8706 commented Mar 6, 2025

@FredrikOseberg
Thank you for your thoughtful feedback regarding the major update. We can address this without requiring a major update by implementing it in the following way:

public protocol StorageProvider {
    func set(value: Toggle?, key: String)
    func reset(keyedValues: [String: Toggle])
    func value(key: String) -> Toggle?
    func clear()
}

extension StorageProvider {
  func reset(keyedValues: [String: Toggle]) {
    for (key, value) in keyedValues {
      set(value: value, key: key)
    }
  }
}

By using Swift's protocol default implementation for the reset method, existing users won't need to implement the reset method separately. This addresses your concern since only those who need it can implement a custom reset method, while others can rely on the default implementation.

@FredrikOseberg
Copy link
Contributor

FredrikOseberg commented Mar 11, 2025

@FredrikOseberg Thank you for your thoughtful feedback regarding the major update. We can address this without requiring a major update by implementing it in the following way:

public protocol StorageProvider {
    func set(value: Toggle?, key: String)
    func reset(keyedValues: [String: Toggle])
    func value(key: String) -> Toggle?
    func clear()
}

extension StorageProvider {
  func reset(keyedValues: [String: Toggle]) {
    for (key, value) in keyedValues {
      set(value: value, key: key)
    }
  }
}

By using Swift's protocol default implementation for the reset method, existing users won't need to implement the reset method separately. This addresses your concern since only those who need it can implement a custom reset method, while others can rely on the default implementation.

@lsj8706

Aiden Lee, thanks a lot for discussing this with me. I apologize for the time this has taken, but we have many SDKs and we try to make sure they are aligned as much as possible in how they work to reduce the cognitive load on users and ourselves. I've had a look at how our other SDKs handle this scenario in the StorageProviders and they all take the entire featureset and set it in the storageprovider in one go. I'm not sure why we opted for iterating over the features here, but it's clear that it's the wrong approach.

I'm leaning towards doing a major update and changing the interface of the StorageProvider, but in that case I think we should just replace the set method entirely and have the set method do what you propose instead of adding a new method. It would keep the StorageProvider small and the interface more in line with other SDKs, and we'll remove this anti-pattern as a possibility. Here's an example of how it works in other SDKs:

I'd also love a test for this functionality if you have the time.

What do you think? Could you make those changes to this PR?

@lsj8706 lsj8706 force-pushed the improve-performance-of-saving-toggles-to-stroage-provider branch from 15bf176 to 4fcca3a Compare March 12, 2025 13:32
@lsj8706
Copy link
Contributor Author

lsj8706 commented Mar 12, 2025

@lsj8706

Aiden Lee, thanks a lot for discussing this with me. I apologize for the time this has taken, but we have many SDKs and we try to make sure they are aligned as much as possible in how they work to reduce the cognitive load on users and ourselves. I've had a look at how our other SDKs handle this scenario in the StorageProviders and they all take the entire featureset and set it in the storageprovider in one go. I'm not sure why we opted for iterating over the features here, but it's clear that it's the wrong approach.

I'm leaning towards doing a major update and changing the interface of the StorageProvider, but in that case I think we should just replace the set method entirely and have the set method do what you propose instead of adding a new method. It would keep the StorageProvider small and the interface more in line with other SDKs, and we'll remove this anti-pattern as a possibility. Here's an example of how it works in other SDKs:

I'd also love a test for this functionality if you have the time.

What do you think? Could you make those changes to this PR?

@FredrikOseberg
Thank you for your kind explanation. I agree that we need to unify the behavior with other SDKs.

I've checked the code from other SDKs that you attached, and I also think it's appropriate to change the implementation of the set method.

Therefore, I've modified the implementation to store all Toggles at once in the set method. I've tested the modified implementation and confirmed that it passes without errors. 2d91e8d

Additionally, I've added information about the injectability of the Poller to the README, as this information was missing.
a3ae1f3

Please check and review. Thank you!

@lsj8706 lsj8706 force-pushed the improve-performance-of-saving-toggles-to-stroage-provider branch from 4fcca3a to a3ae1f3 Compare March 12, 2025 13:40
Copy link
Contributor

@FredrikOseberg FredrikOseberg left a comment

Choose a reason for hiding this comment

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

LGTM

@github-project-automation github-project-automation bot moved this from In Progress to Approved PRs in Issues and PRs Mar 17, 2025
@FredrikOseberg FredrikOseberg merged commit 078a17f into Unleash:main Mar 17, 2025
2 checks passed
@github-project-automation github-project-automation bot moved this from Approved PRs to Done in Issues and PRs Mar 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Feature: Improve the performance of saving toggles to the StorageProvider
4 participants