Cache Invalidation - มีวิธีแก้ปัญหาทั่วไปหรือไม่?


118

"มีปัญหาหนักเพียงสองประการในวิทยาการคอมพิวเตอร์: การทำให้แคชไม่ถูกต้องและการตั้งชื่อสิ่งต่างๆ"

ฟิลคาร์ลตัน

มีวิธีแก้ปัญหาหรือวิธีการทั่วไปในการทำให้แคชเป็นโมฆะหรือไม่ หากต้องการทราบว่ารายการค้างดังนั้นคุณจึงรับประกันได้ว่าจะได้รับข้อมูลใหม่เสมอ?

ตัวอย่างเช่นพิจารณาฟังก์ชันgetData()ที่รับข้อมูลจากไฟล์ มันจะแคชตามเวลาที่แก้ไขล่าสุดของไฟล์ซึ่งจะตรวจสอบทุกครั้งที่เรียก
จากนั้นคุณเพิ่มฟังก์ชันที่สองtransformData()ซึ่งจะแปลงข้อมูลและเก็บผลลัพธ์ไว้สำหรับครั้งต่อไปที่เรียกใช้ฟังก์ชัน ไม่มีความรู้เกี่ยวกับไฟล์ - คุณจะเพิ่มการอ้างอิงได้อย่างไรว่าหากไฟล์ถูกเปลี่ยนแคชนี้จะไม่ถูกต้อง?

คุณสามารถโทรgetData()ทุกครั้งที่transformData()มีการเรียกและเปรียบเทียบกับค่าที่ใช้ในการสร้างแคช แต่อาจมีค่าใช้จ่ายสูงมาก


6
ฉันเชื่อว่าเขามีส่วนเกี่ยวข้องกับการเขียน X Windows
Greg

1
ฉันคิดว่าชื่อนั้นน่าจะดีกว่าในฐานะ "Cache Invalidation - มีวิธีแก้ปัญหาทั่วไปหรือไม่" เนื่องจากอ้างถึงคลาสเฉพาะของปัญหาการแคช
RBarryYoung

71
ไม่เขาไม่รู้วิทยาศาสตร์คอมพิวเตอร์มากนัก ฉันแน่ใจว่าการมีส่วนร่วมของเขาในการสร้าง OpenGL, X11 และ SSLv3 ทำให้เขายุ่งเกินกว่าที่จะศึกษามันมากนัก :-)
Tim Lesher

80
ปัญหาหนักในวิทยาการคอมพิวเตอร์มีเพียง 2 ปัญหาเท่านั้น: การทำให้แคชไม่ถูกต้อง การตั้งชื่อสิ่งต่างๆ และข้อผิดพลาดแบบ off-by-one
The Dag

8
ฉันเคยได้ยินเรื่องนี้ในชื่อ"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Jonathon Reinhart

คำตอบ:


55

สิ่งที่คุณกำลังพูดถึงคือการผูกมัดการพึ่งพาตลอดชีวิตสิ่งหนึ่งขึ้นอยู่กับสิ่งอื่นซึ่งสามารถแก้ไขได้นอกเหนือจากการควบคุม

หากคุณมีฟังก์ชั่น 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และ:xa

อย่างไรก็ตามมันค่อนข้างเป็นไปได้ที่คุณจะได้รับอินพุตสามตัวที่ความถูกต้องเป็นอิสระทั้งหมด (หรือเป็นวัฏจักร) ดังนั้นจึงไม่สามารถแบ่งเลเยอร์ได้ ซึ่งหมายความว่าบรรทัดที่ทำเครื่องหมาย // สำคัญจะต้องเปลี่ยนเป็น

ถ้า (endCache [a] หมดอายุหรือไม่มีอยู่)


3
หรือบางทีถ้าค่าใช้จ่ายในการตรวจสอบ b สูงคุณใช้ pubsub เพื่อที่เมื่อ b เปลี่ยนแปลงมันจะแจ้ง c รูปแบบผู้สังเกตการณ์เป็นเรื่องปกติ
user1031420

15

ปัญหาในการทำให้แคชไม่ถูกต้องคือสิ่งต่าง ๆ เปลี่ยนแปลงโดยที่เราไม่รู้ ดังนั้นในบางกรณีอาจมีทางแก้ไขได้หากมีสิ่งอื่นที่รู้เกี่ยวกับเรื่องนี้และสามารถแจ้งให้เราทราบได้ ในตัวอย่างที่กำหนดฟังก์ชัน getData สามารถเชื่อมต่อกับระบบไฟล์ซึ่งจะรู้เกี่ยวกับการเปลี่ยนแปลงทั้งหมดในไฟล์ไม่ว่ากระบวนการใดจะเปลี่ยนไฟล์ก็ตามและส่วนประกอบนี้สามารถแจ้งส่วนประกอบที่แปลงข้อมูลได้

ฉันไม่คิดว่าจะมีการแก้ไขเวทมนตร์ทั่วไปที่จะทำให้ปัญหานี้หมดไป แต่ในทางปฏิบัติหลาย ๆ กรณีอาจมีโอกาสที่จะเปลี่ยนแนวทางที่ใช้ฐาน "โพลล์" เป็นแนวทาง "ขัดจังหวะ" ซึ่งสามารถทำให้ปัญหาหมดไปได้


3

หากคุณจะ getData () ทุกครั้งที่คุณทำการแปลงแสดงว่าคุณได้กำจัดประโยชน์ทั้งหมดของแคช

สำหรับตัวอย่างของคุณดูเหมือนว่าวิธีแก้ปัญหาจะเกิดขึ้นเมื่อคุณสร้างข้อมูลที่แปลงแล้วเพื่อจัดเก็บชื่อไฟล์และเวลาที่แก้ไขล่าสุดของไฟล์ที่สร้างข้อมูล (คุณเก็บสิ่งนี้ไว้แล้วในโครงสร้างข้อมูลใดก็ตามที่ getData ส่งคืน ( ) ดังนั้นคุณเพียงแค่คัดลอกบันทึกนั้นลงในโครงสร้างข้อมูลที่ส่งคืนโดย transformData ()) จากนั้นเมื่อคุณเรียก transformData () อีกครั้งให้ตรวจสอบเวลาที่แก้ไขล่าสุดของไฟล์


3

IMHO, Functional Reactive Programming (FRP) เป็นวิธีทั่วไปในการแก้ปัญหาแคชไม่ถูกต้อง

นี่คือเหตุผลที่: ข้อมูลเก่าใน FRP คำศัพท์ที่จะเรียกว่าเป็นความผิดพลาด เป้าหมายอย่างหนึ่งของ FRP คือการรับประกันว่าจะไม่มีข้อบกพร่อง

FRP มีการอธิบายรายละเอียดเพิ่มเติมในการพูดคุย 'Essence of FRP'และในคำตอบ SOนี้นี้

ในการพูดคุยCell s แทนวัตถุแคช / Entity และCellจะมีการรีเฟรชถ้าหนึ่งในนั้นจะมีการรีเฟรชพึ่งพา

FRP ซ่อนรหัสประปาที่เกี่ยวข้องกับกราฟการพึ่งพาและทำให้แน่ใจว่าจะไม่มีกลิ่นอับCells


อีกวิธีหนึ่ง (แตกต่างจาก FRP) ที่ฉันคิดได้คือการรวมค่าที่คำนวณ (ประเภทb) ไว้ในตัวเขียน Monad บางประเภทโดยWriter (Set (uuid)) bที่Set (uuid)(สัญกรณ์ Haskell) มีตัวระบุทั้งหมดของค่าที่ไม่แน่นอนซึ่งค่าที่คำนวณbขึ้นอยู่กับ ดังนั้นuuidเป็นตัวระบุที่ไม่ซ้ำกันบางประเภทที่ระบุค่า / ตัวแปรที่ไม่แน่นอน (เช่นแถวในฐานข้อมูล) ซึ่งbขึ้นอยู่กับการคำนวณ

รวมแนวคิดนี้เข้ากับตัวผสมที่ดำเนินการกับ Monad นักเขียนประเภทนี้และอาจนำไปสู่การแก้ปัญหาการยกเลิกแคชทั่วไปบางประเภทหากคุณใช้ตัวผสมเหล่านี้ในการคำนวณใหม่bเท่านั้น combinators ดังกล่าว (พูดรุ่นพิเศษfilter) ใช้เวลาเขียน monads และ(uuid, a)-s เป็นปัจจัยการผลิตที่aเป็นข้อมูลที่ไม่แน่นอน / uuidตัวแปรระบุ

ดังนั้นทุกครั้งที่คุณเปลี่ยนข้อมูล "ต้นฉบับ" (uuid, a)(เช่นข้อมูลที่ทำให้เป็นมาตรฐานในฐานข้อมูลที่bคำนวณ) ซึ่งค่าที่คำนวณได้bขึ้นอยู่กับประเภทคุณสามารถทำให้แคชที่มีอยู่เป็นโมฆะbหากคุณเปลี่ยนค่าใด ๆaซึ่งbค่าที่คำนวณนั้นขึ้นอยู่กับ เนื่องจากจากSet (uuid)ใน Writer Monad คุณสามารถบอกได้เมื่อสิ่งนี้เกิดขึ้น

ดังนั้นเมื่อใดก็ตามที่คุณกลายพันธุ์ด้วยสิ่งที่กำหนดuuidคุณจะถ่ายทอดการกลายพันธุ์นี้ไปยัง cache-s ทั้งหมดและทำให้ค่าbที่ขึ้นอยู่กับค่าที่ไม่แน่นอนที่ระบุด้วยคำพูดนั้นไม่ถูกต้องuuidเนื่องจาก Writer monad ที่bมีการห่อสามารถบอกได้ว่าbขึ้นอยู่กับที่กล่าวuuidหรือ ไม่.

แน่นอนว่าสิ่งนี้จะคุ้มค่าหากคุณอ่านบ่อยกว่าที่คุณเขียน


แนวทางที่สามในทางปฏิบัติคือการใช้มุมมองที่เป็นรูปธรรมในฐานข้อมูลและใช้เป็นแคช AFAIK พวกเขายังตั้งเป้าหมายที่จะแก้ปัญหาการไม่ถูกต้อง แน่นอนว่านี่เป็นการ จำกัด การดำเนินการที่เชื่อมต่อข้อมูลที่ไม่แน่นอนกับข้อมูลที่ได้รับ


2

ตอนนี้ฉันกำลังหาแนวทางตามPostSharpและฟังก์ชันการบันทึก ฉันเคยเรียกใช้ที่ปรึกษาของฉันและเขายอมรับว่าการแคชเป็นวิธีที่ดีในการเข้าใจเนื้อหา

ทุกฟังก์ชันสามารถถูกทำเครื่องหมายด้วยแอตทริบิวต์ที่ระบุระยะเวลาหมดอายุ แต่ละฟังก์ชันที่ทำเครื่องหมายด้วยวิธีนี้จะถูกบันทึกไว้และผลลัพธ์จะถูกเก็บไว้ในแคชโดยมีแฮชของการเรียกใช้ฟังก์ชันและพารามิเตอร์ที่ใช้เป็นคีย์ ฉันใช้Velocityสำหรับแบ็กเอนด์ซึ่งจัดการการกระจายข้อมูลแคช


1

มีวิธีแก้ปัญหาหรือวิธีการทั่วไปในการสร้างแคชหรือไม่หากต้องการทราบว่ารายการนั้นค้างคุณจึงรับประกันได้ว่าจะได้รับข้อมูลใหม่เสมอ

ไม่เพราะข้อมูลทั้งหมดแตกต่างกัน ข้อมูลบางอย่างอาจ "ไม่อัปเดต" หลังจากผ่านไปหนึ่งนาทีบางข้อมูลหลังจากผ่านไปหนึ่งชั่วโมงและบางข้อมูลอาจไม่ดีสำหรับวันหรือเดือน

