Привет! Меня зовут Андрей Барболин, я Senior Software Engineer в команде Order Management System. Сегодня я расскажу вам, как мы сделали шардированный кэш и под стресс-тестами добились 30 миллионов операций в секунду, а также про первую open source библиотеку от AliExpress Россия.

Вводные

  • Необходимо держать в кэше 200-300GB данных;

  • Целевая нагрузка на сервис 30 миллионов key values в секунду.

И это профиль нагрузки только одного из сервисов. Также бывают периоды распродаж, когда нагрузка может увеличиваться в несколько раз. Поэтому нам нужно уметь горизонтально масштабировать инстансы кэшей.

Классическое решение этой проблемы — шардирование на клиенте. Что будет стоять в плане кэша на конечной машине, уже не так важно. Это может быть Redis, Memcached или любое другое решение.

Подбор решения

Для того чтобы выбрать конкретное решение по кэшированию, нужно знать как минимум предполагаемую нагрузку и объем данных. Если у вас нет тысяч RPS, а объем данных небольшой, стоит выбрать зрелое решение с большим комьюнити и низким уровнем входа. На данный момент это Redis. Тем более, его можно рассматривать как этакий швейцарский нож, ведь это уже не просто кэш. Даже если вам не нужна его богатая функциональность прямо сейчас, он дает пространство для дальнейшего развития проекта.

Дальше нужно смотреть на конкретную задачу. Нам в AliExpress необходимо держать в кэше достаточно много данных и использовать CPU по максимуму. Redis однопоточный — многопоточку завезли только в 6-й версии в мае 2020, да и то только на сетевой I/O, то есть на чтение из сокетов и запись в них. Стоит посмотреть в сторону других решений. Энтузиасты форкнули Redis, сделали его многопоточным, и так появился KeyDb. Еще есть Dragonfly, который активно развивается и ругает Redis в своем блоге за слабый фундамент на высоких нагрузках. Тут стоит подумать, насколько вы готовы использовать менее зрелые технологии в продакшене: возможно, придется постоянно держать руку на пульсе и отслеживать, что пофиксили и завезли в новых релизах.

Мы выбрали Memcached, потому что он проверен годами, максимально предсказуем и надежен. Это топор, который просто кэш, и всё. К тому же, даже новомодный Dragonfly по их же бенчмаркам не смог полностью обойти Memcached:

Из-за своей простоты Memcached:

  • Лучше использует оперативную память;

  • Изначально использует многопоточный подход, который позволяет выжать максимум из одной машины.

Это накладывает определенные ограничения на использование Memcached:

  • 250 bytes на ключ. Это никак не правится в настройках. Чтобы это изменить, нужно будет пересобирать Memcached;

  • 1MB на значение. Эта величина настраивается;

  • Eviction policy только LRU (Last recently used).

Ограничения обоснованы изначальной архитектурой Memcached с slub-классами и LRU. Подробнее можно почитать в их Wiki.

Шардирование на клиенте

Итак, мы взяли максимально простой инструмент для кэша. Пора научиться его готовить и шардировать. Мы можем взять уже готовое решение от Facebook, одного из самых известных пользователей Memcached. Они написали к нему mc router, у которого широкая функциональность — на нем можно сделать вообще любой роутинг, какой пожелаешь. Но все-таки это еще один черный ящик на пути к распределенному кэшу. А нам нужен максимально простой, управляемый и тонкий клиент.

На входе используем consistent hashing aka HashRing. Это подход позволит нам добавлять и удалять инстансы Memcached в реальном времени без редеплоя сервиса, который его использует.

Представим, что A, B, C — инстансы Memcached. Мы можем высчитать их расположение на круге, например, вычислением hash по IP-адресам подов. Дальше мы вычисляем hash по ключу, находим ближайший инстанс по часовой стрелке (или против) и отправляем запрос на него:

Если один из подов откажет, мы должны убрать его из HashRing. Но теперь у нас есть перекос по нагрузке — все значения, которые должны были идти на инстанс B, теперь уходят на инстанс C:

Нам нужно, чтобы нагрузка распределилась максимально равномерно между инстансами A и C. Для этого необходимо добавить на круг «виртуальные» инстансы, которые будут ассоциированы с физическими. Таким образом, если один из инстансов откажет, вся нагрузка должна будет равномерно распределиться между остальными инстансами:

Такой подход позволит нам делать запросы на несколько инстансов одновременно, что Redis из коробки не умеет:

Redis Cluster supports multiple key operations as long as all of the keys involved in a single command execution (or whole transaction, or Lua script execution) belong to the same hash slot. The user can force multiple keys to be part of the same hash slot by using a feature called hash tags.

То есть придется поупражняться, чтобы запустить команду на несколько ключей и не получить:

(error) ERR CROSSSLOT Keys in request don't hash to the same slot

Hash tags используются для объединения ключей по некоему тэгу, по которому будет считаться hash, чтобы отправить ключи в определенный hash slot (пример из документации):

MSET {user:1000}.name Angela {user:1000}.surname White

Возможно, сработает пайплайнинг нескольких команд get/set, но я сходу не нашел примеров без hash tag’ов. Здесь говорится, что:

Redis Enterprise has a few workarounds for simple commands, notably MGET and MSET.

Примеров или документации я найти не смог. Но даже если эти обходные пути существуют, Redis Enterprise все еще стоит денюжку.

Если знаете примеры запросов на несколько ключей в несколько инстансов Redis, напишите в комментарии.

Немного кода по HashRing

С помощью бинарного поиска добиваемся O(log(n)):

private TNode GetNodeInternal(string key)
{
    var keyHash = GetHash(key);

    var index = Array.BinarySearch(_sortedNodeHashKeys, keyHash);
    if (index < 0) // no exact match
    {
        // If the Array does not contain the specified value, the method returns a negative integer.
        // You can apply the bitwise complement operator to the negative result to produce an index.
        // If this index is one greater than the upper bound of the array, there are no elements larger than value in the array.
        // Otherwise, it is the index of the first element that is larger than value.
        index = ~index;

        if (index >= _sortedNodeHashKeys.Length)
        {
            index = 0;
        }
    }

    var hashNodeKey = _sortedNodeHashKeys[index];

    return _hashToNodeMap[hashNodeKey];
}

Выполняем поиск нод параллельно:

public IDictionary<TNode, ConcurrentBag<string>> GetNodes(IEnumerable<string> keys)
{
    var result = new ConcurrentDictionary<TNode, ConcurrentBag<string>>(Comparer);

    try
    {
        _locker.EnterReadLock();
        
        if (_sortedNodeHashKeys == null || _sortedNodeHashKeys.Length == 0)
        {
            return result;
        }

        Parallel.ForEach(keys, new ParallelOptions { MaxDegreeOfParallelism = 16 },key =>
        {
            var node = GetNodeInternal(key);

            var bag = result.GetOrAdd(node, (Func<TNode, ConcurrentBag<string>>) ValueFactory);
            bag.Add(key);
        });
    }
    finally
    {
        _locker.ExitReadLock();
    }

    return result;
}

Бенчмарки при 256 виртуальных нодах на одну физическую:

BenchmarkDotNet=v0.13.1, OS=macOS Monterey 12.3.1 (21E258)

[Darwin 21.4.0]
Apple M1, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.400

[Host]
: .NET 6.0.8 (6.0.822.36306), Arm64 RyuJIT DefaultJob
: .NET 6.0.8 (6.0.822.36306), Arm64 RyuJIT

Method

KeysNumber

NodesNumber

Mean

Error

StdDev

Median

GetNodes

1

1

5.683 us

0.5613 us

1.655 us

4.692 us

GetNodes

1

16

5.485 us

0.5255 us

1.549 us

4.639 us

GetNodes

1

32

6.239 us

0.7027 us

2.072 us

5.060 us

GetNodes

128

1

33.824 us

2.7571 us

8.086 us

29.377 us

GetNodes

128

16

118.482 us

7.0747 us

20.860 us

114.546 us

GetNodes

128

32

188.920 us

12.1676 us

35.877 us

181.387 us

GetNodes

512

1

73.147 us

4.7427 us

13.984 us

66.924 us

GetNodes

512

16

175.545 us

10.4275 us

30.746 us

168.805 us

GetNodes

512

32

293.666 us

13.3944 us

39.494 us

277.418 us

GetNodes

2048

1

193.951 us

8.5778 us

24.749 us

189.355 us

GetNodes

2048

16

326.530 us

15.2840 us

44.825 us

309.335 us

GetNodes

2048

32

466.940 us

18.1174 us

52.849 us

456.591 us

GetNodes

5000

1

427.750 us

16.5527 us

48.806 us

420.915 us

GetNodes

5000

16

574.372 us

25.4257 us

74.569 us

564.302 us

GetNodes

5000

32

688.616 us

26.3884 us

76.558 us

663.938 us

GetNodes

10000

1

814.684 us

27.5884 us

80.039 us

807.244 us

GetNodes

10000

16

1,020.214 us

36.8499 us

108.074 us

1,021.344 us

GetNodes

10000

32

1,269.259 us

35.2069 us

103.256 us

1,288.021 us

GetNodes

20000

1

1,617.165 us

44.5917 us

131.480 us

1,629.595 us

GetNodes

20000

16

1,899.443 us

63.8206 us

188.176 us

1,828.317 us

GetNodes

20000

32

2,059.760 us

60.0584 us

174.240 us

2,015.047 us

1 us : 1 Microsecond (0.000001 sec)

Изначально в качестве hash-алгоритма использовался MurMurHash3. Затем мы перешли на xxHash, что дало четырехкратный выигрыш в скорости. У xxHash есть реализация на C# без дополнительных аллокаций. В своей библиотеке мы тоже встали на путь zero allocation и активно используем ArrayPool и Span'ы. Но нам еще есть над чем поработать.

Почитать про Array Pool.

Почитать и посмотреть про Span'ы.

Распределение нод на HashRing

Добиться идеального распределения нод невозможно в силу самого подхода. Мы можем добавить достаточное количество виртуальных нод, чтобы разница находилась в пределах 1–2% по нагрузке.

Нагрузочное тестирование при 64 виртуальных нодах показало до 5% разницы в нагрузке. Проводим тесты в консоли и получаем оптимальный результат при 256 виртуальных нодах со средним отклонением в 1–2% между самой нагруженной и минимально нагруженной нодами:

var keysNumber = 2000000;
var nodesNumber = new[] {1, 2, 4, 8, 16, 32};
var virtualNodesNumber = new[] {16, 32, 64, 128, 256, 512};

foreach (var nodeNumber in nodesNumber)
{
    foreach (var virtualNodeNumber in virtualNodesNumber)
    {
        var hashRing = new HashRing<Pod>(new HashCalculator(), virtualNodeNumber);
        
        var pods = Enumerable.Range(0, nodeNumber).Select(n => new Pod
        {
            IpAddress = Guid.NewGuid().ToString()
        });
        
        hashRing.AddNodes(pods);
        var keys = Enumerable.Range(0, keysNumber).Select(n => Guid.NewGuid().ToString()).ToArray();
        
        var nodes = hashRing.GetNodes(keys);
        
        Console.WriteLine($"Nodes number: {nodeNumber}, Virtual nodes number: {virtualNodeNumber}");
        var percentages = new List<decimal>();
        foreach (var node in nodes)
        {
            percentages.Add((decimal) node.Value.Count / keysNumber);
        }

        var max = percentages.Max();
        var min = percentages.Min();
        var diff = max - min;
        
        Console.WriteLine($"Max: {max}, Min: {min}, Diff: {diff}");
    }
}

Headless service

Возникает вопрос, как нам в реальном времени получать все активные инстансы Memcached. Мы деплоимся в Kubernetes и можем воспользоваться теми базовыми решениями, которые он предлагает. Подробнее можно почитать тут.

IP-адрес для сервиса не аллоцируется. Используя селекторы, сервис знает про все поды, которые задеплоены под ним. Сделав DNS lookup на сервис, мы можем узнать адреса всех инстансов Memcached:

// HeadlessServiceAddress: my-memcached-headless.<namespace>.svc.cluster.local

IPAddress[] ipAddresses = Dns.GetHostAddresses(_config.HeadlessServiceAddress);
return ipAddresses.Select(i => new Pod
{
    IpAddress = i.ToString()
});

Socket pool

Ни один похожий клиент не может обойтись без пулинга подключений. Концепция тоже базовая — нам нужно открыть n подключений в зависимости от нагрузки и переиспользовать их:

Делаем простую реализацию через семафор и ConcurrentStack:

private readonly ConcurrentStack<PooledSocket> _availableSockets;
...
  
public SocketPool(MemcachedConfiguration.SocketPoolConfiguration config, ILogger logger)
{
  ...
  _semaphore = new SemaphoreSlim(_config.MaxPoolSize, _config.MaxPoolSize);
  _availableSockets = new ConcurrentStack<PooledSocket>();
}
...
  
if (!await _semaphore.WaitAsync(_config.SocketPoolingTimeout, token))
{
  _logger.LogWarning("Pool is run out of sockets");
  return result;
}

// Get available socket
_availableSockets.TryPop(out var pooledSocket)
  
// or create one
...

Поддержание актуального состояния

Для этого запускается фоновый процесс, который занимается обслуживанием HashRing и Socket Pool. Раз в n секунд этот процесс запрашивает все доступные инстансы через headless service, добавляет новые ноды в HashRing и удаляет те, что пропали.

Другой процесс отвечает за опрос текущих инстансов на предмет того, живы ли они, и как только инстанс перестает отвечать, помечает его как умерший. После этого инстанс выбрасывается из HashRing до момента, когда он снова оживет.

Еще один процесс постепенно уничтожает по n сокетов из socket pool’a, чтобы иметь возможность после спада нагрузки убрать лишние.

Итоговая схема

  1. Приходит запрос с n key values. Вычисляем по всем ключам hash и по ним выбираем ноды из HashRing

  2. Для каждой ноды создается свой Socket Pool, если он еще не создан. Если создан, берем уже существующий

  3. В каждом пуле берем доступный сокет из уже созданных либо создаем новый, если пул не переполнен

  4. В каждый сокет пишем пайплайном несколько команд get или set для Memcached. Протокол Memcached позволяет сделать несколько операций за раз только таким образом

  5. Вычитываем последовательно из сокета ответ от Memcached и отдаем на клиент

Протокол общения с Memcached и наша библиотека

Если посмотреть доступные библиотеки под .NET Core, становится немного грустно. Есть портированная с .NET Framework на .NET Core библиотека с очень старым стилем кодирования и кучей ненужных абстракций. Но если нужно сразу начать использовать Memcached, вполне можно ею воспользоваться.

Мы же взяли ее за основу, чтобы не писать весь протокол общения с нуля. Затюнили ее под динамическое изменение количества инстансов Memcached, что-то переписали и оптимизировали. Если хочется разобраться с нуля, то вам сюда: Memcached binary protocol.

Мы выкатили библиотеку в open source. Ее можно настраивать как через headless service, так и указав напрямую IP-адреса Memcached. То есть библиотека умеет работать с любым количеством инстансов.

Профили нагрузки

Ставим один инстанс Memcached и нагружаем его на SET. Ключ и значение — гуиды, то есть объем данных в несколько байт. Прогон в 21.5k RPS на каждый из кластеров (у нас их три), общая нагрузка примерно 65k RPS, 1 ключ-значение на запрос.

На уровне Memcached видим ровные графики по ресурсам — занимаем меньше 1 CPU. Смотрим графики на одном из кластеров:

Берем 20 ключей на запрос, то есть нагрузка на Memcached вырастает в 20 раз. На инстанс Memcached приходится около 18k RPS * 20 key values = 360k операций SET в секунду до того момента, как начинает расти RT. На графиках видим, что начинаем использовать в разы больше CPU:

Увеличиваем количество ключей до 50. На инстанс Memcached приходится около 10k RPS * 50 key values = 500k операций SET в секунду до того момента, как начинает расти RT. Потребление CPU растет незначительно, но можем наблюдать, что RT выросло гораздо сильнее по сравнению со случаем в 20 key values:

Приходим к выводу, что количество операций в пайплайне команды однозначно влияет и на CPU, и на RT. Лучше придерживаться адекватных цифр и в одной команде посылать ~10 операций на Memcached. Иначе время отклика начнет расти в геометрической прогрессии из-за дополнительного времени, которое требуется Memcached для сброса данных в сокет.

Также стоит заметить, что здесь мы попадаем в один и тот же slab-класс, так как размер значений всегда одинаковый. Внутри Memcached каждый slab-класс обслуживает только один поток, поэтому здесь мы можем упереться в ограничение. Так что ~10 операций на команду — это базовая рекомендация, которую нужно проверять именно на вашем профиле нагрузки.

А теперь берем 8 инстансов Memcached в каждом кластере, 100 key values на запрос и наблюдаем, что легко держим нагрузку, которая равномерно распределяется между инстансами. На каждый инстанс приходится примерно по 12–13 key values на запрос:

Помещаемся в 1–2 CPU на инстанс Memcached:

Рекомендуем брать по 10–20 операций SET на команду и 2 CPU на инстанс Memcached. При этом пропускная способность операций GET может быть примерно в два раза больше, чем у операций SET, так как они легче. Но все равно лучше ограничивать количество операций в пайплайне, чтобы меньше грузить Memcached и не увеличивать время отклика. Если текущее количество инстансов не вывозит нагрузку, добавляем еще инстансов.

Профиль нагрузки одного из сервисов. 6 инстансов Memcached, ожидаем прирост нагрузки. Сейчас имеем около 2k RPS в среднем по 15–20 key values на запрос:

На стороне Memcached получаем 40k RPS:

Количество команд = RPS сервиса * количество инстансов Memcached. RT составляет 0,5ms:

Большая часть метрик экспортируется из Memcached в Прометей. Данные с последней картинки пишет наша клиентская библиотека.

Самый нагруженный вариант. Мы взяли 40 инстансов Memcached с 1 CPU и добили нагрузку до 30 миллионов key values в секунду. 2,5KB каждое значение, 3 кластера, получается ~24GB/s сетевого трафика на один кластер и 72GB/s всего:

При самой высокой нагрузке держимся в районе 1ms на одну операцию в Memcached:

Тут мы разбили каждый входящий запрос на пачки. Нам нужно за один запрос получить одновременно 3000 key values. Мы не можем запихнуть всё сразу в один пайплайн — нужно держать примерно 10–20 операций на пайплайн. Без разбития на каждую ноду приходилось бы 3000 / 40 = 75 key values. Разбиваем их примерно на 4 разные части и отправляем параллельно, чтобы избежать роста RT.

Настройка Memcached

При запуске стоит обратить внимание на параметры. Полезно по ним пройтись и посмотреть, что из этого вам понадобится подтюнить. Как минимум, проверьте следующие параметры:

# MaxMemoryLimit, this should be less than the resources.limits.memory, or memcached will crash.  Default is 64MB
- -m 8192 
# Specify the maximum number of simultaneous connections to the memcached service. The default is 1024. 
- -c 20000

Память сразу стоит поднять — редко можно влезть в дефолтное значение. А количество подключений надо рассчитывать так, чтобы хватило на редеплой сервисов, которые используют Memcached. Дело в том, что в момент редеплоя количество подключений подскочит.

Из неочевидного есть, например, такой параметр:

- -R 40
# The command-line parameter -R is in charge of the maximum number of requests per 
# network IO event (default value is 20). The application should adopt its batch 
# size according to this parameter. Please note that the requests limit does not 
# affect multi-key reads, or the number of keys per get request.

И если неаккуратно пользоваться пайплайнингом операций, то можно нарваться на такую вот неприятную стату:

STAT conn_yields 126672162
Number of times any connection yielded to another due to hitting the -R limit

