Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeleteStateOnClear support for AdoNet storage provider #8535

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public class AdoNetGrainStorageOptions : IStorageProviderSerializerOptions

/// <inheritdoc/>
public IGrainStorageSerializer GrainStorageSerializer { get; set; }

/// <summary>
/// Delete record row from db when state is cleared.
/// </summary>
public bool DeleteStateOnClear { get; set; } = false;
}

/// <summary>
Expand Down
17 changes: 17 additions & 0 deletions src/AdoNet/Orleans.Persistence.AdoNet/PostgreSQL-Persistence.sql
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,20 @@ VALUES
AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL
Returning Version as NewGrainStateVersion
');

INSERT INTO OrleansQuery(QueryKey, QueryText)
VALUES
(
'DeleteStorageKey','
DELETE FROM OrleansStorage
WHERE
GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL
AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL
AND GrainIdN0 = @GrainIdN0 AND @GrainIdN0 IS NOT NULL
AND GrainIdN1 = @GrainIdN1 AND @GrainIdN1 IS NOT NULL
AND GrainTypeString = @GrainTypeString AND @GrainTypeString IS NOT NULL
AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL)
AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL
AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL
Returning Version + 1 as NewGrainStateVersion
');
21 changes: 21 additions & 0 deletions src/AdoNet/Orleans.Persistence.AdoNet/SQLServer-Persistence.sql
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,24 @@ VALUES
AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL
OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN));'
);

INSERT INTO OrleansQuery(QueryKey, QueryText)
VALUES
(
'DeleteStorageKey',
'BEGIN TRANSACTION;
SET XACT_ABORT, NOCOUNT ON;
DELETE FROM OrleansStorage OUTPUT DELETED.Version + 1
WHERE
GrainIdHash = @GrainIdHash AND @GrainIdHash IS NOT NULL
AND GrainTypeHash = @GrainTypeHash AND @GrainTypeHash IS NOT NULL
AND (GrainIdN0 = @GrainIdN0 OR @GrainIdN0 IS NULL)
AND (GrainIdN1 = @GrainIdN1 OR @GrainIdN1 IS NULL)
AND (GrainTypeString = @GrainTypeString OR @GrainTypeString IS NULL)
AND ((@GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString IS NOT NULL AND GrainIdExtensionString = @GrainIdExtensionString) OR @GrainIdExtensionString IS NULL AND GrainIdExtensionString IS NULL)
AND ServiceId = @ServiceId AND @ServiceId IS NOT NULL
AND Version IS NOT NULL AND Version = @GrainStateVersion AND @GrainStateVersion IS NOT NULL
OPTION(FAST 1, OPTIMIZE FOR(@GrainIdHash UNKNOWN, @GrainTypeHash UNKNOWN));

COMMIT TRANSACTION;'
);
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public class AdoNetGrainStorage: IGrainStorage, ILifecycleParticipant<ISiloLifec
/// <summary>
/// The default query to initialize this structure from the Orleans database.
/// </summary>
public const string DefaultInitializationQuery = "SELECT QueryKey, QueryText FROM OrleansQuery WHERE QueryKey = 'WriteToStorageKey' OR QueryKey = 'ReadFromStorageKey' OR QueryKey = 'ClearStorageKey'";
public const string DefaultInitializationQuery = "SELECT QueryKey, QueryText FROM OrleansQuery WHERE QueryKey = 'WriteToStorageKey' OR QueryKey = 'ReadFromStorageKey' OR QueryKey = 'ClearStorageKey' OR QueryKey = 'DeleteStorageKey'";

/// <summary>
/// The queries currently used. When this is updated, the new queries will take effect immediately.
Expand Down Expand Up @@ -155,20 +155,29 @@ public async Task ClearStateAsync<T>(string grainType, GrainId grainReference, I
{
logger.LogTrace(
(int)RelationalStorageProviderCodes.RelationalProviderClearing,
"Clearing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag}.",
"Clearing grain state: ServiceId={ServiceId} ProviderName={Name} GrainType={BaseGrainType} GrainId={GrainId} ETag={ETag} DeleteStateOnClear={DeleteStateOnClear}.",
serviceId,
name,
baseGrainType,
grainId,
grainState.ETag);
grainState.ETag,
options.DeleteStateOnClear);
}

