PostgreSQL için Azure Veri Tabanı esnek sunucu ile önbellek kullanarak performansı geliştirme

Esnek PostgreSQL için Azure Veri Tabanı sunucuda bir uygulama oluşturduğunuzda, önbelleğe alma katmanı eklemek yanıt sürelerini iyileştirmenin, veritabanınızdaki yükü azaltmanın ve dayanıklılığı artırmanın en etkili yollarından biridir. Uygulamanız, sık okunan verileri bellek içi önbellekten sunarak PostgreSQL'e daha az sorgu gönderir. Bu, daha düşük CPU ve IOPS tüketimi anlamına gelir; böylece daha küçük bir işlem katmanında çalışabilir, sunucuyu ölçeklendirmeden okumaları ölçeklendirebilir ve trafik artışlarını emebilirsiniz. Önbellek dayanıklılık da ekleyebilir. PostgreSQL kısa bir kesintiye neden olursa, önbellekte bulunan verilere isabet eden istekler başarılı olabilir, bu nedenle veritabanı kurtarılırken okuma yolları kullanılabilir durumda kalır.

Bu makale, önbelleğe almanın ne zaman yardımcı olduğunu ve uygulamanıza hangi desenin uyduğunu belirlemenize yardımcı olur. Ardından Python'da, Azure Managed Redis, psycopg ve redis kitaplıkları ile Microsoft Entra ID kimlik doğrulamasını kullanarak dört önbelleğe alma desenini (önbellek-yanı, başvuru verilerini önceden getirme, doğrudan yazma ve olay odaklı) uygular.

Önbellek ne zaman eklenir?

PostgreSQL, sık erişilen veri sayfalarını zaten arabellek önbelleğinde önbelleğe alır ve işletim sisteminin dosya önbelleğinden de yararlanır. Bu önbellekler yinelenen erişimi hızlandırır, ancak veritabanı sunucusuna ayrılan belleği sorgu yürütme ve diğer işlemler ile paylaşır. Ayrıca bazı bakım ve yük devretme işlemlerinden sonra içeriklerinin yeniden ısınması gerekir.

Daha fazla bellek içeren bir işlem seçeneğine ölçeklendirerek daha fazla veritabanı önbellek kapasitesi elde edebilirsiniz. PostgreSQL bellek ayarlarını da ayarlayabilirsiniz, ancak arabellek önbelleğine daha fazla bellek ayırdığınızda sorgu yürütme ve işletim sistemi için daha az bellek kalır. Bellek yetersizliği durumlarını önlemek için bellek değişikliklerini dikkatle test edin.

Azure Yönetilen Redis bu yerel önbellekleri tamamlar. Seçilen uygulama verilerini ve sorgu sonuçlarını veritabanı sunucusunun dışında depolar, zamana duyarlı okuma yolları için daha düşük gecikme süreli erişim sağlar ve PostgreSQL önbelleğini kurtarırken veya ısıtırken önbelleğe alınmış okumaları kullanılabilir durumda tutabilir. Bu avantajlar, ek uygulama mantığını ve işletilecek ek bir hizmeti haklı çıkarıyorsa bunu kullanın. PostgreSQL'in yerini gerçeğin kaynağı olarak değiştirmez.

Azure Managed Redis gibi bir dış önbellek eklemek, iş yükünüz şu özelliklere sahip olduğunda en çok yardımcı olur:

  • Okuma ağırlıklı erişim örüntüleri. Aynı satırlar ürün katalogları, kullanıcı profilleri, yapılandırma verileri veya başvuru tabloları gibi değiştiklerinden çok daha sık okunur.
  • Pahalı veya yinelenen sorgular. Toplamalar, birleştirmeler veya hesaplanan sonuçlar, kısa zaman pencerelerinde üretilmesi maliyetli ancak kararlıdır.
  • Gecikme süresine duyarlı uç noktalar. Veritabanına gidiş dönüş yerine bellek içi okumanın (milisaniyenin altında) tercih edildiği kullanıcıya dönük işlemler.
  • Öngörülebilir ani artışlar. Önbelleğe alma işleminin yükü emdiği mevsimsel veya olay odaklı trafik, aksi takdirde işlemi ölçeklendirmeye zorlar.
  • Bakım ve yük devretme hassasiyeti. PostgreSQL örneği bir bakım veya yük devretme işleminden sonra önbelleğini kurtarır veya ısıtırken sabit yanıt sürelerine ihtiyaç duyan yolları okuyun.

