Parallel old and new site operation with A/B migration

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

A/B Migration Implementation: Parallel Operation of Old and New Site

A/B migration is an approach where old and new sites work simultaneously. Traffic is gradually switched from one to the other. This allows you to detect issues on a small audience before full switchover.

A/B Migration Architecture

DNS/CDN → Load Balancer (nginx/Cloudflare)
                 ↓
    ┌────────────┴────────────┐
    │                        │
Old Site (90%)          New Site (10%)
old-site:8080          new-site:8081

Critically important: both systems must work with a single database (or synchronize data in real-time).

Option 1: Nginx Weighted Upstream

upstream site_upstream {
    server old-site:8080 weight=9;
    server new-site:8081 weight=1;  # 10% of traffic
}

server {
    listen 80;
    server_name company.com;
    proxy_pass http://site_upstream;
}

Gradual switchover: 10% → 25% → 50% → 90% → 100% with intervals of several days.

Option 2: Cookie-based Routing (Stable Experience)

A user who lands on the new site stays on it for subsequent visits:

split_clients "${remote_addr}${http_user_agent}" $new_site_user {
    10% "yes";   # 10% of users → new site
    *   "";
}

server {
    set $upstream_server "old-site:8080";

    # If already marked — route to new
    if ($cookie_site_version = "new") {
        set $upstream_server "new-site:8081";
    }
    # If fell into 10% — route to new and set cookie
    if ($new_site_user = "yes") {
        set $upstream_server "new-site:8081";
        add_header Set-Cookie "site_version=new; Path=/; Max-Age=86400; SameSite=Lax";
    }

    proxy_pass http://$upstream_server;
}

Option 3: Cloudflare Workers for Flexible Routing

// cloudflare-worker.js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

async function handleRequest(request) {
  const url = new URL(request.url)
  const cookie = request.headers.get('Cookie') || ''

  // Already has version
  if (cookie.includes('site_version=new')) {
    return fetch(NEW_SITE_URL + url.pathname + url.search, request)
  }
  if (cookie.includes('site_version=old')) {
    return fetch(OLD_SITE_URL + url.pathname + url.search, request)
  }

  // Distribute new users (hash by IP)
  const clientIP = request.headers.get('CF-Connecting-IP') || ''
  const hash = await hashString(clientIP)
  const percentage = 10  // 10% to new site
  const useNew = (hash % 100) < percentage

  const targetUrl = useNew ? NEW_SITE_URL : OLD_SITE_URL
  const response = await fetch(targetUrl + url.pathname + url.search, {
    ...request,
    headers: request.headers
  })

  const newHeaders = new Headers(response.headers)
  const cookieValue = useNew ? 'new' : 'old'
  newHeaders.append('Set-Cookie', `site_version=${cookieValue}; Path=/; Max-Age=86400; SameSite=Lax`)

  return new Response(response.body, {
    status: response.status,
    headers: newHeaders
  })
}

Data Synchronization Between Sites

If old and new sites have different databases, data must be synchronized:

# Webhook on old site when content is created/updated
def on_content_updated(post_id, event_type):
    payload = serialize_post(post_id)

    # Send to new site
    requests.post(
        'http://new-site-internal/api/sync/content',
        json={
            'event': event_type,  # 'created', 'updated', 'deleted'
            'data': payload,
            'timestamp': time.time()
        },
        headers={'X-Sync-Key': SYNC_SECRET}
    )

Or via shared queue:

# RabbitMQ/Redis Streams as message bus between sites
publisher.publish('content.updated', {
    'legacy_id': post_id,
    'action': 'update',
    'data': post_data
})

Monitoring During A/B Migration

Compare metrics of both versions:

# Grafana dashboard: Old vs New side by side
panels:
  - title: "Response Time (p95)"
    queries:
      - {expr: "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{app='old-site'}[5m]))", legend: "Old Site"}
      - {expr: "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{app='new-site'}[5m]))", legend: "New Site"}

  - title: "Error Rate"
    queries:
      - {expr: "rate(http_requests_total{app='old-site',status=~'5..'}[5m])", legend: "Old Errors"}
      - {expr: "rate(http_requests_total{app='new-site',status=~'5..'}[5m])", legend: "New Errors"}

Alert on new site anomaly:

- alert: NewSiteErrorSpike
  expr: |
    rate(http_requests_total{app="new-site",status=~"5.."}[2m]) >
    2 * rate(http_requests_total{app="old-site",status=~"5.."}[2m])
  for: 3m
  annotations:
    summary: "New site error rate 2x higher than old site"

Full Switchover Criteria

Before moving 100% traffic to the new site:

  • Error rate ≤ error rate of old site
  • p95 response time ≤ p95 of old site
  • No regression in conversion (by GA4/Metrika)
  • All critical functions tested on real users
  • Team ready for quick rollback

Rollback

# Instant rollback via nginx
# Change weights: old-site weight=10, new-site weight=0
nginx -s reload

# Or via Cloudflare: change Worker environment variable NEW_SITE_PERCENTAGE=0

Timeline

Setting up A/B migration with gradual traffic switchover, data synchronization and monitoring — 4–7 working days.