Работният процес умря. Синхронизацията не.

Спрете работния процес по средата на пакет и проследете един контакт: кой курсор е запазен, защо повторният запис не попада два пъти, кой притежава синхронизацията, докато тя се рестартира. След това двата инцидента, които показаха, че възстановяването е само половината от работата.

възстановяване на синхронизация след срив

Какво се случва с текуща синхронизация, когато работният процес умре по средата на пакет?

Курсорът се премества само в края на чист пакет, така че работен процес, убит по средата на пакет, го оставя в началото на този пакет и следващото изпълнение чете същата страница. Ключът за доставка на всеки запис се извежда от курсора на страницата и позицията му, а седемдневен регистър на завършените записи не позволява на повторението да попадне два пъти. Един наем на синхронизация не позволява две изпълнения да работят едновременно. Това беше лесната половина. Една синхронизация продължаваше да се проваля, изпълнение след изпълнение, преди нещо да я спре на пауза, а едно работно пространство мълчеше, с грешки и без нито един успех, преди нещо да забележи. Затова вече има предпазител, който спира на пауза след пет последователни грешки, и ежечасно известие при двадесет и четири часа без успех.

Научете повече
На тази страница

В доклад на екипа на Crusoe за работа с разработчиците, публикуван на 3 октомври 2026 г. от AI Engineer, беше стартирана задача за обучение с PyTorch на два възела A100. След това на системата за наблюдение на самото демо беше съобщено, че един GPU е изхвърлил грешка XID 79. Възелът беше маркиран като недостъпен, а задачата отменена. Процесът получи SIGTERM и „до две минути да обработи този SIGTERM и или да запази контролните си точки, или да изчисти логовете си“. Влезе резервен възел и „задачата в slurm ще се рестартира. Ще зареди контролната точка и ще продължи обучението оттам, докъдето е стигнала“. Малко под петнадесет минути от началото до края. Никого не събудиха с повикване.

Синхронизацията е много по-малко животно от едно обучение. Въпросът е същият. Какво е нужно на едно изпълнение, така че убиването на работния процес да струва минути и нито един дубликат?

Ето отговора за собствения изпълнител на синхронизации на Tajo, прочетен от кода, а не от брошура. Статията за Reverse ETL описва формата на задачата. Това тук е какво се случва, когато задачата умре.

Проследете един контакт

Изпълнението чете източника страница по страница, от запазен курсор. Нека наречем курсора на страницата c3, а нашия контакт 212-ия запис в тази страница. Три неща са верни за този контакт, преди да е записано каквото и да било.

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

Второ, записът минава през регистър, а не директно към целевата система. Преди извикването на целевата система ключът се заявява като „чакащ“. Когато записът успее, редът се маркира като завършен и се пази седем дни.

Трето, курсорът не се е преместил. Той се премества само в края на пакет, само ако всеки запис в този пакет е стигнал, и само след като изпълнението е доказало, че все още притежава синхронизацията.

Сега убийте работния процес между записа и контролната точка.

При SIGTERM всеки работен процес на опашката приключва текущата си работа в рамките на един общ петнадесетсекунден срок, а процесът излиза след двадесет и пет секунди, каквото и да се изпълнява още; systemd му дава четиридесет и пет, преди да го убие. Изпълнение, което не е завършило в този прозорец, все още е маркирано като текущо и следващият работен процес го поема чрез механизма на опашката за възстановяване на блокирали задачи. При твърдо убиване изобщо няма приключване. И в двата случая важат същите два факта. Целевата система има нашия контакт. Нашият курсор все още показва c3.

Повторението

Следващото изпълнение започва от c3, чете същата страница, извежда същия ключ за 212-ия запис и пита регистъра. Регистърът казва: завършено. Целевата система не се извиква; запазеният резултат се възпроизвежда и изпълнението продължава. При пакетния път, където ендпойнтът за импорт на Brevo приема цяла страница с едно извикване, същата проверка се изпълнява за всеки ключ, преди заявката да бъде съставена. Страница, в която всеки ключ вече е завършен, не изпраща нищо.

Това е целият трик и си струва да кажем защо работи. Ключът се извежда от курсора на страницата в началото на пакета, а не от изпълнението, което случайно е вървяло. Срив, повторен опит шестдесет секунди по-късно и планирано изпълнение утре извеждат един и същ ключ за един и същ запис. На регистъра му е все едно кой от тях пита.

