Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
När du skapar ett program på Azure Database for PostgreSQL flexibel server är tillägg av ett cachelagringslager ett av de mest effektiva sätten att förbättra svarstiderna, minska belastningen på databasen och öka motståndskraften. Genom att hantera ofta lästa data från en minnesintern cache skickar ditt program färre frågor till PostgreSQL. Det innebär lägre CPU- och IOPS-förbrukning, så att du kan köra på en mindre beräkningsnivå, skala läsningar utan att skala upp servern och absorbera trafiktoppar. En cache kan också öka motståndskraften. Om PostgreSQL har ett kort avbrott kan begäranden som träffar data som redan finns i cacheminnet fortsätta att lyckas, så lässökvägar förblir tillgängliga medan databasen återställs.
Den här artikeln hjälper dig att avgöra när cachelagring hjälper och vilket mönster som passar ditt program. Sedan implementeras fyra cachningsmönster i Python (cache-aside, förhämtning av referensdata, write-through och event-driven) med hjälp av Azure Managed Redis, biblioteken redis och psycopg samt Microsoft Entra ID-autentisering.
När du ska lägga till en cache
PostgreSQL cachelagrar redan datasidor som används ofta i buffertcachen och drar också nytta av operativsystemets filcachen. Dessa cacheminnen gör upprepad åtkomst snabbare, men de delar det minne som allokerats till databasservern med frågekörning och andra processer. Innehållet måste också värmas upp igen efter vissa underhålls- och redundansåtgärder.
Du kan få mer kapacitet för databascache genom att skala till ett beräkningsalternativ med mer minne. Du kan också justera PostgreSQL-minnesinställningarna, men att allokera mer minne till buffertcachen lämnar mindre för frågekörning och operativsystem. Testa ändringar i minnesanvändningen noggrant för att undvika situationer med otillräckligt minne.
Azure Managed Redis kompletterar dessa interna cacheminnen. Den lagrar valda programdata och frågeresultat utanför databasservern, ger åtkomst med kortare svarstid för tidskänsliga lässökvägar och kan hålla cachelagrade läsningar tillgängliga medan PostgreSQL återställer eller värmer cacheminnet. Använd det när dessa fördelar motiverar den extra programlogiken och ytterligare en tjänst att drifta. Den ersätter inte PostgreSQL som sanningskälla.
Att lägga till en extern cache som Azure Managed Redis hjälper mest när din arbetsbelastning har följande egenskaper:
- Läsintensiva åtkomstmönster. Samma rader läss mycket oftare än de ändras, till exempel produktkataloger, användarprofiler, konfigurationsdata eller referenstabeller.
- Dyra eller upprepade frågor. Sammansättningar, kopplingar eller beräknade resultat som är kostsamma att producera men stabila över korta fönster.
- Svarstidskänsliga slutpunkter. Användarnära åtgärder där en läsning i minnet (under en millisekund) är att föredra framför en tur och retur till databasen.
- Förutsägbara toppar. Säsongs- eller händelsedriven trafik där cachelagring absorberar belastning som annars skulle tvinga dig att skala beräkning.
- Underhålls- och redundanskänslighet. Läs sökvägar som behöver stadiga svarstider medan en PostgreSQL-instans återställer eller värmer cacheminnet efter ett underhåll eller en redundansåtgärd.
Cachelagring hjälper mindre för skrivintensiva arbetsbelastningar, data som alltid måste vara transaktionsmässigt konsekventa eller frågor som redan är snabba och sällan upprepas.
Mönster för cachelagring
I den här artikeln används en detaljhandelsbutik som ett återkommande exempel. Olika delar av appen drar nytta av olika cachelagringsmönster. Följande avsnitt implementerar de första fyra mönstren i Python. Artikeln beskriver sessions- och tillstånds-avlastningsmönster och cachelagringsmönster för flera regioner, men tillhandahåller inte kodimplementeringar för dem.
| Pattern | Så här fungerar det | I butiksbutiken |
|---|---|---|
| Cache-aside (lat laddning) | Programmet kontrollerar cachen först. Vid en miss läses den från PostgreSQL och fyller sedan i cacheminnet. | Produktkatalog och informationssidor, där några populära objekt driver de flesta läsningar. |
| Förhämtning av referensdata | Stabila data läses in i cachen i förväg och uppdateras när källan ändras, i stället för vid en miss. | Kategorier, varumärken och leveranskonfiguration. |
| Skriv-igenom | Programmet skriver till cachen och PostgreSQL i samma åtgärd, vilket gör dem konsekventa. | Pris- och lageruppdateringar som måste vara synliga omedelbart. |
| Händelsedriven ogiltighet | Cacheposter uppdateras eller invalideras som svar på dataändringshändelser i stället för utifrån en timer. | Orderstatus när en order går genom orderhanteringen. |
| Sessions- och tillståndsavlastning | Tillfälligt tillstånd finns i cacheminnet i stället för databasen. | Kundvagnar och användarsessioner. |
| Cachelagring i flera regioner | En cache i varje region hanterar lokala läsningar som hålls synkroniserade med aktiv geo-replikering. | Ett globalt skyltfönster som betjänar kunder i flera regioner. |
Förutsättningar
- Ett Azure-konto med en aktiv prenumeration. Skapa ett konto kostnadsfritt.
- En Azure Database for PostgreSQL flexibel serverinstans med Microsoft Entra autentisering aktiverat. Information om hur du skapar en finns i Skapa en Azure Database for PostgreSQL flexibel server.
- En Azure Managed Redis-instans. Information om hur du skapar en finns i Skapa en Azure Hanterad Redis-instans. Skapa den i samma region som PostgreSQL-servern (och för produktion samma virtuella nätverk) för att minimera svarstiden.
- Dataåtkomst för din utvecklare eller programidentitet för båda tjänsterna: en Microsoft Entra-administratör eller en roll på PostgreSQL-servern, och en tilldelning av en Redis-åtkomstprincip för cachen. Se Microsoft Entra autentisering för Azure Database for PostgreSQL och Använd Microsoft Entra ID för autentisering med Azure Managed Redis.
- Python 3.10 eller senare.
- Azure CLI. Information om hur du installerar den finns i Installera Azure CLI.
Tip
Den fullständiga, distribuerade versionen av det här exemplet, inklusive infrastruktur som kod och alla fyra mönstren, finns i lagringsplatsen amr-caching-pattern-samples på GitHub.
Steg 1: Installera klientbiblioteken
Installera Redis- och PostgreSQL-klientbiblioteken tillsammans med Azure Identity-biblioteket för Microsoft Entra autentisering.
pip install "redis>=5.0,<6.0" "psycopg[binary]>=3.1,<4.0" "azure-identity>=1.17,<2.0"
Steg 2: Anslut med Microsoft Entra ID
Använd Microsoft Entra ID autentisering i stället för åtkomstnycklar. Microsoft Entra ID tar bort behovet av att lagra hemligheter i ditt program och gör att du kan hantera åtkomst centralt.
Följande kod skapar en Redis-klient och en PostgreSQL-anslutning, båda autentiserade med en hanterad identitet eller utvecklarautentiseringsuppgifter via DefaultAzureCredential. I exemplet används den klustermedvetna RedisCluster klienten, som matchar OSS-klustringsprincipen som används i det här exemplet. Om cacheminnet använder enterprise-klustringsprincipen använder du standardklienten redis.Redis i stället.
import os
import json
import redis
from redis.cluster import RedisCluster
import psycopg
from azure.identity import DefaultAzureCredential
REDIS_HOST = os.environ["REDIS_HOST"] # for example, mycache.eastus.redis.azure.net
REDIS_PORT = 10000
PG_HOST = os.environ["PG_HOST"] # for example, myserver.postgres.database.azure.com
PG_DATABASE = os.environ["PG_DATABASE"]
credential = DefaultAzureCredential()
# Acquire a token for Azure Managed Redis and use it as the password.
redis_token = credential.get_token("https://redis.azure.com/.default")
# The username is the object ID of the Microsoft Entra identity.
redis_client = RedisCluster(
host=REDIS_HOST,
port=REDIS_PORT,
ssl=True,
ssl_check_hostname=False, # cluster nodes are reached by IP; the certificate chain is still validated
username=os.environ["REDIS_USER_OBJECT_ID"],
password=redis_token.token,
decode_responses=True,
)
# Acquire a token for Azure Database for PostgreSQL and use it as the password.
pg_token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default")
pg_conn = psycopg.connect(
host=PG_HOST,
dbname=PG_DATABASE,
user=os.environ["PG_USER"],
password=pg_token.token,
sslmode="require",
)
Note
Microsoft Entra åtkomsttoken upphör att gälla, vanligtvis efter ungefär en timme. För program som körs länge uppdaterar du token innan den löper ut och återansluter för både Azure Managed Redis och Azure Database for PostgreSQL, eller använder en hjälpfunktion som transparent hämtar token på nytt. Mer information finns i Använda Microsoft Entra ID för autentisering med Azure Managed Redis.
Steg 3: Cache-aside
Cache-aside är det vanligaste mönstret och används för produktläsningar i det här exemplet. Programmet kontrollerar Redis först och vid en miss frågar det PostgreSQL och fyller cacheminnet med en TTL (time-to-live). Några få populära produkter står för de flesta läsningarna, så träfffrekvensen är hög.
Eftersom de flesta läsningar hanteras från minnet tar cache-aside ihållande läsbelastning från PostgreSQL. Den här minskningen innebär färre anslutningar, mindre buffertcacheomsättning och lägre CPU och IOPS. Du kan absorbera lästoppar utan att skala upp servern eller lägga till läsrepliker. Du kör frågor mot PostgreSQL endast vid en miss (första åtkomsten eller efter att TTL upphör att gälla). Se Hitta vad du ska cachelagra för att identifiera de frågor som är värda att cachelagra.
CACHE_TTL_SECONDS = 300 # 5 minutes
def get_product(product_id: int) -> dict | None:
cache_key = f"product:{product_id}"
# 1. Try the cache first.
cached = redis_client.get(cache_key)
if cached is not None:
return json.loads(cached)
# 2. On a miss, read from PostgreSQL.
with pg_conn.cursor() as cursor:
cursor.execute("SELECT id, name, price FROM products WHERE id = %s", (product_id,))
row = cursor.fetchone()
if row is None:
return None
product = {"id": row[0], "name": row[1], "price": float(row[2])}
# 3. Populate the cache with a TTL, then return.
redis_client.set(cache_key, json.dumps(product), ex=CACHE_TTL_SECONDS)
return product
Steg 4: Förhämtning av referensdata
Data som läses hela tiden men sällan ändras (till exempel kategorier, varumärken eller fraktkonfiguration) behöver inte vänta på en cachemiss. Läs in den i cacheminnet och uppdatera den när källan ändras. I ett PostgreSQL-schema är dessa vanligtvis de små uppslags- och dimensionstabellerna som kopplas till många frågor. Om du hanterar dem från minnet tar du bort en stor mängd upprepade kopplingar och sökningar från databasen. Till skillnad från cache-aside finns det ingen miss per begäran och inget TTL-lopp. Den uppdateras vid ändringar, så alla läsningar är alltid aktuella.
def prefetch_categories() -> None:
with pg_conn.cursor() as cursor:
cursor.execute("SELECT id, name FROM categories ORDER BY name")
categories = [{"id": r[0], "name": r[1]} for r in cursor.fetchall()]
redis_client.set("ref:categories", json.dumps(categories)) # no TTL; refreshed on change
def get_categories() -> list[dict]:
cached = redis_client.get("ref:categories")
return json.loads(cached) if cached else []
Steg 5: Genomskrivning
När en ändring måste visas omedelbart skriver du PostgreSQL och cacheminnet i samma åtgärd i stället för att vänta på att en TTL ska upphöra att gälla eller ogiltigförklara nyckeln. Det här mönstret används till exempel för att uppdatera prisinformationen i exemplet. PostgreSQL förblir sanningskällan. Ändringen skrivs där först, sedan uppdateras cachen, så en läsning efter skrivningen returnerar det nya värdet.
När du skriver till två system som inte delar en transaktion kan databasincheckningen lyckas medan cacheuppdateringen misslyckas och det inte finns någon enkel korrigering. Följande kodfragment visar normalfallet och utelämnar felhantering. I produktion avgör du hur du vill hantera en misslyckad uppdatering, till exempel genom att försöka igen om felet verkar vara tillfälligt, eller genom att ogiltigförklara nyckeln så att nästa läsning laddar om den från PostgreSQL. Hur som helst har PostgreSQL rätt värde, så en inaktuell eller saknad cachepost kan alltid återställas. När cacheuppdateringen måste slå igenom på ett tillförlitligt sätt bör den i stället styras av databasens ändringsström (se händelsestyrd invalidering).
def update_price(product_id: int, new_price: float) -> None:
# 1. Write to PostgreSQL, the source of truth.
with pg_conn.cursor() as cursor:
cursor.execute("UPDATE products SET price = %s WHERE id = %s", (new_price, product_id))
pg_conn.commit()
# 2. Refresh the cached entry so reads see the new price right away.
with pg_conn.cursor() as cursor:
cursor.execute("SELECT id, name, price FROM products WHERE id = %s", (product_id,))
row = cursor.fetchone()
if row is not None:
product = {"id": row[0], "name": row[1], "price": float(row[2])}
redis_client.set(f"product:{product_id}", json.dumps(product), ex=CACHE_TTL_SECONDS)
Steg 6: Händelsedriven ogiltighet
Händelsedriven ogiltighet håller cacheminnet konsekvent med databasen genom att reagera på dataändringshändelser. Den uppdaterar eller ogiltigförklarar poster när de ändras, i stället för att låta dem upphöra att gälla efter en viss tid. Författare lägger till händelser i en varaktig Redis-ström, en logg med endast tillägg och en eller flera användare läser dessa händelser och uppdaterar cacheminnet. Eftersom strömmen finns kvar finns händelserna kvar efter att en konsument startats om. En konsumentgrupp levererar varje händelse till en enskild arbetare, spårar bekräftelser så att ingenting går förlorat eller bearbetas två gånger och gör att du kan skala bearbetningen mellan arbetare.
Använd det här mönstret när ett cachelagrat värde härleds från data som ändras någon annanstans (en status, en projektion eller en aggregering), där en TTL antingen skulle tjäna inaktuella data eller tvinga fram konstant omberäkning. I webbshoppen styr detta mönster orderstatusen genom fullgörandeprocessen. När du gör en beställning skrivs ordern till PostgreSQL, cachelagras dess ursprungliga status och lägger till en placed händelse i dataströmmen:
ORDER_STREAM = "orders:events"
def place_order(product_id: int, quantity: int) -> int:
with pg_conn.cursor() as cursor:
cursor.execute(
"INSERT INTO orders (product_id, quantity, status) VALUES (%s, %s, 'placed') RETURNING id",
(product_id, quantity),
)
order_id = cursor.fetchone()[0]
pg_conn.commit()
redis_client.set(f"order:{order_id}:status", "placed", ex=86400)
redis_client.xadd(ORDER_STREAM, {"order_id": order_id, "status": "placed"}, maxlen=10000, approximate=True)
return order_id
En fulfillment-worker kör en konsumentgrupp: den läser nya händelser, för varje order vidare i PostgreSQL, uppdaterar den cachade order:{id}:status-projektionen och kvitterar händelsen. Ordersidan läser den projektionen, så statuskontrollerna förblir snabba och rör aldrig databasen. Värdet förblir korrekt eftersom händelserna håller det aktuellt.
GROUP = "fulfillment"
def process_orders() -> None:
try:
redis_client.xgroup_create(ORDER_STREAM, GROUP, id="0", mkstream=True)
except redis.exceptions.ResponseError:
pass # group already exists
while True:
events = redis_client.xreadgroup(GROUP, "worker-1", {ORDER_STREAM: ">"}, count=10, block=5000)
for _stream, entries in events or []:
for event_id, fields in entries:
order_id = int(fields["order_id"])
with pg_conn.cursor() as cursor:
cursor.execute("UPDATE orders SET status = 'shipped' WHERE id = %s", (order_id,))
pg_conn.commit()
redis_client.set(f"order:{order_id}:status", "shipped", ex=86400)
redis_client.xack(ORDER_STREAM, GROUP, event_id)
Händelsekällan i det här exemplet är programmet som skriver PostgreSQL och lägger till händelsen i samma sökväg. PostgreSQL kan också generera ändringar: LISTEN/NOTIFY för lätta meddelanden eller logisk avkodning (insamling av ändringsdata) för en varaktig ändringsström på radnivå. Att köra cachen från PostgreSQL:s egen ändringsström innebär att den reagerar på varje bekräftad ändring, även skrivningar som kringgår programmet.
Metodtips
Följ dessa metoder för att hålla cachen korrekt, effektiv och kostnadseffektiv.
- Ange en TTL där inaktuellhet är en risk. En TTL begränsar hur inaktuell något kan bli om invalideringen misslyckas. Anpassa TTL-värdet efter den maximala inaktualitet som applikationen kan acceptera. Använd en lång TTL eller ingen TTL endast för referensdata när det finns tillförlitliga ogiltighets- och uppdateringsprocesser.
- Använd ett konsekvent namngivningsschema för nycklar. Namnområdesnycklar efter tjänst, entitet och identifierare, till exempel
product:42elleruser:1001:profile. Lägg till en version när nyckelformatet eller värdeschemat kan ändras. - Cacha på rätt detaljnivå. Lagra enskilda entiteter eller små resultatuppsättningar i cache när de återanvänds ofta och är lätta att invalidera. Cacha inte data som återanvänds i liten utsträckning. Överdriven cachelagring slösar minne och kan minska träfffrekvensen.
- Hantera cachemissar och avbrott på ett smidigt sätt. Behandla cachen som en optimering, inte en sanningskälla. Om Redis inte är tillgängligt använder du en begränsad återställning till PostgreSQL. Lägg till tidsgränser, kretsbrytare, backoff- och begärandegränser för att skydda PostgreSQL. Om PostgreSQL har ett kort avbrott kan du fortsätta att hantera läsningar för data som redan finns i cacheminnet medan skrivningar väntar på att databasen ska återställas.
- Förhindra cache-rusningar. När en populär nyckel upphör att gälla kan många begäranden nå databasen samtidigt. Använd TTL-jitter, samordning av förfrågningar, stale-while-revalidate eller ett kort distribuerat lås för att låta en förfrågan återfylla posten.
- Rätt storlek på cachen. Övervaka träffkvot, minnesanvändning, elimineringsfrekvens, utgångsfrekvens, svarstid, heta nycklar och nyckelkardinalitet. En låg träfffrekvens kan tyda på en cache som är för liten, överdriven borttagning, dåligt val av nyckel eller ett dåligt åtkomstmönster. Information om dimensionering finns i vägledningen för val av Azure Managed Redis-nivå.
- Välj en borttagningsprincip som passar dina nycklar. För en cachedatabas börjar du med allkeys-lru eller allkeys-lfu. Använd endast en volatile-*-princip när samma databas innehåller cachenycklar som upphör att gälla och skyddade nycklar som inte upphör att gälla. En volatile-princip kan sluta avvisa om inga nycklar har något TTL-värde. Separera cachedata från skyddade data när det är möjligt.
- Serialisera effektivt. JSON är läsbar och portabel. För sökvägar med högt dataflöde testar du ett kompakt binärt format för att minska minnes- och nätverkskostnaderna. Mät minnesanvändning, CPU, svarstid, schemaändringar och hur felsökningen påverkas innan du ändrar formatet.
Hitta vad som ska cachelagrats
De mest effektiva cachemålen är de frågor som ditt program oftast kör mot de data som ändras minst. I stället för att gissa vilka frågor som passar den här beskrivningen jämför du de historiska och aktuella vyerna i frågetelemetrin som Azure Database for PostgreSQL kan samla in när du aktiverar relevanta funktioner:
- Query Store lagrar frågekörningsstatistik för historisk analys. Använd antal anrop samt total och genomsnittlig exekveringstid för att hitta databasfrågor som konsekvent dominerar databasbelastningen över längre perioder. Se Övervaka prestanda med Query Store.
- Query Performance Insight visualiserar Query Store data i Azure portalen, så att du kan upptäcka frekventa och resursintensiva frågor och jämföra deras beteende över tid. Se Query Performance Insight.
-
pg_stat_statementsvisar kumulativ statistik per sats i databasen, vilket ger en direkt bild av det aktuella observationsfönstret. Dess statistik kan återställas, så använd Query Store när du behöver behålla historiken över observationsfönster.
Använd båda vyerna innan du väljer en cachekandidat. En kortsiktig topp kanske inte representerar arbetsbelastningens normala beteende, medan ett historiskt genomsnitt kan dölja en aktuell regression. Prioritera frågor som är frekventa, dyra och stabila, vilket innebär högt antal anrop, hög total körningstid och resultat som inte ändras för varje begäran. Dessa frågor ger den högsta träffhastigheten för cacheminnet och den största minskningen av databasbelastningen. En fråga som körs konstant men returnerar samma rader i minuter, till exempel en produktlista, ett kategoriträd eller en pristabell, är en idealisk kandidat.