LLM WikiAccess-protected knowledge portal

WIKI

crawler-lib-golang

AI Summary Purpose Durable note for the Go module crawler crawler lib golang . Records the TB COMP LIB GOLANG LIST V2 redesign event grain queue, declared module path resolution, and the skip existing version mode. Key points index.golang.o

경로ai/repo-notes/crawler-lib-golang.md
카테고리Repo Note
태그#cicd #crawler #data-pipeline #golang #lib #license #notes #repo #repo-note #report

# crawler-lib-golang

AI Summary

Purpose:

Key points:

Relevant when:

Do not read full document unless:

Linked documents:

Repo Info

- Pure helpers: python3 -m unittest tests.test_go_crawler_regressions (no DB/network). - V2 integration: tests/test_go_crawler_v2_integration.py (real MySQL + mocked network, 9 tests incl. gatheringdb routing + RESYNC + list case-sensitivity). Skips unless sqlalchemy + TEST_DB_URL are present; run inside the iotcube/golangcrawler image against a throwaway MySQL 8 container. Host python3 -m unittest discover -s tests = 57 tests OK (9 integration skipped).

Terms

Source-of-Truth Rules

  1. pkg.go.dev API is for documentation/metadata enrichment only. Never use it to decide existence.
  2. proxy.golang.org decides module/version existence.
  3. If pkg.go.dev is 404 but proxy @latest or @v/list is alive, still collect.
  4. If pkg.go.dev metadata is absent, description/license/repoUrl may be empty.
  5. Go module paths are case-sensitive. Do not normalize with LOWER() for identity.
  6. Proxy URLs escape uppercase as ! + lowercase (e.g. eduVPN -> edu!v!p!n). See escape_go_module_proxy_path.

TB_COMP_LIB_GOLANG_LIST_V2

DDL: sql/TB_COMP_LIB_GOLANG_LIST_V2.sql in the repo.

Grain: one row per (INDEX_PATH, INDEX_VERSION) event.

ColumnTypeMeaning
IDCHAR(64) PKsha256(INDEX_PATH + "@" + INDEX_VERSION), case-sensitive dedup key
INDEX_PATHVARCHAR(255) 0900_as_csindex Path
INDEX_VERSIONVARCHAR(255) 0900_as_csindex Version
PRODUCT_KEYVARCHAR(255) 0900_as_cs NULLdeclared module path; NULL until resolved
URLLONGTEXTpkg.go.dev URL (PRODUCT_KEY preferred)
PROCESSEDTINYINTstatus code (below)
DEPRECATEDTINYINT1 when INDEX_PATH is not the representative
RESOLVE_KINDVARCHAR(32)ops label: same / alias_git / subpath / declared_diff / proxy_only / not_found
RESYNCTINYINT NOT NULL DEFAULT 00 = default (worker checks DB, skips if exists); 1 = re-collect (worker skips the DB check). Set 1 + PROCESSED=0 to re-queue a module for re-collection. Added 2026-05-29 (sql/ALTER_TB_COMP_LIB_GOLANG_LIST_V2_add_resync.sql).
TIMESTAMPTIMESTAMPindex Timestamp (same name/meaning as V1)
LAST_UPDATEDTIMESTAMProw update time (ON UPDATE)

Design choices:

PROCESSED status codes

Running V2 (docker-compose)

docker-compose.yml services: main (indexer + worker), worker (worker only, scalable), backfill (profile index, one-shot). All set GO_LIST_SCHEMA=v2, GO_TARGET_GATHERINGDB=1, GO_SKIP_EXISTING_VERSION=1. DB conn comes from src/config.ini DbUrl (not env) — so any server with the repo + image + DB reachability works.

Deploy order (important):

  1. Schema once: sql/TB_COMP_LIB_GOLANG_LIST_V2.sql (LIST_V2, includes RESYNC), sql/TB_COMP_LIB_VERSION_GOLANG_gatheringdb.sql, sql/TB_COMP_LIB_PRODUCT_GOLANG_gatheringdb.sql — all into gatheringdb. If LIST_V2 already exists without RESYNC, apply sql/ALTER_TB_COMP_LIB_GOLANG_LIST_V2_add_resync.sql.
  2. Initial backfill (one-shot, before any worker): GO_INDEX_SINCE=2019-04-10... docker compose --profile index run --rm backfill (fast mode via GO_INDEX_SKIP_EXISTS_CHECK=1 baked into the service). Let it run to completion (auto-exits Exited 0 when caught up to now). Do NOT stop it midway — a partial backfill leaves a gap (main's indexer resumes from the V1 cursor ~now, so the gap is never filled). Resume after a crash with GO_INDEX_SINCE=<cursor> (cursor read from TB_CRAWLER_STATUS TYPE=GoCralwerIndexListBackfill).
  3. Migrate existing labradordb data (optional, parallel-safe): scripts/migrate_golang_to_gatheringdb.py.
  4. THEN workers: main server -> docker compose up -d main; each worker server -> docker compose up -d worker. ALTER must be applied before workers (claim SELECT reads RESYNC).

4-server topology (main 1 + worker 3): one main host, three worker hosts, each docker compose up -d <service>. They coordinate via the shared DB.

Workers on different servers coordinate purely through the shared DB: per-event GET_LOCK prevents double-processing, and (with GO_TARGET_GATHERINGDB=1) the skip-check + writes hit the gatheringdb as_cs tables (case-sensitive). Caveats:

Feature Flags (env)

Collection Routine (V2 worker)

For each claimed event (INDEX_PATH, INDEX_VERSION):

  1. Compute do_skip_check = skip_enabled AND not (GO_RESYNC_ALL or event.RESYNC). If the event is flagged resync, all DB-existence checks below are bypassed (force re-collect). The version table checked is the gatheringdb as_cs one when GO_TARGET_GATHERINGDB=1 (so the check is case-sensitive).
  2. If do_skip_check and (INDEX_PATH, INDEX_VERSION) already exists in the version table -> set PROCESSED=1, skip fetch (cheap pre-check).
  3. Fetch the index version's go.mod via proxy (getCanonicalModulePath). If it 404s, fall back to @latest.
  4. If still no go.mod, restore the event to the retry ladder.
  5. Parse the module line -> declared module path. store_path = declared or INDEX_PATH.
  6. Classify with classify_go_index_resolution(INDEX_PATH, store_path).
  7. If skip mode is on and (store_path, version) already exists -> set status and skip heavy work (second check).
  8. Build the single version row + product + license under store_path.
  9. Persist version + product + the resolved event row in one transaction.

- same path: PROCESSED=1, DEPRECATED=0. - different path: PROCESSED=6, DEPRECATED=1, PRODUCT_KEY=store_path.

Claim concurrency mirrors V1: GET_LOCK queue lock + per-event GET_LOCK (keyed by event ID) + processing-status ladder.

"Missing" Pattern Catalog

PatternExampleHandling
proxy-only (pkg.go.dev 404)codeberg.org/go-toolbox/x, codeberg.org/elbtech/polivalcollect from proxy; if @v/list empty use @latest only
declared-diff (declared not in index)2024-shandong.shylinux.com/x/juxianjinggan/src -> 2024-contexts.shylinux.com/x/juxianjingganfetch from index path, store under declared; index path row PROCESSED=6
alias_gitcodeberg.org/go-latex/latex.git -> codeberg.org/go-latex/latex.git row is alias PROCESSED=6; non-.git is PRODUCT_KEY
subpath41.neocities.org/diana/widevine -> 41.neocities.org/dianamodule root from .mod is PRODUCT_KEY
case-onlycodeberg.org/eline/AOC2022 vs aoc2022distinct modules; do not lower-normalize; escape uppercase for proxy
duplicate index eventsame path seen multiple timesdedup, decide once

gatheringdb case-sensitive migration

The downstream version/product tables in labradordb are ai_ci, so case-distinct Go modules collapse (see Known Pitfalls). Rather than ALTER the huge shared tables, the fix routes V2 writes to NEW gatheringdb tables with as_cs identity:

Migration of existing rows: scripts/migrate_golang_to_gatheringdb.py.

Code Map (V2 additions)

Known Pitfalls / Open Questions

- 'AOC2022' = 'aoc2022' COLLATE utf8mb4_0900_ai_ci -> 1 (equal). - PRODUCT_KEY != LOWER(PRODUCT_KEY) returns 0 rows (the != operator itself can't see case), but PRODUCT_KEY != BINARY LOWER(PRODUCT_KEY) returns ~1.14M rows (out of ~19M) -> uppercase PRODUCT_KEYs are common (e.g. github.com/Aman-Codes/e2e-dashboard-backend). - Querying that real row by its lowercased path returns it (getDBVersionExists false positive proven). - Impact is NOT bounded to skip mode: because the PK is ai_ci, INSERT ... ON DUPLICATE KEY UPDATE into the version/product tables silently COLLAPSES two case-distinct Go modules (same version) into one row -> overwrite / wrong attribution, independent of GO_SKIP_EXISTING_VERSION. The V2 list table fixed case-sensitivity at the queue layer (as_cs), but the downstream version/product/license tables (ai_ci PKs) still lose case-distinct modules. - Reproduced in code: tests/test_go_crawler_v2_integration.py::test_case_insensitive_pk_collapses_distinct_modules. - FIX (2026-05-29): instead of altering the huge shared tables, route V2 writes + skip-check to NEW gatheringdb as_cs tables via GO_TARGET_GATHERINGDB=1 (see "gatheringdb case-sensitive migration"). The skip-check becomes case-sensitive automatically (the column is as_cs). Still out of scope: the license table (TB_COMP_LIB_LICENSE_GOLANG, ai_ci) and dependency table — version+product only for now. Legacy labradordb tables remain ai_ci (untouched, still used when the flag is off).

Decisions