Skip to content

Instantly share code, notes, and snippets.

@chris-hmmr
Last active April 12, 2021 12:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chris-hmmr/0f820da9b9e311dd036d27e2ef20a2b1 to your computer and use it in GitHub Desktop.
Save chris-hmmr/0f820da9b9e311dd036d27e2ef20a2b1 to your computer and use it in GitHub Desktop.
This class can be used to clean up corrupted events and contact facets (holding very long exceptions) for xDB interactions stored in MongoDB. Code was tested and used for Sitecore 9.1 initial
using System;
using System.Collections.Generic;
using System.Linq;
using Foundation.Maintenance.Logging;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using Sitecore.Configuration;
namespace Foundation.Maintenance.xConnect
{
public class MongoDbXdbCollectionCleanupManager
{
protected IMongoDatabase _database { get; }
public MongoDbXdbCollectionCleanupManager(string databaseName)
{
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
string mongoConnectionString = Settings.GetSetting("Foundation.Maintenance.xDB.ConnectionString");
var mongoClient = new MongoClient(mongoConnectionString);
_database = mongoClient.GetDatabase(databaseName);
}
public void RemoveCorruptedInteractions()
{
MaintenanceLog.LogInfo("Removing corrupted interaction data");
try
{
var interactionData =
_database.GetCollection<InteractionData>("Interactions");
var interactions = interactionData.AsQueryable().ToList();
foreach (var interaction in interactions)
{
if (!HasCorruptInteractionData(interaction, out var matches))
{
continue;
}
foreach (var match in matches.Where(match => DeleteEvent(interactionData, match)))
{
MaintenanceLog.LogInfo("Successfully deleted event with Id " + match.Id);
}
}
}
catch (Exception e)
{
MaintenanceLog.LogError("There was an error during cleanup operation: " + e.Message);
throw;
}
}
public void RemoveCorruptedContactFacets()
{
MaintenanceLog.LogInfo("Removing corrupted facet data");
try
{
var contactFacetData =
_database.GetCollection<ContactFacet>("ContactFacets");
var contactFacets = contactFacetData.AsQueryable().ToList();
foreach (ContactFacet contactFacet in contactFacets)
{
if (!HasCorruptFacetData(contactFacet.Content))
{
continue;
}
if (DeleteFacet(contactFacetData, contactFacet))
{
MaintenanceLog.LogInfo("Successfully deleted ContactFacet with Id " + contactFacet.Id.FacetKey + " " + contactFacet.Id.RecordId);
}
else
{
MaintenanceLog.LogInfo("No contact facet found for ContactFacet with Id " + contactFacet.Id.RecordId);
}
}
}
catch (Exception e)
{
MaintenanceLog.LogError("There was an error during cleanup operation: " + e.Message);
throw;
}
MaintenanceLog.LogInfo("Finished deleting corrupt contact facets");
}
private bool HasCorruptFacetData(Content contentElement)
{
if (contentElement.PageEvents == null)
return false;
var matches = contentElement.PageEvents
.Where(x => x.Data != null)
.Where(interactionEvent => System.Text.Encoding.Unicode.GetByteCount(interactionEvent.Data) > 20000).ToList();
return matches.Any();
}
private bool HasCorruptInteractionData(InteractionData interaction, out List<Event> matches)
{
matches = interaction.Events
.Where(x => x.Data != null)
.Where(interactionEvent => System.Text.Encoding.Unicode.GetByteCount(interactionEvent.Data) > 20000).ToList();
return matches.Any();
}
public bool DeleteEvent(IMongoCollection<InteractionData> interactionData, Event eventToDelete)
{
var result = interactionData.DeleteOne(p => p.Events.Any(x => x.Id == eventToDelete.Id));
return result.DeletedCount != 0;
}
public bool DeleteFacet(IMongoCollection<ContactFacet> contactFacetsData, ContactFacet facetToDelete)
{
var result = contactFacetsData.DeleteOne(f => f.Id.Equals(facetToDelete.Id));
return result.DeletedCount != 0;
}
[BsonIgnoreExtraElements]
public class ContactFacet : SystemModel<FacetIdModel>, IFacet, ISystemModel<FacetIdModel>
{
[BsonElement("content")]
public Content Content { get; set; }
}
[BsonIgnoreExtraElements]
public class InteractionData
{
[BsonElement("events")]
public IEnumerable<Event> Events
{
get; set;
}
}
[BsonIgnoreExtraElements]
public class Content
{
[BsonElement("PageEvents")]
public IEnumerable<PageEvent> PageEvents
{
get; set;
}
}
[BsonIgnoreExtraElements]
public class Event
{
[BsonId]
public string EventId { get; set; }
[BsonElement("DefinitionId")]
public string DefinitionId { get; set; }
[BsonElement("Id")]
public string Id { get; set; }
[BsonElement("ItemId")]
public string ItemId { get; set; }
[BsonElement("Text")]
public string Text { get; set; }
[BsonElement("Data")]
public string Data { get; set; }
[BsonElement("DataKey")]
public string DataKey { get; set; }
}
[BsonIgnoreExtraElements]
public class PageEvent
{
[BsonElement("DefinitionId")]
public string DefinitionId { get; set; }
[BsonElement("InteractionId")]
public string InteractionId { get; set; }
[BsonElement("Data")]
public string Data { get; set; }
}
public class SimpleIdModel : IEquatable<SimpleIdModel>
{
public SimpleIdModel(Guid recordId) => this.RecordId = recordId;
[BsonElement("id")]
[BsonRequired]
public Guid RecordId { get; set; }
public bool Equals(SimpleIdModel other)
{
if (other == null)
return false;
return this == other || this.RecordId.Equals(other.RecordId);
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
if (this == obj)
return true;
return !(obj.GetType() != this.GetType()) && this.Equals((SimpleIdModel) obj);
}
public override int GetHashCode() => this.RecordId.GetHashCode();
internal static class FieldNames
{
public const string RecordId = "id";
}
}
public class FacetIdModel : IdModel
{
public FacetIdModel(Guid parentId, Guid referenceId, string facetKey)
: base(parentId, referenceId)
=> this.FacetKey = facetKey;
[BsonElement("f_key")]
[BsonRequired]
public string FacetKey { get; set; }
internal new static class FieldNames
{
public const string FacetKey = "f_key";
}
}
public class IdModel : SimpleIdModel
{
public IdModel(Guid parentId, Guid recordId)
: base(recordId)
=> this.ParentId = parentId;
[BsonElement("parent_id")]
[BsonRequired]
public Guid ParentId { get; set; }
internal new static class FieldNames
{
public const string ParentId = "parent_id";
}
}
public interface IFacet : ISystemModel<FacetIdModel>
{
[BsonRequired]
Content Content { get; set; }
}
public interface ISystemModel<T>
{
[BsonId]
T Id { get; set; }
Guid ConcurrencyToken { get; set; }
long LastModified { get; set; }
}
public class SystemModel<TId> : ISystemModel<TId>
{
[BsonId]
public TId Id { get; set; }
[BsonElement("ct")]
public Guid ConcurrencyToken { get; set; }
[BsonElement("lm")]
public long LastModified { get; set; }
internal static class FieldNames
{
public const string ConcurrencyToken = "ct";
public const string LastModified = "lm";
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment