LLM WikiAccess-protected knowledge portal

WIKI

Crawler Collection-Count Log (product/version insert·update)

AI Summary Purpose Define the shared per batch "collection count" log format emitted by the library crawlers crawler lib {golang,php,ruby,swift,dotnet} and how the insert vs update counts are derived. One parser handles all five crawlers. K

경로ai/wiki/concepts/crawler-collection-count-log.md
카테고리Concept
태그#collection #concept #concepts #count #crawler #kubernetes #log #monitoring #mysql #report #security

# Crawler Collection-Count Log (product/version insert·update)

AI Summary

Purpose:

library crawlers (crawler-lib-{golang,php,ruby,swift,dotnet}) and how the insert vs update counts are derived. One parser handles all five crawlers.

Key points:

- [batch summary] product insert=<n> update=<n>, version insert=<n> update=<n> - [total summary] product insert=<n> update=<n>, version insert=<n> update=<n>

= cumulative over the whole run, emitted right before the run's ... STOP ... log line.

INSERT ... ON DUPLICATE KEY UPDATE (upsert), which does NOT report per-row insert vs update. So the count is derived by SELECTing the batch's primary keys just before the upsert: key already in DB = update, key absent = insert.

swift spells it baseCrawler.py), hooked at the top of setInsertIntoDB (and, in golang, also _setInsertIntoDBWithConnection). Only the PRODUCT_TABLE / VERSION_TABLE (golang also *_V2) are counted; status / list / license tables are ignored.

(same key written again to set SORT_ORDER) from being double-counted as an update.

Relevant when:

Do not read full document unless:

Open Questions

overlapping keys concurrently can each classify the same key as "insert" (both SELECT before either commits). Acceptable for a rough collection report, not an exact ledger.

pass (not present in the batch's main version list) is counted as an update. This is data genuinely re-touched in the batch, so it is intentionally kept, but it can make version update slightly larger than "distinct versions crawled".

was unit-tested in isolation and all files byte-compile. A one-window container run is recommended to confirm at runtime.

Details

Source: implementation across the five repos, recorded in [[../../worklog/2026/2026-W24.md]] (2026-06-10 entry). Crawler internals: [[../../repo-notes/crawler-lib-golang.md]], crawler-lib-php, crawler-lib-ruby, crawler-lib-swift, crawler-lib-dotnet repo-notes.

Log format

Full log line (the logger format string is identical in all five crawlers):

%(asctime)s %(levelname)s %(process)s %(filename)s:%(lineno)d %(message)s

The <message> is one of:

[batch summary] product insert=12 update=3, version insert=45 update=8
[total summary] product insert=1340 update=210, version insert=5021 update=880

(golang V2: PRODUCT_TABLE_V2).

(golang V2: VERSION_TABLE_V2).

message itself is byte-identical, so one regex parses all five.

Suggested parse regex (message portion):

\[(batch|total) summary\] product insert=(\d+) update=(\d+), version insert=(\d+) update=(\d+)

How insert vs update is derived ("lightweight A")

Why not affected_rows: a batch is sent as ONE multi-row INSERT ... ON DUPLICATE KEY UPDATE statement. MySQL returns a single affected_rows for the whole statement (1 per insert, 2 per changed update, 0 per unchanged update), so the per-row insert/update split cannot be recovered from it. The arithmetic trick (inserts = 2N - affected) breaks as soon as any update is a no-op (unchanged row → 0).

Chosen approach: before the upsert, take the batch's primary keys and run one SELECT ... WHERE PRODUCT_KEY IN (...) (chunked at 500, parameter-bound). Keys returned = updates, keys absent = inserts.

constant).

batch's distinct PRODUCT_KEYs and matches (PRODUCT_KEY, VERSION) pairs in memory.

Shared base-class helper

Added to each crawler's base class:

and the _collectSeen de-dup sets; initializes the cumulative counters once.

/ None (golang also recognizes PRODUCT_TABLE_V2 / VERSION_TABLE_V2).

classifies and accumulates into per-batch + cumulative counters. Wrapped in try/except so a stats failure logs [collect stats] skipped ... and never blocks crawling.

The hook is one line at the top of setInsertIntoDB: self._recordCollectStats(table_name, datas) (runs before the write so the SELECT sees the pre-upsert state).

Per-crawler placement

Crawlerresetbatch summarytotal summarynotes
phptop of dataCrawling()end of dataCrawling()before run() STOPuses self.log
rubytop of dataCrawling()end of dataCrawling()before run() STOPuses self.log
dotnettop of dataCrawling()end of dataCrawling()before run() STOPuses self.log
swifttop of crawl methodend of crawl method (finally:)end of crawl method (finally:)single-pass (no batch loop) → whole crawl = 1 batch; both incremental (cocoapodsCrawler) and resync (cocoapodsCrawlerResync); base uses self.log, cocoapods uses self.logger
golangtop of dataCrawling() (V1) + dataCrawlingV2() (V2)end of eachbefore each STOP (V1×2 + V2×1)product/version write goes through both setInsertIntoDB (V1 sort) and _setInsertIntoDBWithConnection (V1-core + V2); both are hooked, and the instance-level _collectSeen keeps a key counted once even if it passes through both paths

Verification (2026-06-10)

version insert/update, version-sorting re-insert de-dup, non-target table ignored, and per-batch reset with cumulative total all matched expectations.

연결 문서