Итог

  • Имеем горизонтально масштабируемый кэш, который можем развернуть для любого сервиса

  • Умеем динамически добавлять и убирать инстансы Memcached

  • Путем нагрузочных тестов выведено оптимальное количество ресурсов на один инстанс: 2.5 CPU и 8GB RAM. Больше CPU брать нет смысла из-за специфики работы LRU — лучше развернуть дополнительный инстанс. Как только текущее количество инстансов перестает справляться с нагрузкой, накидываем еще. Выбор 8GB RAM продиктован тем, что часть данных не жалко потерять

  • 0.5 миллисекунды RT из сервиса в Memcached при нагрузке с умеренным количеством ключей на команду;

  • Выкатили библиотеку в open source.

Комментарии (17)


  1. QuAzI
    19.10.2022 12:10
    +7

    Отличная штука... это из-за него приходится раз надцать страницу обновлять, потому что в корзине валится примерно всё?

    А когда исправят принудительный редирект на RU-домен для тех, кто в RU не живёт, когда перестанут портить настройки валюты и страны доставки и зачем от людей спрятали настройки страны доставки и валюты, особенно пока не залогинен (особенно с учётом того что с сохранением сессий и настроек всегда была беда)? Поиск по валюте или хотя бы сортировка по алфавиту или популярности - вообще никак?

    А когда поиск по продуктам починят? А когда при указанной стране РБ перестанут первыми в поиске отбиваться шараги, которые в РБ не доставляют примерно НИКОГДА?

    Почему вообще дизайн отличается от стиля оф.сайта, по ощущениям выглядит будто фишинг.


    1. otezvikentiy
      19.10.2022 17:43

      Смиритесь - это новая платформа для СНГ примерно так. Сейчас идет активная разработка и то что есть ряд багов - это норма для it-проектов на время активной разработки. Подождите - всё наладится. )))


      1. QuAzI
        20.10.2022 13:23
        +1

        Идёт активное разваливание которое вообще никак в современную разработку не вписывается. За один только принудительный форвард на RU при разваленном сайте нужно кому-то по рукам дать хорошенько. Раньше хотя бы на глобальный сайт можно было свичнуться, теперь НЕТ. Так же как в нормальных компаниях принято нерабочие версии, в которых клиент не может найти и купить нужный товар, откатывать, потому что это сулит огромными убытками и сваливанием клиентов.
        И нет, не наладится, проблемы с локализованным стором были уже давно, даже на всяких пикабу куча статей в духе "как извратиться чтобы хоть как-то али работало", "как прикрутить форвард на глобал сайт".

        Сейчас это выглядит как намеренное вредительство. Особенно мутки со страной доставки, языком и валютой.


        1. anatolix
          21.10.2022 11:17

          Смотрите, вот есть такие компании как Shein и Farfetch, которые в Россию теперь не доставляют и русские карты не берут, но зато у них нет никакого редиректа на ru сайт, и в них нет никакого "активного разваливания", если вы хотите пользуйтесь ими.

          А вообще народ в офисе ночевал, чтобы быстро переписать платежи, чтобы они вообще ходили, научились переводить деньги в китай без доллара в цепочке, но конечно про это иначе как "разваливание" в интернетах не напишут


          1. QuAzI
            21.10.2022 14:26

            Когда будет косячить Shein, будем говорить о их косяках и как делать НЕ НАДО НИКОГДА. Все мы рано или поздно ночуем в офисе, но есть же общие правила хотя бы не делать ещё хуже, тестирование, открытие новой фичи только для части аудитории... Редирект - это не про код же. Вы доблестно воевали с проблемами, которые других вообще не должны были касаться, потому что не было проблем оплатить через доллар.


    1. tandzan
      19.10.2022 19:48
      +2

      Русский Али для меня умер с этим новым дизайном.


      1. anatolix
        21.10.2022 11:18

        А что именно не нравится? Просто в нем конверсии лучше, global его потихоньку копирует даже.


    1. rombell
      19.10.2022 21:38
      +2

      Мне кажется, что Али вообще не принимает обратную связь по дизайну. Что головная контора, что российская. Или же у них какая-то крайне специфическая тестовая фокус-группа, или же решения принимаются без учёта мнения потребителей.
      Новый дизайн головного сайта стал настолько неудобным, что руки опускаются. Но российский сайт смог переплюнуть.
      Обнаружив, что по старым заказам невозможно получить информацию, а она бывает иногда нужна, я начал сохранять снэпшоты. И вот в этом году с российского сайта возможность перейти к снэпшоту убрали. Пока ещё можно с приседаниями и бубном попасть на головной, а там сохранилась незаметная картинка-ссылка поверх фотографии. Однако, опасаюсь, вскоре эту возможность тоже срежут.
      Это уже не говоря про отключение прочего достаточно удобного функционала на странице заказа. Аляповатый минимализм, не могу по-другому назвать.


      1. anatolix
        21.10.2022 11:19

        А вы можете как ниубдь написать, что именно неудобно?


        1. rombell
          21.10.2022 13:37

          1) Вырвиглазный дизайн, в котором теряется информация. По счастью, информации почти нет.
          2) В заказах — удалили номер, сумму заказа. Убрали фильтр заказов. Убрали поиск заказов. Убрали состав заказа. На-я? По счастью, ещё можно перейти на глобал, чтобы нормально работать с заказами. К сожалению, на глобал заказы тоже стали хуже — но старых скринов не сохранено.

          Заголовок спойлера
          image
          image

          3) В заказах — исчез финансовый блок, или я не могу его найти. Где отображались все эти коины и промо, применённые к заказу. Сейчас вижу только стоимость доставки.
          4) В заказах — раньше указывалась предельная дата доставки. Теперь — только начальная и The system will automatically confirm receipt after 65 days. Это важно для отслеживания, пора ли уже подавать претензию. Теперь для всех заказов надо вычислять самому.
          5) В заказах — из заказа пропала ссылка на трекинг. Надо возвращаться в список и только оттуда есть трекинг.
          6) В заказах — из заказа пропала ссылка на Snapshot.
          7) В заказах — мониторы стали шире, поэтому давайте добавим больше фигни по краям, а всю существенную информацию вытянем в столбик. Так? Зачем нам горизонтальный дизайн на мониторах? На глобале тоже порезали информацию, было же несколько вкладок и куча всего, теперь одна страница. Но хоть читать можно на одном листе:
          Заголовок спойлера
          с небольшим уменьшением

          с большим уменьшением


          8) В заказах — пропала информация об адресе и получателе. Я часто надолго езжу, и заказываю товары на свой адрес на своё имя или на имя жены, на второй адрес в другой город на своё имя и на имя родственника, иногда — на адрес и имя знакомых, которым помогаю что-то купить. Раньше я мог видеть, куда какой заказ идёт. Теперь — только угадывать. Особенно если заказал одно и то же в три адреса.
          9) В каталоге — исчезла часть фильтров. Причём чем дальше, тем меньше фильтров остаётся.
          10) В каталоге — картинки заслоняют информацию. Всё больше картинок, всё меньше описания. Куда делась информация о стоимости доставки? Зачем убрали название магазина? Что за «Реклама» вместо названия магазинов в половине позиций?
          Заголовок спойлера



          11) Какого вообще чёрта сразу перебрасывает на ру-сайт при выборе страны доставки Россия? Чтобы посмотреть нормальную карточку, теперь даже через VPN надо выбирать левые страны
          Заголовок спойлера



          12) Кто вообще решил, что круглый дизайн — это круто? Фотографии занимают тот же размер, но информации на них теперь меньше. Гениально!
          13) см.11: на глобале я вижу на одной странице большую часть необходимой информации. На ру мне надо скроллить. Когда я ищу товар, приходится просмотреть до полусотни карточек. И каждую проскроллить.
          14) На глобале описание, характеристики, отзывы — разные табы, между которыми можно быстро переключаться. Страница вниз, клик — отзывы, клик — описание. Теперь скроллить и скроллить. Быстрого перехода к описанию нет. Это монитор и компьютер, на мобилах у вас приложение! Зачем тащить дизайн под узкий сенсор на широкий монитор с мышой?!
          Заголовок спойлера



          15) Самая ценная информация в отзывах — additional feedback; зачем убрали фильтр?
          Заголовок спойлера



          Это даже не говоря о том, что кликнуть в нужную точку гораздо быстрее и удобнее, чем выбирать из выпадающего списка.
          16) Вот эта плавающая табличка справа, отнимающая кучу места и дико раздражающая. Если я захочу купить эту позицию, я без проблем прыгну вверх и добавлю в корзину. Зачем мне всё время подсовывать это под руку?! Неужели раздражать пользователя — хороший путь к продажам? Я-то, конечно, скрою и этот элемент к чертям.
          Заголовок спойлера



          Извините, надоело перечислять


          1. anatolix
            21.10.2022 13:47

            Спасибо за конкретику, давайте договоримся, мы это починим, а вы напишите вторую часть списка которую сейчас лень.

            А если серьезно: то как мы это рассмариваем, у AliExpress всегда было 2 дизайна, приложение и сайт. Они были кардинально разные, и почти неть людей которые пользуются тем и тем.

            У нас 70% пользователей в мобильном приложении, и в нем стало лучше, метрики улучшились, никто не жалуется на дизайн, etc.

            Сейчас по идее сайт надо приводить к тому же дизайну, навернеое мы делаем это излишне аггрессивно, и теряем функциональность, надо восстановить побыстрее


            1. rombell
              21.10.2022 14:44
              +1

              Извините, но ЗАЧЕМ приводить сайт к дизайну приложения?!
              Это совершенно разный UX — тыкать мышой в широкий экран или возить пальцем по узкому. И моторика разная, и задержки разные, и поле взгляда — очевидно же!
              Да, лично мне пофиг мобильное приложение с его дизайном, у меня комп есть, на нём гораздо удобнее (было), пожтому я и не жаловался. В мобильное я заходил только за коинами, когда это ещё имело смысл, и эти коины попробовать применить.
              Хм, там ещё была акция «на халяву» или как-то так, где я выиграл две ставки за три дня, после чего заказы аннулировали, и вроде как меня забанили. Я пытался выяснить причину — поддержка ссылается на продавцов, продавцы прислали мне скрины, на которых видно, что им заказы закрыла система. После чего я полностью перестал в это ваше приложение заходить.
              А вот на сайте закупаюсь (закупался) постоянно. Потому что было удобно.
              Зачем делать из так себе сайта поганую копию приложения — уму непостижимо.

              Но если вы действительно хотя бы вернёте функциональность и UX к уровню глобального, я уж не мечтаю про то, что было пару лет назад, то
              1) вы заслужите искреннюю благодарность всего не-мобильного рунета;
              2) вам с удовольствием накидают ещё идей. Сайт-то нужный.


    1. anatolix
      21.10.2022 11:13

      Привет. РБ и СНГ вообще в следующем году займемся, мы правда мало времени уделяли СНГ не до этого было.


  1. sebasww
    19.10.2022 13:58

    Как я понял, это свой Orleans?


    1. Pur Автор
      19.10.2022 18:01

      Не совсем. Orleans все-таки более мощная и функциональная обертка, основанная на акторной модели. У нас же достаточно тонкая прослойка для управления шардированным кэшем.


  1. Andriuha077
    20.10.2022 11:55
    +1

    Кто-то готов смириться со сломанным поиском и всеми вот этими ценами, но опция "вернуть нормальный дизайн (переключив на стилевые таблицы оригинала по адресу ком)", которая включалась бы в личном кабинете, как первый шаг к поправке изумительных подвижек, ну вот просто необходима.

    Какие провалы?
    Какие провалы?


  1. moooV
    21.10.2022 11:42

    Сделайте возможность изменить страну профиля. Давно живу не в РФ, но безальтернативно перекидывает на русский сайт хотя хочу пользоваться глобальным куда надо заходить каждый раз методом тыка через зарытые (оставшиеся) внутри ссылки. На русском сайте, само собой, доступны только страны СНГ для выбора. ????‍♂️

    Причем, как понимаю, он определяет страну не по айпишнику а по первому когда-то давно зарегистрированному адресу, который уже даже удален из профиля давно.

    А что по русскому сайту — история покупок тоже не подтягивается с глобального. Мега полезен, да.