A Global Problem

For a platform serving billions of daily requests from a worldwide user base, the laws of physics eventually become the bottleneck. Even a perfectly optimized backend in a single region cannot overcome the latency imposed by distance. This was the exact challenge facing Picsart's remote configuration and experimentation platform, a critical service that determines how the app looks and behaves on every single launch.

The service is a data-driven engine: product managers and researchers segment users into test groups to test feature variations, designs, and promotions. On each app start, a unique, uncachable request is sent to the backend to fetch the appropriate configuration. For mobile clients, that response is a hefty 1.5 MB file. Crucially, this request is blocking and on the hot path. The app waits only 1500ms before falling back to default settings, meaning a slow response results in a user being excluded from the experiment entirely.

Before any architectural changes, half of all devices failed to complete this transaction in time. The problem was compounded by the request's nature: it depends on dozens of user attributes, thousands of variations, and live experiment metadata, making each request unique and impossible to cache at the edge.

Diagnosing the Delay

The Picsart team analyzed the full request lifecycle, which involves three distinct steps:

  1. The request travels from the device to a centralized backend.
  2. The server processes the request using user attributes and config metadata to build the correct response.
  3. The response traverses the network back to the device.

The processing itself was not the culprit. The backend service was already highly optimized, taking only 30 milliseconds to handle each of the billions of monthly requests. The real issue was network latency, particularly volatile last-mile connections on mobile. The data confirmed this: users geographically closer to the origin server had a much higher success rate, while those out of region were far more likely to miss the 1500ms budget.

Weighing the Architectural Options

The obvious solution was to replicate the existing cloud infrastructure across multiple regions with global load balancing. However, this path introduces significant operational and financial overhead. Beyond the cost of extra compute and database clusters, there is the complexity of cross-region data replication and the ongoing burden of monitoring and maintaining infrastructure in several places. For a technical team, this cognitive load translates directly into slower development velocity.

Instead, Picsart turned to Cloudflare's Developer Platform. With Workers providing compute and Workers KV offering globally distributed storage across 310+ cities, the platform promised to shrink the physical distance between users and the experimentation service. The global-by-default deployment model also meant minimal operational overhead compared to running a multi-region setup, with no additional data distribution fees.

Measuring Real-World Performance

The primary objective was to increase the percentage of devices receiving their configuration within the strict time budget. While synthetic testing has its uses, the Picsart engineers devised a more realistic method for tracking progress. They had already built a testing endpoint into their web and mobile apps that fires a duplicate request to a new service, silently discarding the response and swallowing any errors. This pattern allows data collection from real user traffic without any impact on performance or reliability.

// Simplified version of the pattern used to shadow-test new endpoints
try {
  const response = await fetch(newEndpoint);
  // Discard the response body
  await response.arrayBuffer();
} catch (error) {
  // Swallow errors – no impact on the production client
}

Using existing analytics tools, the team could observe the new service from day one. They started with a v0 dummy endpoint that returned a 'hello world' response but simulated the real workload by reading multiple values from KV and generating a payload of realistic size. This shadow-testing methodology provided instant, real-user data on the new architecture's viability before any significant logic was migrated.

Rethinking the Data Model

When Picsart first moved its experimentation service to Cloudflare Workers, the results were unexpectedly poor. Despite placing compute closer to end users, requests ran slower than in the previous regional setup. The investigation traced the problem back to the way data was structured and loaded.

The original service relied on a single, stateful configuration blob exceeding 100MB. In a long-running VM, that data could be loaded into memory once and served quickly thereafter. But Worker isolates have short lifespans, so every cold start meant re-parsing that massive JSON payload. Fitting even a fraction of that data into KV was impractical given KV's 25MB per-value limit.

Segmenting Configuration by Context

The core inefficiency was that the service loaded configuration for every platform and geography on every request, even though only a small subset applied to any given user. A request from a US user on Android, for instance, had no use for configurations targeted at European iOS users.

The team addressed this by splitting the configuration into separate KV records per platform and country. The system now fetches only the record relevant to the requesting user's context — for example, Android users in the US. This produced roughly 600 KV records, each capped at 10MB. While this introduced storage duplication, it dramatically cut the data parsed per request. Because Cloudflare operates in over 120 countries, only a subset of records is needed in any single location, and over 99.5% of KV reads are now served from local cache.

Key Size
com.picsart.studio_apple_us.json 6.1MB
com.picsart.studio_apple_de.json 6.1MB
com.picsart.studio_android_us.json 5.9MB

