Engineering for the Ephemeral: How to Handle Short-Lived Data on Cloud Run Without Building an Accidental Document Archive

In modern software development, a silent data accumulation crisis unfolds daily. A routine customer support ticket with an attached screenshot, a single-use print job submitted at a local kiosk, or a one-time document verification upload all share a common lifecycle requirement: they only need to exist until the task is complete. Yet, conventional backend architectures routinely treat these transient inputs like enterprise assets. They land in cloud object storage with permanent retention policies, write descriptive records into relational databases, and leave behind searchable digital breadcrumbs that linger for years.

This habit of permanent storage transforms mundane queues into accidental document libraries. It bloats storage budgets, complicates regulatory compliance, and needlessly expands the attack surface for potential data breaches. For independent developers and enterprise architects alike, managing temporary files requires a deliberate shift away from traditional, accumulation-heavy design patterns.


The Architecture of the Ephemeral

To understand how to keep a file for minutes rather than years, developers must rethink the standard serverless pipeline. A robust production-tested pattern—utilized in real-world infrastructure like the Indian print relay service Docshy—relies on a lean, serverless architecture deployed on Google Cloud Platform (GCP). The underlying design principles, however, are universally applicable across AWS, Azure, or private cloud environments.

Client
  |
  |  HTTPS
  v
Cloud Run (One Node.js Service)
  |-- Cloud Storage     (Cipher-text Object)
  |-- Cloud KMS         (Wraps the Per-File Key)
  |-- Redis             (Job JSON with a Time-To-Live [TTL])
  |
Authorized Caller  <--  (Stream, then Delete)

At its core, the contract is deceptively simple:

  1. Accept raw bytes securely from an authorized client.
  2. Hold them in an encrypted, temporary state.
  3. Hand them off to an authorized downstream processor.
  4. Purge all traces immediately upon completion.

Success, in this architecture, is an empty bucket rather than a searchable archive. Achieving this demands strict adherence to specific architectural boundaries, rejecting the temptation to scale out microservices prematurely, and utilizing ephemeral memory structures.


One Service, Not a Fleet: Consolidating Compute

A file destined to vanish in twenty minutes does not require an always-on virtual machine fleet, a dedicated Kubernetes cluster, or complex container orchestration.

By consolidating both the frontend web interface and the backend API into a single Cloud Run service, developers align infrastructure costs directly with actual usage. Cloud Run bills CPU cycles strictly when requests are actively being processed. When user traffic drops to zero, the infrastructure scales down to zero, eliminating the cost of idle compute resources.

However, this consolidation introduces a classic distributed systems trap: horizontal scaling. Because Cloud Run can spin up multiple container instances dynamically, relying on local instance memory for job queues leads to catastrophic failures. If Instance A accepts a file upload, but a subsequent user refresh routes the client to Instance B, the job appears to vanish entirely—or worse, triggers erratic behavior in the processing queue.

Enforcing Distributed State and Fail-Closed Health Checks

To prevent state fragmentation, engineers must externalize the queue state entirely using a centralized, in-memory data store like Redis. Furthermore, health checks must be designed with a fail-closed philosophy.

If the Redis cluster experiences an outage, the container must not silently fall back to local disk storage on whichever replica happened to receive the request. Instead, the service must report unhealthiness immediately.

app.get("/up", (_req, res) => 
  // Confirms the container process is alive
  res.status(200).send("ok");
);

app.get("/health", async (_req, res) => 
  // Confirms critical dependencies are reachable
  const redisUp = await pingRedis();
  if (!redisUp) return res.status(503).send("queue down");
  res.status(200).send("ok");
);

Load balancers and platform probes should target the /health endpoint to ensure traffic is never routed to a degraded instance incapable of managing the shared queue accurately.


Eradicating Metadata: Give Objects Boring Names

One of the primary drivers of architectural drift is semantic object naming. When an application stores an uploaded document under a descriptive key such as customer-tax-return-john-doe.pdf, it has effectively constructed a filing cabinet. Inevitably, the next feature request will demand a search bar, followed by an administrative dashboard, transforming a temporary relay into a permanent repository.

To enforce ephemerality, object keys must be entirely devoid of semantic meaning. Files should be stored under high-entropy, cryptographically random identifiers:

const  randomUUID  = require("crypto");

function generateSecureObjectName() 
  return `jobs/$randomUUID().bin`;

Original human-readable filenames, if strictly required for downstream processing, should be stored ephemerally within the job’s transient metadata record rather than embedded in the cloud storage path. Additionally, applications must enforce hard limits on ingestion volume per session—such as restricting a user to a maximum of five files before requiring a fresh session token. Unlimited uploads turn a functional relay into an unmonitored digital junk drawer.


Encryption and Transparency: Securing Bytes Without Accumulating Liability

Discussions regarding data privacy often conflate two distinct security models: zero-knowledge end-to-end encryption (where the server has zero visibility into the payload) and secure transit encryption (where an authorized intermediary must inspect or process the bytes).

For use cases involving print relays, document previews, or single-use downloads, the server must unwrap and validate the payload. Skipping input validation—such as failing to check magic bytes on ingestion—invites malicious uploads that can compromise downstream systems.

A pragmatic, secure storage pipeline balances cryptographic protection with necessary operational visibility:

const crypto = require("crypto");

function encryptForStorage(plainBuffer) 
  // Generate a unique data key per file
  const dataKey = crypto.randomBytes(32);
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", dataKey, iv);

  const ciphertext = Buffer.concat([cipher.update(plainBuffer), cipher.final()]);
  const tag = cipher.getAuthTag();

  return  ciphertext, iv, tag, dataKey ;

By wrapping the generated dataKey via Cloud Key Management Service (Cloud KMS) and persisting only the encrypted ciphertext, the application ensures that raw data at rest remains unreadable. The inclusion of AES-GCM authentication tags is critical: if a bit is intentionally or accidentally flipped within the object store, decryption fails gracefully rather than returning a corrupted, partially rendered file to an end-user.


Volatile Queues: Leveraging Time-To-Live (TTL)

While object storage holds the encrypted binary payloads, Redis manages the active job lifecycle through native Time-To-Live (TTL) configurations.

const JOB_TTL_SECONDS = 60 * 60; // 1-hour lifecycle

async function saveTransientJob(redis, job) 
  await redis.set(
    `job:$job.id`,
    JSON.stringify(
      id: job.id,
      objectName: job.objectName,
      shopId: job.shopId,
      status: "queued",
      createdAt: Date.now()
    ),
    "EX",
    JOB_TTL_SECONDS
  );

A one-hour TTL is not an arbitrary constraint; it mirrors the real-world duration of a physical transaction. A print job or a temporary document verification does not warrant database indexing for auditing next year. When the TTL expires, the job record vanishes automatically from Redis, severing the navigational link to the underlying binary.


Intentional Deletion and Automated Sweepers

Relying solely on automated TTL expiration is insufficient for robust systems architecture. Users close browser tabs unexpectedly, network connections drop mid-transfer, and asynchronous tasks fail. Consequently, applications must implement an explicit deletion sequence followed by a background sweep routine.

When an authorized downstream caller successfully consumes a payload, the cleanup sequence must execute in a precise order:

async function completeJob( storage, redis, bucket, job ) 
  // 1. Delete the binary object first
  await storage.bucket(bucket).file(job.objectName).delete( ignoreNotFound: true );

  // 2. Delete the pointer record second
  await redis.del(`job:$job.id`);

Deleting the object storage file before removing the Redis pointer ensures that if the process crashes midway, the orphan record can still be resolved. Conversely, deleting the pointer first while leaving the object behind creates unmapped, orphaned files that bloat storage costs indefinitely.

To catch orphaned anomalies caused by dropped sessions, a distributed locking sweeper should run at regular intervals:

async function executeSweepWithLock(redis, cleanupTask) 
  // Acquire a distributed lock valid for 55 seconds
  const acquired = await redis.set("lock:sweep", "1", "NX", "EX", 55);
  if (acquired !== "OK") return; // Another worker is already sweeping

  try 
    await cleanupTask();
   finally 
    await redis.del("lock:sweep");
  

Relational databases like PostgreSQL or managed document stores like Firestore should be strictly reserved for structural metadata—such as merchant accounts, operational usage counters, and billing ledgers. Storing binary payloads alongside customer accounts transforms a transactional application into an unintended content repository.


Architectural Commitments and Future Outlook

Adopting an ephemeral data architecture requires clear operational boundaries. Engineering teams must codify these limitations directly into project documentation to manage stakeholder expectations:

  • No Long-Term Archival: Files are automatically purged after the designated TTL elapses; recovery of expired jobs is cryptographically and operationally impossible.
  • Ephemeral Storage Boundaries: Cloud object storage functions exclusively as a temporary transit buffer, not a backup vault.
  • Strict Payload Validation: Ingested files undergo immediate cryptographic wrapping and schema validation; malformed or malicious inputs are rejected at the edge.

By embracing this paradigm, developers can successfully build high-performance, cost-effective serverless applications that respect user privacy by design. A well-engineered system does not merely process data efficiently—it knows precisely when to let it go.

Back To Top