Недійсність кешу - чи є загальне рішення?


118

"У комп'ютерних науках є лише дві важкі проблеми: недійсність кешу та іменування речей."

Філ Карлтон

Чи є загальне рішення або метод недійсного кешу; щоб знати, коли запис несвіжий, тож вам гарантовано завжди отримувати нові дані?

Наприклад, розглянемо функцію, getData()яка отримує дані з файлу. Він кешує його на основі останнього зміненого часу файлу, який він перевіряє кожного разу, коли він викликається.
Потім ви додаєте другу функцію, transformData()яка перетворює дані, і зберігає її результат в наступний раз, коли функція буде викликана. Він не знає про файл - як ви додасте залежність від того, що якщо файл буде змінено, цей кеш стає недійсним?

Ви можете викликати getData()кожен раз, коли transformData()викликається, і порівнювати його зі значенням, яке використовувалося для створення кешу, але це може закінчитися дуже дорого.


6
Я вважаю, що він щось стосується написання X Windows
Грег,

1
Я думаю, що цей заголовок був би кращим як "Недійсна кеш - чи є загальне рішення?" оскільки це стосується конкретного класу проблеми кешування.
RBarryYoung

71
Ні, він не знав багато інформатики. Я впевнений, що його участь у створенні OpenGL, X11 та SSLv3 зробила його занадто зайнятим, щоб справді багато чого вивчити. :-)
Тім Лешер

80
В інформатиці є лише дві важкі проблеми: відключення кешу. Назви речі. І поодинці помилки.
Даг

8
Я колись чув це як"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Джонатан Райнхарт

Відповіді:


55

Те, про що ви говорите, - це ланцюгова залежність від життя, що одна річ залежить від іншої, яку можна змінити поза її контролем.

Якщо у вас є функція ідемпотентна від a, bдо cякої, якщо aі bті ж , то cодне і те ж , але вартість перевірки bвисока , то ви або:

  1. прийміть, що колись ви працюєте із застарілою інформацією, і не завжди перевіряйте b
  2. зробіть свій рівень найкращим чином, щоб перевірка була bякомога швидшою

Ви не можете мати свій торт і їсти його ...

Якщо ви можете нанести додатковий кеш, заснований на aвершині, це вплине на початкову проблему не на один біт. Якщо ви вибрали 1, то у вас є будь-яка свобода, яку ви дали собі, і, таким чином, можете кешувати більше, але ви повинні пам’ятати, щоб врахувати справедливість кешованого значення b. Якщо ви вибрали 2, ви все одно повинні перевіряти bкожен раз, але ви можете потрапити назад у кеш, aякщо він bперевіряється.

Якщо ви кешуєте шари, ви повинні врахувати, чи порушили ви "правила" системи внаслідок поєднаної поведінки.

Якщо ви знаєте, що aзавжди має дійсність, якщо bтак, то ви можете організувати кеш так (псевдокод):

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] закінчився або немає)


3
або, можливо, якщо вартість перевірки b висока, ви використовуєте pubsub, щоб при зміні b вона сповіщала c. Звичайна модель спостерігача.
користувач1031420

15

Проблема в інвалідізації кешу полягає в тому, що матеріал змінюється, не знаючи про нас. Тож у деяких випадках рішення можливе, якщо є якась інша річ, яка насправді знає про це і може сповістити нас. У наведеному прикладі функція getData може підключитися до файлової системи, яка знає про всі зміни у файлах, незалежно від того, який процес змінює файл, і цей компонент, в свою чергу, може повідомити компонент, який перетворює дані.

Я не думаю, що існує якесь загальне магічне виправлення, щоб усунути проблему. Але в багатьох практичних випадках може бути можливість перетворити підхід, заснований на опитуванні, в "переривання", який може змусити проблему просто усунутись.


3

Якщо ви збираєтеся отримати getData () щоразу, коли будете робити перетворення, ви усунули всю користь кешу.

Для вашого прикладу, схоже, було б рішення, коли ви генеруєте трансформовані дані, також зберігати ім'я файлу та останній змінений час файлу, з якого дані генерувались (ви вже зберігали це в будь-якій структурі даних, яку повертає getData ( ), тож ви просто скопіюєте цей запис у структуру даних, повернуту transformData ()), а потім, коли ви знову зателефонуєте transformData (), перевірте останній змінений час файлу.


3

IMHO, Функціональне реактивне програмування (FRP) є в певному сенсі загальним способом вирішення недійсного кешу.

Ось чому: несвіжі дані в термінології FRP називаються глюком . Одна з цілей FRP - гарантувати відсутність збоїв.

FRP пояснюється більш докладно в цій розмові «Суть FRP» та в цій відповіді «SO» .

У розмові , що Cells є кешированниє Object / Entity і Cellоновлюється , якщо один з його залежності оновлюється.

FRP приховує код сантехніки, пов'язаний з графіком залежності, і переконує, що немає несвіжих Cells.


Іншим способом (відмінним від FRP), про який я можу придумати, є введення обчисленої величини (типу b) в якусь письменницю Монаду, Writer (Set (uuid)) bде Set (uuid)(позначення Haskell) містяться всі ідентифікатори змінних значень, від яких bзалежить обчислене значення . Отже, uuidце якийсь унікальний ідентифікатор, який ідентифікує змінне значення / змінну (скажімо, рядок у базі даних), від якої bзалежить обчислене .

Поєднайте цю ідею з комбінаторами, які працюють над цим автором Monad, і це може призвести до якогось загального рішення про відключення кешу, якщо ви використовуєте лише ці комбінатори для обчислення нового b. Такі комбінатори (скажімо, спеціальну версію filter) приймають монарди та (uuid, a)-s Writer як вхідні дані, де aє змінні дані / змінна, ідентифікована uuid.

Отже, кожного разу, коли ви змінюєте "вихідні" дані (uuid, a)(скажімо, нормалізовані дані в базі даних, з якої bбуло обчислено), від якого bзалежить обчислене значення типу, ви можете визнати недійсним кеш, який міститься, bякщо змінити будь-яке значення, aвід якого bзалежить обчислене значення. , тому що, виходячи з Set (uuid)монади Writer Monad, ви можете сказати, коли це станеться.

Отже, щоразу, коли ви щось мутуєте із заданим uuid, ви транслюєте цю мутацію в усі кеш-пам'ятки, і вони призначають недійсні значення, bякі залежать від значення, що змінюється, ідентифікованого з сказаним, uuidоскільки монада Writer, в яку bвкладено, може сказати, чи bзалежить це від сказаного uuidчи ні.

Звичайно, це окупається, лише якщо ти читаєш набагато частіше, ніж пишеш.


Третій, практичний підхід - використовувати матеріалізовані представлення даних у базах даних та використовувати їх як кеші. AFAIK вони також прагнуть вирішити проблему недійсності. Це, звичайно, обмежує операції, які з'єднують мутаційні дані з отриманими даними.


2

Зараз я працюю над підходом на основі функцій PostSharp та запам'ятовування . Я пропустив його повз мого наставника, і він погоджується, що це хороша реалізація кешування змістово-агностичним способом.

Кожна функція може бути позначена атрибутом, який вказує термін її дії. Кожна функція, позначена таким чином, запам'ятовується, а результат зберігається в кеш-пам'яті з хешем виклику функції та параметрами, що використовуються як ключ. Я використовую швидкість для бекенда, який обробляє розподіл даних кешу.


1

Чи є загальне рішення чи метод створення кешу, щоб знати, коли запис несвіжий, тому ви гарантовано завжди отримуєте свіжі дані?

Ні, тому що всі дані різні. Деякі дані можуть бути "несвіжими" через хвилину, інші - через годину, а деякі - штрафом протягом днів або місяців.

Що стосується конкретного прикладу, найпростішим рішенням є функція "перевірка кешу" для файлів, на які ви дзвоните з обох getDataта transformData.


1

Не існує загального рішення, але:

  • Кеш може діяти як проксі (тягнути). Припустимо, ваш кеш знає часову позначку зміни останнього походження, коли хтось дзвонить getData(), кеш запитує джерело для часової позначки останньої зміни, якщо такий же, він повертає кеш, інакше він оновлює його вміст вихідним і повертає його вміст. (Варіант - клієнт безпосередньо надсилає часову позначку за запитом; джерело повертає вміст лише у випадку, якщо його часова мітка відрізняється.)

  • Ви все ще можете використовувати процес сповіщення (push), кеш спостерігає за джерелом, якщо джерело змінюється, він надсилає повідомлення в кеш, який потім позначений як "брудний". Якщо хтось викликає getData()кеш, спочатку оновиться джерело, видаліть "брудний" прапор; потім повернути його вміст.

Вибір, загалом кажучи, залежить від:

  • Частота: багато дзвінків getData()бажають натиснути, щоб уникнути заливання джерела функцією getTimestamp
  • Ваш доступ до джерела: Ви володієте моделлю джерела? Якщо ні, швидше за все, ви не можете додати жодного процесу повідомлення.

Примітка. Оскільки використання часової позначки є традиційним способом роботи http-проксі, іншим підходом є обмін хешем вмісту, що зберігається. Єдиний спосіб, коли я знаю, щоб дві особи оновлювалися разом - я тобі дзвоню (потягну) або ти зателефонуєш мені… (натиснути), це все.



-2

Можливо, алгоритми, що не враховують кеш, були б найзагальнішими (Або принаймні, менш залежними від конфігурації апаратних засобів), оскільки вони спочатку будуть використовувати найшвидший кеш і рухатимуться звідти. Ось ця лекція MIT про це: « Кешові очевидні алгоритми»


3
Я думаю, що він не говорить про апаратні кеші - він говорить про свій код getData (), який має функцію, яка "кешує" дані, які він отримав з файлу, в пам'ять.
Alex319
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.