public SubscriptionGeneralDataAndStats GetSubscriptionFromServerStore(TransactionOperationContext context, string name)
        {
            var subscriptionBlittable = _serverStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(_db.Name, name));

            if (subscriptionBlittable == null)
            {
                throw new SubscriptionDoesNotExistException($"Subscription with name {name} was not found in server store");
            }

            var subscriptionState     = JsonDeserializationClient.SubscriptionState(subscriptionBlittable);
            var subscriptionJsonValue = new SubscriptionGeneralDataAndStats(subscriptionState);

            return(subscriptionJsonValue);
        }
示例#2
0
        public SubscriptionConnectionState GetSubscriptionConnection(TransactionOperationContext context, string subscriptionName)
        {
            var subscriptionBlittable = _serverStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(_db.Name, subscriptionName));

            if (subscriptionBlittable == null)
            {
                return(null);
            }

            var subscriptionState = JsonDeserializationClient.SubscriptionState(subscriptionBlittable);

            if (_subscriptionConnectionStates.TryGetValue(subscriptionState.SubscriptionId, out SubscriptionConnectionState subscriptionConnection) == false)
            {
                return(null);
            }

            return(subscriptionConnection);
        }
        public override unsafe void Execute(TransactionOperationContext context, Table items, long index, DatabaseRecord record, RachisState state, out object result)
        {
            result = null;
            var itemKey = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);

            using (Slice.From(context.Allocator, itemKey.ToLowerInvariant(), out Slice valueNameLowered))
                using (Slice.From(context.Allocator, itemKey, out Slice valueName))
                {
                    if (items.ReadByKey(valueNameLowered, out TableValueReader tvr) == false)
                    {
                        throw new InvalidOperationException("Cannot find subscription " + index);
                    }

                    var ptr = tvr.Read(2, out int size);
                    var doc = new BlittableJsonReaderObject(ptr, size, context);

                    var subscriptionState = JsonDeserializationClient.SubscriptionState(doc);
                    subscriptionState.Disabled = Disable;
                    using (var obj = context.ReadObject(subscriptionState.ToJson(), "subscription"))
                    {
                        ClusterStateMachine.UpdateValue(index, items, valueNameLowered, valueName, obj);
                    }
                }
        }
