Skip to content

feat: Rewrite edge-proxy in rust#1

Merged
gagantrivedi merged 1 commit intomainfrom
init-v2
Jan 19, 2026
Merged

feat: Rewrite edge-proxy in rust#1
gagantrivedi merged 1 commit intomainfrom
init-v2

Conversation

@gagantrivedi
Copy link
Copy Markdown
Member

@gagantrivedi gagantrivedi commented Jan 2, 2026

Summary

High-performance Rust implementation of the Flagsmith Edge Proxy using Axum web framework.

Features

  • Full API compatibility with Python edge-proxy
  • 6-10x faster than Python version
  • LRU caching for flags and identities endpoints
  • If-Modified-Since optimization for environment polling
  • Health checks with staleness detection

API Endpoints

  • GET /api/v1/flags - Get all flags (with optional ?feature= filter)
  • GET/POST /api/v1/identities/ - Get flags for identity
  • GET /api/v1/environment-document - Get environment document (server key only)
  • GET /health, /proxy/health/* - Health checks

Configuration

  • Environment key pairs (server + client keys)
  • Configurable polling frequency (default: 60s)
  • Optional LRU endpoint caching
  • Health check grace period

@gagantrivedi gagantrivedi force-pushed the init-v2 branch 13 times, most recently from 5ff0371 to 8fc4662 Compare January 2, 2026 10:05
High-performance Axum-based edge proxy with:
- Full API compatibility with Python version
- Environment document caching with polling
- LRU endpoint response caching
- Health check endpoints
- Server/client key validation

Architecture:
- routes/ - HTTP handlers
- services/ - Business logic
- cache/ - Caching layer
- models/ - Domain types
- config/ - Settings
@gagantrivedi gagantrivedi requested review from a team and khvn26 and removed request for a team January 2, 2026 11:31
@gagantrivedi gagantrivedi marked this pull request as ready for review January 2, 2026 11:31
@khvn26 khvn26 requested a review from emyller January 15, 2026 12:50
Copy link
Copy Markdown
Member

@khvn26 khvn26 left a comment

Choose a reason for hiding this comment

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

No blocking comments, I love it. Makes me wish to write some Rust as well 👍

Comment on lines +29 to +30
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I suggest using Depot to save ourselves time and hassle.

Comment on lines +41 to +51
- name: Build and push
uses: docker/build-push-action@v6
id: build
with:
push: true
platforms: linux/amd64,linux/arm64
file: Dockerfile
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should target quay.io as well.

Comment thread src/config/logging.rs
Comment on lines +20 to +33
match settings.log_format.as_str() {
"json" => {
let fmt_layer = fmt::layer().json();
subscriber.with(fmt_layer).init();
}
_ => {
// "generic" format
let fmt_layer = fmt::layer()
.with_ansi(settings.use_colors)
.with_target(false);
subscriber.with(fmt_layer).init();
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note: the amount of Python code required to achieve the same result makes me sad.

Comment thread src/cache/endpoint.rs
Comment on lines +31 to +34
flags_cache: Option<Arc<RwLock<LruCache<CacheKey, Value>>>>,
identities_cache: Option<Arc<RwLock<LruCache<CacheKey, Value>>>>,
/// Cache for pre-serialized environment document bytes (zero serialization per request)
environment_document_cache: Option<DocumentCache>,
Copy link
Copy Markdown
Member

@khvn26 khvn26 Jan 15, 2026

Choose a reason for hiding this comment

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

I'm interested in stress-testing this to find out how many environments these bad boys can fit before RwLock<LruCache> becomes a bottleneck. Probably quite a lot, I assume, so there's no immediate need to look into alternative caches like threadsafe-lru or locallru (not sure if you ever considered it?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Did some stress testing on this. Results show RwLock handles the load well:

┌──────────────┬────────────┬───────────────┬────────────────┐
│ Environments │ Cache Size │ Single-thread │ 256 concurrent │
├──────────────┼────────────┼───────────────┼────────────────┤
│ 10           │ 1,000      │ 4.7M ops/sec  │ 3.3M ops/sec   │
├──────────────┼────────────┼───────────────┼────────────────┤
│ 100          │ 10,000     │ 6.1M ops/sec  │ 3.3M ops/sec   │
├──────────────┼────────────┼───────────────┼────────────────┤
│ 1000         │ 100,000    │ 4.9M ops/sec  │ 3.1M ops/sec   │
└──────────────┴────────────┴───────────────┴────────────────┘

Scaling to more environments doesn't degrade performance (LRU ops are O(1)). The lock is held for microseconds, so even though get() requires a write lock (LRU mutation), it's not a bottleneck in
practice.

Comment thread Dockerfile
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Consider stratch/distroless for a truly slim image — see e.g. https://oneuptime.com/blog/post/2026-01-07-rust-minimal-docker-images/view

I'll create an issue if you choose not to pursue this.

Comment thread src/config/logging.rs
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it difficult to implement the enable_access_log setting from the get go?

Comment thread src/models/response.rs
feature: APIFeature {
id: flag_result.metadata.feature_id as i64,
name: flag_result.name.clone(),
feature_type: Cow::Borrowed("STANDARD"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

tiny little inconsequential nit: feature_type could be part of engine metadata as well as id?

I've no idea who uses this field but it's not hard to support it IMO.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed here: #3

@gagantrivedi
Copy link
Copy Markdown
Member Author

I have created an issue here from @khvn26's review. All the points will be addressed in later pull requests.

@gagantrivedi gagantrivedi merged commit fd30964 into main Jan 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants