ObjectRepository - ඔබගේ නිවසේ ව්‍යාපෘති සඳහා .NET මතක ගබඩා රටාව

සියලුම දත්ත මතකයේ ගබඩා කරන්නේ ඇයි?

වෙබ් අඩවිය හෝ පසුපෙළ දත්ත ගබඩා කිරීම සඳහා, බොහෝ සිහිබුද්ධිය ඇති පුද්ගලයින්ගේ පළමු ආශාව වනුයේ SQL දත්ත සමුදායක් තෝරා ගැනීමයි. 

නමුත් සමහර විට සිතුවිල්ල මතකයට එන්නේ දත්ත ආකෘතිය SQL සඳහා සුදුසු නොවන බවයි: උදාහරණයක් ලෙස, සෙවුමක් හෝ සමාජ ප්‍රස්ථාරයක් ගොඩනඟන විට, ඔබ වස්තූන් අතර සංකීර්ණ සම්බන්ධතා සෙවිය යුතුය. 

නරකම තත්වය වන්නේ ඔබ කණ්ඩායමක වැඩ කරන විට සහ සගයෙකු ඉක්මන් විමසුම් ගොඩනඟන්නේ කෙසේදැයි නොදන්නා විටය. ප්‍රධාන පිටුවේ ඇති SELECT සාධාරණ කාලයකින් සම්පූර්ණ වන පරිදි N+1 ගැටලු විසඳීමට සහ අමතර දර්ශක ගොඩනැගීමට ඔබ කොපමණ කාලයක් ගත කළාද?

තවත් ජනප්‍රිය ප්‍රවේශයක් වන්නේ NoSQL ය. මීට වසර කිහිපයකට පෙර මෙම මාතෘකාව වටා විශාල ප්‍රබෝධයක් ඇති විය - ඕනෑම පහසු අවස්ථාවක් සඳහා ඔවුන් MongoDB යෙදවූ අතර json ලේඛන ආකාරයෙන් පිළිතුරු ගැන සතුටු විය. (මාර්ගය වන විට, ලේඛනවල චක්‍රලේඛ සබැඳි නිසා ඔබට කිහිලිකරු කීයක් ඇතුළු කිරීමට සිදු වූවාද?).

වෙනත් විකල්ප ක්‍රමයක් උත්සාහ කිරීමට මම යෝජනා කරමි - සියලුම දත්ත යෙදුම් මතකයේ ගබඩා කිරීමට උත්සාහ නොකරන්න, වරින් වර අහඹු ගබඩාවට (ගොනුව, දුරස්ථ දත්ත ගබඩාව) සුරැකීමට උත්සාහ නොකරන්නේ මන්ද? 

මතකය ලාභදායී වී ඇති අතර බොහෝ කුඩා හා මධ්‍යම ප්‍රමාණයේ ව්‍යාපෘති සඳහා හැකි ඕනෑම දත්තයක් 1 GB මතකයට ගැලපේ. (උදාහරණයක් ලෙස, මගේ ප්රියතම නිවාස ව්යාපෘතිය වේ මූල්ය ට්රැකර්, වසර එකහමාරක් සඳහා මගේ වියදම්, ශේෂයන් සහ ගනුදෙනු පිළිබඳ දෛනික සංඛ්‍යාලේඛන සහ ඉතිහාසය තබා ගන්නා, මතකය 45 MB පමණක් පරිභෝජනය කරයි.)

වාසි:

  • දත්ත වෙත ප්‍රවේශය පහසු වේ - ඔබ විමසුම්, කම්මැලි පැටවීම, ORM විශේෂාංග ගැන කරදර විය යුතු නැත, ඔබ සාමාන්‍ය C# වස්තු සමඟ වැඩ කරයි;
  • විවිධ නූල්වලින් ප්රවේශ වීම සම්බන්ධ ගැටළු නොමැත;
  • ඉතා වේගවත් - ජාල ඉල්ලීම් නැත, කේත විමසුම් භාෂාවකට පරිවර්තනය කිරීමක් නැත, වස්තු (නි) අනුක්‍රමිකකරණයක් අවශ්‍ය නොවේ;
  • ඕනෑම ආකාරයක දත්ත ගබඩා කිරීම පිළිගත හැකිය - එය තැටියේ XML හෝ SQL සේවාදායකයේ හෝ Azure Table Storage හි වේවා.

එය:

  • තිරස් පරිමාණය නැති වී ඇති අතර, එහි ප්රතිඵලයක් වශයෙන්, ශුන්ය අක්රීයව යෙදවීම සිදු කළ නොහැක;
  • යෙදුම බිඳ වැටුණහොත්, ඔබට අර්ධ වශයෙන් දත්ත අහිමි විය හැකිය. (නමුත් අපගේ යෙදුම කිසි විටෙකත් බිඳ වැටෙන්නේ නැත, හරිද?)

එය ක්රියාත්මක වන්නේ කෙසේද?

ඇල්ගොරිතම පහත පරිදි වේ:

  • ආරම්භයේදී, දත්ත ගබඩාව සමඟ සම්බන්ධතාවයක් ස්ථාපිත කර ඇති අතර, දත්ත පටවනු ලැබේ;
  • වස්තු ආකෘතියක්, ප්‍රාථමික දර්ශක සහ සම්බන්ධතා දර්ශක (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();
    }
}

ObjectRepository උදාහරණයක් සාදන්න:

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));

එතකොට මොකද වෙන්නේ? සකසන්න () ආපසු පැමිණේ වගු ශබ්දකෝෂය, අඩංගු වේ සමගාමී ශබ්දකෝෂය සහ ප්රාථමික සහ ද්විතියික දර්ශකවල අතිරේක ක්රියාකාරිත්වය සපයයි. මෙමගින් ඔබට සියලු වස්තු මත සම්පූර්ණයෙන්ම පුනරුච්චාරණය නොකර Id (හෝ වෙනත් අත්තනෝමතික පරිශීලක දර්ශක) මගින් සෙවීමේ ක්‍රම ලබා ගැනීමට ඉඩ සලසයි.

වස්තු එකතු කරන විට ObjectRepository ඒවායේ ගුණාංග වෙනස් කිරීම සඳහා දායකත්වයක් එකතු කරනු ලැබේ, එබැවින් ගුණාංගවල යම් වෙනසක් මෙම වස්තුව ලිවීමේ පෝලිමට එකතු වේ. 
පිටතින් ගුණාංග යාවත්කාලීන කිරීම POCO වස්තුවක් සමඟ වැඩ කිරීම හා සමානයි:

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

ඔබට පහත ආකාරවලින් වස්තුවක් මකා දැමිය හැක:

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

මෙය ද වස්තුව මකාදැමීමේ පෝලිමට එක් කරයි.

ඉතිරිකිරීම ක්‍රියා කරන්නේ කෙසේද?

ObjectRepository නිරීක්‍ෂණය කරන ලද වස්තූන් වෙනස් වන විට (ගුණාංග එකතු කිරීම හෝ මකා දැමීම හෝ වෙනස් කිරීම), සිදුවීමක් මතු කරයි ආකෘතිය වෙනස් කරන ලදීදායක විය ගබඩා කිරීම. ක්‍රියාත්මක කිරීම් ගබඩා කිරීම සිදුවීමක් සිදු වූ විට ආකෘතිය වෙනස් කරන ලදී වෙනස්කම් පෝලිම් 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 හෝ ගොනුවක දත්ත ගබඩා කිරීම මට සාමාන්‍යයෙන් ප්‍රමාණවත් වේ. 

නමුත් අතීතයේදී, දැන් අක්‍රිය ආරම්භක EscapeTeams (මම හිතුවා මෙන්න මේක සල්ලි - නමුත් නැහැ, නැවතත් අත්දැකීම්) - Azure Table Storage හි දත්ත ගබඩා කිරීමට භාවිතා කරයි.

අනාගතය සඳහා සැලසුම්

මෙම ප්රවේශයේ ප්රධාන අවාසි වලින් එකක් නිවැරදි කිරීමට මම කැමතියි - තිරස් පරිමාණය. මෙය සිදු කිරීම සඳහා, ඔබට බෙදා හරින ලද ගනුදෙනු (sic!) අවශ්‍ය වේ, නැතහොත් විවිධ අවස්ථාවන්හි එකම දත්ත වෙනස් නොවිය යුතු බවට කැමැත්තෙන් තීරණයක් ගන්න, නැතහොත් “අන්තිම තැනැත්තා නිවැරදි” මූලධර්මය අනුව වෙනස් වීමට ඉඩ දෙන්න.

තාක්ෂණික දෘෂ්ටි කෝණයකින්, මම පහත යෝජනා ක්රමය හැකි ලෙස දකිමි:

  • වස්තු ආකෘතිය වෙනුවට EventLog සහ Snapshot ගබඩා කරන්න
  • වෙනත් අවස්ථා සොයන්න (සැකසීම් වලට සියලුම අවස්ථා වල අවසාන ලක්ෂ්‍ය එක් කරන්න? udp Discovery? master/slave?)
  • RAFT වැනි ඕනෑම සම්මුති ඇල්ගොරිතමයක් හරහා EventLog අවස්ථා අතර ප්‍රතිවර්තනය කරන්න.

මා කනස්සල්ලට පත් කරන තවත් ගැටළුවක් ද තිබේ - කඳුරැල්ල මකා දැමීම හෝ වෙනත් වස්තූන්ගෙන් සබැඳි ඇති වස්තූන් මකා දැමීමේ අවස්ථා හඳුනා ගැනීම. 

ප්‍රභව කේතය

ඔබ මෙහි දක්වාම කියවා ඇත්නම්, ඉතිරිව ඇත්තේ කේතය කියවීම පමණි; එය GitHub හි සොයාගත හැකිය:
https://github.com/DiverOfDark/ObjectRepository

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න