feat: complete remaining jetstream parity implementation plan
This commit is contained in:
@@ -43,6 +43,23 @@ public sealed class FileStore : IStreamStore, IAsyncDisposable
|
||||
return ValueTask.FromResult(msg);
|
||||
}
|
||||
|
||||
public ValueTask<StoredMessage?> LoadLastBySubjectAsync(string subject, CancellationToken ct)
|
||||
{
|
||||
var match = _messages.Values
|
||||
.Where(m => string.Equals(m.Subject, subject, StringComparison.Ordinal))
|
||||
.OrderByDescending(m => m.Sequence)
|
||||
.FirstOrDefault();
|
||||
return ValueTask.FromResult(match);
|
||||
}
|
||||
|
||||
public ValueTask<bool> RemoveAsync(ulong sequence, CancellationToken ct)
|
||||
{
|
||||
var removed = _messages.Remove(sequence);
|
||||
if (removed)
|
||||
RewriteDataFile();
|
||||
return ValueTask.FromResult(removed);
|
||||
}
|
||||
|
||||
public ValueTask PurgeAsync(CancellationToken ct)
|
||||
{
|
||||
_messages.Clear();
|
||||
@@ -52,6 +69,49 @@ public sealed class FileStore : IStreamStore, IAsyncDisposable
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<byte[]> CreateSnapshotAsync(CancellationToken ct)
|
||||
{
|
||||
var snapshot = _messages
|
||||
.Values
|
||||
.OrderBy(x => x.Sequence)
|
||||
.Select(x => new FileRecord
|
||||
{
|
||||
Sequence = x.Sequence,
|
||||
Subject = x.Subject,
|
||||
PayloadBase64 = Convert.ToBase64String(x.Payload.ToArray()),
|
||||
})
|
||||
.ToArray();
|
||||
return ValueTask.FromResult(JsonSerializer.SerializeToUtf8Bytes(snapshot));
|
||||
}
|
||||
|
||||
public ValueTask RestoreSnapshotAsync(ReadOnlyMemory<byte> snapshot, CancellationToken ct)
|
||||
{
|
||||
_messages.Clear();
|
||||
_last = 0;
|
||||
|
||||
if (!snapshot.IsEmpty)
|
||||
{
|
||||
var records = JsonSerializer.Deserialize<FileRecord[]>(snapshot.Span);
|
||||
if (records != null)
|
||||
{
|
||||
foreach (var record in records)
|
||||
{
|
||||
var message = new StoredMessage
|
||||
{
|
||||
Sequence = record.Sequence,
|
||||
Subject = record.Subject ?? string.Empty,
|
||||
Payload = Convert.FromBase64String(record.PayloadBase64 ?? string.Empty),
|
||||
};
|
||||
_messages[record.Sequence] = message;
|
||||
_last = Math.Max(_last, record.Sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RewriteDataFile();
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public ValueTask<StreamState> GetStateAsync(CancellationToken ct)
|
||||
{
|
||||
return ValueTask.FromResult(new StreamState
|
||||
|
||||
@@ -6,6 +6,10 @@ public interface IStreamStore
|
||||
{
|
||||
ValueTask<ulong> AppendAsync(string subject, ReadOnlyMemory<byte> payload, CancellationToken ct);
|
||||
ValueTask<StoredMessage?> LoadAsync(ulong sequence, CancellationToken ct);
|
||||
ValueTask<StoredMessage?> LoadLastBySubjectAsync(string subject, CancellationToken ct);
|
||||
ValueTask<bool> RemoveAsync(ulong sequence, CancellationToken ct);
|
||||
ValueTask PurgeAsync(CancellationToken ct);
|
||||
ValueTask<byte[]> CreateSnapshotAsync(CancellationToken ct);
|
||||
ValueTask RestoreSnapshotAsync(ReadOnlyMemory<byte> snapshot, CancellationToken ct);
|
||||
ValueTask<StreamState> GetStateAsync(CancellationToken ct);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
using System.Text.Json;
|
||||
using NATS.Server.JetStream.Models;
|
||||
|
||||
namespace NATS.Server.JetStream.Storage;
|
||||
|
||||
public sealed class MemStore : IStreamStore
|
||||
{
|
||||
private sealed class SnapshotRecord
|
||||
{
|
||||
public ulong Sequence { get; init; }
|
||||
public string Subject { get; init; } = string.Empty;
|
||||
public string PayloadBase64 { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
private readonly object _gate = new();
|
||||
private ulong _last;
|
||||
private readonly Dictionary<ulong, StoredMessage> _messages = new();
|
||||
@@ -32,6 +40,26 @@ public sealed class MemStore : IStreamStore
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<StoredMessage?> LoadLastBySubjectAsync(string subject, CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var match = _messages.Values
|
||||
.Where(m => string.Equals(m.Subject, subject, StringComparison.Ordinal))
|
||||
.OrderByDescending(m => m.Sequence)
|
||||
.FirstOrDefault();
|
||||
return ValueTask.FromResult(match);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<bool> RemoveAsync(ulong sequence, CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
return ValueTask.FromResult(_messages.Remove(sequence));
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask PurgeAsync(CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
@@ -42,6 +70,53 @@ public sealed class MemStore : IStreamStore
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<byte[]> CreateSnapshotAsync(CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var snapshot = _messages
|
||||
.Values
|
||||
.OrderBy(x => x.Sequence)
|
||||
.Select(x => new SnapshotRecord
|
||||
{
|
||||
Sequence = x.Sequence,
|
||||
Subject = x.Subject,
|
||||
PayloadBase64 = Convert.ToBase64String(x.Payload.ToArray()),
|
||||
})
|
||||
.ToArray();
|
||||
return ValueTask.FromResult(JsonSerializer.SerializeToUtf8Bytes(snapshot));
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask RestoreSnapshotAsync(ReadOnlyMemory<byte> snapshot, CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_messages.Clear();
|
||||
_last = 0;
|
||||
|
||||
if (!snapshot.IsEmpty)
|
||||
{
|
||||
var records = JsonSerializer.Deserialize<SnapshotRecord[]>(snapshot.Span);
|
||||
if (records != null)
|
||||
{
|
||||
foreach (var record in records)
|
||||
{
|
||||
_messages[record.Sequence] = new StoredMessage
|
||||
{
|
||||
Sequence = record.Sequence,
|
||||
Subject = record.Subject,
|
||||
Payload = Convert.FromBase64String(record.PayloadBase64),
|
||||
};
|
||||
_last = Math.Max(_last, record.Sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask<StreamState> GetStateAsync(CancellationToken ct)
|
||||
{
|
||||
lock (_gate)
|
||||
|
||||
Reference in New Issue
Block a user