Сега цената. Регистърът забравя след седем дни. Собственият коментар в кода казва, че седем дни покриват забавен повторен опит и кратко прекъсване, и това е всичко, което твърди. Повторение от курсор, по-стар от това, записва отново. А изпълнение, прекъснато твърдо, може да чака. Наемът на мъртвото изпълнение се държи до петнадесет минути, а при планирана синхронизация изпълнение, чийто наем е свободен, се прибира и планира отново едва когато е започнало преди повече от час. Демото на Crusoe се връща за под петнадесет минути. Нашето не винаги.

Кой притежава синхронизацията, докато тя се рестартира

Във всеки момент синхронизацията се притежава от едно изпълнение. Изпълнението взима наем в Redis със собствен токен, живот от петнадесет минути и подновяване на всеки пет минути. Преди всяка контролна точка то проверява, че все още държи този наем. Ако не го държи, спира. Изпълнението се записва с простото изречение, което човекът ще прочете: „The run lost its slot part-way through and stopped to avoid writing twice.“ (изпълнението загуби мястото си по средата и спря, за да не запише два пъти). Второ изпълнение, което се сблъска с жив притежател, не се опитва повторно. То завършва със съобщението „Another run of this sync was already in progress“ (вече вървеше друго изпълнение на тази синхронизация), а следващото планирано изпълнение започва нормално.

Отговорността за поправката също е записана. Всеки код на грешка съответства на едно изречение за това какво се е случило, едно за това какво да се направи след това, и на собственик: вие или Tajo. Целева система, която е отхвърлила всеки запис, защото съпоставянето е грешно, е ваша отговорност. Загубеният наем е наша. Страницата на изпълнението показва следващата стъпка; ръководството за възстановяване на синхронизация посочва кое от двете.

Дотук това е лесната половина. Сривът струва едно повторение и нищо не попада два пъти. Двата инцидента ни научиха на другата половина.

Грешка след грешка

Една синхронизация се проваляше отново и отново. Не в тесен цикъл: веднъж на интервал на планировчика, изпълнение след изпълнение, всеки път със същото съобщение, че потребителски идентификатор не може да съдържа двоеточие. Изпълнението така и не стигна до целевата система. То умираше, докато се поставяше на опашката, преди някой изпълнител да го види, така че известие, вързано за изпълнителя, никога нямаше на какво да реагира.

Нищо не следеше поредицата. Това е цялата находка.

Поправката е предпазител. След всяка записана грешка той чете последните пет завършени изпълнения на синхронизацията от последната ѝ промяна. Ако и петте са грешки, той спира синхронизацията на пауза, записва един ред в одитния журнал с причина circuit_breaker_tripped и изпраща едно критично известие. Поредицата се извежда от запазените изпълнения, а не от брояч в работния процес. Брои се грешка, записана по какъвто и да е път, включително грешката при поставяне на опашката, която причини инцидента. Коментарът в кода казва, че той би спрял този инцидент на петото изпълнение. Един успех нулира поредицата. Махането на паузата презарежда предпазителя, вместо да го задейства отново върху старите редове. Ръководството за коннектора за Shopify казва същото с един ред: след пет последователни неуспешни изпълнения синхронизацията сама спира на пауза и страницата ѝ показва последната грешка.

Това, което предпазителят не прави, е да поправи каквото и да било. Crusoe сменя възела и рестартира задачата, без да пита. Tajo спира и пита: синхронизация на пауза остава на пауза, докато човек не отстрани причината и не я публикува отново. Предпазливото поведение по подразбиране е съзнателно: автоматичен повторен опит на синхронизация, която пише в клиентски записи, сам по себе си е запис.

Тишината, която никой не видя

Възражението е справедливо и звучи така: „Предпазител, който спира на пауза след пет грешки, щеше да хване и втория инцидент. Защо да строим втори пазач?“ Защото вторият инцидент не изглеждаше като грешка. Дълго време едно работно пространство имаше активна синхронизация, растяща купчина редове с грешки и нито един успех, никога, и нищо не подаде известие. Всеки съществуващ сигнал беше вързан за активност: изпълнение се провали, пристигна уебхук, бюджет беше надвишен. Нула успехи не е събитие. Това е отсъствието на събитие, а предпазителят работи само когато е записана грешка. Мъртъв планировчик, заседнала опашка, работен процес, който никога не стартира: те не записват нищо и предпазителят никога не се събужда.

Устройството за бдителност на машиниста в локомотива (dead man’s handle) работи по обратния начин. Машинистът трябва постоянно да го натиска, а тишината спира влака. Версията за синхронизация се изпълнява веднъж на час, в тринадесет минути след кръглия час. За всяко работно пространство с активна синхронизация тя задава един въпрос: успяло ли е някое изпълнение през последните двадесет и четири часа? Работно пространство, чиято най-стара активна синхронизация е включена преди по-малко от ден, се оставя на мира; то още не е имало ден да успее. Едно по-старо и мълчаливо получава едно критично известие. Известието казва за коя тишина става дума: записани са изпълнения, но нито едно не успява, или изобщо не са записани изпълнения. То се задейства веднъж на инцидент. Маркер в Redis го държи затворено до първото успешно изпълнение, след което се изчиства, така че следващият инцидент отново подава известие.

И двата пазача влязоха в един къмит, със закъснение. Коментарът на предпазителя казва, че той би спрял първия инцидент на петото изпълнение. Коментарът на контрола на бдителността казва, че синхронизация, включена отдавна и с нула изпълнения, трябва да подаде известие. Нито един от двата не съществуваше, когато беше нужен. Написахме ги след това, като записахме и двата инцидента в заглавната част на файла, за да не забрави никой защо.

Какво е изградено и какво не е

Изградено към момента на писане:

  • Курсорът се запазва в края на всеки чист пакет и отново при срив.
  • Ключът за доставка на всеки запис се извежда от курсора на страницата и позицията му и се проверява в седемдневен регистър преди всяко извикване на целевата система.
  • Един наем на синхронизация, с живот от петнадесет минути.
  • Пет опита на изпълнение, с нарастващо изчакване, започващо от шестдесет секунди.
  • Предпазител, който спира синхронизацията на пауза след пет последователни грешки и записва причината.
  • Ежечасно известие при двадесет и четири часа без успех.

Не е изградено и си струва да го знаете, преди да разчитате на това:

  • Контролът на бдителността следи работното пространство, а не синхронизацията. Една здрава синхронизация скрива мълчаща съседна синхронизация в същото работно пространство.
  • Известието му стига до операторите на Tajo: винаги като ред в лога, а и чрез Sentry, уебхук или имейл, където това е настроено. То не се появява на страницата на синхронизацията. Паузата от предпазителя се появява.
  • Нищо не рестартира синхронизация на пауза. Човек го прави.
  • Възстановяването след твърдо убиване не е бързо. Наемът може да се държи петнадесет минути, а почистването чака един час.
  • И двата прага, пет грешки и двадесет и четири часа, са настройки на внедряването. Не можете да ги зададете за отделна синхронизация.

Дълга верига от почти перфектни стъпки пак се проваля; изходът е да определите крайното състояние и да проверявате за него. Тази статия е оперативната половина от това. Определете какво оставя след себе си едно мъртво изпълнение и проверявайте за нищото, което оставя след себе си тишината.

Заключителното изречение на Crusoe беше, че „сривовете са неизбежни“. Нашето е една стъпка по-нататък. Сривът беше лесната половина, защото остави редове. Тишината беше половината, която трябваше да потърсим.

Често Задавани Въпроси

Какво се случва с текуща синхронизация, когато работният процес умре по средата на пакет?

Курсорът все още сочи началото на прекъснатия пакет, защото Tajo го премества само след като пакетът е записан чисто. Следващото изпълнение отново чете същата страница. Всеки запис носи ключ за доставка, изведен от курсора на страницата и позицията му в нея, а регистър пази всеки завършен запис седем дни, така че вече записаните данни се възпроизвеждат от регистъра, вместо да бъдат изпратени до целевата система втори път.

Защо повторената синхронизация не записва един и същ контакт два пъти?

Преди всяко извикване на целевата система изпълнението проверява ключа за доставка на записа в регистъра. Ключът е хеш на синхронизацията, източника, курсора на страницата, позицията в страницата и самия запис, така че срив, повторен опит минута по-късно и планирано изпълнение на следващия ден извеждат един и същ ключ за един и същ запис. Ключ, който вече е отбелязан като завършен в рамките на седемдневния прозорец, не се изпраща отново.

Какво е контрол на бдителността (dead man) за синхронизация?

Проверка, която се задейства при отсъствие, а не при грешка. Веднъж на час Tajo преглежда всяко работно пространство с активна синхронизация и подава известие, когато нито едно изпълнение не е успяло през последните двадесет и четири часа, включително когато изобщо не е записано изпълнение. Тя се задейства веднъж на инцидент и се презарежда след първото успешно изпълнение. Тя съществува, защото известието за грешка вижда само грешките, които са били записани.

Заявете ранен достъп

Въведете собственото си име и имейл адрес. Ще ви изпратим връзка за потвърждение на заявката за достъп до Tajo.

Вземете Brevo

Тази страница като Markdown: worker-died-sync-didnt.md

Зареждане на Markdown…
Отвори в Claude Отвори в ChatGPT