Skip to content

Commit 010aa72

Browse files
DataLoader automatically executes based on timer
1 parent c40025a commit 010aa72

File tree

5 files changed

+160
-62
lines changed

5 files changed

+160
-62
lines changed

README.md

+44-18
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,31 @@ This is a Swift version of the Facebook [DataLoader](https://github.com/facebook
88

99
## Installation 💻
1010

11-
Update your `Package.swift` file.
11+
Include this repo in your `Package.swift` file.
1212

1313
```swift
1414
.Package(url: "https://github.com/GraphQLSwift/DataLoader.git", .upToNextMajor(from: "1.1.0"))
1515
```
1616

1717
## Gettings started 🚀
1818
### Batching
19-
Batching is not an advanced feature, it's DataLoader's primary feature.
19+
Batching is not an advanced feature, it's DataLoader's primary feature.
2020

21-
Create a DataLoader by providing a batch loading function
21+
Create a DataLoader by providing a batch loading function:
2222
```swift
2323
let userLoader = Dataloader<Int, User>(batchLoadFunction: { keys in
2424
try User.query(on: req).filter(\User.id ~~ keys).all().map { users in
2525
return users.map { DataLoaderFutureValue.success($0) }
2626
}
2727
})
2828
```
29-
#### Load single key
29+
#### Load individual keys
3030
```swift
3131
let future1 = try userLoader.load(key: 1, on: eventLoopGroup)
3232
let future2 = try userLoader.load(key: 2, on: eventLoopGroup)
3333
let future3 = try userLoader.load(key: 1, on: eventLoopGroup)
3434
```
3535

36-
Now there is only one thing left and that is to dispathc it `try userLoader.dispatchQueue(on: req.eventLoop)`
37-
3836
The example above will only fetch two users, because the user with key `1` is present twice in the list.
3937

4038
#### Load multiple keys
@@ -43,18 +41,42 @@ There is also a method to load multiple keys at once
4341
try userLoader.loadMany(keys: [1, 2, 3], on: eventLoopGroup)
4442
```
4543

44+
#### Execution
45+
By default, a DataLoader will wait for a short time (2ms) from the moment `load` is called to collect keys prior
46+
to running the `batchLoadFunction` and completing the `load` futures. This is to let keys accumulate and
47+
batch into a smaller number of total requests. This amount of time is configurable using the `executionPeriod`
48+
option:
49+
50+
```swift
51+
let myLoader = DataLoader<String, String>(
52+
options: DataLoaderOptions(executionPeriod: .milliseconds(50)),
53+
batchLoadFunction: { keys in
54+
self.someBatchLoader(keys: keys).map { DataLoaderFutureValue.success($0) }
55+
}
56+
)
57+
```
58+
59+
Longer execution periods reduce the number of total data requests, but also reduce the responsiveness of the
60+
`load` futures.
61+
62+
If desired, you can manually execute the `batchLoadFunction` and complete the futures at any time, using the
63+
`.execute()` method.
64+
65+
Scheduled execution can be disabled by setting `executionPeriod` to `nil`, but be careful - you *must* call `.execute()`
66+
manually in this case. Otherwise, the futures will never complete.
67+
4668
#### Disable batching
47-
It is possible to disable batching `DataLoaderOptions(batchingEnabled: false)`
48-
It will invoke `batchLoadFunction` immediately whenever any key is loaded
69+
It is possible to disable batching by setting the `batchingEnabled` option to `false`
70+
It will invoke the `batchLoadFunction` immediately when a key is loaded.
71+
4972

5073
### Caching
5174

52-
DataLoader provides a memoization cache for all loads which occur in a single
53-
request to your application. After `.load()` is called once with a given key,
54-
the resulting value is cached to eliminate redundant loads.
75+
DataLoader provides a memoization cache. After `.load()` is called with a key, the resulting value is cached
76+
for the lifetime of the DataLoader object. This eliminates redundant loads.
5577

56-
In addition to relieving pressure on your data storage, caching results per-request
57-
also creates fewer objects which may relieve memory pressure on your application:
78+
In addition to relieving pressure on your data storage, caching results also creates fewer objects which may
79+
relieve memory pressure on your application:
5880

5981
```swift
6082
let userLoader = DataLoader<Int, Int>(...)
@@ -164,16 +186,20 @@ let myLoader = DataLoader<String, String>(batchLoadFunction: { keys in
164186

165187
## Contributing 🤘
166188

167-
All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and enhancement requests, or better yet, contribute directly by creating a PR. 😎
189+
All your feedback and help to improve this project is very welcome. Please create issues for your bugs, ideas and
190+
enhancement requests, or better yet, contribute directly by creating a PR. 😎
168191

169-
When reporting an issue, please add a detailed instruction, and if possible a code snippet or test that can be used as a reproducer of your problem. 💥
192+
When reporting an issue, please add a detailed instruction, and if possible a code snippet or test that can be used
193+
as a reproducer of your problem. 💥
170194

171-
When creating a pull request, please adhere to the current coding style where possible, and create tests with your code so it keeps providing an awesome test coverage level 💪
195+
When creating a pull request, please adhere to the current coding style where possible, and create tests with your
196+
code so it keeps providing an awesome test coverage level 💪
172197

173198
## Acknowledgements 👏
174199

175-
This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader). Developed by [Lee Byron](https://github.com/leebyron) and
176-
[Nicholas Schrock](https://github.com/schrockn) from [Facebook](https://www.facebook.com/).
200+
This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader).
201+
Developed by [Lee Byron](https://github.com/leebyron) and [Nicholas Schrock](https://github.com/schrockn)
202+
from [Facebook](https://www.facebook.com/).
177203

178204
[swift-badge]: https://img.shields.io/badge/Swift-5.2-orange.svg?style=flat
179205
[swift-url]: https://swift.org

Sources/DataLoader/DataLoader.swift

+20-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ final public class DataLoader<Key: Hashable, Value> {
2525
private var cache = [Key: EventLoopFuture<Value>]()
2626
private var queue = LoaderQueue<Key, Value>()
2727

28+
private var dispatchScheduled = false
2829
private let lock = Lock()
2930

3031
public init(options: DataLoaderOptions<Key, Value> = DataLoaderOptions(), batchLoadFunction: @escaping BatchLoadFunction<Key, Value>) {
@@ -45,6 +46,12 @@ final public class DataLoader<Key: Hashable, Value> {
4546

4647
if options.batchingEnabled {
4748
queue.append((key: key, promise: promise))
49+
if let executionPeriod = options.executionPeriod, !dispatchScheduled {
50+
eventLoopGroup.next().scheduleTask(in: executionPeriod) {
51+
try self.execute()
52+
}
53+
dispatchScheduled = true
54+
}
4855
} else {
4956
_ = try batchLoadFunction([key]).map { results in
5057
if results.isEmpty {
@@ -149,12 +156,22 @@ final public class DataLoader<Key: Hashable, Value> {
149156

150157
/// Executes the queue of keys, completing the `EventLoopFutures`.
151158
///
152-
/// The client must run this manually to compete the `EventLoopFutures` of the keys.
159+
/// If `executionPeriod` was provided in the options, this method is run automatically
160+
/// after the specified time period. If `executionPeriod` was nil, the client must
161+
/// run this manually to compete the `EventLoopFutures` of the keys.
153162
public func execute() throws {
163+
// Take the current loader queue, replacing it with an empty queue.
154164
var batch = LoaderQueue<Key, Value>()
155165
lock.withLockVoid {
156166
batch = self.queue
157167
self.queue = []
168+
if dispatchScheduled {
169+
dispatchScheduled = false
170+
}
171+
}
172+
173+
guard batch.count > 0 else {
174+
return ()
158175
}
159176

160177
// If a maxBatchSize was provided and the queue is longer, then segment the
@@ -194,11 +211,11 @@ final public class DataLoader<Key: Hashable, Value> {
194211
}
195212
}
196213
}.recover { error in
197-
self.failedDispatch(batch: batch, error: error)
214+
self.failedExecution(batch: batch, error: error)
198215
}
199216
}
200217

201-
private func failedDispatch(batch: LoaderQueue<Key, Value>, error: Error) {
218+
private func failedExecution(batch: LoaderQueue<Key, Value>, error: Error) {
202219
for (key, promise) in batch {
203220
_ = clear(key: key)
204221
promise.fail(error)

Sources/DataLoader/DataLoaderOptions.swift

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import NIO
2+
13
public struct DataLoaderOptions<Key: Hashable, Value> {
24
/// Default `true`. Set to `false` to disable batching, invoking
35
/// `batchLoadFunction` with a single load key. This is
@@ -13,16 +15,26 @@ public struct DataLoaderOptions<Key: Hashable, Value> {
1315
/// for every load of the same key.
1416
public let cachingEnabled: Bool
1517

18+
/// Default `2ms`. Defines the period of time that the DataLoader should
19+
/// wait and collect its queue before executing. Faster times result
20+
/// in smaller batches quicker resolution, slower times result in larger
21+
/// batches but slower resolution.
22+
/// This is irrelevant if batching is disabled.
23+
public let executionPeriod: TimeAmount?
24+
1625
/// Default `nil`. Produces cache key for a given load key. Useful
1726
/// when objects are keys and two objects should be considered equivalent.
1827
public let cacheKeyFunction: ((Key) -> Key)?
1928

2029
public init(batchingEnabled: Bool = true,
2130
cachingEnabled: Bool = true,
2231
maxBatchSize: Int? = nil,
23-
cacheKeyFunction: ((Key) -> Key)? = nil) {
32+
executionPeriod: TimeAmount? = .milliseconds(2),
33+
cacheKeyFunction: ((Key) -> Key)? = nil
34+
) {
2435
self.batchingEnabled = batchingEnabled
2536
self.cachingEnabled = cachingEnabled
37+
self.executionPeriod = executionPeriod
2638
self.maxBatchSize = maxBatchSize
2739
self.cacheKeyFunction = cacheKeyFunction
2840
}

Tests/DataLoaderTests/DataLoaderAbuseTests.swift

+8-12
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ class DataLoaderAbuseTests: XCTestCase {
1212
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
1313
}
1414

15-
let identityLoader = DataLoader<Int, Int>(options: DataLoaderOptions(batchingEnabled: false)) { keys in
15+
let identityLoader = DataLoader<Int, Int>(
16+
options: DataLoaderOptions(batchingEnabled: false)
17+
) { keys in
1618
eventLoopGroup.next().makeSucceededFuture([])
1719
}
1820

1921
let value = try identityLoader.load(key: 1, on: eventLoopGroup)
2022

21-
XCTAssertNoThrow(try identityLoader.execute())
22-
2323
XCTAssertThrowsError(try value.wait(), "Did not return value for key: 1")
2424
}
2525

@@ -29,14 +29,12 @@ class DataLoaderAbuseTests: XCTestCase {
2929
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
3030
}
3131

32-
let identityLoader = DataLoader<Int, Int>(options: DataLoaderOptions()) { keys in
32+
let identityLoader = DataLoader<Int, Int>() { keys in
3333
eventLoopGroup.next().makeSucceededFuture([])
3434
}
3535

3636
let value = try identityLoader.load(key: 1, on: eventLoopGroup)
3737

38-
XCTAssertNoThrow(try identityLoader.execute())
39-
4038
XCTAssertThrowsError(try value.wait(), "The function did not return an array of the same length as the array of keys. \nKeys count: 1\nValues count: 0")
4139
}
4240

@@ -46,7 +44,7 @@ class DataLoaderAbuseTests: XCTestCase {
4644
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
4745
}
4846

49-
let identityLoader = DataLoader<Int, Int>(options: DataLoaderOptions()) { keys in
47+
let identityLoader = DataLoader<Int, Int>() { keys in
5048
var results = [DataLoaderFutureValue<Int>]()
5149

5250
for key in keys {
@@ -63,8 +61,6 @@ class DataLoaderAbuseTests: XCTestCase {
6361
let value1 = try identityLoader.load(key: 1, on: eventLoopGroup)
6462
let value2 = try identityLoader.load(key: 2, on: eventLoopGroup)
6563

66-
XCTAssertNoThrow(try identityLoader.execute())
67-
6864
XCTAssertThrowsError(try value2.wait())
6965

7066
XCTAssertTrue(try value1.wait() == 1)
@@ -76,7 +72,9 @@ class DataLoaderAbuseTests: XCTestCase {
7672
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
7773
}
7874

79-
let identityLoader = DataLoader<Int, Int>(options: DataLoaderOptions(batchingEnabled: false)) { keys in
75+
let identityLoader = DataLoader<Int, Int>(
76+
options: DataLoaderOptions(batchingEnabled: false)
77+
) { keys in
8078
var results = [DataLoaderFutureValue<Int>]()
8179

8280
for key in keys {
@@ -93,8 +91,6 @@ class DataLoaderAbuseTests: XCTestCase {
9391
let value1 = try identityLoader.load(key: 1, on: eventLoopGroup)
9492
let value2 = try identityLoader.load(key: 2, on: eventLoopGroup)
9593

96-
XCTAssertNoThrow(try identityLoader.execute())
97-
9894
XCTAssertThrowsError(try value2.wait())
9995

10096
XCTAssertTrue(try value1.wait() == 1)

0 commit comments

Comments
 (0)