เกี่ยวกับตัวอย่างที่เฉพาะเจาะจงของคุณทางออกที่ง่ายที่สุดคือการมีฟังก์ชั่น 'แคชตรวจสอบ' สำหรับไฟล์ที่คุณโทรจากทั้งสองและgetDatatransformData


1

ไม่มีวิธีแก้ปัญหาทั่วไป แต่:

  • คุณแคชสามารถทำหน้าที่เป็นพร็อกซี (ดึง) สมมติว่าแคชของคุณทราบการประทับเวลาของการเปลี่ยนแปลงจุดเริ่มต้นล่าสุดเมื่อมีคนโทรgetData()มาแคชจะถามที่มาของการประทับเวลาของการเปลี่ยนแปลงล่าสุดหากเหมือนกันจะส่งคืนแคชมิฉะนั้นจะอัปเดตเนื้อหาด้วยแหล่งที่มาหนึ่งและส่งคืนเนื้อหา (รูปแบบคือไคลเอนต์ที่จะส่งการประทับเวลาตามคำขอโดยตรงแหล่งที่มาจะส่งคืนเนื้อหาก็ต่อเมื่อการประทับเวลาแตกต่างกัน)

  • คุณยังคงสามารถใช้กระบวนการแจ้งเตือน (พุช) ได้แคชจะสังเกตแหล่งที่มาหากแหล่งที่มาเปลี่ยนไประบบจะส่งการแจ้งเตือนไปยังแคชซึ่งถูกตั้งค่าสถานะว่า "สกปรก" หากมีคนเรียกgetData()แคชจะได้รับการอัปเดตไปยังแหล่งที่มาก่อนให้ลบแฟล็ก "สกปรก" จากนั้นส่งคืนเนื้อหา

ทางเลือกที่พูดโดยทั่วไปขึ้นอยู่กับ:

  • ความถี่: การโทรจำนวนมากgetData()ต้องการการกดเพื่อหลีกเลี่ยงแหล่งที่มาที่จะถูกท่วมโดยฟังก์ชัน getTimestamp
  • การเข้าถึงแหล่งที่มาของคุณ: คุณเป็นเจ้าของโมเดลต้นทางหรือไม่ หากไม่เป็นเช่นนั้นคุณอาจไม่สามารถเพิ่มขั้นตอนการแจ้งเตือนใด ๆ ได้

หมายเหตุ: เนื่องจากการใช้การประทับเวลาเป็นวิธีการทำงานดั้งเดิมของพร็อกซี http วิธีอื่นคือการแชร์แฮชของเนื้อหาที่จัดเก็บไว้ วิธีเดียวที่ฉันรู้สำหรับ 2 เอนทิตีในการอัปเดตร่วมกันคือฉันเรียกคุณว่า (ดึง) หรือคุณเรียกฉันว่า ... (ดัน) นั่นคือทั้งหมด


0

แคชยากเพราะคุณต้องพิจารณา: 1) แคชเป็นหลายโหนดต้องการความเห็นพ้องต้องกันสำหรับพวกเขา 2) เวลาที่ไม่ถูกต้อง 3) สภาพการแข่งขันเมื่อรับ / ตั้งค่าหลายจุดเกิดขึ้น

นี่เป็นการอ่านที่ดี: https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/


-2

บางทีอัลกอริทึมที่ลบเลือนแคชอาจเป็นวิธีที่ทั่วไปที่สุด (หรืออย่างน้อยก็ขึ้นอยู่กับการกำหนดค่าฮาร์ดแวร์น้อยกว่า) เนื่องจากจะใช้แคชที่เร็วที่สุดก่อนและไปจากที่นั่น นี่คือการบรรยายของ MIT เกี่ยวกับเรื่องนี้: Cache Oblivious Algorithms


3
ฉันคิดว่าเขาไม่ได้พูดถึงแคชฮาร์ดแวร์ - เขากำลังพูดถึงโค้ด getData () ของเขาที่มีฟีเจอร์ "แคช" ข้อมูลที่เขาได้รับจากไฟล์ลงในหน่วยความจำ
Alex319
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.