Aktualna wersja: 0.3.0
Wydania: https://github.com/lukaszj321/github-data-sync-service/releases
github-data-sync-service to backend do rejestrowania publicznych repozytoriów GitHuba i synchronizacji wybranych danych do PostgreSQL. API tworzy lokalne zadania synchronizacji, a osobny worker pobiera issues stronami z GitHub REST API, filtruje pull requesty, idempotentnie zapisuje issues oraz obsługuje rate limiting, recovery, błędy pojedynczego joba i trwały kursor synchronizacji.
Wersja 0.3.0 dodaje pełny i przyrostowy tryb synchronizacji issues. Pierwszy przebieg nadal wykonuje bootstrap full, a następne przebiegi mogą używać parametru since z bezpiecznym oknem nakładania.
Projekt rozwiązuje problem bezpiecznego przeniesienia synchronizacji z requestu HTTP do kontrolowanej kolejki PostgreSQL. Klient rejestruje repozytorium i tworzy job, worker wykonuje synchronizację poza requestem API, a aplikacja zapisuje wynik i stan kursora w lokalnej bazie.
Zakres wersji 0.3.0 obejmuje synchronizację issues, paginację po Link, filtrowanie pull requestów, idempotentny upsert, liczniki joba, rate-limit rescheduling, stale recovery, izolację błędów pojedynczego joba, tryby full i incremental, trwały ResourceSyncState oraz endpoint odczytu stanu synchronizacji.
Kolejność czytania:
- Status projektu i zakres wersji
0.3.0. - Architektura oraz przepływ synchronizacji.
- Tryby synchronizacji i trwały kursor.
- Uruchomienie przez Docker Compose.
- Rejestracja repozytorium.
- Utworzenie zadania synchronizacji.
- Sprawdzanie statusu joba i stanu synchronizacji.
- Odczyt zsynchronizowanych issues.
- Testy, ograniczenia i informacje o wydaniach.
- Zacznij tutaj
- Status projektu
- Architektura
- Tryby synchronizacji
- Trwały kursor
- Zakres wersji 0.3.0
- Struktura projektu
- API
- Konfiguracja
- Uruchomienie
- Przykład end-to-end
- Semantyka liczników
- Rate limiting i ponowne próby
- Recovery zadań
- Najczęstsze zadania
- Testy i quality gates
- Ograniczenia
- Wydania
- Nawigacja
- Rejestracja publicznych repozytoriów.
- Walidacja repozytorium przez GitHub REST API.
- PostgreSQL job queue z
FOR UPDATE SKIP LOCKED. - Osobny proces workera.
- Synchronizacja issues.
- Tryby
fulliincremental. - Bootstrap full przy pierwszym żądaniu incremental bez kursora.
- Trwały stan
resource_sync_statesdla repozytorium i zasobu. - Paginacja przez nagłówek
Linkirel="next". - Sortowanie issues po
updatedrosnąco. - Parametr GitHub
sincedla synchronizacji przyrostowej. - Konfigurowane overlap window.
- Filtrowanie pull requestów zwracanych przez GitHub Issues API.
- Idempotentny upsert issues.
- Statystyki joba.
- Rate-limit rescheduling.
- Stale job recovery.
- Endpoint
GET /repositories/{repository_id}/sync-state. - Docker Compose.
- Migracje Alembic.
- Testy jednostkowe, integracyjne PostgreSQL i opcjonalne live.
- GitHub Actions.
- Obsługiwane są wyłącznie publiczne repozytoria.
- Nie ma ETag ani conditional requests.
- Nie ma persistent page-level resume.
- Numer strony i
next_urlnie są trwałymi checkpointami. - Nie ma wznawiania od środka częściowo przetworzonego joba.
- Nie ma automatycznego usuwania lokalnych issues nieobecnych w późniejszej odpowiedzi GitHuba.
- Nie ma synchronizacji innych zasobów GitHuba.
Te obszary nie są zaimplementowane w 0.3.0:
- Pull requesty jako osobny zasób.
- Commity, releases, workflow runs, komentarze, labels, milestones i assignees.
- Webhooki i harmonogram cykliczny.
- Endpoint cancellation i ręczny retry.
- GraphQL, OAuth i prywatne repozytoria wymagające nowego modelu autoryzacji.
- Frontend, Redis, Celery, Kafka, Kubernetes i wdrożenie chmurowe.
- LLM.
- FastAPI odpowiada za endpointy HTTP, walidację requestów, mapowanie odpowiedzi i cykl życia zależności.
- PostgreSQL przechowuje repozytoria, joby synchronizacji, zsynchronizowane issues i trwały stan zasobu.
sync_jobsjest kolejką pracy opartą o statusy,available_at, lock metadata iFOR UPDATE SKIP LOCKED.resource_sync_statesprzechowuje kursor dla paryrepository_idorazresource_type.- Worker przejmuje dostępne joby i wykonuje synchronizację poza requestem HTTP.
GitHubClientobsługuje requesty do GitHub REST API, retry błędów tymczasowych, klasyfikację rate limitów,sincei bezpieczne diagnostyki.- Issues store zapisuje issues idempotentnie i rozróżnia rekordy utworzone, zaktualizowane oraz niezmienione.
- Alembic zarządza schematem bazy danych.
- Docker Compose uruchamia PostgreSQL, jednorazowe migracje, API i workera.
- GitHub Actions wykonuje lint, format, mypy, testy, build pakietu i smoke kontenera.
flowchart LR
Client[Client] -->|POST /sync mode| API[FastAPI]
API -->|pending job| Jobs[(sync_jobs)]
API -->|read cursor| State[(resource_sync_states)]
Jobs -->|FOR UPDATE SKIP LOCKED| Worker[Worker]
Worker -->|GET issues sort=updated since optional| GitHub[GitHub Issues API]
GitHub -->|issues plus pull requests| Worker
Worker -->|filtered issue upsert| Issues[(issues)]
Worker -->|status and counters| Jobs
Worker -->|atomic cursor advance| State
Kroki przepływu:
- Klient rejestruje repozytorium albo korzysta z istniejącego
repository_id. - Klient tworzy zadanie przez
POST /repositories/{repository_id}/sync. - API sprawdza
ResourceSyncState, rozstrzyga faktycznysync_mode, wyliczacursor_beforeisince_at, a następnie zapisuje lokalny jobpending. - Jeżeli istnieje aktywny job dla tego repozytorium i zasobu, API zwraca go bez zmiany trybu.
- Worker claimuje dostępny job przez krótką transakcję z
FOR UPDATE SKIP LOCKED. - Worker ustawia
sync_window_started_attylko przy pierwszym claimie joba. - Worker pobiera strony z GitHub Issues API, używając
sincetylko dla faktycznego trybu incremental. - Rekordy z polem
pull_requestsą liczone jako pominięte i nie trafiają do tabeliissues. - Issues są zapisywane idempotentnie, a statystyki joba są aktualizowane.
- Po pełnym sukcesie job i
ResourceSyncStatesą aktualizowane w jednej transakcji.
Endpoint tworzący job nie wykonuje requestu do GitHuba. Odczyt stanu i utworzenie joba są kontrolowane przez przepływ transakcyjny, a ochronę przed duplikatem aktywnego joba zapewnia partial unique index.
Claim joba jest krótką transakcją, która kończy się przed komunikacją HTTP, więc worker nie trzyma blokady bazy podczas requestów do GitHuba.
Każda poprawnie sparsowana strona jest zapisywana w osobnej krótkiej transakcji. Upsert issues i aktualizacja liczników danej strony są atomowe: jeżeli commit strony się nie powiedzie, zapisy issues z tej strony są wycofywane razem z licznikami. Błąd późniejszej strony nie usuwa wcześniejszych zatwierdzonych stron.
Completion jest osobną transakcją: job może zostać oznaczony jako completed tylko razem z poprawnym przesunięciem trwałego stanu zasobu.
Kontrolowane błędy GitHuba są klasyfikowane przez klienta. Błędy tymczasowe mogą zostać ponowione zgodnie z konfiguracją. Rate limit kończy iterację statusem rate_limited, czyści lock metadata, zachowuje sync_mode, cursor_before, since_at oraz sync_window_started_at, ustawia available_at i nie przesuwa kursora.
Nieoczekiwany błąd aplikacji albo bazy w trakcie pojedynczego joba jest izolowany. Worker wykonuje rollback sesji, próbuje oznaczyć job jako failed, zapisuje bezpieczne last_error, czyści lock metadata i przechodzi do kolejnej iteracji. Stale recovery jest zabezpieczeniem awaryjnym dla przerwanych procesów albo niedostępnej bazy, a nie podstawową ścieżką obsługi wyjątków pojedynczego joba.
Źródła implementacji:
- API i routing:
src/github_data_sync_service/api/ - Klient GitHuba i paginacja:
src/github_data_sync_service/github/ - Kolejka i joby:
src/github_data_sync_service/queue/ - Worker:
src/github_data_sync_service/worker/ - Upsert i odczyt issues:
src/github_data_sync_service/issues/ - Modele bazy danych:
src/github_data_sync_service/db/models/ - Migracje:
alembic/versions/
Domyślny request używa mode = incremental, ale jeżeli dla repozytorium nie istnieje jeszcze udany ResourceSyncState.cursor_at, API tworzy faktyczny job sync_mode = full.
W takim jobie:
cursor_before = nullsince_at = null- GitHub request nie zawiera
since - response pokazuje faktyczny
sync_mode = full
To zachowanie pozwala klientowi zawsze wysyłać ten sam domyślny request i bezpiecznie przejść od pustej bazy do późniejszych synchronizacji przyrostowych.
Gdy istnieje cursor_at, request mode = incremental tworzy job z:
sync_mode = incrementalcursor_before = state.cursor_atsince_at = cursor_before - ISSUES_SYNC_OVERLAP_SECONDS
Worker wysyła do GitHuba:
state=all
per_page=100
sort=updated
direction=asc
since=2026-07-17T11:59:00Z
Następne strony są pobierane wyłącznie przez zwalidowany Link rel="next". Aplikacja nie dokleja ponownie since do next_url, nie zapisuje next_url jako kursora i nie traktuje numeru strony jako checkpointu.
Request z mode = full wymusza pełną synchronizację:
{
"resource_type": "issues",
"mode": "full"
}Jeżeli istnieje poprzedni kursor, cursor_before może go zawierać diagnostycznie, ale since_at musi pozostać null, a request do GitHuba nie zawiera since. Po pełnym sukcesie stan kursora przesuwa się do nowego sync_window_started_at.
Kursor nie jest numerem strony, next_url, ETagiem ani maksymalnym github_updated_at znalezionym w odpowiedzi. Bezpiecznym kandydatem na nowy kursor jest czas rozpoczęcia okna synchronizacji, czyli sync_window_started_at, zapisany przed pierwszym requestem HTTP danego joba.
Znaczenie nowych pól joba:
sync_mode: faktyczny tryb wykonania joba,fullalboincremental.cursor_before:cursor_atodczytany ze stanu synchronizacji podczas tworzenia joba.since_at:cursor_beforecofnięty o overlap;nulldla full.sync_window_started_at: czas rozpoczęcia okna synchronizacji, ustawiany tylko raz przy pierwszym claimie joba.cursor_after: ustawiany wyłącznie po pełnym sukcesie, równysync_window_started_at.
ISSUES_SYNC_OVERLAP_SECONDS domyślnie wynosi 60. Dla kursora:
cursor_at = 2026-07-17T12:00:00Z
overlap = 60 sekund
since_at = 2026-07-17T11:59:00Z
Duplikaty z overlap window są oczekiwane. Idempotentny upsert rozpozna je jako created, updated albo unchanged. Jeżeli overlap wynosi 0, since_at jest równe cursor_before.
Timestamp wysyłany jako since jest timezone-aware, normalizowany do UTC, obcinany do sekund i serializowany z końcowym Z, na przykład 2026-07-17T11:59:00Z. Naiwny datetime bez strefy jest odrzucany w kontrolowanej funkcji klienta.
resource_sync_states przechowuje stan dla pary repozytorium i zasób:
idrepository_idresource_typecursor_atlast_successful_job_idlast_sync_modelast_started_atlast_completed_atcreated_atupdated_at
Stan nie powstaje przy samym utworzeniu joba. initialized = true dopiero po udanym completion.
Kursor przesuwa się tylko po pełnym, poprawnym zakończeniu wszystkich stron danego joba. Completion w jednej transakcji blokuje job, tworzy albo aktualizuje ResourceSyncState, ustawia cursor_at, last_successful_job_id, last_sync_mode, last_started_at, last_completed_at, job.cursor_after, status = completed, finished_at i czyści lock metadata.
Kursor nie przesuwa się po:
failedrate_limitedcancelled- przerwaniu procesu
- stale recovery
- błędzie zapisu późniejszej strony
- błędzie transakcji completion
Jeżeli starszy odzyskany job zakończy się po nowszym udanym jobie, nie może cofnąć state.cursor_at. Job zachowuje własne cursor_after diagnostycznie, ale trwały high-watermark pozostaje nowszy.
ETag nie jest jeszcze użyty do pomijania całej paginowanej kolekcji, ponieważ obecny przepływ nie wysyła conditional headers. Nieoczekiwane 304 w tym trybie jest traktowane jako błąd odpowiedzi, nie jako sukces synchronizacji.
Wersja 0.1.0 dostarczyła rejestrację repozytoriów, fundament kolejki PostgreSQL, migracje, podstawową strukturę API i walidacje jakości.
Wersja 0.2.0 dodała wykonanie jobów issues przez workera, lokalną tabelę issues, endpointy jobów i issues, paginację po rel="next", filtrowanie pull requestów, liczniki synchronizacji, rate-limit rescheduling, stale recovery oraz izolację błędów pojedynczego joba.
Wersja 0.3.0 dodaje trwały stan synchronizacji zasobu, tryby full i incremental, parametr GitHub since, overlap window, atomowe przesuwanie kursora i endpoint sync-state.
To podsumowanie nie zastępuje pełnej historii zmian. Szczegóły są w CHANGELOG.md. Opisy stabilnych wydań pozostają dostępne w RELEASE_NOTES_v0.2.0.md i RELEASE_NOTES_v0.1.0.md.
src/github_data_sync_service/
api/
routes/
schemas/
core/
db/
models/
github/
issues/
queue/
repositories/
worker/
alembic/
versions/
tests/
unit/
integration/
live/
Najważniejsze obszary:
api/zawiera aplikację FastAPI, routing, schematy odpowiedzi i zależności.core/zawiera konfigurację, logowanie i błędy aplikacyjne.db/zawiera sesje SQLAlchemy i modele tabel, w tymResourceSyncState.github/zawiera klienta GitHuba, modele odpowiedzi i obsługę paginacji.issues/zawiera idempotentny zapis i odczyt issues.queue/zawiera operacje nasync_jobsoraz serwis tworzenia jobów.repositories/zawiera rejestrację i odczyt repozytoriów.worker/zawiera proces workera i processor synchronizacji.alembic/versions/zawiera migracje schematu.tests/zawiera testy jednostkowe, integracyjne PostgreSQL i opcjonalne testy live.
Aktualne endpointy:
POST /repositories
GET /repositories
GET /repositories/{repository_id}
POST /repositories/{repository_id}/sync
GET /repositories/{repository_id}/sync-state
GET /sync-jobs
GET /sync-jobs/{job_id}
GET /repositories/{repository_id}/issues
GET /health
GET /ready
POST /repositories waliduje publiczne repozytorium przez GitHub REST API i zapisuje je lokalnie.
Request:
{
"owner": "lukaszj321",
"name": "github-data-sync-service"
}Status 201 Created oznacza nową lokalną rejestrację. Status 200 OK oznacza, że repozytorium było już zapisane lokalnie i zostało odświeżone.
GET /repositories zwraca listę lokalnie zapisanych repozytoriów. Parametry limit i offset są ograniczane do bezpiecznych wartości; limit mieści się w zakresie 1..100.
GET /repositories/{repository_id} zwraca pojedyncze repozytorium z PostgreSQL.
POST /repositories/{repository_id}/sync tworzy job synchronizacji issues.
Domyślny incremental request:
{
"resource_type": "issues",
"mode": "incremental"
}Wymuszony full request:
{
"resource_type": "issues",
"mode": "full"
}Nowy job zwraca 202 Accepted. Jeżeli istnieje aktywny job pending, running albo rate_limited dla tego repozytorium i zasobu, API zwraca go z 200 OK, nie zmienia jego trybu i zachowuje nagłówek Location.
Endpoint nie wykonuje requestu do GitHuba. Zapisuje wyłącznie lokalny job dla workera.
GET /sync-jobs listuje joby. Obsługiwane są parametry limit, offset, repository_id, status, resource_type i mode.
GET /sync-jobs/{job_id} zwraca pojedynczy job.
Przykładowe pola odpowiedzi:
{
"id": "00000000-0000-0000-0000-000000000000",
"repository_id": "00000000-0000-0000-0000-000000000000",
"resource_type": "issues",
"sync_mode": "incremental",
"cursor_before": "2026-07-17T12:00:00Z",
"since_at": "2026-07-17T11:59:00Z",
"cursor_after": "2026-07-17T12:10:00Z",
"sync_window_started_at": "2026-07-17T12:10:00Z",
"status": "completed",
"attempt_count": 1,
"available_at": "2026-07-17T12:09:59Z",
"locked_at": null,
"locked_by": null,
"heartbeat_at": null,
"started_at": "2026-07-17T12:10:00Z",
"finished_at": "2026-07-17T12:10:03Z",
"current_page": 1,
"fetched_count": 0,
"skipped_count": 0,
"created_count": 0,
"updated_count": 0,
"unchanged_count": 0,
"error_count": 0,
"last_error": null,
"github_request_id": "request-id",
"rate_limit_remaining": 50,
"created_at": "2026-07-17T12:09:59Z",
"updated_at": "2026-07-17T12:10:03Z"
}GET /repositories/{repository_id}/sync-state zwraca stan synchronizacji issues bez requestu do GitHuba.
Przed pierwszym sukcesem:
{
"repository_id": "00000000-0000-0000-0000-000000000000",
"resource_type": "issues",
"initialized": false,
"cursor_at": null,
"last_successful_job_id": null,
"last_sync_mode": null,
"last_started_at": null,
"last_completed_at": null
}Po sukcesie:
{
"repository_id": "00000000-0000-0000-0000-000000000000",
"resource_type": "issues",
"initialized": true,
"cursor_at": "2026-07-17T12:10:00Z",
"last_successful_job_id": "00000000-0000-0000-0000-000000000000",
"last_sync_mode": "incremental",
"last_started_at": "2026-07-17T12:10:00Z",
"last_completed_at": "2026-07-17T12:10:03Z"
}Nieistniejące repozytorium zwraca 404.
GET /repositories/{repository_id}/issues czyta lokalne issues z PostgreSQL i nie odpytuje GitHuba.
Parametry:
limit, domyślnie50, ograniczane do1..100.offset, domyślnie0, wartości ujemne są sprowadzane do0.state, opcjonalny filtr stanu issue.
Wynik jest sortowany po number DESC. Brak lokalnych issues zwraca 200 OK z pustą listą.
GET /health zwraca prosty status procesu API.
GET /ready sprawdza połączenie z PostgreSQL przez SELECT 1.
Konfiguracja pochodzi z pydantic-settings, zmiennych środowiskowych i opcjonalnego pliku .env.
| Zmienna | Domyślnie | Przeznaczenie |
|---|---|---|
APP_ENV |
local |
Nazwa środowiska aplikacji. |
LOG_LEVEL |
INFO |
Poziom logowania. |
DATABASE_URL |
lokalny PostgreSQL github_data_sync |
Adres bazy danych SQLAlchemy. |
GITHUB_TOKEN |
brak | Opcjonalny token zwiększający limity GitHub API; pochodzi wyłącznie ze środowiska. |
GITHUB_API_BASE_URL |
https://api.github.com |
Bazowy URL GitHub REST API. |
GITHUB_API_VERSION |
2022-11-28 |
Wersja API wysyłana w nagłówkach requestu. |
GITHUB_USER_AGENT |
github-data-sync-service/0.3.0 |
User-Agent klienta; domyślnie wykorzystuje aktualną wersję pakietu. |
GITHUB_CONNECT_TIMEOUT_SECONDS |
5 |
Timeout zestawiania połączenia. |
GITHUB_READ_TIMEOUT_SECONDS |
15 |
Timeout odczytu odpowiedzi. |
GITHUB_MAX_ATTEMPTS |
3 |
Maksymalna liczba prób dla błędów tymczasowych; minimum 1. |
GITHUB_ISSUES_PER_PAGE |
100 |
Liczba elementów na stronie issues; zakres 1..100. |
GITHUB_MAX_PAGES_PER_SYNC |
1000 |
Bezpiecznik przed nieskończoną paginacją; minimum 1. |
ISSUES_SYNC_OVERLAP_SECONDS |
60 |
Cofnięcie since_at względem cursor_before; zakres 0..86400. |
WORKER_POLL_INTERVAL_SECONDS |
5 |
Odstęp workera, gdy nie ma dostępnego joba albo potrzebny jest backoff. |
WORKER_RATE_LIMIT_FALLBACK_SECONDS |
60 |
Fallback rate limitu, gdy odpowiedź nie ma jednoznacznych nagłówków. |
WORKER_STALE_JOB_TIMEOUT_SECONDS |
300 |
Czas po którym porzucony running job może zostać odzyskany. |
WORKER_ID |
worker-local |
Identyfikator workera zapisywany w lock metadata. |
GITHUB_USER_AGENT pozwala na jawny override, ale nie trzeba go ustawiać w typowym uruchomieniu. GITHUB_MAX_PAGES_PER_SYNC chroni przed pętlą paginacji. Fallback rate limitu jest używany tylko wtedy, gdy nie można wyznaczyć czasu wznowienia z odpowiedzi GitHuba. Stale timeout określa moment odzyskania porzuconego running joba.
Nie umieszczaj sekretów w repozytorium ani w przykładach komend. Token GitHuba powinien pochodzić ze środowiska uruchomieniowego.
Start:
docker compose up --build -d
docker compose psSprawdzenie:
curl http://localhost:8000/health
curl http://localhost:8000/readyZatrzymanie:
docker compose down --volumes --remove-orphansRole usług:
dburuchamia PostgreSQL.migratewykonuje jednorazowoalembic upgrade head.apiuruchamia FastAPI przez Uvicorn.workeruruchamiagithub-data-sync-workeri wykonuje joby synchronizacji.
alembic upgrade head
alembic downgrade -1
alembic upgrade headW Compose migracje wykonuje osobna usługa migrate, od której zależą api i worker.
Poniższy przykład uruchamia stack, rejestruje publiczne repozytorium, tworzy bootstrap full, potem incremental i wymuszony full.
docker compose down --volumes --remove-orphans
docker compose up --build -d
Invoke-RestMethod -Uri "http://localhost:8000/health"
Invoke-RestMethod -Uri "http://localhost:8000/ready"
$repo = Invoke-RestMethod -Method Post `
-Uri "http://localhost:8000/repositories" `
-ContentType "application/json" `
-Body '{"owner":"lukaszj321","name":"github-data-sync-service"}'
$repositoryId = $repo.id
$firstJob = Invoke-RestMethod -Method Post `
-Uri "http://localhost:8000/repositories/$repositoryId/sync" `
-ContentType "application/json" `
-Body '{"resource_type":"issues","mode":"incremental"}'
for ($i = 0; $i -lt 60; $i++) {
$firstCurrent = Invoke-RestMethod -Uri "http://localhost:8000/sync-jobs/$($firstJob.id)"
if ($firstCurrent.status -in @("completed", "failed", "rate_limited")) { break }
Start-Sleep -Seconds 2
}
$stateAfterBootstrap = Invoke-RestMethod -Uri "http://localhost:8000/repositories/$repositoryId/sync-state"
$secondJob = Invoke-RestMethod -Method Post `
-Uri "http://localhost:8000/repositories/$repositoryId/sync" `
-ContentType "application/json" `
-Body '{"resource_type":"issues","mode":"incremental"}'
for ($i = 0; $i -lt 60; $i++) {
$secondCurrent = Invoke-RestMethod -Uri "http://localhost:8000/sync-jobs/$($secondJob.id)"
if ($secondCurrent.status -in @("completed", "failed", "rate_limited")) { break }
Start-Sleep -Seconds 2
}
$fullJob = Invoke-RestMethod -Method Post `
-Uri "http://localhost:8000/repositories/$repositoryId/sync" `
-ContentType "application/json" `
-Body '{"resource_type":"issues","mode":"full"}'
$issues = Invoke-RestMethod -Uri "http://localhost:8000/repositories/$repositoryId/issues?limit=100"
$syncState = Invoke-RestMethod -Uri "http://localhost:8000/repositories/$repositoryId/sync-state"
$firstCurrent.sync_mode
$secondCurrent.sync_mode
$secondCurrent.cursor_before
$secondCurrent.since_at
$syncState.initialized
$issues.items.Count
docker compose down --volumes --remove-orphansRepozytorium może mieć zero zwykłych issues. Pusty wynik jest poprawny, jeżeli worker odebrał job, job zakończył się kontrolowanym statusem, a cursor state został zaktualizowany po sukcesie.
fetched_count: wszystkie elementy zwrócone przez GitHub Issues API, włącznie z pull requestami.skipped_count: elementy pominięte, bo zawierały polepull_request.created_count: nowe lokalne issues.updated_count: istniejące issues, których pola domenowe realnie się zmieniły.unchanged_count: istniejące issues identyczne z aktualną odpowiedzią GitHuba, w tym rekordy ponownie pobrane przez overlap.error_count: przerwane próby, błędy joba i odzyskane stale locki; licznik jest kumulatywny.
Dla poprawnie zakończonej synchronizacji bez duplikatów po stronie GitHuba:
created_count + updated_count + unchanged_count = fetched_count - skipped_count
Pierwszy request issues używa:
state=all
per_page=100
sort=updated
direction=asc
Dla incremental dodawany jest since. Kolejne strony są pobierane wyłącznie z rel="next" w nagłówku Link. Worker nie konstruuje ręcznie numerów stron. next_url musi używać https, wskazywać ten sam host i port co GITHUB_API_BASE_URL oraz nie może zawierać danych uwierzytelniających. Powtórzony next_url, pętla paginacji albo przekroczenie GITHUB_MAX_PAGES_PER_SYNC kończą job statusem failed.
Dla rate-limited 403 oraz 429 worker nie śpi długo. Job przechodzi w rate_limited, czyści lock metadata, zapisuje bezpieczny last_error, request ID, remaining i available_at:
now + Retry-After, jeżeli nagłówek istnieje.X-RateLimit-Reset, jeżeli remaining wynosi0.now + WORKER_RATE_LIMIT_FALLBACK_SECONDS, domyślnie 60 sekund.
Po available_at ten sam job może zostać ponownie pobrany i zaczyna od pierwszej strony. Zachowuje sync_mode, cursor_before, since_at i sync_window_started_at; aktualizuje started_at dla nowej próby; zwiększa attempt_count oraz error_count.
Worker odzyskuje stare joby running, których heartbeat_at jest starszy niż WORKER_STALE_JOB_TIMEOUT_SECONDS. Recovery ustawia pending, available_at = now, czyści lock metadata i zwiększa error_count.
Recovery nie przesuwa ResourceSyncState.cursor_at, nie czyści sync_window_started_at, nie zmienia sync_mode, cursor_before ani since_at. Retry zaczyna od pierwszej strony i używa pierwotnego zakresu synchronizacji.
| Zadanie | Gdzie zacząć |
|---|---|
| Uruchomić cały stack lokalnie | Docker Compose |
| Sprawdzić, czy API działa | Health i readiness |
| Zarejestrować publiczne repozytorium | Rejestracja repozytorium |
| Utworzyć synchronizację issues | Tworzenie zadania synchronizacji |
| Zrozumieć full i incremental | Tryby synchronizacji |
| Sprawdzić status joba | Sprawdzanie statusu zadania |
| Sprawdzić durable cursor | Odczyt stanu synchronizacji |
| Odczytać lokalne issues | Odczyt issues |
| Zrozumieć liczniki joba | Semantyka liczników |
| Sprawdzić zachowanie przy rate limit | Rate limiting i ponowne próby |
| Zrozumieć odzyskiwanie porzuconych jobów | Recovery zadań |
| Uruchomić komplet walidacji | Testy i quality gates |
| Sprawdzić historię zmian | Wydania |
Podstawowe walidacje:
$env:UV_CACHE_DIR=".uv-cache"
$env:UV_PYTHON_INSTALL_DIR=".uv-python"
uv run --extra dev python -m ruff check .
uv run --extra dev python -m ruff format --check .
uv run --extra dev python -m mypy src
uv run --extra dev python -m pytest -m "not integration and not live" -W error `
--cov=github_data_sync_service `
--cov-branch `
--cov-report=term-missing `
--cov-fail-under=85Integracyjne PostgreSQL:
docker compose up -d db
uv run --extra dev alembic upgrade head
uv run --extra dev python -m pytest -m integration -W error
docker compose down --volumes --remove-orphansOpcjonalne testy live:
$env:RUN_LIVE_TESTS="1"
uv run --extra dev python -m pytest -m live -W error
Remove-Item Env:RUN_LIVE_TESTSGitHub Actions wykonuje te same główne bramki jakości, build pakietu, instalację wheel, sprawdzenie wersji workera i Docker non-root smoke.
- Projekt działa z publicznymi repozytoriami GitHuba.
- Issues są synchronizowane jako zasób z GitHub Issues API; pull requesty są filtrowane, ale nie są synchronizowane jako osobny zasób.
- Nie ma ETag ani conditional request support.
- Incremental sync nie gwarantuje trwałego snapshotu całej paginowanej kolekcji.
- Numer strony i
next_urlnie są checkpointami. - Lokalny prune issues nie jest wykonywany, bo częściowa synchronizacja nie jest bezpiecznym kompletnym snapshotem.
- Nie ma synchronizacji commits, releases, workflow runs, komentarzy, labels ani milestones.
- Nie ma OAuth, frontendu, Redis, Celery, Kafka, Kubernetes ani wdrożenia chmurowego.
- Projekt nie jest kompletną platformą analityczną GitHuba.
- Wszystkie wydania: https://github.com/lukaszj321/github-data-sync-service/releases
- Changelog: CHANGELOG.md
- Release notes
v0.2.0: RELEASE_NOTES_v0.2.0.md - Release notes
v0.1.0: RELEASE_NOTES_v0.1.0.md
Historia:
v0.1.0: fundament rejestracji repozytoriów, kolejki, migracji, Docker Compose i CI.v0.2.0: wykonanie synchronizacji issues przez workera, lokalne issues, API jobów, obsługa rate limitów, recovery i izolacja błędów joba.0.3.0: incremental issue synchronization with durable cursors. Nie utworzono jeszcze taga ani release notes dlav0.3.0.
- Kod aplikacji:
src/github_data_sync_service/ - Endpointy API:
src/github_data_sync_service/api/routes/ - Schematy API:
src/github_data_sync_service/api/schemas/ - Klient GitHuba:
src/github_data_sync_service/github/ - Worker:
src/github_data_sync_service/worker/ - Kolejka jobów:
src/github_data_sync_service/queue/ - Issues store:
src/github_data_sync_service/issues/ - Modele bazy:
src/github_data_sync_service/db/models/ - Model stanu zasobu:
src/github_data_sync_service/db/models/resource_sync_state.py - Migracje:
alembic/versions/ - Testy jednostkowe:
tests/unit/ - Testy integracyjne:
tests/integration/ - Testy live:
tests/live/ - Docker Compose:
compose.yaml - Dockerfile:
Dockerfile - CI:
.github/workflows/ci.yml - Konfiguracja pakietu:
pyproject.toml - Changelog:
CHANGELOG.md - Release notes
v0.2.0:RELEASE_NOTES_v0.2.0.md - GitHub Releases: https://github.com/lukaszj321/github-data-sync-service/releases