示例#4
0
        public Task GetOngoingTaskInfo()
        {
            if (ResourceNameValidator.IsValidResourceName(Database.Name, ServerStore.Configuration.Core.DataDirectory.FullPath, out string errorMessage) == false)
            {
                throw new BadRequestException(errorMessage);
            }

            var key     = GetLongQueryString("key");
            var typeStr = GetQueryStringValueAndAssertIfSingleAndNotEmpty("type");

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
            {
                using (context.OpenReadTransaction())
                {
                    var clusterTopology = ServerStore.GetClusterTopology(context);
                    var record          = ServerStore.Cluster.ReadDatabase(context, Database.Name);
                    var dbTopology      = record?.Topology;

                    if (Enum.TryParse <OngoingTaskType>(typeStr, true, out var type) == false)
                    {
                        throw new ArgumentException($"Unknown task type: {type}", "type");
                    }

                    string tag;

                    switch (type)
                    {
                    case OngoingTaskType.Replication:

                        var watcher = record?.ExternalReplication.Find(x => x.TaskId == key);
                        if (watcher == null)
                        {
                            HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            break;
                        }
                        var taskInfo = GetExternalReplicationInfo(dbTopology, clusterTopology, watcher);

                        WriteResult(context, taskInfo);

                        break;

                    case OngoingTaskType.Backup:

                        var backupConfiguration = record?.PeriodicBackups?.Find(x => x.TaskId == key);
                        if (backupConfiguration == null)
                        {
                            HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            break;
                        }

                        tag = dbTopology?.WhoseTaskIsIt(backupConfiguration, ServerStore.Engine.CurrentState);
                        var backupDestinations = GetBackupDestinations(backupConfiguration);
                        var backupStatus       = Database.PeriodicBackupRunner.GetBackupStatus(key);
                        var nextBackup         = Database.PeriodicBackupRunner.GetNextBackupDetails(record, backupConfiguration, backupStatus);

                        var backupTaskInfo = new OngoingTaskBackup
                        {
                            TaskId          = backupConfiguration.TaskId,
                            BackupType      = backupConfiguration.BackupType,
                            TaskName        = backupConfiguration.Name,
                            TaskState       = backupConfiguration.Disabled ? OngoingTaskState.Disabled : OngoingTaskState.Enabled,
                            ResponsibleNode = new NodeId
                            {
                                NodeTag = tag,
                                NodeUrl = clusterTopology.GetUrlFromTag(tag)
                            },
                            BackupDestinations    = backupDestinations,
                            LastFullBackup        = backupStatus.LastFullBackup,
                            LastIncrementalBackup = backupStatus.LastIncrementalBackup,
                            NextBackup            = nextBackup
                        };

                        WriteResult(context, backupTaskInfo);
                        break;

                    case OngoingTaskType.SqlEtl:

                        var sqlEtl = record?.SqlEtls?.Find(x => x.TaskId == key);
                        if (sqlEtl == null)
                        {
                            HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            break;
                        }

                        WriteResult(context, new OngoingTaskSqlEtlDetails()
                        {
                            TaskId        = sqlEtl.TaskId,
                            TaskName      = sqlEtl.Name,
                            Configuration = sqlEtl,
                            TaskState     = GetEtlTaskState(sqlEtl)
                        });
                        break;

                    case OngoingTaskType.RavenEtl:

                        var ravenEtl = record?.RavenEtls?.Find(x => x.TaskId == key);
                        if (ravenEtl == null)
                        {
                            HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            break;
                        }

                        WriteResult(context, new OngoingTaskRavenEtlDetails()
                        {
                            TaskId        = ravenEtl.TaskId,
                            TaskName      = ravenEtl.Name,
                            Configuration = ravenEtl,
                            TaskState     = GetEtlTaskState(ravenEtl)
                        });
                        break;

                    case OngoingTaskType.Subscription:

                        var nameKey = GetQueryStringValueAndAssertIfSingleAndNotEmpty("taskName");
                        var itemKey = SubscriptionState.GenerateSubscriptionItemKeyName(record.DatabaseName, nameKey);
                        var doc     = ServerStore.Cluster.Read(context, itemKey);
                        if (doc == null)
                        {
                            HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                            break;
                        }

                        var subscriptionState = JsonDeserializationClient.SubscriptionState(doc);
                        tag = dbTopology?.WhoseTaskIsIt(subscriptionState, ServerStore.Engine.CurrentState);

                        var subscriptionStateInfo = new SubscriptionStateWithNodeDetails
                        {
                            Query = subscriptionState.Query,
                            ChangeVectorForNextBatchStartingPoint = subscriptionState.ChangeVectorForNextBatchStartingPoint,
                            SubscriptionId           = subscriptionState.SubscriptionId,
                            SubscriptionName         = subscriptionState.SubscriptionName,
                            LastBatchAckTime         = subscriptionState.LastBatchAckTime,
                            Disabled                 = subscriptionState.Disabled,
                            LastClientConnectionTime = subscriptionState.LastClientConnectionTime,
                            MentorNode               = subscriptionState.MentorNode,
                            ResponsibleNode          = new NodeId
                            {
                                NodeTag = tag,
                                NodeUrl = clusterTopology.GetUrlFromTag(tag)
                            }
                        };

                        // Todo: here we'll need to talk with the running node? TaskConnectionStatus = subscriptionState.Disabled ? OngoingTaskConnectionStatus.NotActive : OngoingTaskConnectionStatus.Active,

                        WriteResult(context, subscriptionStateInfo.ToJson());
                        break;

                    default:
                        HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        break;
                    }
                }
            }

            return(Task.CompletedTask);
        }
 public override string GetItemId() => SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);
        public async Task SubscripitonDeletionFromCluster()
        {
            const int nodesAmount = 5;
            var       leader      = await this.CreateRaftClusterAndGetLeader(nodesAmount);

            var defaultDatabase = "ContinueFromThePointIStopped";

            await CreateDatabaseInCluster(defaultDatabase, nodesAmount, leader.WebUrl).ConfigureAwait(false);

            using (var store = new DocumentStore
            {
                Urls = new[] { leader.WebUrl },
                Database = defaultDatabase
            }.Initialize())
            {
                var usersCount            = new List <User>();
                var reachedMaxDocCountMre = new AsyncManualResetEvent();

                var subscriptionId = await store.Subscriptions.CreateAsync <User>();

                using (var session = store.OpenAsyncSession())
                {
                    await session.StoreAsync(new User
                    {
                        Name = "Peter"
                    });

                    await session.SaveChangesAsync();
                }

                var subscription = store.Subscriptions.GetSubscriptionWorker <User>(new SubscriptionWorkerOptions(subscriptionId));

                subscription.AfterAcknowledgment += b => { reachedMaxDocCountMre.Set(); return(Task.CompletedTask); };

                GC.KeepAlive(subscription.Run(x => { }));

                await reachedMaxDocCountMre.WaitAsync();


                foreach (var ravenServer in Servers)
                {
                    using (ravenServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                        using (context.OpenReadTransaction())
                        {
                            Assert.NotNull(ravenServer.ServerStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(defaultDatabase, subscriptionId.ToString())));
                        }
                }

                await subscription.DisposeAsync();

                var deleteResult = store.Maintenance.Server.Send(new DeleteDatabasesOperation(defaultDatabase, hardDelete: true));

                foreach (var ravenServer in Servers)
                {
                    await ravenServer.ServerStore.WaitForCommitIndexChange(RachisConsensus.CommitIndexModification.GreaterOrEqual, deleteResult.RaftCommandIndex + nodesAmount).WaitWithTimeout(TimeSpan.FromSeconds(60));
                }
                Thread.Sleep(2000);

                foreach (var ravenServer in Servers)
                {
                    using (ravenServer.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                        using (context.OpenReadTransaction())
                        {
                            Assert.Null(ravenServer.ServerStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(defaultDatabase, subscriptionId.ToString())));
                        }
                }
            }
        }
示例#7
0
        public override unsafe void Execute(ClusterOperationContext context, Table items, long index, RawDatabaseRecord record, RachisState state, out object result)
        {
            long i            = 1;
            var  originalName = SubscriptionName;
            var  tryToSetName = true;

            result = null;
            var subscriptionId = SubscriptionId ?? index;

            SubscriptionName = string.IsNullOrEmpty(SubscriptionName) ? subscriptionId.ToString() : SubscriptionName;
            var baseName = SubscriptionName;

            if (SubscriptionName.Length > DocumentIdWorker.MaxIdSize)
            {
                throw new SubscriptionNameException($"Subscription Name is too long, must be at most {DocumentIdWorker.MaxIdSize} bytes");
            }

            while (tryToSetName)
            {
                var subscriptionItemName = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);
                using (Slice.From(context.Allocator, subscriptionItemName, out Slice valueName))
                    using (Slice.From(context.Allocator, subscriptionItemName.ToLowerInvariant(), out Slice valueNameLowered))
                    {
                        if (items.ReadByKey(valueNameLowered, out TableValueReader tvr))
                        {
                            var ptr = tvr.Read(2, out int size);
                            var doc = new BlittableJsonReaderObject(ptr, size, context);

                            var existingSubscriptionState = JsonDeserializationClient.SubscriptionState(doc);
                            if (SubscriptionId != existingSubscriptionState.SubscriptionId)
                            {
                                if (string.IsNullOrEmpty(originalName))
                                {
                                    SubscriptionName = $"{baseName}.{i}";
                                    i++;
                                    continue;
                                }
                                throw new RachisApplyException("A subscription could not be modified because the name '" + subscriptionItemName +
                                                               "' is already in use in a subscription with different Id.");
                            }

                            if (string.IsNullOrEmpty(InitialChangeVector) == false && InitialChangeVector == nameof(Constants.Documents.SubscriptionChangeVectorSpecialStates.DoNotChange))
                            {
                                InitialChangeVector = existingSubscriptionState.ChangeVectorForNextBatchStartingPoint;
                            }
                            else
                            {
                                AssertValidChangeVector();
                                if (InitialChangeVector != existingSubscriptionState.ChangeVectorForNextBatchStartingPoint)
                                {
                                    // modified by the admin
                                    var subscriptionStateTable = context.Transaction.InnerTransaction.OpenTable(ClusterStateMachine.SubscriptionStateSchema, ClusterStateMachine.SubscriptionState);
                                    using (SubscriptionConnectionsState.GetDatabaseAndSubscriptionPrefix(context, DatabaseName, subscriptionId, out var prefix))
                                    {
                                        using var _ = Slice.External(context.Allocator, prefix, out var prefixSlice);
                                        subscriptionStateTable.DeleteByPrimaryKeyPrefix(prefixSlice);
                                    }
                                }
                            }
                        }
                        else
                        {
                            AssertValidChangeVector();
                        }

                        using (var receivedSubscriptionState = context.ReadObject(new SubscriptionState
                        {
                            Query = Query,
                            ChangeVectorForNextBatchStartingPoint = InitialChangeVector,
                            SubscriptionId = subscriptionId,
                            SubscriptionName = SubscriptionName,
                            LastBatchAckTime = null,
                            Disabled = Disabled,
                            MentorNode = MentorNode,
                            LastClientConnectionTime = null
                        }.ToJson(), SubscriptionName))
                        {
                            ClusterStateMachine.UpdateValue(index, items, valueNameLowered, valueName, receivedSubscriptionState);
                        }

                        tryToSetName = false;
                    }
            }
        }
示例#8
0
        public void HandleDatabaseRecordChange(DatabaseRecord databaseRecord)
        {
            using (_serverStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                using (context.OpenReadTransaction())
                {
                    foreach (var subscriptionStateKvp in _subscriptionConnectionStates)
                    {
                        var subscriptionName = subscriptionStateKvp.Value.Connection?.Options?.SubscriptionName;
                        if (subscriptionName == null)
                        {
                            continue;
                        }

                        var subscriptionBlittable = _serverStore.Cluster.Read(context, SubscriptionState.GenerateSubscriptionItemKeyName(databaseRecord.DatabaseName, subscriptionName));
                        if (subscriptionBlittable == null)
                        {
                            DropSubscriptionConnection(subscriptionStateKvp.Key, new SubscriptionDoesNotExistException($"The subscription {subscriptionName} had been deleted"));
                            continue;
                        }

                        var subscriptionState = JsonDeserializationClient.SubscriptionState(subscriptionBlittable);
                        if (subscriptionState.Disabled)
                        {
                            DropSubscriptionConnection(subscriptionStateKvp.Key, new SubscriptionClosedException($"The subscription {subscriptionName} is disabled and cannot be used until enabled"));
                            continue;
                        }

                        if (subscriptionState.Query != subscriptionStateKvp.Value.Connection?.SubscriptionState.Query)
                        {
                            DropSubscriptionConnection(subscriptionStateKvp.Key, new SubscriptionClosedException($"The subscription {subscriptionName} query has been modified, connection must be restarted"));
                            continue;
                        }

                        var whoseTaskIsIt = _db.WhoseTaskIsIt(databaseRecord.Topology, subscriptionState, subscriptionState);
                        if (whoseTaskIsIt != _serverStore.NodeTag)
                        {
                            DropSubscriptionConnection(subscriptionStateKvp.Key,
                                                       new SubscriptionDoesNotBelongToNodeException("Subscription operation was stopped, because it's now under different server's responsibility"));
                        }
                    }
                }
        }
示例#9
0
        public bool Update(UpdateStep step)
        {
            var          ids                 = new HashSet <long>();
            var          minimal             = long.MaxValue;
            const string dbKey               = "db/";
            var          continueAfterCommit = true;
            var          skip                = 0;

            while (continueAfterCommit)
            {
                continueAfterCommit = false;
                var fixedItems = 0;

                var items = step.WriteTx.OpenTable(ClusterStateMachine.ItemsSchema, ClusterStateMachine.Items);
                using (Slice.From(step.WriteTx.Allocator, dbKey, out Slice loweredPrefix))
                {
                    foreach (var result in items.SeekByPrimaryKeyPrefix(loweredPrefix, Slices.Empty, 0))
                    {
                        var databaseName = ClusterStateMachine.GetCurrentItemKey(result.Value).Substring(3);
                        using (Slice.From(step.WriteTx.Allocator, dbKey + databaseName.ToLowerInvariant(), out var key))
                        {
                            if (items.VerifyKeyExists(key) == false)
                            {
                                continue;
                            }
                        }

                        using (Slice.From(step.WriteTx.Allocator, SubscriptionState.SubscriptionPrefix(databaseName), out var startWith))
                            using (var ctx = JsonOperationContext.ShortTermSingleUse())
                            {
                                foreach (var holder in items.SeekByPrimaryKeyPrefix(startWith, Slices.Empty, skip))
                                {
                                    skip++;
                                    var reader = holder.Value.Reader;
                                    var ptr    = reader.Read(2, out int size);
                                    using (var doc = new BlittableJsonReaderObject(ptr, size, ctx))
                                    {
                                        if (doc.TryGet(nameof(SubscriptionState.SubscriptionId), out long id) == false)
                                        {
                                            continue;
                                        }

                                        if (minimal > id)
                                        {
                                            minimal = id;
                                        }

                                        if (ids.Add(id))
                                        {
                                            continue;
                                        }

                                        minimal--;
                                        ids.Add(minimal);

                                        var subscriptionState = JsonDeserializationClient.SubscriptionState(doc);
                                        subscriptionState.SubscriptionId = minimal;
                                        var subscriptionItemName = SubscriptionState.GenerateSubscriptionItemKeyName(databaseName, subscriptionState.SubscriptionName);
                                        using (Slice.From(step.WriteTx.Allocator, subscriptionItemName, out Slice valueName))
                                            using (Slice.From(step.WriteTx.Allocator, subscriptionItemName.ToLowerInvariant(), out Slice valueNameLowered))
                                                using (var receivedSubscriptionState = ctx.ReadObject(subscriptionState.ToJson(), subscriptionState.SubscriptionName))
                                                {
                                                    ClusterStateMachine.UpdateValue(0, items, valueNameLowered, valueName, receivedSubscriptionState);
                                                }
                                    }

                                    fixedItems++;
                                    if (fixedItems < 1024)
                                    {
                                        continue;
                                    }

                                    continueAfterCommit = true;
                                    break;
                                }
                            }

                        if (continueAfterCommit)
                        {
                            break;
                        }
                    }
                }

                if (continueAfterCommit)
                {
                    step.Commit(null);
                    step.RenewTransactions();
                }
            }

            return(true);
        }
        public override unsafe void Execute(ClusterOperationContext context, Table items, long index, RawDatabaseRecord record, RachisState state, out object result)
        {
            result = null;
            var shouldUpdateChangeVector = true;
            var subscriptionName         = SubscriptionName;

            if (string.IsNullOrEmpty(subscriptionName))
            {
                subscriptionName = SubscriptionId.ToString();
            }

            //insert all docs to voron table. If exists, then batchId will be replaced
            var subscriptionStateTable = context.Transaction.InnerTransaction.OpenTable(ClusterStateMachine.SubscriptionStateSchema, ClusterStateMachine.SubscriptionState);

            var itemKey = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, subscriptionName);

            using (Slice.From(context.Allocator, itemKey.ToLowerInvariant(), out Slice valueNameLowered))
                using (Slice.From(context.Allocator, itemKey, out Slice valueName))
                {
                    if (items.ReadByKey(valueNameLowered, out var tvr) == false)
                    {
                        throw new RachisApplyException($"Cannot find subscription {subscriptionName} @ {DatabaseName}");
                    }

                    var ptr           = tvr.Read(2, out int size);
                    var existingValue = new BlittableJsonReaderObject(ptr, size, context);

                    if (existingValue == null)
                    {
                        throw new SubscriptionDoesNotExistException($"Subscription with name '{subscriptionName}' does not exist in database '{DatabaseName}'");
                    }

                    var subscriptionState = JsonDeserializationClient.SubscriptionState(existingValue);

                    var topology            = record.Topology;
                    var lastResponsibleNode = AcknowledgeSubscriptionBatchCommand.GetLastResponsibleNode(HasHighlyAvailableTasks, topology, NodeTag);
                    var appropriateNode     = topology.WhoseTaskIsIt(RachisState.Follower, subscriptionState, lastResponsibleNode);
                    if (appropriateNode == null && record.DeletionInProgress.ContainsKey(NodeTag))
                    {
                        throw new DatabaseDoesNotExistException($"Stopping subscription '{subscriptionName}' on node {NodeTag}, because database '{DatabaseName}' is being deleted.");
                    }

                    if (appropriateNode != NodeTag)
                    {
                        throw new SubscriptionDoesNotBelongToNodeException(
                                  $"Cannot apply {nameof(AcknowledgeSubscriptionBatchCommand)} for subscription '{subscriptionName}' with id '{SubscriptionId}', on database '{DatabaseName}', on node '{NodeTag}'," +
                                  $" because the subscription task belongs to '{appropriateNode ?? "N/A"}'.")
                              {
                                  AppropriateNode = appropriateNode
                              };
                    }

                    if (CurrentChangeVector == nameof(Constants.Documents.SubscriptionChangeVectorSpecialStates.DoNotChange))
                    {
                        context.ReadObject(existingValue, subscriptionName);
                        shouldUpdateChangeVector = false;
                    }

                    if (subscriptionState.ChangeVectorForNextBatchStartingPoint != PreviouslyRecordedChangeVector)
                    {
                        throw new SubscriptionChangeVectorUpdateConcurrencyException($"Can't record subscription with name '{subscriptionName}' due to inconsistency in change vector progress. Probably there was an admin intervention that changed the change vector value. Stored value: {subscriptionState.ChangeVectorForNextBatchStartingPoint}, received value: {PreviouslyRecordedChangeVector}");
                    }

                    if (shouldUpdateChangeVector)
                    {
                        subscriptionState.ChangeVectorForNextBatchStartingPoint =
                            ChangeVectorUtils.MergeVectors(CurrentChangeVector, subscriptionState.ChangeVectorForNextBatchStartingPoint);
                        subscriptionState.NodeTag = NodeTag;
                        using (var obj = context.ReadObject(subscriptionState.ToJson(), "subscription"))
                        {
                            ClusterStateMachine.UpdateValue(index, items, valueNameLowered, valueName, obj);
                        }
                    }
                }

            foreach (var deletedId in Deleted)
            {
                using (SubscriptionConnectionsState.GetDatabaseAndSubscriptionAndDocumentKey(context, DatabaseName, SubscriptionId, deletedId, out var key))
                {
                    using var _ = Slice.External(context.Allocator, key, out var keySlice);
                    subscriptionStateTable.DeleteByKey(keySlice);
                }
            }

            foreach (var documentRecord in Documents)
            {
                using (SubscriptionConnectionsState.GetDatabaseAndSubscriptionAndDocumentKey(context, DatabaseName, SubscriptionId, documentRecord.DocumentId, out var key))
                    using (subscriptionStateTable.Allocate(out var tvb))
                    {
                        using var _  = Slice.External(context.Allocator, key, out var keySlice);
                        using var __ = Slice.From(context.Allocator, documentRecord.ChangeVector, out var changeVectorSlice);

                        tvb.Add(keySlice);
                        tvb.Add(changeVectorSlice);
                        tvb.Add(Bits.SwapBytes(index)); // batch id

                        subscriptionStateTable.Set(tvb);
                    }
            }
            foreach (var revisionRecord in Revisions)
            {
                using (SubscriptionConnectionsState.GetDatabaseAndSubscriptionAndRevisionKey(context, DatabaseName, SubscriptionId, revisionRecord.Current, out var key))
                    using (subscriptionStateTable.Allocate(out var tvb))
                    {
                        using var _  = Slice.External(context.Allocator, key, out var keySlice);
                        using var __ = Slice.From(context.Allocator, revisionRecord.Previous ?? string.Empty, out var changeVectorSlice);

                        tvb.Add(keySlice);
                        tvb.Add(changeVectorSlice);     //prev change vector
                        tvb.Add(Bits.SwapBytes(index)); // batch id

                        subscriptionStateTable.Set(tvb);
                    }
            }
        }