This shift from a regional cloud to Cloudflare's globally distributed network brought two major wins. First, performance improved because only the necessary data is fetched and parsed based on the user's platform and location. Second, scalability became simpler: adding support for a new app or region requires inserting new standalone KV records rather than growing a single monolithic file.

Immutability for Faster Updates

KV performs best when records are read far more often than they are written. Frequent updates force short cache TTLs, which hurts cache hit ratios and slows reads in low-traffic regions. For Picsart's experimentation feature flags, even KV's minimum cache TTL of 60 seconds was too slow for propagating configuration changes.

The solution was to make records immutable. Instead of updating an existing KV entry, the team writes a new record on every configuration change, appending a content hash to the key. Since these records never change, they can safely use longer cache TTLs without risking stale data.

Key TTL
com.picsart.studio_apple_us_b58b59.json 86400s
com.picsart.studio_apple_us_273678.json 86400s
-

This required a way to track which key is current. The team stores references to the latest KV keys in the Worker's environment variables. Each configuration change writes a new KV pair and updates those environment variables. Since global Workers deployments propagate in seconds, configuration updates become near-instantaneously available worldwide.

Avoiding Unnecessary JSON Parsing

A closer look at request handling revealed that only a small fraction of the configuration data — the metadata used to assign experiments — was actually needed during processing. The rest consisted of JSON values destined for the response payload but not required for the core logic.

The initial implementation used KV's get() method with type="json", which parses the entire value into an object on every read. This is computationally expensive compared to retrieving the value as plain text. Most of those CPU cycles went into serializing JSON that the business logic never touched.

The team redesigned the storage layout once more, splitting each record into two parts: a small metadata record of about 300KB (parseable into an object quickly) and a larger text record of around 9.7MB containing configuration values delimited by newline characters. The metadata entry references specific line numbers in the text file, enabling the service to merge the correct values back into the response later.

{

  "name": "shape_replace_items",

  "default_value": "<large json object>",

  "segments": [

    {

      "id": "f1244",

      "value": "<Another json object json object>"

    },

    {

      "id": "a2lfd",

      "value": "<Yet another large json object>"

    }

  ]

}

Before: Metadata and Values in one JSON object (simplified)

// com.picsart.studio_apple_am_metadata


1 {

2   "name": "shape_replace_items",

3   "default_value": 1,

4   "segments": [

5     {

6       "id": "f1244",

7       "value": 2

8     },

9     {

10       "id": "a2lfd",

11      "value": 3

12     }

13   ]

14 }

// com.picsart.studio_apple_am_values


1 "<large json object>"

2 "<Another json object>"

3 "<Yet another json object>"

After:  Metadata and Values are split (simplified)

At request time, the service parses only the small metadata JSON to determine experiment variants. It constructs the response JSON string with placeholders referencing line numbers in the text file, then replaces those placeholders with the corresponding serialized values. This avoids parsing and re-serializing large JSON objects altogether. Parsing the metadata and loading the configuration values file run in parallel, saving additional milliseconds.

Client-Side Optimizations in Parallel

The server-side changes — moving from a single-region setup to Cloudflare's global architecture — cut median response times globally by more than a second. That alone was a major improvement, but the data revealed two further opportunities on the client side.

Both the web and mobile apps call the service at startup, when no active connection exists. Establishing that connection at request time costs valuable milliseconds. For the web app, adding a preconnect header on page load helped. The mobile team went further: analysis showed that three modules — the error tracker, HTTP client, and SDK — had to initialize before a connection could be made. Reordering them to initialize the HTTP client first allowed the connection to be established while the SDK and error tracker were still loading, shaving another 200ms off the user experience.

Results from the Rollout

The phased rollout — web first, then mobile — was a success. The dashboards showed billions of requests handled smoothly, and the share of successfully delivered experiments rose from 50% to 85%. Median response times dropped from 1500ms to 280ms overall, and to 70ms on the web, where response payloads are smaller.

BLOG-2274 Embedded Image - lIjAAc

For Picsart, the faster service translates directly into more personalized, data-driven experiences for a larger share of its user base.

Next Steps with Cloudflare

The team is already planning the next evolution of its experimentation platform. The goal is to use Durable Objects to store hundreds of millions of user data records in a decentralized manner, keeping user data in-region near the edge. That architecture would enable more powerful experiments without the performance penalty of centralized data access.

Beyond internal use, Picsart's experimentation team is also preparing to offer the platform to external B2B customers, relying on Cloudflare's scale and global network to absorb the additional traffic and data load. Further guidance on building similar services is available in Cloudflare's developer documentation and tutorials, with community support on the Cloudflare Discord server.