Event log persistence for the SQL Server and PostgreSQL persisters - #5655
Event log persistence for the SQL Server and PostgreSQL persisters#5655warwickschroeder wants to merge 12 commits into
Conversation
| public long Id { get; set; } | ||
|
|
||
| // The API-visible identity, assigned by EventLogMappingDefinition as | ||
| // "EventLogItem/{Category}/{EventType}/{guid}" |
There was a problem hiding this comment.
This feels a lot like Raven Ids!
There was a problem hiding this comment.
It sure is, which is why I havent made this the PK and introduced a long for that. Ill take another look into if we need this field at all in EF.
There was a problem hiding this comment.
I think we should keep the API-visible identity for each event log item the same. The database ID field used by the persistence doesn't need to be this though.
There was a problem hiding this comment.
I'm not sure I understand what you mean
There was a problem hiding this comment.
This commit should move the Id generation into the persistence seams and out of the abstraction layer.
|
|
||
| public DateTime RaisedAt { get; set; } | ||
|
|
||
| public List<string> RelatedTo { get; set; } = []; |
There was a problem hiding this comment.
is EF smart to serialize this as xml/json?
There was a problem hiding this comment.
In SQL server, yes. It maps to a json array string. In Postrgres, maps to a a native text[] type
There was a problem hiding this comment.
I just looked in SP and it seems this is the only place we are using the "related_to" prop, see https://github.com/Particular/ServicePulse/blob/5f0ede166f9a1209a288659d4f8f0c59067525ea/src/Frontend/src/components/EventLogItem.vue#L30-L33 and it seems we are only looking at the first item!
There was a problem hiding this comment.
Yeh looks that way, only the leading /message/ link. Ill add a board card to discuss.
| // rather than the shape of MessageFailed. | ||
| class EventLogWriterTests : PersistenceTestBase | ||
| { | ||
| public EventLogWriterTests() => RegisterServices = services => services.AddSignalR(); |
There was a problem hiding this comment.
So, looks like SignalR is still fully wired up and broadcasting 25 domain event types in ServiceControl... just to nobody it seems!
I did a search in ServicePulse and ServiceInsight and neither are clients of it. Integration events is something seperate and dont use SignalR. I dont know what else to check to make sure this isnt being used. Ill add something to our board to confirm and potentially clean up.
There was a problem hiding this comment.
If there's no clients signalR doesn't actually "broadcast" anything, the only cost would be if there's a distributed backplane set up but looking at that it's only using the in-memory one.
There was a problem hiding this comment.
The only product that consumed SignalR was SP a long long time ago, so I think is time to remove it
There was a problem hiding this comment.
Once (if) SignalR is removed, I will remove it from this test too
| if (results is null) | ||
| { | ||
| return StatusCode((int)HttpStatusCode.NotModified); | ||
| } | ||
|
|
||
| return Ok(results); |
There was a problem hiding this comment.
This feels weird.
My thought is that we have middleware that handles it for us
There was a problem hiding this comment.
We do have middleware for this, but the problem with that is that it was still querying the database. This skips the query if the version tag matches the header
| { | ||
| var query = dbContext.EventLogItems.AsNoTracking(); | ||
|
|
||
| var total = await query.LongCountAsync(); |
There was a problem hiding this comment.
are these data pages really so big that doing two database roundtrips is faster than just going to get the data?
There was a problem hiding this comment.
To reliably create a version id, we need to count all rows. There could be a lot of rows in the event logs. Maybe this would work? AI recons that would be 1 round trip, matching (and improving) what Raven is currently doing as its 1 trip, but also returning data.
var stats = await query
.GroupBy(_ => 1)
.Select(g => new { Total = g.LongCount(), Newest = g.Max(e => (DateTime?)e.RaisedAt) })
.FirstOrDefaultAsync();
There was a problem hiding this comment.
Yep that worked. 1 round trip now.
|
|
||
| return results; | ||
| // Null items means "you already hold this version" | ||
| if (results is null) |
There was a problem hiding this comment.
| if (results is null) | |
| if (results is null && totalCount > 0) |
There was a problem hiding this comment.
This suggestion doesnt look right. results being null means that the If-None-Match matched the current version of the data. I dont think we want to check for actual data
bddff53 to
1233101
Compare
…QL Server, and tests
…og items with versioning support
…em retention and cache validation behavior
- Seperate EventLogItem into a read/dto, and write model. The persistence now takes care of setting an Id, not the application layer - Add tests
fb53f38 to
8ce240f
Compare
The event log is the list ServicePulse shows on its Events page. It worked on RavenDB and threw
NotImplementedExceptionon both EF Core persisters, so nothing could be written or read. This implements it.Both EF persisters still log "is still under development and is not ready for use" at startup, so no customer is affected today.
What changed
EventLogItemEntity, its configuration, and one migration per provider.EventLogDataStorenow implements both methods.AuditEventLogWritercalledIErrorMessageDataStore.StoreEventLogItemwhileIEventLogDataStore.Addsat unused. Now it callsAdd, and the stray member is gone fromIErrorMessageDataStoreand both implementations.RetentionSweepersweeps them on its existing hourly timer against a separateEventsRetentionPeriod, independent ofErrorRetentionPeriod.GetEventLogItemstakes an optionalknownVersion. On a match the persister skips the page query andEventLogApiControlleranswers304, withETagandTotal-Countstill sent.knownVersiondrops a query per poll on the EF persisters. A read is three queries: count, newestRaisedAt, then the page. When the caller's version matches, the page query is skipped and the two that remain are satisfied byIX_EventLogItems_RaisedAt_Idwithout reading row data. ServicePulse polls this every five seconds, so on an idle instance that is the common case rather than the exception. RavenDB saves no database work, because its version is the query's ownResultEtagand does not exist until the query has run; there the saving is only the serialised response body.IEventLogDataStorenow documents retention, which was previously invisible at the seam despite each persister enforcing it differently.Tests
21 new tests plus 3 added to
RetentionSweepTests, and one acceptance test for the304. The 14 inEventLogDataStoreTestsare the valuable ones: they live in the shared root ofServiceControl.Persistence.Tests, so they run as conformance tests against RavenDB, SQL Server and PostgreSQL. Round-tripping, ordering, paging boundaries and all fourknownVersioncases have to behave identically on all three.