Commit 453b2a8
authored
perf: persist source client across requests (#95)
## What I'm changing
In an effort to improve the data proxy's performance when communicating
with the Source API, we can re-use our `reqwest::Client`.
From the [`reqwest::Client`
Docs](https://docs.rs/reqwest/latest/reqwest/struct.Client.html):
> An asynchronous `Client` to make Requests with.
>
> ...
>
> The `Client` holds a connection pool internally, so it is advised that
> you create one and **reuse** it.
>
> You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to
**reuse** it,
> because it already uses an [`Arc`] internally.
## How I did it
Rather than creating a new client on each outbound call to the Source
API, we instead create it once when we initialize the `SourceApi` struct
and utilize it to make API calls when needed.
<details>
<summary>Summary of discussion with Claude</summary>
```
> Should we persists the reqwest client on the SourceApi struct for better performance?
⏺ Great question! Let me analyze the current implementation and the potential benefits.
⏺ Read(src/apis/source/mod.rs)
⎿ Read 50 lines
⏺ Search(pattern: "build_req_client", path: "src/apis/source/mod.rs", output_mode: "content")
⎿ Found 6 lines (ctrl+o to expand)
⏺ Read(src/apis/source/mod.rs)
⎿ Read 15 lines
⏺ Read(src/apis/source/mod.rs)
⎿ Read 45 lines
Yes, absolutely! This would be a significant performance improvement. Let me explain why:
Current Issues
Looking at the code, build_req_client() is called 5 times across different API methods:
- get_account (line 255)
- get_repository_record (line 428)
- fetch_data_connection (line 446)
- fetch_api_key (line 511)
- fetch_permission (line 591)
Each call creates a brand new reqwest::Client, which means:
Performance Problems
1. Lost Connection Pooling: reqwest::Client maintains an internal HTTP connection pool that reuses TCP connections. By creating a new client each time, you lose this major
benefit - every request has to:
- Perform a new DNS lookup
- Establish a new TCP connection
- Perform TLS handshake (for HTTPS)
- Tear down the connection after use
2. Resource Overhead: Each client initialization has CPU and memory overhead
3. Against Best Practices: The reqwest documentation specifically recommends creating one client and reusing it
Benefits of Persisting the Client
1. Connection Reuse: Subsequent requests to api.source.coop can reuse existing TCP connections (~20-50ms savings per request)
2. DNS Caching: The client caches DNS lookups internally
3. Better Resource Management: Fewer file descriptors, more efficient connection management
4. Thread Safe: reqwest::Client uses Arc internally, so it's cheap to clone and thread-safe - perfect for this struct that's already using Arc for caches
Recommended Changes
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Ready to code? │
│ │
│ Here is Claude's plan: │
│ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Persist reqwest::Client on SourceApi │ │
│ │ │ │
│ │ Changes to make: │ │
│ │ │ │
│ │ 1. Update SourceApi struct in src/apis/source/mod.rs: │ │
│ │ - Add client: reqwest::Client field (after proxy_url) │ │
│ │ - Add documentation explaining the client is reused for performance │ │
│ │ 2. Update SourceApi::new() constructor: │ │
│ │ - Build the reqwest::Client once during initialization │ │
│ │ - Store it in the struct instead of building it on every request │ │
│ │ 3. Update all API methods: │ │
│ │ - Replace 5 calls to self.build_req_client() with &self.client │ │
│ │ - Methods: get_account, get_repository_record, fetch_data_connection, fetch_api_key, fetch_permission │ │
│ │ 4. Remove build_req_client() method: │ │
│ │ - No longer needed since client is initialized once │ │
│ │ │ │
│ │ Performance Impact: │ │
│ │ - Enables HTTP connection pooling and reuse │ │
│ │ - Reduces latency by ~20-50ms per request (avoiding TCP handshake + TLS) │ │
│ │ - Better resource utilization │ │
│ │ - Follows reqwest best practices │ │
│ │ │ │
│ │ Risk: Very low - reqwest::Client is designed for this pattern and the struct is already Clone-compatible │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
```
</details>
## How to test it
<!-- Inform the reviewer how they can validate that these changes work
-->
## PR Checklist
- [ ] This PR has **no** breaking changes.
- [ ] I have updated or added new tests to cover the changes in this PR.
- [ ] This PR affects the [Source Cooperative Frontend &
API](https://github.com/source-cooperative/source.coop),
and I have opened issue/PR #XXX to track the change.
## Related Issues
<!-- Reference any existing related GitHub Issues -->1 parent b25e8d1 commit 453b2a8
1 file changed
Lines changed: 22 additions & 35 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
122 | | - | |
123 | | - | |
| 122 | + | |
| 123 | + | |
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
| |||
252 | 252 | | |
253 | 253 | | |
254 | 254 | | |
255 | | - | |
256 | 255 | | |
257 | 256 | | |
258 | 257 | | |
| |||
266 | 265 | | |
267 | 266 | | |
268 | 267 | | |
269 | | - | |
| 268 | + | |
| 269 | + | |
270 | 270 | | |
271 | 271 | | |
272 | 272 | | |
| |||
330 | 330 | | |
331 | 331 | | |
332 | 332 | | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
333 | 342 | | |
334 | 343 | | |
335 | 344 | | |
336 | 345 | | |
337 | 346 | | |
338 | 347 | | |
339 | 348 | | |
340 | | - | |
| 349 | + | |
341 | 350 | | |
342 | 351 | | |
343 | 352 | | |
344 | | - | |
345 | | - | |
346 | | - | |
347 | | - | |
348 | | - | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | | - | |
357 | 353 | | |
358 | 354 | | |
359 | 355 | | |
360 | 356 | | |
361 | 357 | | |
362 | 358 | | |
363 | | - | |
364 | | - | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
| 359 | + | |
370 | 360 | | |
371 | 361 | | |
372 | 362 | | |
| |||
425 | 415 | | |
426 | 416 | | |
427 | 417 | | |
428 | | - | |
429 | 418 | | |
430 | | - | |
| 419 | + | |
431 | 420 | | |
432 | 421 | | |
433 | 422 | | |
| |||
443 | 432 | | |
444 | 433 | | |
445 | 434 | | |
446 | | - | |
447 | 435 | | |
448 | 436 | | |
449 | 437 | | |
450 | 438 | | |
451 | 439 | | |
452 | 440 | | |
453 | | - | |
| 441 | + | |
| 442 | + | |
454 | 443 | | |
455 | 444 | | |
456 | 445 | | |
| |||
508 | 497 | | |
509 | 498 | | |
510 | 499 | | |
511 | | - | |
512 | | - | |
513 | 500 | | |
514 | 501 | | |
515 | 502 | | |
516 | 503 | | |
517 | 504 | | |
518 | 505 | | |
519 | | - | |
| 506 | + | |
| 507 | + | |
520 | 508 | | |
521 | 509 | | |
522 | 510 | | |
| |||
588 | 576 | | |
589 | 577 | | |
590 | 578 | | |
591 | | - | |
592 | | - | |
593 | 579 | | |
594 | 580 | | |
595 | 581 | | |
| |||
603 | 589 | | |
604 | 590 | | |
605 | 591 | | |
606 | | - | |
| 592 | + | |
| 593 | + | |
607 | 594 | | |
608 | 595 | | |
609 | 596 | | |
| |||
0 commit comments