string storageVersion = null;
try
{
var grainIdHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(grainId.GetHashBytes());
var grainTypeHash = HashPicker.PickHasher(serviceId, this.name, baseGrainType, grainReference, grainState).Hash(Encoding.UTF8.GetBytes(baseGrainType));
var clearRecord = (await Storage.ReadAsync(CurrentOperationalQueries.ClearState, command =>

var queryText = options.DeleteStateOnClear ? CurrentOperationalQueries.DeleteState : CurrentOperationalQueries.ClearState;
//Backward compatibility checks in Init should handle this case and throw prior to reaching this state.
if (queryText is null)
{
//We should not be here.
throw new UnreachableException($"QueryText is null for options.DeleteStateOnClear={options.DeleteStateOnClear}");
}
var clearRecord = (await Storage.ReadAsync(queryText, command =>
{
command.AddParameter("GrainIdHash", grainIdHash);
command.AddParameter("GrainIdN0", grainId.N0Key);
Expand Down Expand Up @@ -202,8 +211,9 @@ public async Task ClearStateAsync<T>(string grainType, GrainId grainReference, I
throw inconsistentStateException;
}

//if delete on clear is set, ETag should be set to null since record has been deleted, but db script returns deleted record version + 1 as storageVersion for CheckVersionInconsistency method.
//No errors found, the version of the state held by the grain can be updated and also the state.
grainState.ETag = storageVersion;
grainState.ETag = options.DeleteStateOnClear ? null : storageVersion;
grainState.RecordExists = false;
if(logger.IsEnabled(LogLevel.Trace))
{
Expand Down Expand Up @@ -409,10 +419,19 @@ private async Task Init(CancellationToken cancellationToken)
return Task.FromResult(Tuple.Create(selector.GetValue<string>("QueryKey"), selector.GetValue<string>("QueryText")));
}).ConfigureAwait(false);

//This check is for backward compatibility:
//1. Some AdoNet storage invariants may not support delete on clear
//2. AdoNet invariant supports delete on clear but updated persistence scripts have not been deployed to management db.
var deleteStateQuery = queries.SingleOrDefault(i => i.Item1 == "DeleteStorageKey")?.Item2;
if (options.DeleteStateOnClear && deleteStateQuery is null)
{
throw new ArgumentException("DeleteStateOnClear=true option is not supported. Use options.DeleteStateOnClear=false instead or check persistence scripts.");
}
CurrentOperationalQueries = new RelationalStorageProviderQueries(
queries.Single(i => i.Item1 == "WriteToStorageKey").Item2,
queries.Single(i => i.Item1 == "ReadFromStorageKey").Item2,
queries.Single(i => i.Item1 == "ClearStorageKey").Item2);
queries.Single(i => i.Item1 == "ClearStorageKey").Item2,
deleteStateQuery);

logger.LogInformation(
(int)RelationalStorageProviderCodes.RelationalProviderInitProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;


namespace Orleans.Storage
Expand All @@ -24,14 +24,19 @@ public class RelationalStorageProviderQueries
/// </summary>
public string ClearState { get; set; }

/// <summary>
/// The clause to clear the storage, deleting row in db.
/// </summary>
public string DeleteState { get; set; }


/// <summary>
/// Constructor.
/// </summary>
/// <param name="writeToStorage">The clause to write to a storage.</param>
/// <param name="readFromStorage">The clause to read from a storage.</param>
/// <param name="clearState">The clause to clear the storage.</param>
public RelationalStorageProviderQueries(string writeToStorage, string readFromStorage, string clearState)
public RelationalStorageProviderQueries(string writeToStorage, string readFromStorage, string clearState, string deleteState)
{
if(writeToStorage == null)
{
Expand All @@ -48,9 +53,12 @@ public RelationalStorageProviderQueries(string writeToStorage, string readFromSt
throw new ArgumentNullException(nameof(clearState));
}

//no null check on deleteState for backward compatibility

WriteToStorage = writeToStorage;
ReadFromStorage = readFromStorage;
ClearState = clearState;
DeleteState = deleteState;
}
}
}