fix(moysklad/import): per-page retry + чаще SaveChanges
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 27s
CI / Web (React + Vite) (push) Successful in 23s
Docker Images / API image (push) Successful in 35s
Docker Images / Web image (push) Successful in 5s
Docker Images / Deploy stage (push) Successful in 17s
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 27s
CI / Web (React + Vite) (push) Successful in 23s
Docker Images / API image (push) Successful in 35s
Docker Images / Web image (push) Successful in 5s
Docker Images / Deploy stage (push) Successful in 17s
Почему импорт раньше обрывался на ~9500/29500 товаров: - StreamPagedAsync бросал исключение при любом сетевом глюке или таймауте HttpClient (90s) на одной из страниц и весь цикл сыпался. - Флаш делался раз в 500 товаров, так что при обрыве на 9500-м можно было потерять последние 499. Фиксы: - Per-page retry до 5 раз с exp-backoff (2,4,8,16с) — обрабатываем только сетевые ошибки (HttpRequestException / TaskCanceledException / IOException). API-ошибки типа 4xx проходят наверх как есть. - SaveChangesAsync теперь каждые 100 товаров вместо 500 — меньше вероятность потерять при внезапном обрыве на границе. - При исчерпании retries — бросаем осмысленное исключение с offset'ом. Пользователь сейчас имеет 9500 из 29509 товаров (группа "Алкоголь" — 20 из 518). Нужно перезапустить импорт в UI с overwriteExisting=true — существующие товары обновит, недостающие подтянет.
This commit is contained in:
parent
69e6fd808a
commit
bd15854b42
|
|
@ -98,14 +98,39 @@ public async Task<List<MsProductFolder>> GetAllFoldersAsync(string token, Cancel
|
|||
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
const int pageSize = 1000;
|
||||
const int maxAttempts = 5;
|
||||
var offset = 0;
|
||||
var filterSuffix = archivedOnly ? "&filter=archived=true" : "";
|
||||
while (true)
|
||||
{
|
||||
using var req = Build(HttpMethod.Get, $"{path}?limit={pageSize}&offset={offset}{filterSuffix}", token);
|
||||
using var res = await _http.SendAsync(req, ct);
|
||||
res.EnsureSuccessStatusCode();
|
||||
var page = await res.Content.ReadFromJsonAsync<MsListResponse<T>>(Json, ct);
|
||||
MsListResponse<T>? page = null;
|
||||
Exception? lastErr = null;
|
||||
for (var attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var req = Build(HttpMethod.Get, $"{path}?limit={pageSize}&offset={offset}{filterSuffix}", token);
|
||||
using var res = await _http.SendAsync(req, ct);
|
||||
res.EnsureSuccessStatusCode();
|
||||
page = await res.Content.ReadFromJsonAsync<MsListResponse<T>>(Json, ct);
|
||||
lastErr = null;
|
||||
break;
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException)
|
||||
{
|
||||
lastErr = ex;
|
||||
if (attempt == maxAttempts) break;
|
||||
// Exponential-ish backoff: 2s, 4s, 8s, 16s.
|
||||
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)), ct);
|
||||
}
|
||||
}
|
||||
if (lastErr is not null)
|
||||
{
|
||||
// Re-throw after retries so the caller sees a real failure instead of silent halt.
|
||||
throw new InvalidOperationException(
|
||||
$"MoySklad paging failed at {path} offset={offset} after {maxAttempts} attempts: {lastErr.Message}",
|
||||
lastErr);
|
||||
}
|
||||
if (page is null || page.Rows.Count == 0) yield break;
|
||||
foreach (var row in page.Rows) yield return row;
|
||||
if (page.Rows.Count < pageSize) yield break;
|
||||
|
|
|
|||
|
|
@ -273,8 +273,9 @@ await foreach (var p in _client.StreamProductsAsync(token, ct))
|
|||
created++;
|
||||
}
|
||||
|
||||
// Flush periodically to keep change tracker light.
|
||||
if ((created + updated) % 500 == 0) await _db.SaveChangesAsync(ct);
|
||||
// Flush чаще (каждые 100) чтобы при сетевом обрыве на следующей странице
|
||||
// мы сохранили как можно больше и смогли безопасно продолжить с overwrite.
|
||||
if ((created + updated) % 100 == 0) await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue