Teljesítmény javítása gyorsítótár használatával Azure Database for PostgreSQL rugalmas kiszolgálóval

Amikor Azure Database for PostgreSQL rugalmas kiszolgálóra hoz létre alkalmazást, a gyorsítótárazási réteg hozzáadása az egyik leghatékonyabb módszer a válaszidők javítására, az adatbázis terhelésének csökkentésére és a rugalmasság növelésére. A memóriabeli gyorsítótárból származó gyakori olvasási adatok kiszolgálásával az alkalmazás kevesebb lekérdezést küld a PostgreSQL-nek. Ez alacsonyabb CPU- és IOPS-igényt jelent, így kisebb számítási csomagon futhat, növelheti az olvasási kapacitást a kiszolgáló felskálázása nélkül, és kezelheti a forgalmi kiugrásokat. A gyorsítótárak rugalmasságot is adhatnak. Ha a PostgreSQL rövid megszakítást tapasztal, a gyorsítótárban lévő adatokat elérő kérések továbbra is sikeresek maradnak, így az olvasási útvonalak elérhetők maradnak az adatbázis helyreállítása során.

Ez a cikk segít eldönteni, hogy mikor segít a gyorsítótárazás, és hogy melyik minta felel meg az alkalmazásnak. Ezután négy gyorsítótárazási mintát valósít meg Pythonban (cache-aside, referenciaadatok előzetes betöltése, írási átvezetéses és eseményvezérelt) a Azure Managed Redis, a psycopg és redis könyvtárak, valamint a Microsoft Entra ID-hitelesítés felhasználásával.

Mikor kell gyorsítótárat hozzáadni?

A PostgreSQL már gyorsítótárazza a gyakran használt adatoldalakat a puffergyorsítótárban, és az operációs rendszer fájlgyorsítótárából is profitált. Ezek a gyorsítótárak gyorsabbá teszik az ismételt hozzáférést, de a lekérdezések végrehajtásával és más folyamatokkal megosztják az adatbázis-kiszolgáló számára lefoglalt memóriát. A tartalmuknak bizonyos karbantartási és feladatátvételi műveletek után újra fel kell melegednie.

Az adatbázis-gyorsítótár kapacitása több memóriával rendelkező számítási beállításra való skálázással szerezhető be. A PostgreSQL memóriabeállításai is hangolhatók, de ha több memóriát helyez ki a puffergyorsítótárba, kevesebb marad a lekérdezések végrehajtása és az operációs rendszer számára. A memóriával kapcsolatos módosításokat gondosan tesztelje, hogy elkerülje a memóriahiányos állapotokat.

Azure Managed Redis kiegészíti ezeket a natív gyorsítótárakat. A kiválasztott alkalmazásadatokat és lekérdezési eredményeket az adatbázis-kiszolgálón kívül tárolja, alacsonyabb késésű hozzáférést biztosít az időérzékeny olvasási útvonalakhoz, és a Gyorsítótárban tárolt olvasásokat elérhetővé teheti, miközben a PostgreSQL helyreállítja vagy felmelegíti a gyorsítótárat. Akkor használja, ha ezek az előnyök indokolják a többlet alkalmazáslogikát és egy további üzemeltetendő szolgáltatást. Nem helyettesíti a PostgreSQL-t az igazság forrásaként.

Ha olyan külső gyorsítótárat ad hozzá, mint a Azure Managed Redis, akkor segít a legjobban, ha a számítási feladat az alábbi jellemzőkkel rendelkezik:

  • Olvasásintenzív hozzáférési minták. Ugyanazokat a sorokat sokkal gyakrabban olvassa be, mint amennyit módosítanak, például termékkatalógusokat, felhasználói profilokat, konfigurációs adatokat vagy referenciatáblákat.
  • Költséges vagy ismétlődő lekérdezések. Olyan összesítések, illesztések vagy számított eredmények, amelyek költségesek, de rövid idő alatt stabilak.
  • Késésre érzékeny végpontok. Olyan felhasználói műveletek, ahol a memórián belüli olvasás (ezredmásodperc alatti) előnyösebb az adatbázisok közötti utazáshoz.
  • Kiszámítható csúcsok. Szezonális vagy eseményvezérelt forgalom, amikor a gyorsítótárazás leveszi a terhelést, ami egyébként a számítási kapacitás bővítését tenné szükségessé.
  • Karbantartási és feladatátvételi érzékenység. Olvasási útvonalak, amelyek folyamatos válaszidőt igényelnek, miközben egy PostgreSQL-példány karbantartási vagy feladatátvételi művelet után helyreállítja vagy felmelegíti a gyorsítótárat.

A gyorsítótárazás kevesebbet segít az írásintenzív terheléseknél, azoknál az adatoknál, amelyeknek mindig tranzakciósan konzisztensnek kell lenniük, illetve az olyan lekérdezéseknél, amelyek már eleve gyorsak és ritkán ismétlődnek.

Gyorsítótárazási minták

Ez a cikk egy kiskereskedelmi áruházat használ futó példaként. Az alkalmazás különböző részei különböző gyorsítótárazási mintákat használhatnak. A következő szakaszok az első négy mintát implementálják a Python. A cikk ismerteti a munkamenet- és állapotletöltést, valamint a többrégiós gyorsítótárazási mintákat, de nem biztosít számukra kód implementációkat.

Pattern Hogyan működik? A kiskereskedelmi üzletben
Cache-aside (késleltetett betöltés) Az alkalmazás először ellenőrzi a gyorsítótárat. Egy kihagyás után beolvassa a PostgreSQL-ből, majd feltölti a gyorsítótárat. Termékkatalógus és részletes oldalak, ahol néhány népszerű elem a legtöbb olvasást hajtja.
Referenciaadatok előzetes beolvasása A stabil adatok előre betöltődnek a gyorsítótárba, és a forrás változásakor frissülnek, nem pedig cache miss esetén. Kategóriák, márkák és szállítási konfiguráció.
Írási műveletek Az alkalmazás ugyanabban a műveletben ír a gyorsítótárba és a PostgreSQL-be, így azok konzisztensek maradnak. Azonnal látható ár- és készletfrissítések.
Eseményvezérelt érvénytelenítés A gyorsítótár-bejegyzéseket az időmérő helyett az adatváltozási eseményekre válaszul frissíti vagy érvényteleníti. A megrendelés állapota, ahogy a megrendelés végighalad a teljesítési folyamaton.
A munkamenet és az állapot tehermentesítése Az átmeneti állapot az adatbázis helyett a gyorsítótárban található. Bevásárlókocsik és felhasználói munkamenetek.
Többrégiós gyorsítótárazás Az egyes régiókban található gyorsítótárak helyi olvasásokat szolgálnak ki, és szinkronban maradnak az aktív georeplikációs szolgáltatásokkal. Egy globális kirakat, amely több régióban szolgálja ki a vásárlókat.

Prerequisites

Tip

A minta teljes, üzembe helyezhető verziójáért, beleértve az infrastruktúrát kódként és mind a négy mintát, tekintse meg a GitHub amr-caching-pattern-samples adattárát.

1. lépés: Az ügyfélkódtárak telepítése

Telepítse a Redis- és PostgreSQL-ügyfélkódtárakat, valamint a Azure identitástárat Microsoft Entra hitelesítéshez.

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

2. lépés: Kapcsolódás a Microsoft Entra ID-hez

Használjon Microsoft Entra ID hitelesítést a hozzáférési kulcsok helyett. Microsoft Entra ID eltávolítja a titkos kulcsok alkalmazásbeli tárolásának szükségességét, és lehetővé teszi a hozzáférés központi kezelését.

Az alábbi kód létrehoz egy Redis-ügyfelet és egy PostgreSQL-kapcsolatot, amely felügyelt identitással vagy fejlesztői hitelesítő adatokkal DefaultAzureCredentialvan hitelesítve. A példa a fürttudatos RedisCluster klienst használja, amely illeszkedik az ez a minta által használt OSS klaszterezési szabályzathoz. Ha a gyorsítótár az Enterprise-fürtözési szabályzatot használja, használja inkább a szabványos redis.Redis ügyfélprogramot.

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 hozzáférési jogkivonatok lejárnak, általában körülbelül egy óra elteltével. Hosszú ideig futó alkalmazások esetén frissítse a tokent, mielőtt lejár, és csatlakozzon újra mind az Azure Managed Redishez, mind az Azure Database for PostgreSQL-hez, vagy használjon olyan segédprogramot, amely automatikusan újra lekéri a tokeneket. További információ: Microsoft Entra ID használata a Azure Managed Redis használatával történő hitelesítéshez.

3. lépés: Cache-aside

A cache-aside minta a leggyakoribb, és ebben a példában a termékek lekérdezésére használják. Az alkalmazás először a Redis-t ellenőrzi, és ha ott nincs találat, lekérdezi a PostgreSQL-t, majd az eredményt megadott élettartammal (TTL-lel) eltárolja a gyorsítótárban. Néhány népszerű termék generálja a legtöbb olvasást, ezért magas a találati arány.

Mivel a legtöbb olvasási művelet a memóriából történik, a cache-aside minta leveszi a tartós olvasási terhelést a PostgreSQL-ről. Ez a csökkentés kevesebb kapcsolatot, kevesebb puffer-gyorsítótárazást, valamint alacsonyabb PROCESSZOR- és IOPS-értéket jelent. A kiszolgáló felskálázása vagy olvasási replikák hozzáadása nélkül is elnyelheti az olvasási csúcsokat. A PostgreSQL-t csak cache miss esetén kell lekérdezni (az első hozzáféréskor, vagy a TTL lejárta után). A gyorsítótárazásra érdemes lekérdezések azonosításához lásd: Mit érdemes gyorsítótárazni.

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. lépés: Referenciaadatok előkezelése

A folyamatosan olvasott, de ritkán változó stabil adatoknak (például kategóriáknak, márkáknak vagy szállítási konfigurációknak) nem kell várniuk a gyorsítótár hiányára. Töltse be elölről a gyorsítótárba, és frissítse, amikor a forrás megváltozik. A PostgreSQL-sémákban ezek általában a kis keresési és dimenziótáblák, amelyek számos lekérdezéshez csatlakoznak. A memóriából való kiszolgálás nagy mennyiségű ismétlődő illesztés és keresés eltávolítását teszi el az adatbázisból. A cache-aside mintával ellentétben nincs kérésenkénti cache miss, és nincs TTL-lejáratból adódó versenyhelyzet. A módosításkor frissít, így az olvasás mindig meleg.

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. lépés: Azonnali írás

Ha a módosításnak azonnal láthatónak kell lennie, írja be a PostgreSQL-t és a gyorsítótárat ugyanabba a műveletbe ahelyett, hogy arra vár, hogy egy TTL lejárjon vagy érvénytelenítse a kulcsot. Ez a minta például a mintában szereplő díjszabási információk frissítésére szolgál. A PostgreSQL marad az igazság forrása. A módosítás először ott véglegesíti, majd frissíti a gyorsítótárat, így az írás utáni olvasás visszaadja az új értéket.

Ha két olyan rendszerre ír, amelyek nem osztanak meg tranzakciót, az adatbázis véglegesítése sikeres lehet, miközben a gyorsítótár frissítése meghiúsul, és nincs egyszerű javítás. Az alábbi kódrészlet az ideális esetet mutatja be, és nem tér ki a hibakezelésre. Éles környezetben Ön dönti el, hogyan kezelje a sikertelen frissítést, például újrapróbálja, ha a hiba átmenetinek tűnik, vagy érvényteleníti a kulcsot, hogy a következő olvasáskor az adat újra betöltődjön a PostgreSQL-ből. Akárhogy is, a PostgreSQL a megfelelő értéket tárolja, így egy elavult vagy hiányzó gyorsítótárbejegyzés mindig helyreállítható. Ha a gyorsítótár-frissítésnek megbízhatóan kell érvényesülnie, inkább az adatbázis változásfolyamára alapozza azt (lásd: eseményvezérelt érvénytelenítés).

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. lépés: Eseményvezérelt érvénytelenítés

Az eseményvezérelt érvénytelenítés az adatváltozási eseményekre reagálva konzisztenssé teszi a gyorsítótárat az adatbázissal. A bejegyzéseket a változásukkor frissíti vagy érvényteleníti ahelyett, hogy időzítő alapján járatná le őket. Az írók eseményeket fűznek egy tartós Redis-streamhez, egy csak hozzáfűző naplóhoz, és egy vagy több felhasználó felolvassa ezeket az eseményeket, és frissíti a gyorsítótárat. Mivel az adatfolyam fennmarad, az események egy fogyasztó újraindítása után is megmaradnak. A fogyasztói csoportok minden eseményt egyetlen feldolgozónak kézbesítenek, nyomon követik a nyugtázásokat, így semmit sem vesznek el vagy dolgoznak fel kétszer, és lehetővé teszi a feldolgozás skálázását a feldolgozók között.

Ezt a mintát akkor használja, ha a gyorsítótárazott érték máshol változó adatokból (állapotból, előrejelzésből vagy összesítésből) származik, ahol a TTL elavult adatokat szolgálna ki, vagy állandó újraszámítást kényszerítene ki. A webáruházban ez a minta a teljesítési folyamaton keresztül vezérli a rendelési állapotot. A rendelés elküldése megírja a rendelést a PostgreSQL-be, gyorsítótárazza annak kezdeti állapotát, és hozzáfűz egy placed eseményt a streamhez:

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

Egy teljesítést végző feldolgozófolyamat egy fogyasztói csoportot kezel: beolvassa az új eseményeket, minden rendelést továbbléptet a PostgreSQL-ben, frissíti a gyorsítótárban tárolt order:{id}:status kivetítést, és nyugtázza az eseményt. A megrendelési oldal felolvassa ezt a vetítést, így az állapot-ellenőrzések gyorsak maradnak, és soha nem érintik az adatbázist. Az érték helyes marad, mert az események naprakészek maradnak.

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)

A mintában szereplő eseményforrás az alkalmazás, amely a PostgreSQL-et írja, és ugyanahhoz az útvonalhoz fűzi hozzá az eseményt. A PostgreSQL saját maga is kibocsáthat módosításokat: LISTEN/NOTIFY egyszerűsített értesítésekhez vagy logikai dekódoláshoz (adatrögzítés) egy tartós, sorszintű változásfolyamhoz. Ha a gyorsítótárat a PostgreSQL saját változásfolyamából hajtja, az azt jelenti, hogy minden véglegesített változásra reagál, még az alkalmazást megkerülő írásokra is.

Bevált gyakorlatok

Kövesse az alábbi eljárásokat a gyorsítótár helyes, hatékony és költséghatékony megőrzéséhez.

  • Állítson be TTL-t, ahol az elavultság kockázatnak számít. A TTL korlátozza az elavultságot, ha az érvénytelenítés sikertelen. Igazítsa a TTL-t az alkalmazás által elfogadható maximális lejártsághoz. Referenciaadatok esetén csak akkor használjon hosszú TTL-t vagy TTL nélküli beállítást, ha megbízható érvénytelenítési és frissítési folyamatok állnak rendelkezésre.
  • Használjon konzisztens kulcselnevezési sémát. Névtérkulcsok szolgáltatás, entitás és azonosító, például product:42 vagy user:1001:profile. Adjon hozzá egy verziót, ha a kulcsformátum vagy az értékséma módosítható.
  • Gyorsítótárazzon a megfelelő részletességi szinten. Gyorsítótárazzon egyes entitásokat vagy kisebb eredményhalmazokat, amikor ezeket gyakran újra felhasználják, és könnyen érvényteleníthetők. Ne gyorsítótárazzon olyan adatokat, amelyeket ritkán használ fel újra. A túlzott gyorsítótárazás memóriavesztést okozhat, és csökkentheti a találatok számát.
  • A gyorsítótárhibák és a leállások kezelése kecsesen. A gyorsítótárat optimalizálásként, nem pedig igazságforrásként kezelje. Ha a Redis nem érhető el, használjon korlátozott tartalékot a PostgreSQL-re. A PostgreSQL védelme érdekében időtúllépéseket, megszakítókat, visszakapcsolást és kéréskorlátokat adhat hozzá. Ha a PostgreSQL-nek van egy rövid megszakítása, akkor továbbra is kiszolgálhatja a gyorsítótárban lévő adatok olvasását, miközben az írások várják az adatbázis helyreállítását.
  • Előzze meg a gyorsítótárrohamokat. Ha egy népszerű kulcs lejár, számos kérés egyszerre érheti el az adatbázist. Használjon TTL-jittert, kérések összevonását, stale-while-revalidate stratégiát vagy rövid elosztott zárolást, hogy egyetlen kérés töltse újra a bejegyzést.
  • Méretezze megfelelően a gyorsítótárat. Figyelheti a találati arányt, a memóriahasználatot, a kiürítési arányt, a lejárati gyakoriságot, a késést, a gyorsbillentyűket és a kulcsok számosságát. Az alacsony találati arány utalhat túl kicsi gyorsítótárra, túl gyakori kiszorításokra, nem megfelelő kulcsválasztásra vagy nem megfelelő hozzáférési mintára. A méretezési útmutatót Azure Felügyelt Redis-szint kiválasztására vonatkozó útmutatóban talál.
  • Válasszon a kulcsaihoz illő kiürítési szabályt. Csak gyorsítótárazásra szolgáló adatbázis esetén kezdésként használja az allkeys-lru vagy az allkeys-lfu beállítást. Csak akkor használjon illékony*-szabályzatot, ha ugyanaz az adatbázis lejáró gyorsítótárkulcsokat és védett, nem lejáró kulcsokat tartalmaz. Az illékony szabályzatok leállhatnak a kizárással, ha egyetlen kulcs sem rendelkezik TTL-sel. Ha lehetséges, különítse el a gyorsítótáradatokat a védett adatoktól.
  • Szerializáljon hatékonyan. A JSON olvasható és hordozható. A nagy átviteli sebességű útvonalakhoz teszteljen egy kompakt bináris formátumot a memória és a hálózati terhelés csökkentése érdekében. A formátum módosítása előtt mérje fel a memóriát, a processzort, a késést, a sémafejlődést és a hibakeresési hatást.

Keresse meg, mit szeretne gyorsítótárazni

A leghatékonyabb gyorsítótár-célok azok a lekérdezések, amelyeket az alkalmazás a leggyakrabban a legkevésbé változó adatokon futtat. Ahelyett, hogy kitalálja, hogy mely lekérdezések felelnek meg ennek a leírásnak, hasonlítsa össze a lekérdezési telemetria előzményeit és aktuális nézeteit, amelyeket Azure Database for PostgreSQL gyűjthet a releváns funkciók engedélyezésekor:

  • Query Store megőrzi a lekérdezések végrehajtási statisztikáit az előzményelemzéshez. A hívásszám és az összesített és átlagos végrehajtási idő használatával megkereshet olyan lekérdezéseket, amelyek folyamatosan uralják az adatbázis terhelését hosszabb időszakokban. Lásd: A teljesítmény figyelése a Query Store használatával.
  • A Lekérdezési teljesítményelemzés Query Store adatokat jelenít meg a Azure portálon, így észlelheti a gyakori és erőforrás-igényes lekérdezéseket, és összehasonlíthatja azok viselkedését. Lásd: Lekérdezési teljesítményelemzés.
  • pg_stat_statements az adatbázison belül az aktuális megfigyelési időszak közvetlen megtekintéséhez elérhetővé teszi az összesítő kimutatásonkénti statisztikát. A statisztikái alaphelyzetbe állíthatók, ezért használja a Query Store-t, ha a megfigyelési időszakok között is meg szeretné őrizni az előzményeket.

A gyorsítótár-jelölt kiválasztása előtt használja mindkét nézetet. Előfordulhat, hogy a rövid távú kiugró érték nem a számítási feladat normál viselkedését jelzi, míg az előzményátlag elrejtheti az aktuális regressziót. A gyakori, költséges és stabil lekérdezések rangsorolása, ami magas hívásszámot, magas teljes végrehajtási időt és olyan eredményeket jelent, amelyek nem változnak minden kérés esetén. Ezek a lekérdezések a legmagasabb gyorsítótár-találati arányt és az adatbázis-terhelés legnagyobb csökkenését biztosítják. Az olyan lekérdezések, amelyek folyamatosan futnak, de percekig ugyanazokat a sorokat adják vissza, mint például egy terméklista, egy kategóriafa vagy egy díjszabási tábla, ideális választás.