سؤال

"لا يوجد سوى مشكلتان صعبان في علوم الكمبيوتر: إبطال ذاكرة التخزين المؤقت وتسمية الأشياء."

فيل كارلتون

هل هناك حل عام أو طريقة لإبطال ذاكرة التخزين المؤقت؛ لمعرفة متى يكون الإدخال قديم، إذا مضمكون دائما للحصول على بيانات جديدة؟

على سبيل المثال، النظر في وظيفة getData() التي تحصل على البيانات من ملف. تخزنها بناء على آخر وقت تعديل للملف، والذي يتحقق في كل مرة يطلق عليه.
ثم تضيف وظيفة ثانية transformData() الذي يقوم بتحويل البيانات، ويخزن مخزارة ناتجته في المرة القادمة التي يتم فيها استدعاء الوظيفة. لا يوجد لديه معرفة بالملف - كيف تضيف التبعية أنه إذا تم تغيير الملف، يصبح هذا التخزين المؤقت غير صالح؟

يمكنك الاتصال getData() كل مرة transformData() يسمى وقارنه بالقيمة التي تم استخدامها لبناء ذاكرة التخزين المؤقت، ولكن هذا قد ينتهي بك الأمر مكلفا للغاية.

هل كانت مفيدة؟

المحلول

ما تتحدث عنه هو تخفيف التبعية مدى الحياة، أن الشيء الوحيد يعتمد على آخر يمكن تعديله خارج نطاق سيطرته.

إذا كان لديك وظيفة idempotent من a, b ل c أين، إذا a و b هي نفسها c هو نفسه ولكن تكلفة التدقيق b هو عال ثم أنت إما:

  1. تقبل أنك تعمل في وقت ما مع عدم وجود معلومات خارجية ولا تحقق دائما b
  2. هل مستواك أفضل لجعل التدقيق b بأسرع ما يمكن

لا يمكنك الحصول على كعكة وأكلها ...

إذا كنت تستطيع طبقة ذاكرة التخزين المؤقت إضافية بناء على a فوق القمة، وهذا يؤثر على المشكلة الأولية وليس قليلا. إذا اخترت 1، فكن لديك أي حرية أعطيتها لنفسك ويمكنك بالتالي ذاكرة التخزين المؤقت أكثر ولكن يجب أن تتذكر النظر في صحة القيمة المخزنة مؤقتا b. وبعد إذا اخترت 2 يجب أن تظل تحقق b في كل مرة ولكن يمكن أن تعود إلى ذاكرة التخزين المؤقت ل a إذا b نفذت الشيكات.

إذا كنت تخبئ طبقة، يجب عليك التفكير في ما إذا كنت قد انتهكت "قواعد" النظام نتيجة السلوك المشترك.

إذا كنت تعرف ذلك a دائما لديه صلاحية إذا b فاخذ بعد ذلك يمكنك ترتيب ذاكرة التخزين المؤقت الخاصة بك مثل (pseudocode):

private map<b,map<a,c>> cache // 
private func realFunction    // (a,b) -> c

get(a, b) 
{
    c result;
    map<a,c> endCache;
    if (cache[b] expired or not present)
    {
        remove all b -> * entries in cache;   
        endCache = new map<a,c>();      
        add to cache b -> endCache;
    }
    else
    {
        endCache = cache[b];     
    }
    if (endCache[a] not present)     // important line
    {
        result = realFunction(a,b); 
        endCache[a] = result;
    }
    else   
    {
        result = endCache[a];
    }
    return result;
}

بوضوح طبقات متتالية (قل x) تافهة طالما، في كل مرحلة صحة المدخلات المضافة حديثا تطابق a:b العلاقة ل x:b و x:a.

ومع ذلك، فمن الممكن تماما أن تحصل على ثلاث مدخلات كانت صلاحيتها مستقلة تماما (أو كانت دورية)، لذلك لا تكون هناك طبقات ممكنة. هذا يعني أن الخط المميز // من المهم أن يتغير إلى

إذا (EndCache [A منتهية الصلاحية أو غير موجود)

نصائح أخرى

المشكلة في إبطال ذاكرة التخزين المؤقت هي أن الاشياء تتغير دون أن تعرفنا عن ذلك. لذلك، في بعض الحالات، يكون الحل ممكن إذا كان هناك شيء آخر يعرف عنه ويمكن أن يخطرنا. في المثال المحدد، يمكن أن توصل وظيفة GetData إلى نظام الملفات، والذي يعرف عن جميع التغييرات على الملفات، بغض النظر عن العملية التي يغير الملف، وقد يقوم هذا المكون بدوره بإعلان المكون الذي يحول البيانات.

لا أعتقد أن هناك أي إصلاح سحري عام لجعل المشكلة تختفي. ولكن في العديد من الحالات العملية، قد تكون هناك فرصا جيدة لتحويل نهج "الاقتراع" في "مقاطعة" في "المقاطعة"، والتي يمكن أن تجعل المشكلة تبعد ببساطة.

إذا كنت ذاهبا إلى GetData () في كل مرة تقوم فيها بالتحويل، فقد قمت بإزالة الفائدة بأكملها من ذاكرة التخزين المؤقت بالكامل.

بالنسبة إلى مثالك، يبدو أن الحل سيكون عند إنشاء البيانات المحولة، وكذلك تخزين اسم الملف وآخر معدل تعديل للملف تم إنشاء البيانات من (قمت بالفعل بتخزينها هذا في أي بنية البيانات التي تم إرجاعها بواسطة GetData ( )، لذلك يمكنك فقط نسخ الرقم القياسي في بنية البيانات التي تم إرجاعها بواسطة TransformData ()) ثم عند استدعاء RussuctionData () مرة أخرى، تحقق من آخر وقت تعديل للملف.

IMHO، البرمجة الفعلية التفاعلية (FRP) هي بمعنى طريقة عامة لحل إبطال ذاكرة التخزين المؤقت.

هنا هو السبب: البيانات التي لا معنى لها في مصطلحات FRP تسمى خلل. وبعد أحد أهداف FRP هو ضمان عدم وجود مواطن الخلل.

شرح FRP بمزيد من التفاصيل في هذا "جوهر الحديث" وفي هذا حتى الإجابة.

في ال حديث ال Cellتمثل كائن / كيان مخزنا مؤقتا Cell يتم تحديثه إذا تم تحديث أحد التبعية.

FRP يخفي رمز السباكة المرتبطة الرسم البياني التبعي وتأكد من عدم وجود لا معنى له Cellس.


طريقة أخرى (مختلفة عن FRP) التي يمكنني التفكير فيها هي لف القيمة المحسوبة (من النوع b) في نوع من الكاتب مناد Writer (Set (uuid)) b أين Set (uuid) (تدوين Haskell) يحتوي على جميع معرفات القيم القابلة للتغيير القيمة التي يتمتع بها القيمة المحسوبة b يعتمد على. وبالتالي، uuid هو نوع من المعرف الفريد الذي يحدد قيمة / متغير قابلية القابلة للتغيير (قل صف في قاعدة بيانات) عليه حساب b يعتمد على.

الجمع بين هذه الفكرة مع مجذاف تعمل على هذا النوع من الكاتب مند، وهذا قد يؤدي إلى نوع من حل إبطال ذاكرة التخزين المؤقت العامة إذا كنت تستخدم هذه المحارك فقط لحساب جديد b. وبعد مثل هذه المحاركين (قل نسخة خاصة من filter) خذ الكاتب منادس و (uuid, a)-في المدخلات، حيث a هي بيانات قابلة للتغيير / متغير، والتي تم تحديدها uuid.

لذلك في كل مرة تقوم فيها بتغيير البيانات "الأصلية" (uuid, a) (قل البيانات الطبيعية في قاعدة بيانات منها b تم احتسابها) والتي القيمة المحسوبة من النوع b يعتمد ذلك، يمكنك إبطال ذاكرة التخزين المؤقت التي تحتوي b إذا كنت تحور أي قيمة a على أي حساب b القيمة تعتمد، لأنه بناء على Set (uuid) في الكاتب موناد يمكنك معرفة متى يحدث هذا.

لذلك في أي وقت تحور شيئا مع معين uuid, ، يمكنك بث هذه الطفرة لجميع مخزئة التخزين المؤقت وتبطل القيم b التي تعتمد على القيمة القابلة للتغيير المحددة مع قال uuid لأن الكاتب موناد الذي b هو ملفوفة يمكن أن أقول إذا كان ذلك b يعتمد على قال uuid أم لا.

بالطبع، هذا يدفع فقط إذا قرأت في كثير من الأحيان أكثر مما تكتب.


النهج الثالث، العملي، هو استخدام طريقة العرض المخصصة في قواعد البيانات واستخدامها كذاكرة التخزين المؤقت- ES. afaik يهدفون أيضا إلى حل مشكلة إبطال. هذا بالطبع يحد من العمليات التي توصل البيانات القابلة للتغيير إلى البيانات المشتقة.

أنا أعمل على نهج الآن بناء على postsharp. و memoizing وظائف. وبعد لقد قمت بتشغيلها مع مرشد بلدي، ويوافق على أنه من التنفيذ الجيد للتخزين المؤقت بطريقة مدرعة في المحتوى.

يمكن وضع علامة كل وظيفة بسمة تحدد فترة انتهاء صلاحيته. يتم تذزم كل وظيفة تم وضع علامة بهذه الطريقة وتتخزين النتيجة في ذاكرة التخزين المؤقت، مع وجود علامة استدعاء الوظيفة والمعلمات المستخدمة كمفتاح. أنا استخدم السرعة الاتجاهية للخلفية، التي تتعامل مع توزيع بيانات ذاكرة التخزين المؤقت.

هل هناك حل عام أو طريقة لإنشاء ذاكرة التخزين المؤقت، لمعرفة متى يكون الدخول قديم، لذلك تكون مضمونا دائما في الحصول على بيانات جديدة؟

لا، لأن جميع البيانات مختلفة. قد تكون بعض البيانات "قديمة" بعد دقيقة واحدة، وبعضها بعد ساعة، وقد يكون البعض على ما يرام لعدة أيام أو أشهر.

فيما يتعلق بمثالتك المحددة، فإن أبسط الحل هو الحصول على وظيفة وظيفة "فحص ذاكرة التخزين المؤقت" للملفات، والتي تتصل بها من كليهما getData و transformData.

لا يوجد حل عام ولكن:

  • يمكن أن تعمل ذاكرة التخزين المؤقت كوكيل (سحب). افترض ذاكرة التخزين المؤقت الخاصة بك يعرف الطابع الزمني الأخير لتغيير المنشأ، عندما اتصل شخص ما getData(), ، تطلب ذاكرة التخزين المؤقت الأصلية لقطاع الطابع الزمني للتغيير الأخير، إذا كان الأمر نفسه، فهو بإرجاع ذاكرة التخزين المؤقت، وإلا فإنه يقوم بتحديث محتواها بمصدر واحد وإرجاع محتواها. (الاختلاف هو العميل لإرسال الطابع الزمني مباشرة على الطلب مباشرة، سيعود المصدر المحتوى فقط إذا كان الطابع الزمني مختلفا.)

  • لا يزال بإمكانك استخدام عملية إعلام (دفع)، مراقبة ذاكرة التخزين المؤقت المصدر، إذا تغير المصدر، فإنه يرسل إشعارا إلى ذاكرة التخزين المؤقت التي يتم وضع علامة عليها بعد ذلك باسم "القذرة". إذا استدعي شخص ما getData() سيتم تحديث ذاكرة التخزين المؤقت لأول مرة إلى المصدر، وإزالة العلم "القذر"؛ ثم إرجاع محتواها.

يعتمد الاختيار بشكل عام على:

  • التردد: العديد من المكالمات getData() يفضل الدفع لتجنب خروج المصدر من خلال وظيفة gettimestamp
  • وصولك إلى المصدر: هل تملك نموذج المصدر؟ إذا لم يكن كذلك، فمن المحتمل أن تضيف أي عملية إعلام.

ملاحظة: نظرا لأن استخدام الطابع الزمني هو الطريقة التقليدية وكلاء HTTP يعملون، فإن اتباع نهج آخر يتقاسم تجزئة المحتوى المخزنة. الطريقة الوحيدة التي أعرفها عن كيانتين للحصول على تحديث معا هي إما أن أتصل بك (سحب) أو تتصل بي ... (دفع) هذا كل شيء.

ذاكرة التخزين المؤقت صعبة لأنك تحتاج إلى النظر في: 1) ذاكرة التخزين المؤقت هي عقد متعددة، والحاجة توافق في الآراء بالنسبة لهم 2) وقت إبطال 3) حالة السباق عندما يحدث الحصول على / مجموعة multple

هذه قراءة جيدة:https://www.confluent.io/blog/turning-tatabase-inside-out-with-apache-samza/

ربما تكون خوارزميات ذاكرة التخزين المؤقت غافل هي الأكثر عمومية (أو على الأقل، أقل تعتمد على تكوين الأجهزة)، لأنها ستستخدم أسرع ذاكرة التخزين المؤقت أولا والمضي قدما من هناك. إليك محاضرة ماساتشوسافية حولها: ذاكرة التخزين المؤقت غارق

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top