CloudFront A/B Testing Without Cache Fragmentation: The Shadow Origin Pattern

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    CloudFront A/B Testing Without Cache Fragmentation: The Shadow Origin Pattern

    In AWS architectures, there is a silent trade-off that often goes unnoticed—until your AWS bill spikes or your latency graphs degrade:


    Personalization vs. Cache Efficiency


    The common approach to A/B testing—executing logic in a viewer-request hook and varying the cache key using cookies—seems convenient, but it is an architectural trap.


    By introducing variants into the cache key, you:
    • Fragment your global edge cache
    • Destroy your Cache Hit Ratio (CHR)
    • Increase S3 origin load and egress costs
    • Reintroduce cold-start latency for users


    There is a better way.





    🧠 The Solution: The Shadow Origin Pattern

    The Shadow Origin Pattern is a technique where the public request URI remains unchanged, while the origin fetch path is internally rewritten after a cache miss. Instead of exposing variants to the CDN cache key, we move the decision behind the cache layer.





    1. The Architecture: Performance-First Routing

    The goal is simple: Do not fragment the cache key at the edge.


    How it works

    • Public Request (Cache Key): /shop
    • Internal Origin Fetch (after cache miss): /variants/B/shop


    This is achieved using a Lambda@Edge origin-request hook, which runs only after CloudFront determines the object is not in cache.


    Why this is powerful

    • The cache key remains clean and stable.
    • Variants are resolved internally.
    • Each variant is cached efficiently once fetched.
    • No unnecessary duplication of edge cache entries.





    2. The Implementation: The “Shadow” Hook

    This Lambda@Edge function acts as a precise traffic controller.






    /**
    * Lambda@Edge: Origin Request Trigger
    * Goal: Internal URI mutation for high-CHR A/B Testing
    */
    'use strict';

    // Remove this line in AWS production
    exports.hookType = 'origin-request';

    exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const { headers } = request;

    // 1. Determine User Segment
    let variant = 'control';

    if (headers.cookie) {
    for (let i = 0; i headers.cookie.length; i++) {
    if (headers.cookie[i].value.includes('X-Variant=B')) {
    variant = 'B';
    break;
    }
    }
    }

    // 2. Internal Path Mutation
    if (variant === 'B') {
    request.uri = `/variants/B${request.uri}`;
    }

    // 3. S3 OAC Normalization
    if (request.uri.endsWith('/')) {
    request.uri += 'index.html';
    }

    return request;
    };








    💡 Save this file as `ab-testing.js`





    ⚠️ Critical AWS Configuration (Do Not Skip)

    This function runs at the origin-request stage. Unlike viewer-request, cookies are not automatically available. You must configure CloudFront to forward them:

    1. Cache Policy: Cookies -> Include (or whitelist X-Variant).
    2. Origin Request Policy: Cookies -> Include (or whitelist X-Variant).


    If missed, your logic silently defaults to variant = 'control', and your A/B test will appear broken.





    3. The Fidelity Gap: Why Testing is Critical

    The biggest barrier to adopting this pattern is what we call the Fidelity Gap. Everything happens inside AWS infrastructure; your browser "lies" to you because it only sees the public URI.


    This leads to the classic (and painful) workflow: Deploy → Wait 20 minutes → Discover a 403 → Repeat.


    Closing the Gap with Local Simulation

    To eliminate this blind spot, we can simulate the environment locally using CloudFrontize.


    Step 1: Mock the S3 Origin

    Recreate your production structure in a local /public folder:






    /public
    ├── index.html
    └── variants/
    └── B/
    └── index.html








    Step 2: Run the CloudFrontize simulator

    Emulate Lambda@Edge behavior locally with the --debug flag to see the "Invisible" rewrite:






    cloudfrontize ./public --edge ./ab-testing.js --debug








    Step 3: Verify the Invisible Rewrite

    1. Open http://localhost:3000/.
    2. Set the variant cookie in the browser console:




    document.cookie = "X-Variant=B";
    location.reload();








    What you should see in the terminal:






    [origin-request] Original URI: /
    [Debug] Rewriting URI: / -> /variants/B/index.html








    Your browser still shows /, but the content has changed. The Shadow Origin Pattern is working.





    4. Professional Gotchas (Hard Lessons)

    • No Environment Variables: Lambda@Edge does not support process.env. Your function must be self-contained.
    • The 40KB Limit: Large cookies can break your function unexpectedly.
    • Cache Invalidation: Invalidating /shop does not invalidate /variants/B/shop. You must invalidate both.
    • Regex Latency: On high-traffic sites, complex regex adds latency. Prefer simple string operations.





    🧾 Summary

    High-performance edge architectures require discipline: keep the cache key clean and move logic behind the cache layer. By applying the Shadow Origin Pattern and validating it locally with CloudFrontize, you eliminate the deployment "black box" and gain full control over your edge behavior.





    Next Step: Are you ready to bridge the fidelity gap? Check out the CloudFrontize GitHub Repository for more edge patterns.




    More...
Working...