Önbelleğe alma, yoğun yazma gerektiren iş yükleri, her zaman işlem açısından tutarlı olması gereken veriler veya zaten hızlı ve nadiren tekrarlanan sorgular için daha az yardımcı olur.

Önbelleğe alma kalıpları

Bu makalede, örnek vaka olarak bir perakende mağazası kullanılmıştır. Uygulamanın farklı bölümleri farklı önbelleğe alma desenlerinden yararlanıyor. Aşağıdaki bölümlerde, Python'daki ilk dört desen uygulanır. Makalede oturum ve durum boşaltma ve çok bölgeli önbelleğe alma desenleri açıklanır, ancak bunlar için kod uygulamaları sağlanmaz.

Pattern Nasıl çalışır? Perakende vitrininde
Önbellek dışında (tembel yükleme) Uygulama önce önbelleği denetler. Bir isabet olmadığında PostgreSQL’den okur, ardından önbelleği doldurur. Birkaç popüler öğenin en çok okunduğu ürün kataloğu ve ayrıntı sayfaları.
Referans verilerini önceden getirme Kararlı veriler ön taraftaki önbelleğe yüklenir ve kaynak değiştiğinde yanıtsız yerine yenilenir. Kategoriler, markalar ve sevkiyat yapılandırması.
Yazma işlemi Uygulama aynı işlemde önbelleğe ve PostgreSQL'e yazıp tutarlı kalmasını sağlar. Hemen görünür olması gereken fiyat ve envanter güncelleştirmeleri.
Olay temelli geçersizleştirme Önbellek girdileri, zamanlayıcı yerine veri değişikliği olaylarına yanıt olarak güncelleştirilir veya geçersiz kılınır. Sipariş karşılama sürecinde ilerledikçe sipariş durumu.
Oturum ve durum yük aktarımı Geçici durum, veritabanı yerine önbellekte yer alır. Alışveriş sepetleri ve kullanıcı oturumları.
Çok bölgeli önbelleğe alma Her bölgedeki önbellek, etkin coğrafi çoğaltma ile senkronize tutularak yerel okuma isteklerini karşılar. Birden fazla bölgedeki alışverişçilere hizmet veren küresel bir mağaza.

Prerequisites

Tip

Kod olarak altyapı ve dört desen de dahil olmak üzere bu örneğin tam, dağıtılabilir sürümü için GitHub üzerindeki amr-caching-pattern-samples deposuna bakın.

1. Adım: İstemci kitaplıklarını yükleme

Redis ve PostgreSQL istemci kitaplıklarının yanı sıra Microsoft Entra kimlik doğrulaması için Azure Kimlik kitaplığını yükleyin.

pip install "redis>=5.0,<6.0" "psycopg[binary]>=3.1,<4.0" "azure-identity>=1.17,<2.0"

2. Adım: Microsoft Entra ID ile bağlanma

Erişim anahtarları yerine Microsoft Entra ID kimlik doğrulaması kullanın. Microsoft Entra ID uygulamanızda gizli dizileri depolama gereksinimini ortadan kaldırır ve erişimi merkezi olarak yönetmenize olanak tanır.

Aşağıdaki kod, DefaultAzureCredential aracılığıyla yönetilen kimlik veya geliştirici kimlik bilgisiyle kimliği doğrulanmış bir Redis istemcisi ve bir PostgreSQL bağlantısı oluşturur. Örnek, bu örneğin kullandığı OSS kümeleme ilkesiyle eşleşen küme farkındalıklı RedisCluster istemcisini kullanır. Önbelleğiniz Kurumsal kümeleme ilkesini kullanıyorsa, bunun yerine standart redis.Redis istemciyi kullanın.

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 erişim belirteçlerinin süresi genellikle yaklaşık bir saat sonra dolar. Uzun süre çalışan uygulamalar için, süresi dolmadan önce belirteci yenileyin ve hem Yönetilen Redis hem de Azure PostgreSQL için Azure Veri Tabanı için yeniden bağlanın veya belirteçleri saydam olarak yeniden alan bir yardımcı kullanın. Ayrıntılar için bkz. Azure Managed Redis ile kimlik doğrulaması için Microsoft Entra ID kullanma.

3. Adım: Cache-aside

Cache-aside en yaygın örüntüdür ve bu örnekte ürün okuma işlemleri için kullanılır. Uygulama önce Redis'i denetler ve yanıtsız olduğunda PostgreSQL'i sorgular ve önbelleği yaşam süresi (TTL) ile doldurur. Birkaç popüler ürün, okunmaların çoğunu oluşturur; bu nedenle erişim oranı yüksektir.

Çoğu okuma işlemi bellekten sunulduğundan, önbelleğe alma işlemi postgreSQL'den sürekli okuma yükünü alır. Bu azaltma, daha az bağlantı, daha az arabellek önbelleği değişim sıklığı ve daha düşük CPU ve IOPS anlamına gelir. Sunucuyu ölçeklendirmeden veya okuma replikaları eklemeden okuma ani artışlarını karşılayabilirsiniz. PostgreSQL’i yalnızca miss durumunda (ilk erişimde veya TTL süresi dolduktan sonra) sorgularsınız. Önbelleğe almaya değer sorguları belirlemek için Önbelleğe alınacakları bulma bölümüne bakın.

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

4. Adım: Referans verilerini önceden getirme

Sürekli okunan ancak nadiren değişen sabit verilerin (örneğin kategoriler, markalar veya gönderim yapılandırması) önbellek ıskasını beklemek zorunda değildir. Ön önbelleğe yükleyin ve kaynak değiştiğinde yenileyin. PostgreSQL şemasında bunlar genellikle birçok sorguya katılan küçük arama ve boyut tablolarıdır. Bunları bellekten sunmak, veritabanındaki çok sayıda yinelenen birleştirme ve arama işlemini ortadan kaldırır. Cache-aside yaklaşımından farklı olarak, istek başına önbellek ıskası ve TTL yarışı yoktur. Değişiklikte yenilersiniz, bu nedenle okumalar her zaman sıcaktır.

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 []

5. Adım: Yazma

Bir değişikliğin hemen görünür olması gerektiğinde, TTL'nin süresinin dolmasına veya anahtarın geçersiz kılınmasına beklemek yerine PostgreSQL ve önbelleği aynı işleme yazın. Örneğin, bu desen örnekteki fiyatlandırma bilgilerini güncelleştirmek için kullanılır. PostgreSQL, gerçeğin kaynağı olarak kalır. Değişiklik önce orada işler, ardından önbellek yenilenir, bu nedenle yazmadan sonra yapılan bir okuma yeni değeri döndürür.

Aynı işlemi paylaşmayan iki sisteme yazarken, veritabanı işleminin onaylanması başarılı olabilirken önbelleğin yenilenmesi başarısız olabilir ve bunun basit bir çözümü yoktur. Aşağıdaki kod parçacığı başarılı senaryoyu gösterir ve hata durumlarını ele almaz. Üretimde, başarısız bir yenilemeyi nasıl ele alacağınıza siz karar verirsiniz; örneğin, hata geçici görünüyorsa yenilemeyi yeniden denemek veya anahtarı geçersiz kılarak bir sonraki okumada verinin PostgreSQL'den yeniden yüklenmesini sağlamak. Her iki durumda da PostgreSQL doğru değeri tutar, bu nedenle eski veya eksik önbellek girdisi her zaman kurtarılabilir. Önbellek güncellemesinin güvenilir biçimde gerçekleşmesi gerekiyorsa, bunun yerine önbellek güncellemesini veritabanının değişiklik akışından tetikleyin (bkz. olay güdümlü geçersiz kılma).

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)

6. Adım: Olay temelli geçersizleştirme

Olay temelli geçersizleştirme, veri değişikliği olaylarına tepki vererek önbelleğin veritabanıyla tutarlı olmasını sağlar. Girdileri, bir zamanlayıcıya göre sürelerini doldurmak yerine, değiştikçe günceller veya geçersiz kılar. Yazarlar olayları dayanıklı bir Redis akışına, yalnızca ekleme günlüğüne ekler ve bir veya daha fazla tüketici bu olayları okur ve önbelleği güncelleştirir. Akış devam ettiğinden, olaylar tüketicinin yeniden başlatılmasından sonra da devam eder. Bir tüketici grubu, her olayı tek bir çalışana teslim eder, hiçbir şeyin kaybolmamasını veya iki kez işlenmemesini sağlamak için alındı onaylarını izler ve işlemeyi çalışanlar arasında ölçeklendirmenizi sağlar.

Önbelleğe alınmış bir değer, başka bir yerde değişen verilerden (bir durum, projeksiyon veya agrega) türetildiğinde ve TTL ya bayat veri sunacak ya da sürekli yeniden hesaplamayı zorlayacaksa bu deseni kullanın. Mağaza vitrininde bu model, sipariş durumunu sipariş karşılama süreci boyunca yönlendirir. Sipariş vermek, siparişi PostgreSQL'e yazar, ilk durumunu önbelleğe alır ve akışa bir placed olay ekler:

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

Bir karşılama çalışanı bir tüketici grubu çalıştırır: yeni olayları okur, PostgreSQL'deki her siparişi ilerletir, önbelleğe alınmış order:{id}:status projeksiyonu yeniler ve olayı onaylar. Sipariş sayfası bu projeksiyonu okur, bu nedenle durum denetimleri hızlı kalır ve veritabanına hiçbir zaman dokunmaz. Olaylar onu güncel tuttuğundan değer doğru kalır.

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)

Bu örnekteki olay kaynağı, PostgreSQL yazan ve olayı aynı yola ekleyen uygulamadır. PostgreSQL ayrıca değişiklikleri kendisi de yayar: LISTEN/NOTIFY basit bildirimler için veya dayanıklı, satır düzeyinde bir değişiklik akışı için mantıksal kod çözme (veri yakalamayı değiştirme). Önbelleği PostgreSQL’in kendi değişiklik akışı üzerinden beslemek, uygulamayı atlayarak yapılan yazma işlemleri de dahil olmak üzere taahhüt edilen her değişikliğe tepki vermesi anlamına gelir.

En iyi uygulamalar

