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)
|
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct)
|
||||||
{
|
{
|
||||||
const int pageSize = 1000;
|
const int pageSize = 1000;
|
||||||
|
const int maxAttempts = 5;
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
var filterSuffix = archivedOnly ? "&filter=archived=true" : "";
|
var filterSuffix = archivedOnly ? "&filter=archived=true" : "";
|
||||||
while (true)
|
while (true)
|
||||||
|
{
|
||||||
|
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 req = Build(HttpMethod.Get, $"{path}?limit={pageSize}&offset={offset}{filterSuffix}", token);
|
||||||
using var res = await _http.SendAsync(req, ct);
|
using var res = await _http.SendAsync(req, ct);
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
var page = await res.Content.ReadFromJsonAsync<MsListResponse<T>>(Json, ct);
|
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;
|
if (page is null || page.Rows.Count == 0) yield break;
|
||||||
foreach (var row in page.Rows) yield return row;
|
foreach (var row in page.Rows) yield return row;
|
||||||
if (page.Rows.Count < pageSize) yield break;
|
if (page.Rows.Count < pageSize) yield break;
|
||||||
|
|
|
||||||
|
|
@ -273,8 +273,9 @@ await foreach (var p in _client.StreamProductsAsync(token, ct))
|
||||||
created++;
|
created++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush periodically to keep change tracker light.
|
// Flush чаще (каждые 100) чтобы при сетевом обрыве на следующей странице
|
||||||
if ((created + updated) % 500 == 0) await _db.SaveChangesAsync(ct);
|
// мы сохранили как можно больше и смогли безопасно продолжить с overwrite.
|
||||||
|
if ((created + updated) % 100 == 0) await _db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue