ObjectRepository - உங்கள் வீட்டுத் திட்டங்களுக்கான .NET இன்-மெமரி களஞ்சிய முறை

எல்லா தரவையும் நினைவகத்தில் ஏன் சேமிக்க வேண்டும்?

இணையதளம் அல்லது பின்தளத்தில் தரவைச் சேமிப்பதற்கு, SQL தரவுத்தளத்தைத் தேர்வு செய்வதே பெரும்பாலான விவேகமுள்ளவர்களின் முதல் ஆசையாக இருக்கும். 

ஆனால் சில சமயங்களில் தரவு மாதிரி SQL க்கு பொருந்தாது என்ற எண்ணம் வருகிறது: எடுத்துக்காட்டாக, ஒரு தேடல் அல்லது சமூக வரைபடத்தை உருவாக்கும்போது, ​​பொருள்களுக்கு இடையே உள்ள சிக்கலான உறவுகளை நீங்கள் தேட வேண்டும். 

மிக மோசமான சூழ்நிலை என்னவென்றால், நீங்கள் ஒரு குழுவில் பணிபுரியும் போது, ​​ஒரு சக ஊழியருக்கு விரைவான வினவல்களை எவ்வாறு உருவாக்குவது என்று தெரியவில்லை. N+1 சிக்கல்களைத் தீர்ப்பதற்கும் கூடுதல் குறியீடுகளை உருவாக்குவதற்கும் எவ்வளவு நேரம் செலவழித்தீர்கள், இதன் மூலம் பிரதான பக்கத்தில் உள்ள SELECT ஒரு நியாயமான நேரத்தில் முடிவடையும்?

மற்றொரு பிரபலமான அணுகுமுறை NoSQL ஆகும். பல ஆண்டுகளுக்கு முன்பு இந்த தலைப்பைச் சுற்றி நிறைய பரபரப்புகள் இருந்தன - எந்தவொரு வசதியான சந்தர்ப்பத்திலும் அவர்கள் மோங்கோடிபியைப் பயன்படுத்தினார்கள் மற்றும் json ஆவணங்கள் வடிவில் பதில்களில் மகிழ்ச்சியடைந்தனர். (ஆவணங்களில் வட்ட இணைப்புகள் இருப்பதால் எத்தனை ஊன்றுகோல்களை நீங்கள் செருக வேண்டியிருந்தது?).

வேறொரு மாற்று முறையை முயற்சிக்குமாறு நான் பரிந்துரைக்கிறேன் - ஏன் எல்லா தரவையும் பயன்பாட்டு நினைவகத்தில் சேமிக்க முயற்சிக்கக்கூடாது, அவ்வப்போது சீரற்ற சேமிப்பகத்தில் (கோப்பு, தொலை தரவுத்தளத்தில்) சேமிக்கவும்? 

நினைவகம் மலிவாகிவிட்டது, மேலும் சிறிய மற்றும் நடுத்தர அளவிலான திட்டங்களுக்கான சாத்தியமான தரவு 1 GB நினைவகத்தில் பொருந்தும். (உதாரணமாக, எனக்கு பிடித்த வீட்டு திட்டம் நிதி கண்காணிப்பாளர், தினசரி புள்ளிவிவரங்கள் மற்றும் எனது செலவுகள், இருப்புக்கள் மற்றும் பரிவர்த்தனைகளின் வரலாற்றை ஒன்றரை வருடங்கள் வைத்திருக்கும், இது 45 MB நினைவகத்தை மட்டுமே பயன்படுத்துகிறது.)

நன்மை:

  • தரவு அணுகல் எளிதாகிறது - வினவல்கள், சோம்பேறி ஏற்றுதல், ORM அம்சங்கள் பற்றி நீங்கள் கவலைப்படத் தேவையில்லை, நீங்கள் சாதாரண C# பொருள்களுடன் வேலை செய்கிறீர்கள்;
  • வெவ்வேறு நூல்களிலிருந்து அணுகல் தொடர்பான சிக்கல்கள் எதுவும் இல்லை;
  • மிக வேகமாக - நெட்வொர்க் கோரிக்கைகள் இல்லை, குறியீட்டை வினவல் மொழியில் மொழிபெயர்ப்பது இல்லை, பொருள்களின் (டி)வரிசைப்படுத்தல் தேவையில்லை;
  • எந்தவொரு வடிவத்திலும் தரவைச் சேமிப்பது ஏற்றுக்கொள்ளத்தக்கது - அது XML இல் டிஸ்கில் இருக்கலாம், அல்லது SQL சர்வரில் அல்லது அஸூர் டேபிள் ஸ்டோரேஜில் இருக்கலாம்.

தீமைகள்:

  • கிடைமட்ட அளவிடுதல் இழக்கப்படுகிறது, இதன் விளைவாக, பூஜ்ஜிய வேலையில்லா வரிசைப்படுத்தல் செய்ய முடியாது;
  • பயன்பாடு செயலிழந்தால், நீங்கள் தரவை ஓரளவு இழக்க நேரிடும். (ஆனால் எங்கள் பயன்பாடு ஒருபோதும் செயலிழக்காது, இல்லையா?)

இது எப்படி வேலை செய்கிறது?

வழிமுறை பின்வருமாறு:

  • தொடக்கத்தில், தரவு சேமிப்பகத்துடன் ஒரு இணைப்பு நிறுவப்பட்டு, தரவு ஏற்றப்படுகிறது;
  • ஒரு பொருள் மாதிரி, முதன்மை குறியீடுகள் மற்றும் தொடர்புடைய குறியீடுகள் (1:1, 1:பல) கட்டமைக்கப்பட்டுள்ளன;
  • பொருள் பண்புகளில் ஏற்படும் மாற்றங்களுக்காகவும் (INotifyPropertyChanged) மற்றும் சேகரிப்பில் கூறுகளைச் சேர்ப்பதற்காக அல்லது அகற்றுவதற்காகவும் (INotifyCollectionChanged) ஒரு சந்தா உருவாக்கப்பட்டது;
  • சந்தா தூண்டப்படும் போது, ​​மாற்றப்பட்ட பொருள் தரவு சேமிப்பகத்தில் எழுதும் வரிசையில் சேர்க்கப்படும்;
  • சேமிப்பகத்திற்கான மாற்றங்கள் பின்னணி நூலில் அவ்வப்போது (டைமரில்) சேமிக்கப்படும்;
  • நீங்கள் பயன்பாட்டிலிருந்து வெளியேறும்போது, ​​மாற்றங்கள் சேமிப்பகத்திலும் சேமிக்கப்படும்.

குறியீடு உதாரணம்

தேவையான சார்புகளைச் சேர்த்தல்

// Основная библиотека
Install-Package OutCode.EscapeTeams.ObjectRepository
    
// Хранилище данных, в котором будут сохраняться изменения
// Используйте то, которым будете пользоваться.
Install-Package OutCode.EscapeTeams.ObjectRepository.File
Install-Package OutCode.EscapeTeams.ObjectRepository.LiteDb
Install-Package OutCode.EscapeTeams.ObjectRepository.AzureTableStorage
    
// Опционально - если нужно хранить модель данных для Hangfire
// Install-Package OutCode.EscapeTeams.ObjectRepository.Hangfire

சேமிப்பகத்தில் சேமிக்கப்படும் தரவு மாதிரியை நாங்கள் விவரிக்கிறோம்

public class ParentEntity : BaseEntity
{
    public ParentEntity(Guid id) => Id = id;
}
    
public class ChildEntity : BaseEntity
{
    public ChildEntity(Guid id) => Id = id;
    public Guid ParentId { get; set; }
    public string Value { get; set; }
}

பின்னர் பொருள் மாதிரி:

public class ParentModel : ModelBase
{
    public ParentModel(ParentEntity entity)
    {
        Entity = entity;
    }
    
    public ParentModel()
    {
        Entity = new ParentEntity(Guid.NewGuid());
    }
    
    public Guid? NullableId => null;
    
    // Пример связи 1:Many
    public IEnumerable<ChildModel> Children => Multiple<ChildModel>(x => x.ParentId);
    
    protected override BaseEntity Entity { get; }
}
    
public class ChildModel : ModelBase
{
    private ChildEntity _childEntity;
    
    public ChildModel(ChildEntity entity)
    {
        _childEntity = entity;
    }
    
    public ChildModel() 
    {
        _childEntity = new ChildEntity(Guid.NewGuid());
    }
    
    public Guid ParentId
    {
        get => _childEntity.ParentId;
        set => UpdateProperty(() => _childEntity.ParentId, value);
    }
    
    public string Value
    {
        get => _childEntity.Value;
        set => UpdateProperty(() => _childEntity.Value, value
    }
    
    // Доступ с поиском по индексу
    public ParentModel Parent => Single<ParentModel>(ParentId);
    
    protected override BaseEntity Entity => _childEntity;
}

இறுதியாக, தரவை அணுகுவதற்கான களஞ்சிய வகுப்பே:

public class MyObjectRepository : ObjectRepositoryBase
{
    public MyObjectRepository(IStorage storage) : base(storage, NullLogger.Instance)
    {
        IsReadOnly = true; // Для тестов, позволяет не сохранять изменения в базу
    
        AddType((ParentEntity x) => new ParentModel(x));
        AddType((ChildEntity x) => new ChildModel(x));
    
        // Если используется Hangfire и необходимо хранить модель данных для Hangfire в ObjectRepository
        // this.RegisterHangfireScheme(); 
    
        Initialize();
    }
}

ஒரு பொருள் களஞ்சிய நிகழ்வை உருவாக்கவும்:

var memory = new MemoryStream();
var db = new LiteDatabase(memory);
var dbStorage = new LiteDbStorage(db);
    
var repository = new MyObjectRepository(dbStorage);
await repository.WaitForInitialize();

திட்டம் HangFire ஐப் பயன்படுத்தினால்

public void ConfigureServices(IServiceCollection services, ObjectRepository objectRepository)
{
    services.AddHangfire(s => s.UseHangfireStorage(objectRepository));
}

புதிய பொருளைச் செருகுதல்:

var newParent = new ParentModel()
repository.Add(newParent);

இந்த அழைப்பின் மூலம், பொருள் பெற்றோர் மாதிரி உள்ளூர் கேச் மற்றும் தரவுத்தளத்தில் எழுதுவதற்கான வரிசை இரண்டிலும் சேர்க்கப்பட்டது. எனவே, இந்த செயல்பாடு O(1) எடுக்கும், மேலும் இந்த பொருளை உடனடியாக வேலை செய்ய முடியும்.

எடுத்துக்காட்டாக, களஞ்சியத்தில் இந்த பொருளைக் கண்டுபிடித்து, திரும்பிய பொருள் அதே நிகழ்வுதானா என்பதைச் சரிபார்க்க:

var parents = repository.Set<ParentModel>();
var myParent = parents.Find(newParent.Id);
Assert.IsTrue(ReferenceEquals(myParent, newParent));

என்ன நடக்கும்? அமைக்கவும் () திரும்புகிறது அட்டவணை அகராதி, இதில் உள்ளது சமகால அகராதி மற்றும் முதன்மை மற்றும் இரண்டாம் நிலை குறியீடுகளின் கூடுதல் செயல்பாட்டை வழங்குகிறது. இது அனைத்துப் பொருட்களையும் முழுமையாக மறுதொடக்கம் செய்யாமல், ஐடி (அல்லது பிற தன்னிச்சையான பயனர் குறியீடுகள்) மூலம் தேடும் முறைகளைப் பெற உங்களை அனுமதிக்கிறது.

பொருள்களைச் சேர்க்கும் போது பொருள் களஞ்சியம் அவற்றின் பண்புகளை மாற்ற சந்தா சேர்க்கப்படுகிறது, எனவே பண்புகளில் ஏதேனும் மாற்றம் ஏற்பட்டால், இந்த பொருள் எழுதும் வரிசையில் சேர்க்கப்படும். 
வெளியில் இருந்து பண்புகளைப் புதுப்பிப்பது POCO பொருளுடன் பணிபுரிவது போல் தெரிகிறது:

myParent.Children.First().Property = "Updated value";

பின்வரும் வழிகளில் நீங்கள் ஒரு பொருளை நீக்கலாம்:

repository.Remove(myParent);
repository.RemoveRange(otherParents);
repository.Remove<ParentModel>(x => !x.Children.Any());

இது நீக்குதல் வரிசையில் பொருளையும் சேர்க்கிறது.

சேமிப்பு எப்படி வேலை செய்கிறது?

பொருள் களஞ்சியம் கண்காணிக்கப்படும் பொருள்கள் மாறும்போது (பண்புகளைச் சேர்ப்பது அல்லது நீக்குவது அல்லது மாற்றுவது), ஒரு நிகழ்வை எழுப்புகிறது மாதிரி மாற்றப்பட்டதுசந்தா சேமிப்பு. அமலாக்கங்கள் சேமிப்பு ஒரு நிகழ்வு நிகழும்போது மாதிரி மாற்றப்பட்டது மாற்றங்கள் 3 வரிசைகளில் வைக்கப்படுகின்றன - சேர்ப்பதற்கும், புதுப்பிப்பதற்கும் மற்றும் நீக்குவதற்கும்.

மேலும் செயலாக்கங்கள் சேமிப்பு துவக்கத்தில், அவை ஒரு டைமரை உருவாக்குகின்றன, இது ஒவ்வொரு 5 வினாடிகளுக்கும் மாற்றங்களைச் சேமிக்கும். 

கூடுதலாக, சேவ் அழைப்பை கட்டாயப்படுத்த API உள்ளது: ObjectRepository.Save().

ஒவ்வொரு சேமிப்பிற்கும் முன், வரிசைகளில் இருந்து அர்த்தமற்ற செயல்பாடுகள் முதலில் அகற்றப்படும் (உதாரணமாக, நகல் நிகழ்வுகள் - ஒரு பொருள் இரண்டு முறை மாற்றப்பட்டால் அல்லது விரைவாக சேர்க்கப்படும்/அகற்றப்பட்ட பொருள்கள்), பின்னர் மட்டுமே சேமிக்கப்படும். 

எல்லா சந்தர்ப்பங்களிலும், முழு தற்போதைய பொருளும் சேமிக்கப்படுகிறது, எனவே பொருள்கள் மாற்றப்பட்டதை விட வேறுபட்ட வரிசையில் சேமிக்கப்படும், அவை வரிசையில் சேர்க்கப்பட்ட நேரத்தில் இருந்ததை விட புதிய பதிப்புகள் உட்பட.

வேறு என்ன உள்ளது?

  • அனைத்து நூலகங்களும் .NET தரநிலை 2.0ஐ அடிப்படையாகக் கொண்டவை. எந்த நவீன .NET திட்டத்திலும் பயன்படுத்தலாம்.
  • API நூல் பாதுகாப்பானது. அதன் அடிப்படையில் உள் சேகரிப்புகள் செயல்படுத்தப்படுகின்றன சமகால அகராதி, நிகழ்வு நடத்துபவர்களுக்கு பூட்டுகள் உள்ளன அல்லது அவை தேவையில்லை. 
    அழைப்பது மட்டுமே நினைவில் கொள்ளத்தக்கது ObjectRepository.Save();
  • தன்னிச்சையான குறியீடுகள் (தனித்துவம் தேவை):

repository.Set<ChildModel>().AddIndex(x => x.Value);
repository.Set<ChildModel>().Find(x => x.Value, "myValue");

யார் பயன்படுத்துகிறார்கள்?

தனிப்பட்ட முறையில், நான் இந்த அணுகுமுறையை எல்லா பொழுதுபோக்கு திட்டங்களிலும் பயன்படுத்தத் தொடங்கினேன், ஏனெனில் இது வசதியானது மற்றும் தரவு அணுகல் அடுக்கை எழுதுவதற்கு அல்லது கனமான உள்கட்டமைப்பைப் பயன்படுத்துவதற்கு பெரிய செலவுகள் தேவையில்லை. தனிப்பட்ட முறையில், litedb அல்லது ஒரு கோப்பில் தரவைச் சேமிப்பது எனக்குப் போதுமானது. 

ஆனால் கடந்த காலத்தில், இப்போது செயல்படாத ஸ்டார்ட்அப் எஸ்கேப் டீம்ஸ் (நான் இங்கே நினைத்தேன், பணம் - ஆனால் இல்லை, மீண்டும் அனுபவம்) - அஸூர் டேபிள் ஸ்டோரேஜில் தரவைச் சேமிக்கப் பயன்படுகிறது.

எதிர்கால திட்டங்கள்

இந்த அணுகுமுறையின் முக்கிய குறைபாடுகளில் ஒன்றை நான் சரிசெய்ய விரும்புகிறேன் - கிடைமட்ட அளவிடுதல். இதைச் செய்ய, உங்களுக்கு விநியோகிக்கப்பட்ட பரிவர்த்தனைகள் தேவை (sic!), அல்லது வெவ்வேறு நிகழ்வுகளில் இருந்து ஒரே தரவு மாறக்கூடாது என்று விருப்பமான முடிவை எடுக்கவும் அல்லது "கடைசியாக இருப்பவர் சரியானவர்" என்ற கொள்கையின்படி அவற்றை மாற்ற அனுமதிக்கவும்.

தொழில்நுட்பக் கண்ணோட்டத்தில், பின்வரும் திட்டத்தை முடிந்தவரை நான் பார்க்கிறேன்:

  • பொருள் மாதிரிக்கு பதிலாக EventLog மற்றும் Snapshot ஐ சேமிக்கவும்
  • பிற நிகழ்வுகளைக் கண்டறியவும் (அனைத்து நிகழ்வுகளின் இறுதிப்புள்ளிகளையும் அமைப்புகளில் சேர்? udp கண்டுபிடிப்பா? மாஸ்டர்/ஸ்லேவ்?)
  • RAFT போன்ற எந்த ஒருமித்த அல்காரிதம் வழியாக EventLog நிகழ்வுகளுக்கு இடையில் நகலெடுக்கவும்.

என்னைக் கவலையடையச் செய்யும் மற்றொரு பிரச்சனையும் உள்ளது - அடுக்கை நீக்குதல் அல்லது பிற பொருட்களிலிருந்து இணைப்புகளைக் கொண்ட பொருட்களை நீக்கும் நிகழ்வுகளைக் கண்டறிதல். 

மூல குறியீடு

நீங்கள் இங்கே எல்லா வழிகளிலும் படித்திருந்தால், குறியீட்டைப் படிப்பதே எஞ்சியிருக்கும்; அதை GitHub இல் காணலாம்:
https://github.com/DiverOfDark/ObjectRepository

ஆதாரம்: www.habr.com

கருத்தைச் சேர்