Önbelleğinizi doğru, verimli ve uygun maliyetli tutmak için bu uygulamaları izleyin.

  • Eskime riski olan her yerde bir TTL ayarlayın. Geçersizleştirme başarısız olursa TTL, verinin ne kadar süre güncelliğini yitirmiş kalacağını sınırlar. TTL'yi uygulamanın kabul edebildiği maksimum eskime durumuyla eşleştirin. Yalnızca güvenilir geçersizleştirme ve yenileme işlemleri mevcutsa, referans verileri için uzun bir TTL kullanın veya hiç TTL kullanmayın.
  • Tutarlı bir anahtar adlandırma düzeni kullanın. Veya gibi product:42user:1001:profilehizmete, varlığa ve tanımlayıcıya göre ad alanı anahtarları. Anahtar biçimi veya değer şeması değişebildiğinde bir sürüm ekleyin.
  • Doğru ayrıntı düzeyini önbelleğe alın. Tek tek varlıkları veya küçük sonuç kümelerini sık sık yeniden kullanıldığında ve kolayca geçersiz kılındığında önbelleğe alın. Çok az yeniden kullanımı olan verileri önbelleğe alma. Aşırı önbelleğe alma, belleği boşa harcar ve isabet hızını azaltabilir.
  • Önbellek eksiklerini ve kesintilerini düzgün bir şekilde işleyebilir. Önbelleği tek doğru kaynak olarak değil, bir optimizasyon olarak değerlendirin. Redis kullanılamıyorsa, PostgreSQL'e sınırlanmış bir geri dönüş kullanın. PostgreSQL'i korumak için zaman aşımları, devre kesiciler, geri çekilme ve istek sınırları ekleyin. PostgreSQL’de kısa süreli bir kesinti yaşanırsa, yazma işlemleri veritabanı kurtulana kadar beklerken önbellekteki veriler için okuma hizmeti vermeye devam edebilirsiniz.
  • Önbellek damgalamalarını önleyin. Popüler bir anahtarın süresi dolduğunda, birçok istek aynı anda veritabanına isabet edebilir. Bir isteğin girişi yeniden doldurmasını sağlamak için TTL dalgalanması, istek birleştirme, stale-while-revalidate veya kısa süreli bir dağıtılmış kilit kullanın.
  • Önbelleği doğru boyutlandırın. İsabet oranını, bellek kullanımını, tahliye oranını, süresi dolma oranını, gecikme süresini, yoğun kullanılan anahtarları ve anahtar kardinalitesini izleyin. Düşük bir isabet oranı, çok küçük bir önbelleğe, aşırı tahliyelere, uygunsuz anahtar seçimine veya verimsiz bir erişim düzenine işaret edebilir. Boyutlandırma kılavuzu için bkz: Azure Managed Redis katman seçimi kılavuzu.
  • Anahtarlarınıza uyan bir çıkarma ilkesi seçin. Yalnızca önbellekli veritabanı için allkeys-lru veya allkeys-lfu ile başlayın. volatile-* ilkesini yalnızca aynı veritabanı hem süresi dolan önbellek anahtarlarını hem de korunan, süresi dolmayan anahtarları içeriyorsa kullanın. Uçucu bir politika, hiçbir anahtarın TTL’si olmadığında tahliyeyi durdurabilir. Mümkün olduğunda önbellek verilerini korumalı verilerden ayırın.
  • Verimli bir şekilde serileştirin. JSON okunabilir ve taşınabilir. Yüksek aktarım hızı yolları için, belleği ve ağ ek yükünü azaltmak için kompakt bir ikili biçimi test edin. Biçimi değiştirmeden önce bellek, CPU, gecikme süresi, şema evrimi ve hata ayıklama etkisini karşılaştırma.

Önbelleğe alınacakları bulma

En etkili önbellek hedefleri, uygulamanızın en az değişen veriler üzerinde en sık çalıştırdığı sorgulardır. Bu açıklamaya uyan sorguları tahmin etmek yerine, PostgreSQL için Azure Veri Tabanı ilgili özellikleri etkinleştirdiğinizde toplayabileceği sorgu telemetrisindeki geçmiş ve geçerli görünümleri karşılaştırın:

  • Query Store geçmiş çözümleme için sorgu yürütme istatistiklerini kalıcı hale ekler. Daha uzun süreler boyunca veritabanı yüküne tutarlı bir şekilde hakim olan sorguları bulmak için çağrı sayılarını ve toplam ve ortalama yürütme süresini kullanın. Bkz. Query Store ile performansı izleme.
  • Sorgu Performansı İçgörüleri, Azure portalında Query Store verileri görselleştirerek sık ve yoğun kaynak kullanan sorguları tespit edebilir ve zaman içindeki davranışlarını karşılaştırabilirsiniz. Bkz. Sorgu Performansı İçgörüleri.
  • pg_stat_statements, geçerli gözlem penceresinin doğrudan bir görünümünü sunmak için veritabanı içinde birikimli sorgu başına istatistikler sunar. İstatistikleri sıfırlanabilir, bu nedenle gözlem pencerelerinde geçmişe ihtiyacınız olduğunda Query Store kullanın.

Önbellek adayı seçmeden önce her iki görünümü de kullanın. Kısa süreli ani artış, iş yükünün normal davranışını temsil etmese de geçmiş ortalama geçerli bir regresyonu gizleyebilir. Sık, pahalı ve kararlı, yüksek çağrı sayısı, toplam yürütme süresi ve her istekte değişmeyen sonuçlar anlamına gelen sorguların önceliklerini belirleyin. Bu sorgular en yüksek önbellek isabet hızını ve veritabanı yükündeki en büyük düşüşü sunar. Sürekli çalışan ancak ürün listesi, kategori ağacı veya fiyatlandırma tablosu gibi dakikalar boyunca aynı satırları döndüren sorgu ideal bir adaydır.