Blazing Fast
20 million cache-resolving calls per core per second. 1,000xβ8,000x faster APIs than traditional approaches.
Add real-time updates and caching to any .NET app with almost no code changes. 10,000x faster APIs. Production-proven.
Building real-time apps is hard. Traditional approaches force you into painful trade-offs:
But if you think about it, caching and real-time updates are facets of the same problem. Both require knowing when something changes and who cares. Yet we treat them as separate concerns with separate infrastructure.
Fusion solves all of this:
The best part: you get all of this without turning your code into a mess. You can think of Fusion as a call middleware or a decorator. That's why Fusion-based code looks as if there is no Fusion at all! So you can focus on building your app and ship faster β and save yourself from dealing with a 2β3Γ larger codebase and a plethora of "why is it stale?" bugs, which are among the hardest to debug.
Fusion doesn't just add real-timeβit makes your app thousands of times faster.
| Scenario | Without Fusion | With Fusion | Speedup |
|---|---|---|---|
| Local service, minimal writes | 38.6K calls/s | 313.8M calls/s | 8,127x |
| Local service, continuous writes | 136.9K calls/s | 263.6M calls/s | 1,926x |
| Remote service, continuous writes | 99.7K calls/s (REST) | 223.2M calls/s | 2,239x |
Benchmarks on AMD Ryzen 9 9950X3D. See full benchmark details.
| Framework | RPC Calls/sec | Streaming Items/sec |
|---|---|---|
| ActualLab.Rpc | 8.87M | 95.10M |
| SignalR | 5.34M | 17.11M |
| gRPC | 1.11M | 38.75M |
8x faster than gRPC for calls. 5.6x faster than SignalR for streaming.
Think of Fusion as MSBuild for data processed by your backend, API, and even client-side UI:
GetUser(userId)GetUserProfile(3)
βββcallsβββΊ GetUser(3)
βββcallsβββΊ GetUserAvatar(3)
βββcallsβββΊ GetThumbnail("user_3_avatar", 64)
When GetThumbnail(imgId, 64) is invalidated:
- GetUserAvatar(3) is immediately marked as inconsistent
- GetUserProfile(3) is immediately marked as inconsistent
- Nothing recomputes yet.
Next request for GetUserProfile(3) triggers recomputation of:
- GetUserAvatar(3)
- GetThumbnail("user_3_avatar", 64)
As for GetUser(3), it won't be recomputed when GetUserProfile(3) calls it,
because it wasn't affected by GetThumbnail("user_3_avatar", 64) invalidation,
so its cached value is going to be used.The invalidation is always immediate and cascading: when you invalidate a given call, its dependency sub-graph is also invalidated, including remote dependencies.
But invalidation doesn't imply immediate recomputation: the recomputation typically happens later, when the call is repeated, typically in a UI component. But old cached values wrapped into Computed<T> instances remain accessible indefinitely, so UI can keep displaying them as long as it needs to (while updates are in progress or even later).
The dependency graph updates automatically as your methods call each other or when invalidation occurs, so typically you don't even need to know it exists.
This is exactly how incremental builds work: you mark targets as dirty by removing them, but they only rebuild when you run the build, and every artifact that's still consistent is reused.
A Fusion service looks almost identical to a regular service:
public class UserService(IServiceProvider services) : DbServiceBase<AppDbContext>(services),
IComputeService // A tagging interface that enables [ComputeMethod] and other Fusion features
{
[ComputeMethod] // Also has to be virtual
public virtual async Task<User?> GetUser(long id, CancellationToken cancellationToken = default)
{
// Fusion services are thread-safe by default, but DbContext is not,
// so we can't use shared DbContext instance here.
// That's why we use DbHub, which provides DbContext-s on demand and pools them.
await using var dbContext = await DbHub.CreateDbContext(cancellationToken);
return await dbContext.Users.FindAsync([id], cancellationToken);
}
[ComputeMethod] // Also has to be virtual
public virtual async Task<UserProfile> GetUserProfile(long id, CancellationToken cancellationToken = default)
{
// Calls other compute methods - dependencies tracked automatically
var user = await GetUser(id, cancellationToken);
var avatar = await GetUserAvatar(id, cancellationToken);
return new UserProfile(user, avatar);
}
// Regular method
public async Task UpdateUser(long id, User update, CancellationToken cancellationToken = default)
{
await using var dbContext = await DbHub.CreateDbContext(readWrite: true, cancellationToken);
var user = await dbContext.Users.FindAsync([id], cancellationToken);
user!.ApplyUpdate(update);
await dbContext.SaveChangesAsync(cancellationToken);
using (Invalidation.Begin()) { // Invalidation block
_ = GetUser(id, default); // Invalidate GetUser(id), this call completes synchronously w/o actual evaluation
}
}
}That's it. No event buses. No cache managers. No subscription tracking.
Fusion provides ComputedStateComponent<T>, which has a State property β a ComputedState<T> instance that holds the latest result of the ComputeState call. Any ComputedState<T> is essentially a compute method + update loop, so it invalidates when any dependency of its last computation gets invalidated, and recomputes after a short delay (configurable via GetStateOptions).
When State gets recomputed, StateHasChanged() is called and the component re-renders.
@inherits ComputedStateComponent<UserProfile>
@inject IUserService UserService
<div class="profile">
<h1>@State.Value.Name</h1>
<p>@State.Value.Bio</p>
<span>Posts: @State.Value.PostCount</span>
</div>
@code {
[Parameter] public long UserId { get; set; }
protected override Task<UserProfile> ComputeState()
=> UserService.GetUserProfile(UserId);
}Voxt is a real-time chat app built by the creators of Fusion. It features:
Check out how it works at Voxt.ai, or reach out to Alex Y. @ Voxt.ai if you want to chat in real time. Fusion handles everything related to real-time there.
Skip building real-time and caching infrastructure. Add [ComputeMethod] to your existing services and get both for free.
No more "it's stale β find out why" debugging sessions. Automatic dependency tracking ensures dependents update when something changes.
Your code stays focused on business logic, Fusion handles the rest. Forget about the boilerplate for real-time updates or cache invalidation.
Handle 1000Γ more traffic with almost no changes to your code.
dotnet add package ActualLab.Fusionservices.AddFusion().AddService<UserService>(); // UserService must "implement" tagging IComputeService[ComputeMethod]
public virtual async Task<User> GetUser(long id) { ... }That's the entire setup. Your service now has automatic caching, dependency tracking, and real-time invalidation.
Questions? Want to see how others use Fusion? Join the discussion:
Indirect contributors & everyone else who made Fusion possible:
ActualLab.Fusion is developed by the creators of Voxt and is the successor of Stl.Fusion, originally created at ServiceTitan. Check out The Story Behind Fusion to learn more about its origins.