Skip to content

Actor State TTL - #1164

Merged
halspang merged 20 commits into
dapr:masterfrom
JoshVanL:actor-state-ttl
Jan 8, 2024
Merged

halspang merged 20 commits into
dapr:masterfrom
JoshVanL:actor-state-ttl

Conversation

@JoshVanL

@JoshVanL JoshVanL commented Oct 12, 2023 •

Copy link
Copy Markdown
Contributor

Dapr Actor state TTL is a feature that was added in Dapr 1.12.

Adds support for Actor State TTL. TTL of actor state is set by using the ttlInSeconds metadata option when saving actor state. The actor state cache uses the ttlExpireTime return metadata field to determine whether the key is still valid, which is important as the cache may become populated with TTL keys which were not created by the current manager.

SDK developers are encouraged to use TTLs when storing any actor state in order to prevent stale data accumulating in the actor state store over time.

Closes #1164

Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: joshvanl <me@joshvanl.dev>
}

public async Task<string> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
public async Task<ActorStateResponse<string>> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We want to keep ActorStateResponse as an internal type. How do we keep complication happy with this constraint?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@philliphoff any ideas here?

@philliphoff philliphoff Oct 24, 2023 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ActorStateResponse is already internal, no, or are you referring to the fact that this method is public? IDaprHttpInteractor and DaprHttpInteractor are already internal types, so nothing of them can be seen outside the SDK. The public on the method only applies to other types within the SDK. (Method visibility is always masked by visibility of the underlying type.)

}

public async Task AddStateAsync<T>(string stateName, T value, CancellationToken cancellationToken)
public async Task AddStateAsync<T>(string stateName, T value, CancellationToken cancellationToken, int? ttlInSeconds)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to instead add an overload for when the TTL is specified:
AddStateAsync<T>(string stateName, T value, int ttlInSeconds) rather than deviate from the .NET guidance that CancellationToken be the last parameter of a method declaration. An exception exists to maintain backward compatibility with existing public APIs, but an overload would seem to provide the same effect while maintaining a consistent API.

Also, is it important for TTL to be specified in integer seconds as opposed to, say, a TimeSpan, which might be more usable for larger values? (What do we expect the typical TTL to be?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @philliphoff! I'm brand new to .Net so please forgive my noob mistakes 😄 Can you take a look at my new changes to see if that is what you are after?

Also, is it important for TTL to be specified in integer seconds as opposed to, say, a TimeSpan, which might be more usable for larger values? (What do we expect the typical TTL to be?)

Indeed, TTL should be given as seconds as this matches what the Dapr API is expecting. What TTL to use is highly dependent on how long the app will be using the state for, but generally it should be set to "a bit more" than the expected usefulness of the key.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Right. I was think more of the actual values used for TTLs. For example, the samples specify a TTL of 360. Would that be better expressed as TimeSpan.FromMinutes(3) so that users need not do the math (and potentially get it wrong), specially if they have much larger TTLs (e.g. hours/days).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, yes that makes sense I'll add that in.

Comment thread src/Dapr.Actors/Communication/ActorStateResponse.cs Outdated
Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: joshvanl <me@joshvanl.dev>
/// <remarks>
/// If null, the state will not expire.
/// </remarks>
public int? TTLInSeconds {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Since this conversion to seconds is only used in a single place (for the specific circumstances of the runtime API), would it make sense to move this logic there and only expose TTLExpireTime? Is there value in exposing both to users of ActorStateChange? (Especially since this property is a constantly changing value, being relative to the current date/time, which users may not expect from a property.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @philliphoff, I've updated the PR to only have hold the TTLExpireTime value which gets converted to int seconds when saving the actor state.

change metadata

Signed-off-by: joshvanl <me@joshvanl.dev>
@JoshVanL
JoshVanL marked this pull request as ready for review October 19, 2023 13:10
@JoshVanL
JoshVanL requested review from a team as code owners October 19, 2023 13:10
// Check if the property was marked as remove in the cache
if (stateMetadata.ChangeKind == StateChangeKind.Remove)
// Check if the property was marked as remove in the cache or is expired
if (stateMetadata.ChangeKind == StateChangeKind.Remove || stateMetadata.TTLExpireTime <= DateTime.UtcNow)

@philliphoff philliphoff Oct 24, 2023 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: DateTimeOffset.UtcNow (both here and elsewhere in the changes)?

I'm virtually certain that the built-in coersion between DateTime and DateTimeOffset make what's there equivalent, but perhaps best to use DateTimeOffset as consistently as possible.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also might be useful to keep a watch on .NET 8 which (finally) adds a means of abstracting time to simplify testing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks! Done

public static StateMetadata Create<T>(T value, StateChangeKind changeKind)
public DateTimeOffset? TTLExpireTime { get; set; }

public static StateMetadata Create<T>(T value, StateChangeKind changeKind, DateTimeOffset? ttlExpireTime = null, TimeSpan? ttl = null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the intent to have a method which allows a TTL, expiry time, or neither? If so, consider three methods, one which doesn't accept either, one that accepts the offset, and a third that accepts the time span. That would help ensure proper usage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, that makes sense- they are intended to be mutually exclusive. I've moved to using 3 functions.

Comment thread src/Dapr.Actors/Runtime/ActorStateManager.cs Outdated
}

if (stateChange.TTLExpireTime.HasValue) {
var ttl = (int)Math.Ceiling((stateChange.TTLExpireTime.Value - DateTime.UtcNow).TotalSeconds);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just a question, but is it spec'd to use the ceiling (vs. the floor or rounding) for TTLs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The spec is that it needs to be an integer value. It makes sense to me to be consistent in how we translate a fractional seconds. Rounding up a fractional second ensures that a value has a TTL of 1 second, rather than 0.

Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: joshvanl <me@joshvanl.dev>
@codecov

codecov Bot commented Oct 31, 2023 •

Copy link
Copy Markdown

Codecov Report

Attention: 51 lines in your changes are missing coverage. Please review.

Comparison is base (7616bfa) 66.58% compared to head (a1b0b00) 68.47%.

Files Patch % Lines
src/Dapr.Actors/Runtime/ActorStateManager.cs 39.02% 44 Missing and 6 partials ⚠️
src/Dapr.Actors/Runtime/DaprStateProvider.cs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1164      +/-   ##
==========================================
+ Coverage   66.58%   68.47%   +1.88%     
==========================================
  Files         171      172       +1     
  Lines        5752     5846      +94     
  Branches      628      648      +20     
==========================================
+ Hits         3830     4003     +173     
+ Misses       1772     1681      -91     
- Partials      150      162      +12     
Flag Coverage Δ
net6 68.46% <54.05%> (+6.89%) ⬆️
net7 68.46% <54.05%> (+1.88%) ⬆️
net8 68.46% <54.05%> (+1.88%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@JoshVanL

Copy link
Copy Markdown
Contributor Author

@philliphoff do you understand why the integration tests are failing? I'm not too sure what the problem is.

@philliphoff

Copy link
Copy Markdown
Collaborator

@philliphoff do you understand why the integration tests are failing? I'm not too sure what the problem is.

Dunno. Maybe retry the job in case it was just a transient issue?

@JoshVanL

JoshVanL commented Nov 6, 2023

Copy link
Copy Markdown
Contributor Author

@philliphoff Doesn't seem to be the case. Struggling to understand what is going wrong here.. at the limit of my .NET knowledge 😬

Comment thread src/Dapr.Actors/DaprHttpInteractor.cs Outdated
Comment thread src/Dapr.Actors/Runtime/ActorStateManager.cs Outdated
Comment thread src/Dapr.Actors/Runtime/ActorStateManager.cs
Comment thread src/Dapr.Actors/Runtime/DaprStateProvider.cs Outdated
@philliphoff

Copy link
Copy Markdown
Collaborator

@philliphoff Doesn't seem to be the case. Struggling to understand what is going wrong here.. at the limit of my .NET knowledge 😬

Poked around this morning and found a couple of issues where existing state was ignored in favor of default values (see above suggestions), which were causing the E2E to fail. It might be good to add some unit tests for DaprHttpInteractor, ActorStateManager, and DaprStateProvider, if reasonably possible, to ensure that all the cases are covered (e.g. when values are cached vs. non-cached), pre- and post-expiration, etc.).

@philliphoff philliphoff left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Needs some changes (added as suggestion comments) in order to resolve E2E failure.

Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: joshvanl <me@joshvanl.dev>
@JoshVanL

Copy link
Copy Markdown
Contributor Author

Thank you @philliphoff, really appreciate your help with this!

I've gone ahead and added those fixes, expanded the E2E state tests and added unit tests for those classed. Please take another look 🙂

Signed-off-by: joshvanl <me@joshvanl.dev>
@JoshVanL JoshVanL mentioned this pull request Nov 27, 2023
2 of 5 tasks
@JoshVanL

Copy link
Copy Markdown
Contributor Author

Hi @philliphoff, please let me know what you think when you have time

@halspang
halspang merged commit 233c620 into dapr:master Jan 8, 2024
@halspang halspang modified the milestone: v1.13 Jan 8, 2024
divzi-p pushed a commit to divzi-p/dotnet-sdk that referenced this pull request Dec 10, 2024
* Actor state TTL support

Signed-off-by: joshvanl <me@joshvanl.dev>
Signed-off-by: Divya Perumal <diperuma@microsoft.com>
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.

3 participants