← Vissza a cikkekhez
technologytutorial

Cloud Functions — a Firebase szerveroldala

Zsaga
#featured#firebase#cloud-functions#serverless#v2#trigger#cold-start#concurrency#idempotency#emulator#deploy
Cloud Functions runtime — események érkeznek balról, a runtime feldolgozza őket, mellékhatások folynak ki jobbra
Esemény jön be, kód lefut, mellékhatás történik — szerver-üzemeltetés nélkül
Mit tanulsz
  • cloud-functionsBevezető

01 — Bevezetés

Mi a Cloud Functions?

A Cloud Functions egy serverless szerveroldali futtatókörnyezet — olyan rövid, célzott függvényeket fogalmazol meg, amik egy eseményre reagálnak, lefutnak, és utána eltűnnek. Nem kell szervert üzemeltetni, nem kell skálázásra figyelni, és csak a tényleges futási idő után fizetsz.

Mit jelent a serverless?

A klasszikus szerveroldali kódot (Express, NestJS, Django, Spring) egy szerveren futtatod, ami akkor is jár, ha senki sem használja. Egy Cloud Function ezzel szemben csak akkor él, ha egy esemény elindítja: amikor lefutott, magától elhal. Egy óra alatt ezerszer is felélesztheti és leállíthatja a Google. Ha ma egy felhasználód van, és holnap egymillió, ugyanaz a kód mindkét esetben működik — neked nem kell beleavatkoznod.

Mire jó konkrétan?

  • Adatreakciók — egy Firestore dokumentum változásakor egy másik dokumentumot frissít, vagy emailt küld.
  • Webhookok fogadása — Stripe, GitHub, Slack, Twilio mind webhook-ot küld, ezeket egy onRequest Cloud Function fogadja.
  • Időzített feladatok — napi statisztika-számolás, heti emlékeztető emailek a felhasználóknak.
  • Privilegizált műveletek — olyan dolgok, amiket a kliens nem futtathat (pl. saját szerepkör beállítása), de egy bejelentkezett felhasználó kérésére szervernek kell elvégeznie.
  • Külső API-k hívása — fizetés, email-szolgáltatás, mesterséges intelligencia, térképszolgáltatás. Ezek az API-kulcsok nem szabad hogy a kliensbe kerüljenek.

Mire nem jó?

A Cloud Functions nem jó tartós háttér-folyamatokra (egy függvény max 9 percig, v2-ben max 60 percig futhat). Nem jó lassan induló munkára, ami másodpercenként ezreket szolgálna ki egy folyamatos szerveren — ott egy Cloud Run vagy egy hagyományos szerver olcsóbb. És nem jó WebSocket-szerű kapcsolatokra — ehhez a Firebase Realtime Database vagy a Firestore valós idejű feliratkozása való.
Spark vs. Blaze
A Cloud Functions kizárólag Blaze planon érhető el — a Spark plan nem támogatja. A Blaze planon havi 2 millió hívás, 400 ezer GB-másodperc memória és 200 ezer CPU-másodperc ingyenes. Egy átlagos KKV-s SaaS-nél ez gyakran elegendő, és a havi költség 0-t mutat. Forgalom-növekedéssel arányosan nő — egy hívás kb. $0.0000004, egy GB-másodperc memória kb. $0.0000025.
Mit tanulsz
  • cloud-functionsKözéphaladó

02 — Két generáció

v1 és v2 — melyik, mikor?

A Cloud Functions-nek két, egymás mellett élő generációja van. A v1 az eredeti, 2017-es kiadás. A v2 2022-ben jelent meg, és a Cloud Run alapjaira épül — több memória, jobb párhuzamosság, hosszabb futási idő. Új projekteknél szinte mindig a v2-t érdemes választani, de a v1-nek is megvannak a maga használati esetei.

A különbség lényege

Szempontv1 (eredeti)v2 (Cloud Run alapú)
Maximum memória8 GB32 GB
Maximum futási idő9 perc (HTTP) / 9 perc (esemény)60 perc (HTTP) / 9 perc (esemény)
Egy példányon párhuzamos kérések1akár 1000
Hideg indulás~2-5 mp~0.5-1.5 mp
Min példányszámállítható (mindig fut)
Több régióIgenIgen
VPC kapcsolatIgenIgen, jobb integrációval
Eventarc eseményekKorlátozottTeljes (sokkal több típus)

Mikor melyik?

Választás

v1 vagy v2?

v2 — szinte mindig

Új projekt, HTTP API, hosszabb futási idő, párhuzamos kérések, nagyobb memória. A Firebase dokumentáció minden új példája v2-vel készül, és Google ezt fejleszti aktívan.

v1 — speciális esetek

Régi projekt karbantartása. Egyes Auth-trigger események, amik még csak v1-ben támogatottak (onCreate, onDelete a felhasználói rekordokra). Speciális Realtime Database trigger-ek.

v2 importok és szintaxis

functions/src/index.tstypescript
// V2: separate imports per type
import { onRequest, onCall } from 'firebase-functions/v2/https';
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import { onSchedule } from 'firebase-functions/v2/scheduler';

// HTTP endpoint
export const api = onRequest(
  { region: 'europe-west1', memory: '512MiB' },
  (req, res) => {
    res.json({ ok: true });
  }
);

// Firestore trigger
export const onOrderCreated = onDocumentCreated(
  { document: 'orders/{orderId}', region: 'europe-west1' },
  async (event) => {
    const order = event.data?.data();
    // ...
  }
);
Migráció v1-ről
A v1 és v2 függvények egymás mellett tudnak élni ugyanabban a projektben. Egy migrációnál nem kell mindent egyszerre átírni — érdemes egyenként cserélni, mindegyikhez tesztelni, és csak a régi v1 verziót törölni, ha az új v2 már fut és bejáratott. A Console-ban a Functions oldalon mindkét generáció látható, és a hívási statisztikák is külön mutatkoznak.
Mit tanulsz
  • cloud-functionsKözéphaladó

03 — Indítók

Indítók — mire reagáljon a függvény?

A Cloud Functions egyik fő ereje, hogy nem csak HTTP-kérésre tud reagálni, hanem szinte bármilyen Firebase- vagy Google Cloud-eseményre. Egy Firestore-dokumentum frissülésére, egy Storage-fájl feltöltésére, egy ütemezett időpontra. Ez teszi lehetővé, hogy a backend-logika ne egy hatalmas API-szerver legyen, hanem kis, fókuszált függvények sokasága.

Az indító-típusok áttekintése

IndítóMikor futTipikus felhasználás
onRequestHTTP-kérés érkezikREST API, webhook fogadás
onCallA kliens-SDK httpsCallable hívásaPrivilegizált műveletek, Firebase auth-tal
onDocumentCreated/Updated/DeletedFirestore-dokumentum változikAggregátum karbantartása, denormalizáció
onObjectFinalizedStorage-fájl feltöltveKép-méretezés, vírus-keresés, metaadat
onUserCreated (v1)Új felhasználó regisztrálÜdvözlő email, alapértelmezett dokumentumok
onScheduleCron-szerű időzítésNapi statisztika, heti emlékeztető
onMessagePublished (Pub/Sub)Üzenet érkezik egy témáraHáttér-feldolgozás, sorok
onTaskDispatched (Cloud Tasks)Időzített / sorba állított feladatKésleltetett munkavégzés

onRequest vs. onCall — a leggyakoribb választás

Mindkettő HTTP-alapú, de fontos különbség van köztük:
HTTP-választás

onRequest vagy onCall?

onRequest

Bárhonnan hívható, plain HTTP. Webhookok fogadásához, vagy ha külső rendszerből jön a kérés. Az auth-ot magadnak kell ellenőrizni.

onCall

Csak a Firebase kliens-SDK-ból hívható. A Firebase Auth tokent magától ellenőrzi és átadja, és az App Check is automatikusan érvényesül. Egyszerűbb biztonság, de zártabb.

Egy onCall példa

setUserRole.tstypescript
import { onCall, HttpsError } from 'firebase-functions/v2/https';
import { getAuth } from 'firebase-admin/auth';

export const setUserRole = onCall(
  { region: 'europe-west1', enforceAppCheck: true },
  async (request) => {
    // request.auth is automatically read from the JWT
    if (request.auth?.token.role !== 'admin') {
      throw new HttpsError('permission-denied', 'Admin only');
    }

    const { uid, role } = request.data;
    await getAuth().setCustomUserClaims(uid, { role });
    return { success: true };
  }
);
A kliensből egy sorban hívható, és a Firebase SDK gondoskodik a JWT-ről, az App Check tokenről és a hibakezelésről:
client/admin.tstypescript
import { getFunctions, httpsCallable } from 'firebase/functions';

const functions = getFunctions(app, 'europe-west1');
const setUserRole = httpsCallable(functions, 'setUserRole');

await setUserRole({ uid: 'usr_abc', role: 'editor' });
Mit tanulsz
  • cloud-functionsKözéphaladó

04 — Hideg indulás

Hideg indulás — az eltűnő szerver ára

A serverless előnye, hogy nem fizetsz a tétlen szerverért. A hátulütője, hogy ha senki nem használja a függvényt, a Google leállítja a példányt — és a következő kérésnél új példány indul. Ennek az indulási ideje a hideg indulás, ami az átlagos felhasználóknak legalább pár száz milliszekundum késést okoz.

Mi történik egy hideg indulásnál?

  1. Példány készítés — a Google létrehoz egy új konténert.
  2. Node.js indítás — a futtatókörnyezet betöltődik.
  3. A te kódod betöltődik — minden import, ami a fájl tetején van, lefut.
  4. Globális inicializáció — Firebase Admin SDK, adatbázis-kapcsolatok, külső kliens-könyvtárak.
  5. A függvény végre lefut — a kérés feldolgozása megkezdődik.
Ez összesen v2-ben kb. 0.5-1.5 másodperc, v1-ben 2-5 másodperc. Ezt a felhasználó várja.

Mit tehetsz ellene?

1) Min példányszám beállítása

Ha azt mondod, hogy minInstances: 1, akkor mindig fut legalább egy példány — soha nincs hideg indulás. Ára: ezt a futó példányt is fizetni kell, akkor is, ha senki sem használja:
min-instances.tstypescript
export const api = onRequest(
  {
    region: 'europe-west1',
    memory: '512MiB',
    minInstances: 1,             // always 1 instance running
  },
  (req, res) => { /* ... */ }
);
Egy 512 MB-os példány havi költsége kb. $10-15 24/7 futtatás esetén. Egy felhasználó-szembesülő API-nál ez gyakran megéri a látható UX-javulásért. Egy belső admin-funkciónál (amit napi 5-ször használnak) felesleges.

2) Karcsú importok

Minden import, ami a fájl tetején van, betöltődik a hideg induláskor. Ha egy 50 MB-os függvénykönyvtárat húzol be, ami 2 másodpercig parsolódik, az közvetlenül a hideg indulás idejét növeli. Néhány konkrét tipp:
  • Specifikus importokimport { onCall } from 'firebase-functions/v2/https' a teljes firebase-functions behúzása helyett.
  • Lusta betöltés — egy nagy könyvtár (pl. PDF-generáló, képfeldolgozó), amit csak ritkán használsz, dynamic import-tal töltsd be (const { generatePdf } = await import('./pdf')).
  • Ne tartsd a teljes Lodash-t — ha csak _.chunk kell, importáld direkt: import chunk from 'lodash/chunk'.

3) Globális változók újrahasznosítása

Ha a függvény „melegen" fut (a példány még nem lett leállítva), akkor a globális változók megőrződnek a következő kéréshez. Ezt érdemes kihasználni — a Firebase Admin SDK-t, az adatbázis-kapcsolatot, az API-klienseket a fájl tetején inicializáld, ne a függvényen belül:
global-init.tstypescript
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
import Stripe from 'stripe';

// Initialised once, reused across all warm calls
initializeApp();
const db = getFirestore();
const stripe = new Stripe(process.env.STRIPE_KEY!);

export const api = onRequest(async (req, res) => {
  // db and stripe are ready — no per-call initialisation
  await db.collection('logs').add({ ts: new Date() });
  res.json({ ok: true });
});
Vigyázz a globális állapottal
A globális változók megosztásra kerülnek a meleg hívások között, de nem tartósak — bármikor új példány indulhat, és minden elveszik. Ne tárolj benne semmit, ami a következő kérés szempontjából fontos lenne. Memória-cache (pl. egy Map, ami az elmúlt 100 felhasználó adatát tartja) működhet, de soha ne lehessen rá számítani, csak optimalizációként.
Mit tanulsz
  • cloud-functionsKözéphaladó

05 — Beállítások

Memória, CPU, időtúllépés

Minden függvénynek vannak alap-beállításai, amik a teljesítményt és a költséget egyensúlyozzák. Túl kevés memória esetén a kód lassul vagy elhalja az OOM. Túl nagy memória esetén a számla feleslegesen nagy. Ezeket nem tippre érdemes beállítani, hanem mérni.

A három fő beállítás

BeállításAlapértékMaximumMire befolyásol
memory256 MiB32 GiB (v2)Memória + CPU együtt — nagyobb memória, gyorsabb CPU
timeoutSeconds60 mp3600 mp (v2 HTTP)Mennyi ideig futhat a függvény, mielőtt elhal
cpuauto (memóriához kötve)8 vCPUKifejezett CPU-kérés (csak v2)

Mennyi memória elég?

A Console-ban (Functions → adott függvény → Logs) láthatod, hogy az utolsó hívások mennyi memóriát fogyasztottak. Ha rendszeresen 80% felett van, érdemes nagyobb keretet adni. Egy gyakorlati skála:
  • 128-256 MiB — egyszerű API-hívás, kis Firestore-művelet, webhook fogadás. Ez a legtöbb függvény.
  • 512 MiB — közepes méretű JSON-feldolgozás, néhány külső API-hívás.
  • 1-2 GiB — kép-méretezés (Sharp), kis PDF-generálás, közepes ML-modell.
  • 4 GiB+ — nagy fájlok feldolgozása (videó, archívum), nehéz ML.
Memória és CPU együtt
A Cloud Functions a CPU-t a memóriához köti — több memória, gyorsabb CPU. Egy 256 MiB-os függvény kb. 0.4 vCPU-t kap, egy 1 GiB-os 1 vCPU-t, egy 4 GiB-os 2 vCPU-t. Ha CPU-intenzív a kódod (pl. titkosítás, hash-elés, kép-feldolgozás), akkor a memória növelése sokszor felgyorsítja akkor is, ha nem is használnád ki a teljes memóriát. Néha gyorsabb és olcsóbb 1 GiB-tal, mint 256 MiB-tal — mert a futási idő egynegyedére csökken.

Időtúllépés

Az alapértelmezett 60 másodperc a legtöbb HTTP-kérésre elég, de néhány esetre több kell:
  • Egy nagy adatbázis-feltöltés (több ezer Firestore-művelet) — 540 mp.
  • Egy hosszú külső API-hívás (pl. videó-feldolgozás) — 60 perc (csak v2 HTTP).
  • Egy időzített napi statisztika sok millió rekorddal — 540 mp.
Ha egy függvény rendszeresen időtúllépésbe fut, az általában nem a beállításokkal van baj, hanem a feladat túl nagy egyetlen futáshoz. Ilyenkor érdemes Pub/Sub-bal vagy Cloud Tasks-szal feldarabolni a feladatot.
Mit tanulsz
  • cloud-functionsKözéphaladó

06 — Párhuzamosság

Párhuzamosság — egy példány, sok kérés

A v2 egyik legfontosabb újdonsága: egy futó példány nem csak egy kérést tud kezelni egyszerre, hanem akár 1000-et is. Ez gyökeresen átalakítja a költséget és a hideg indulások számát — de figyelni kell rá, hogy a kód ezzel jól működjön.

Mit jelent a concurrency?

v1-ben minden hívás új példányon futott. Ha egyszerre 100 felhasználó kérte az API-t, akkor 100 példány indult — és a sokadik mindegyiknél hideg indulás. v2-ben egy példány alapból 80 párhuzamos kérést tud kezelni, és ezt akár 1000-re is felteheted.
Ennek két előnye van:
  1. Kevesebb hideg indulás — a meglévő példány felveszi a szomszédos kéréseket, nem indít újat.
  2. Olcsóbb futás — a memória és a CPU időt csak a tényleges aktivitás alatt fizeted, és egy példányon több kérés osztozik rajta.
concurrency-config.tstypescript
export const api = onRequest(
  {
    region: 'europe-west1',
    memory: '512MiB',
    concurrency: 80,             // default
  },
  (req, res) => { /* ... */ }
);

Mire kell figyelni?

Ha egy példány több kérést kezel egyszerre, akkor a kódnak thread-safe-nek kell lennie a globális állapot tekintetében. Ez Node.js-ben szerencsére természetes (egy szál, eseményhurok), de van pár klasszikus buktató:
concurrency-bug.tstypescript
// BAD — the variable is shared across requests!
let currentUserId: string | null = null;

export const api = onCall(async (request) => {
  currentUserId = request.auth?.uid;        // ← race condition
  await someAsync();
  return { user: currentUserId };           // ← may have flipped to another user
});

// GOOD — each request has its own local variable
export const api = onCall(async (request) => {
  const userId = request.auth?.uid;
  await someAsync();
  return { user: userId };
});
Mikor csökkents párhuzamosságot?
Ha a függvény sok memóriát eszik kérésenként (pl. egy nagy fájlt tölt be a memóriába), akkor a 80 párhuzamos kérés gyorsan kifoghatja a 512 MiB-os keretet. Ilyenkor érdemes csökkenteni: concurrency: 10 vagy akár 1. Ha minden kérés egyenként 50 MB-ot fogyaszt, akkor 80 párhuzamossal együtt 4 GB kell — már a 32 GiB-os tetőtől sem messze.
Mit tanulsz
  • cloud-functionsKözéphaladó

07 — Régiók

Régiók és telepítés

A Cloud Functions egy konkrét Google Cloud-régióban fut. A régió-választás befolyásolja a késleltetést, az adat-szuverenitást, és néhány esetben az árat is. EU-s ügyfeleknél ez nem opcionális — ott jogi szempontból is fontos, hogy az adat ne hagyja el az EU-t.

A régió-választás szempontjai

  • A felhasználók közelsége — minden 1000 km extra távolság kb. 10 ms késleltetés. EU-s felhasználóknál europe-west1 (Belgium) vagy europe-west3 (Frankfurt) az alap.
  • A többi Firebase-szolgáltatás régiója — ha a Firestore europe-west3-ban van, a Cloud Functions is legyen ott. Az inter-régiós forgalom drágább és lassabb.
  • Adat-szuverenitás — GDPR alatt az EU-állampolgárok adata nem szabad, hogy az EU-n kívülre menjen feldolgozásra. Ez gyakran csak EU-s régiókat enged.
  • Régió-eltérések — egyes régiók nem támogatnak minden szolgáltatást vagy minden Firebase-funkciót. A us-central1 a legteljesebb, de EU-s ügyfelekhez nem ideális.

Több régiós telepítés

Egy függvényt több régióba is telepíthetsz — minden régióban külön példányok futnak, a felhasználó automatikusan a legközelebbihez kerül (Hosting routing révén). Ez ritkán kell egy KKV-s SaaS-nél, de globális szolgáltatásnál jól jön:
multi-region.tstypescript
export const api = onRequest(
  { region: ['europe-west1', 'us-central1', 'asia-east1'] },
  (req, res) => { /* ... */ }
);

A telepítés folyamata

terminalbash
# Deploy every function
$ firebase deploy --only functions

# Just one specific function (faster)
$ firebase deploy --only functions:setUserRole

# Just one "group" (if you've defined groups)
$ firebase deploy --only functions:webhooks
Egy első telepítés 3-5 percig tart, a későbbiek 1-2 perc. A telepítés szigorúan atomi: ha közben hiba történik, az élesben futó verzió érintetlen marad.
Környezeti változók
Érzékeny adatokat (API-kulcsok, jelszavak) sose tegyél a kódba. A Firebase Functions v2 a Google Cloud Secret Manager-rel integrált — egy parancs hozzáférést ad: firebase functions:secrets:set STRIPE_KEY. A függvény belsejében pedig process.env.STRIPE_KEY alatt elérhető. A secret titkosítva tárolódik, és csak a futási idő alatt férhető hozzá.
Mit tanulsz
  • cloud-functionsKözéphaladó

08 — Trigger-láncok

Trigger-láncok és körkörösség

A Firestore-trigger egy hasznos minta: amikor egy dokumentum változik, egy másik magától frissül. De ha óvatlanul írod meg, könnyen önmagát futtatja végtelenül — és ez nem csak drága, hanem percek alatt rommá teszi a számlát.

A klasszikus végtelen ciklus

infinite-loop.tstypescript
// BAD — every update fires the trigger again
export const updateTimestamp = onDocumentUpdated(
  'orders/{orderId}',
  async (event) => {
    await event.data?.after.ref.update({
      lastModified: new Date(),
    });                                  // ← re-triggers itself!
  }
);
Ez minden frissítésnél megint megírja a dokumentumot, ami megint elindítja a triggert. A Cloud Functions ezt 5-10 másodpercen belül „felfutáskorlátba" futtatja, de addigra már több ezer hívás megtörtént. Egy óra alatt akár több tízezer felesleges futás.

Megoldás 1: változás-figyelés

change-detection.tstypescript
export const updateTimestamp = onDocumentUpdated(
  'orders/{orderId}',
  async (event) => {
    const before = event.data?.before.data();
    const after = event.data?.after.data();

    // Only update when something "real" actually changed
    if (before?.status === after?.status &&
        before?.total === after?.total) {
      return;                              // do nothing
    }

    await event.data?.after.ref.update({
      lastModified: new Date(),
    });
  }
);

Megoldás 2: különálló kollekció

Ha a meta-adatot (időbélyeg, aggregátum) egy másik kollekcióba írod, akkor sosem indítja önmagát újra. Pl. a rendelés főadatai a /orders-ben, a számlált aggregátumok a /orderStats-ban:
separate-collection.tstypescript
export const updateOrderStats = onDocumentWritten(
  'orders/{orderId}',
  async (event) => {
    // Writes to a different collection — won't re-trigger
    await db.doc('orderStats/global').set({
      lastChangeAt: new Date(),
      total: FieldValue.increment(1),
    }, { merge: true });
  }
);

Trigger-lánc: hosszú láncok

Néha egy folyamat több lépésből áll: új rendelés → email a felhasználónak → értesítés az adminnak → riport-aggregátum frissítése. Ezt nem érdemes egyetlen monolit függvénybe írni — minden lépés saját trigger-rel jobban tesztelhető és hibatűrőbb.
LépésIndítóMit csinál
1onDocumentCreated('orders/{id}')Visszaigazoló email küldése
2onDocumentCreated('orders/{id}')Admin értesítés (push, Slack)
3onDocumentWritten('orders/{id}')Aggregátum frissítése
4onSchedule('every 24 hours')Napi statisztika
Több, kisebb függvény gyorsabban indul, hideg indulása rövidebb, és külön-külön újrapróbálható, ha hiba van.
Mit tanulsz
  • cloud-functionsKözéphaladó

09 — Hibakezelés

Hibakezelés és újrapróbálkozás

A Cloud Functions egy elosztott rendszer, ahol minden hívás bárhol és bármikor elhasalhat — hálózati hiba, külső API timeoutja, OOM, közben a függvényt frissítik. A jó kód elviseli ezt, és gondoskodik róla, hogy a fontos műveletek mindenképp végrehajtódjanak.

A function típus határozza meg a viselkedést

TípusHibakezelés alapbólÚjrapróba
HTTP (onRequest, onCall)A kliens kapja meg a hibátNincs (a kliensnek kell újrapróbálni)
Esemény-trigger (Firestore, Storage, Auth)Visszaadod a hibát, az event érkezik újraIgen, max 7 napon át
Pub/SubVisszaadott hiba esetén újraIgen, beállítható ack-deadline
Időzített (Scheduler)Hiba → következő futás várhatóCsak a következő ütemben

Idempotencia — kötelező esemény-triggereknél

Mivel az esemény-trigger akár többször is lefuthat ugyanarra az eseményre (újrapróbálkozás miatt), a függvénynek idempotensnek kell lennie — kétszeri futás ugyanazt eredményezi, mint egyszeri.
idempotent.tstypescript
// BAD — every run bumps the counter
export const countOrder = onDocumentCreated(
  'orders/{id}',
  async (event) => {
    await db.doc('stats/orders').update({
      count: FieldValue.increment(1),       // ← runs twice → counts twice
    });
  }
);

// GOOD — uses event.id to prevent duplication
export const countOrder = onDocumentCreated(
  'orders/{id}',
  async (event) => {
    const processedRef = db.doc(`processed/${event.id}`);
    const alreadyDone = (await processedRef.get()).exists;
    if (alreadyDone) return;             // already handled

    await db.runTransaction(async (tx) => {
      tx.update(db.doc('stats/orders'), {
        count: FieldValue.increment(1),
      });
      tx.set(processedRef, { at: new Date() });
    });
  }
);

Strukturált logok

A Cloud Functions logjai a Google Cloud Logging-ban vannak — de a sima console.log kimenete csak szöveges, nehezen kereshető. A firebase-functions/logger strukturált JSON-t ír, amiben szűrhetsz mezőkre:
structured-logging.tstypescript
import { logger } from 'firebase-functions/v2';

logger.info('New order received', {
  orderId: order.id,
  userId: order.userId,
  total: order.total,
});

logger.error('Stripe API error', {
  err: error.message,
  orderId: order.id,
});
A Cloud Logging-ban kereshetsz jsonPayload.orderId="ord_123"-ra, és pontosan azt a hívást kapod meg.
Mit tanulsz
  • emulatorKözéphaladó

10 — Tesztelés

Tesztelés helyben emulátorral

A felhőbe való deploy lassú és drága. A Firebase Emulator Suite a teljes platformot helyben futtatja — Firestore, Auth, Functions, Storage, Pub/Sub. A fejlesztő számára ez azt jelenti, hogy a függvény változtatása után pár másodperc alatt látszik az eredmény, és nem kell az élesben kísérletezni.

Az emulátor indítása

terminalbash
# One-time setup
$ firebase init emulators

# Start (Functions + Firestore + Auth)
$ firebase emulators:start

# Just one specific emulator
$ firebase emulators:start --only functions,firestore
Az emulátor ad egy webes felületet (alapból http://localhost:4000), ahol látod a futó függvényeket, a Firestore tartalmát, az Auth-felhasználókat. A logok valós időben jönnek a terminálban.

Mire jó és mire nem?

  • Jó: üzleti logika tesztelése, trigger-láncok kipróbálása, hibakezelés, idempotencia ellenőrzése. A tesztek gyorsak, és nem kerül semmibe.
  • Kevésbé jó: a teljesítmény-mérés (a helyi gép másképp mér), és néhány Google Cloud-szolgáltatás (mint az Eventarc) nem 100%-ban emulált.

Egységtesztek a függvényekhez

A firebase-functions-test SDK-val unit-tesztet is írhatsz, ami nem indít emulátort, csak a függvényt magát teszteli, mock kontextussal:
setUserRole.test.tstypescript
import { initializeApp } from 'firebase-functions-test';
import { setUserRole } from './index';

const testEnv = initializeApp();

describe('setUserRole', () => {
  it('an admin user can set the role', async () => {
    const wrapped = testEnv.wrap(setUserRole);
    const result = await wrapped({
      data: { uid: 'usr_abc', role: 'editor' },
      auth: { uid: 'admin_uid', token: { role: 'admin' } },
    });
    expect(result.success).toBe(true);
  });
});
CI integráció
A Firebase Emulator Suite CI-ben is futtatható (GitHub Actions, GitLab CI). A firebase emulators:exec "npm test" parancs elindítja az emulátort, lefuttatja a teszteket, majd leállítja. Egy átlagos projektnél a CI 1-2 perc alatt végez.
Mit tanulsz
  • cloud-functionsKözéphaladó

11 — Anti-minták

Hat klasszikus hiba

A Cloud Functions önmagában elég megengedő — egy rosszul megírt kód is „működik", de a számlán és a teljesítményen meglátszik. Itt a leggyakoribb buktatók, amik csak akkor derülnek ki, amikor már fáj.

1) A „mindenes" függvény

Egy 800 soros onRequest, ami az egész app backend-jét egyetlen függvényben kezeli (Express-es route-okkal belül). Hideg indulása lassú, hibakeresés nehéz, és a partikuláris szolgáltatás-szintű kontroll (memória, régió) elveszik. Bontsd külön logikus egységekre.

2) Túl sok kis függvény

A másik véglet: 50 független Cloud Function, mindegyik egy kis műveletet csinál. Mindegyiknek külön hideg indulása, külön deploy ciklusa, külön logja. Ennek nincs értelme — ami logikailag összetartozik, az legyen egy függvényben.

3) Szinkron várakozás külső API-ra

anti-3.tstypescript
// BAD — the user waits, the function pays
export const placeOrder = onCall(async (request) => {
  const order = await createOrder();
  await sendConfirmationEmail(order);     // 5 sec
  await notifySlack(order);                // 3 sec
  await syncToCRM(order);                  // 8 sec
  return order;
});

// GOOD — returns fast; the rest runs in the background via a Firestore trigger
export const placeOrder = onCall(async (request) => {
  const order = await createOrder();
  return order;                          // 200 ms
});

4) Nincs idempotencia esemény-triggereknél

Lásd a 9. szekciót — már 3 dollár kárt okozhat, mire észreveszed.

5) Globális állapot a párhuzamos kérések közt

Lásd a 6. szekciót — race condition, ami nehezen reprodukálható.

6) Élesben tesztelés

Ha a teszt a tényleges Firestore-ra vagy a tényleges felhasználói rekordokra fut, akkor egyszer véletlenül letörli az ügyfél adatait. Az emulátor nem opcionális, hanem alap. Egy elkülönített „dev" Firebase projekt is opció — kis költségű, és minden Firebase-szolgáltatást támogat.

A Cloud Functions akkor jó, ha minden függvény egy fókuszált feladatot végez, és a hibák utáni tisztítást automatikusan megoldja. Aki erre nem figyel, az fél év múlva 200 különböző függvény és kiszámíthatatlan számla között találja magát.

12 — Konklúzió

Mire való a Cloud Functions, és mire nem?

A Cloud Functions a Firebase szerveroldali ragasztóanyaga: kis, fókuszált függvények, amik eseményekre reagálnak vagy HTTP-kérést szolgálnak ki. Nem helyettesíti egy teljes backend-rendszer, de a Firebase ökoszisztémában szinte minden szervert igénylő feladatra ez az első választás.

Amire jó

  • Adat-reakciók (Firestore, Storage, Auth eseményekre).
  • Webhookok fogadása külső szolgáltatásoktól.
  • Privilegizált műveletek, amiket a kliens nem futtathat.
  • Időzített háttér-feladatok (napi statisztika, heti emlékeztető).
  • Kis-közepes API-réteg a frontend és a backend-szolgáltatások közt.

Amire nem

  • Tartós háttér-feladatok (1 órán túli) — Cloud Run jobbnak való.
  • Hosszú életű kapcsolatok (WebSocket, server-sent events) — más szolgáltatás kell.
  • Magas fenntartható forgalom alacsony válaszidővel — Cloud Run + min példányszám olcsóbb lehet.
  • Egy nagy „backend monolit" — több, de logikus függvénybe bontva érdemes.

Tervezési checklist élesítés előtt

  • v2 generációt használok, kivéve ahol kifejezetten v1 trigger kell (pl. Auth onCreate).
  • Minden függvénynek be van állítva a régiója (EU-s ügyfeleknél europe-west1 vagy europe-west3).
  • A globális inicializációk (Firebase Admin, külső kliensek) a fájl tetején vannak — nem a függvényben.
  • Az érzékeny adatok (API-kulcsok, jelszavak) a Secret Manager-ben vannak, nem a kódban.
  • A felhasználó-szembesülő API-knak van minInstances: 1 beállítása, ha a hideg indulás zavaró.
  • A memória és időtúllépés a tényleges használatra van mérve, nem találgatva.
  • Az esemény-triggerek idempotensek — kezeli az event.id-t.
  • A trigger-láncok nem önmagukat indítják (változás-figyelés, vagy különálló kollekció).
  • Strukturált logokat használok, ahol a fontos azonosítók (orderId, userId) JSON-mezőként vannak.
  • Az App Check be van kapcsolva minden kliens-szembesülő onCall függvényen.
  • A hosszú folyamatok Pub/Sub-bal vagy Cloud Tasks-szal vannak feldarabolva, nem egyetlen függvényben futnak.
  • Az emulátor-tesztek a CI-ben minden push-on lefutnak, és a deploy csak zöld CI mellett történik.

A Firebase tudásbázis-cikkek sora itt egy szakaszhoz ér — az adatkezelés (Firestore, SQL Connect), a biztonság (Security Rules, Auth), a kiszolgálás (Hosting), és a szerveroldali logika (Cloud Functions) mind át lett tekintve. A következő cikkekben a kiegészítő szolgáltatások jönnek: a Storage a fájlokra, az FCM a push-értesítésekre, az App Check a csaló-védelemre, és néhány konkrét architektúra-példa egy teljes SaaS-projekten keresztül.

Vissza az elejére