Skip to content

Commit

Permalink
fix: improving performance of FlagSynchronizer creation (#420)
Browse files Browse the repository at this point in the history
**Requirements**

- [x] I have added test coverage for new or changed functionality
- [x] I have followed the repository's [pull request submission
guidelines](../blob/v9/CONTRIBUTING.md#submitting-pull-requests)
- [x] I have validated my changes against all supported platform
versions

**Related issues**

SDK-1030

**Describe the solution you've provided**

Reduces total parsing done to retrieve last updated cache value.
  • Loading branch information
tanderson-ld authored Jan 14, 2025
1 parent a378e29 commit e9575a0
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 6 deletions.
6 changes: 2 additions & 4 deletions LaunchDarkly/LaunchDarkly/LDClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,17 +207,15 @@ public class LDClient {
os_log("%s runMode aborted. Old runMode equals new runMode", log: config.logger, type: .debug, typeName(and: #function))
return
}

let cachedData = self.flagCache.getCachedData(cacheKey: self.context.fullyQualifiedHashedKey(), contextHash: self.context.contextHash())

let lastUpdated = self.flagCache.getCachedDataLastUpdatedDate(cacheKey: self.context.fullyQualifiedHashedKey(), contextHash: self.context.contextHash())
let willSetSynchronizerOnline = isOnline && isInSupportedRunMode
flagSynchronizer.isOnline = false
let streamingModeVar = ConnectionInformation.effectiveStreamingMode(config: config, ldClient: self)
connectionInformation = ConnectionInformation.backgroundBehavior(connectionInformation: connectionInformation, streamingMode: streamingModeVar, goOnline: willSetSynchronizerOnline)
flagSynchronizer = serviceFactory.makeFlagSynchronizer(streamingMode: streamingModeVar,
pollingInterval: config.flagPollingInterval(runMode: runMode),
useReport: config.useReport,
lastUpdated: cachedData.lastUpdated,
lastUpdated: lastUpdated,
service: service,
onSyncComplete: onFlagSyncComplete)
flagSynchronizer.isOnline = willSetSynchronizerOnline
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ protocol FeatureFlagCaching {
///
func getCachedData(cacheKey: String, contextHash: String) -> (items: StoredItems?, etag: String?, lastUpdated: Date?)

/// Retrieve the date the cache for the given key was last updated. See getCachedData for more information.
///
/// - parameter cacheKey: The index key into the local cache store.
/// - parameter contextHash: A hash value representing a fully unique context.
///
/// - returns: The date the cache was last considered up-to-date. If there are no cached
/// values, this should return nil.
func getCachedDataLastUpdatedDate(cacheKey: String, contextHash: String) -> Date?

// When we update the cache, we save the flag data and if we have it, an
// etag. For polling, we should always have the flag data and an etag
// value. This is not the case for streaming.
Expand Down Expand Up @@ -100,6 +109,19 @@ final class FeatureFlagCache: FeatureFlagCaching {
return (items: cachedFlags.flags, etag: etag, lastUpdated: Date(timeIntervalSince1970: TimeInterval(lastUpdated / 1_000)))
}

func getCachedDataLastUpdatedDate(cacheKey: String, contextHash: String) -> Date? {

var cachedContexts: [String: Int64] = [:]
if let cacheMetadata = keyedValueCache.data(forKey: "cached-contexts") {
cachedContexts = (try? JSONDecoder().decode([String: Int64].self, from: cacheMetadata)) ?? [:]
}

guard let lastUpdated = cachedContexts[cacheKey]
else { return nil }

return Date(timeIntervalSince1970: TimeInterval(lastUpdated / 1_000))
}

func saveCachedData(_ storedItems: StoredItems, cacheKey: String, contextHash: String, lastUpdated: Date, etag: String?) {

guard self.maxCachedContexts != 0, let encoded = try? JSONEncoder().encode(StoredItemCollection(storedItems))
Expand Down
4 changes: 2 additions & 2 deletions LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ final class LDClientSpec: QuickSpec {
expect(testContext.serviceFactoryMock.makeEventReporterReceivedService?.context) == testContext.context
}
it("uncaches the new contexts flags") {
expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 2
expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 1
expect(testContext.featureFlagCachingMock.getCachedDataReceivedArguments?.cacheKey) == testContext.context.fullyQualifiedHashedKey()
}
it("records an identify event") {
Expand Down Expand Up @@ -421,7 +421,7 @@ final class LDClientSpec: QuickSpec {
expect(testContext.serviceFactoryMock.makeEventReporterReceivedService?.context) == testContext.context
}
it("uncaches the new contexts flags") {
expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 2
expect(testContext.featureFlagCachingMock.getCachedDataCallCount) == 1
expect(testContext.featureFlagCachingMock.getCachedDataReceivedArguments?.cacheKey) == testContext.context.fullyQualifiedHashedKey()
}
it("records an identify event") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,22 @@ final class FeatureFlagCacheSpec: XCTestCase {
let setMetadata = try JSONDecoder().decode([String: Int64].self, from: mockValueCache.setReceivedArguments!.value)
XCTAssertEqual(setMetadata, [hashedContextKey: now.millisSince1970])
}

func testGetCachedDataLastUpdatedDate() {
let now = Date()
let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5)
flagCache.saveCachedData(testFlagCollection.flags, cacheKey: "key", contextHash: "hash", lastUpdated: now, etag: "example-etag")

let lastUpdated = flagCache.getCachedDataLastUpdatedDate(cacheKey: "key", contextHash: "hash")
XCTAssertEqual(lastUpdated!.millisSince1970, now.millisSince1970, accuracy: 1_000)
}

func testGetCachedDataLastUpdatedDateKeyDoesntExist() {
let now = Date()
let flagCache = FeatureFlagCache(serviceFactory: ClientServiceFactory(logger: .disabled), mobileKey: "abc", maxCachedContexts: 5)
flagCache.saveCachedData(testFlagCollection.flags, cacheKey: "key", contextHash: "hash", lastUpdated: now, etag: "example-etag")

let lastUpdated = flagCache.getCachedDataLastUpdatedDate(cacheKey: "bogus", contextHash: "bogusHash")
XCTAssertEqual(lastUpdated, nil)
}
}

0 comments on commit e9575a0

Please sign in to comment.