LLM WikiAccess-protected knowledge portal

WIKI

crawler-lib-java

AI Summary Purpose Durable note for the Maven/Java package crawler at ~/labrador/crawler/crawler lib java . Tracks the Python migration effort on branch python migration maven . Key points Original implementation is Java Spring/MyBatis . Th

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

# crawler-lib-java

AI Summary

Purpose:

Key points:

- getVersionVo(extracted) → 24-column VERSION_JAVA row (TB_COMP_LIB_VERSION_JAVA). JSON via jsonUtil.to_json (Jackson-exact: compact, ensure_ascii=False). ALWAYS-set columns (empty→"[]" / full-null dict): LICENSE, SCM, ISSUE_MANAGEMENT, ORGANIZATION, PARENT, ORIGINAL_INFO, DEPENDENCIES, DEPENDENCY_MANAGEMENT. CONDITIONAL (None when absent): STANDARD_LICENSE, LICENSE_IDS, LICENSE_AI. - getProductVo(version_row) → 9-column PRODUCT row (TB_COMP_LIB_PRODUCT). LICENSE = version_row's LICENSE (raw POM license JSON), matching Java product.setLicense(last.getLicense()) — NOT STANDARD_LICENSE (corrected P2b Task 1). - getMavenArchiveVo(record) → 9-column MAVEN_ARCHIVE row. ORIGINAL_INFO always json.dumps (never None). - getDependencyVo(version_row) → generator of DEPENDENCY rows. ID = sha256(PRODUCT_KEY + VERSION + origin_key + version_range). Yields nothing when DEPENDENCIES is None. - getCrawlerStatusVo(name, json_raw) → STATUS row (TYPE, RAW_DATA, TIMESTAMP).

- P2d Task 1 work-list methods (all LANGUAGE='java', REPOSITORY='Maven'): - selectNotProcessedProducts(limit)labradordb.TB_COMP_LIB_PRODUCT … ORDER BY PRODUCT_KEY LIMIT 0, :limit - selectNotProcessedArchives(repository_name, limit)gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE … REPOSITORY_NAME=:repo AND PROCESSED IS NULL LIMIT 0, :limit - selectPriorityVersions(limit) / selectNotProcessedVersions(limit)TB_COMP_LIB_VERSION_JAVA - selectVersionsOfProduct(product_key)TB_COMP_LIB_VERSION_JAVA … ORDER BY FIXED_SORT_ORDER ASC, SORT_ORDER ASC, VERSION ASC - updateMavenArchiveProcessed(record)UPDATE gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE SET PROCESSED = NOW() WHERE … - updateVersionSortOrder(row)UPDATE TB_COMP_LIB_VERSION_JAVA SET SORT_ORDER = :so WHERE … - Test suite after P2d Task 1: 115/115 passed. Commit 44fb45a.

- crawlVersion 은 더는 transient 를 raise 하지 않음 → 어떤 transient/POM 미제공이든 version+sha1 으로 pending 행 반환. createVersionFromArchive/_processVersion 의 transient try/except 제거(archive 는 version 생성 시점에 처리완료 마킹 → 무한 재처리 제거). - PROCESSED 코드 확장: 90=재수집 대기(version+sha1 확보, 메타 미완), 90~97=재수집 재시도 카운터(매 실패 +1), 98=포기 terminal(version+sha1 유지, 자동 재시도 종료·조회 가능), 99=레거시(6.9M, 미사용 유지), 88=배치 백필 시드. 갓 배포(grace window) 404/200-비POM → pending(90); window 지난 미존재 → 기존대로 minimal(1). - run() 에 pending 재수집 1패스 추가(selectPendingVersions 90~97, _recollectVersion): 성공→1, 실패→+1, 97 다음→98. drain 아닌 run 당 1패스 — 재시도를 run 간격으로 분산해 host 쿨다운 회복 시간 확보(한 run 에서 90→98 소진 방지). - 로그: 200 인데 끝내 파싱 불가(POM/XML 아님)는 WARNING(common.getXmlRequests, lenient 재파싱 실패 시 None+경고). transient→pending 전환도 WARNING. (앞선 be7d818=transient 로그에 원인 host 노출, 8c04e42=인코딩 깨진 200 lenient 재파싱.) - tests 221 green(옛 transient-propagate/keep-retryable 단언을 pending 동작으로 갱신, _recollectVersion 증분/포기 테스트 추가). EtcMavenPomCrawler 는 자체 except 보유라 미적용(해당 repo 대부분 index:False).

- _ensureProductLatest(pk, version, row): selectProductLatest 로 기존 LATEST_VERSION 읽어 — 없으면 insert, 이 version 이 더 최신(versionSort.sortVersions([latest,version])[-1]==version)이면 교체, 아니면 무변경. 전체 버전 SELECT+정렬 없이 O(1)(사용자 요청 방식). insertProduct 는 ON DUP 라 무조건 덮어쓰므로 "최신일 때만"은 코드 판단 필수 → selectProductLatest 신설. sort_order 는 보강 경로 rebuildProductAndSort 가 유지. - run() pending 보강 2분할: fresh(PROCESSED=90)는 같은 run 에서 drain(selectFreshPendingVersions, 90→1 성공 / 91 실패)로 즉시 보강, 재시도(91~97)는 run 당 1패스(selectPendingVersions 범위 90~97→91~97 로 조정, 쿨다운 회복 분산, →98 포기). - 데이터 포맷 불변 보장: 스켈레톤=getVersionVo/_emptyExtracted, product=getProductVo 재사용 → 24컬럼·JSON 바이트 동일, PROCESSED 값(90)만 minimal(1)과 차이(스크립트로 직접 검증: keys 동일, 차이 PROCESSED 1개뿐). tests 221 green.

Relevant when:

Do not read full document unless:

Linked documents:

Open Questions

Live DB facts (verified 2026-06-16, via read-only MySQL)

Source of truth for the physical tables (the VO column orders above are the crawler insert contracts, which differ from physical column order).

gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE, labradordb.TB_COMP_LIB_PRODUCT. (PRODUCT 만 labradordb. host 211.115.125.165:43316.)

collect_maven_missing.py 주석의 "PROCESSED 컬럼 없음" 전제는 outdated.) 현재 값 분포: 1=8.60M(완료), 99=6.93M, 2=4.30M, 0=734K, null=8,959, 10=4. → 88 미사용: 배치 백필 worklist 마커로 안전.

- VERSION_JAVA.PRODUCT_KEY = utf8mb4_bin(대소문자 구분). - VERSION_JAVA.VERSION, PRODUCT.PRODUCT_KEY = utf8mb4_0900_ai_ci(구분 안 함). - → 소문자로 정규화한 좌표를 VERSION_JAVA 에 INSERT 하면 대문자 좌표 product 가 bin 기준 별개 키로 쪼개지고, POM 경로(case-sensitive)는 404. 백필 입력은 원본 case 를 유지해야 한다.

원본 case 보존판: extract_maven_missing_versions_origcase.py (인덱스 재파싱 lower\torigjoin -v1 로 DB 누락만, orig 출력).

Maven version 백필 (2026-06-16, 진행 중)

~/labrador/tool/maven_missing_products/2026-06-16/maven_missing_versions.txt (인덱스에는 있으나 VERSION_JAVA 에 없는 g:a:v 1,045,271건 / distinct product_key 140,221개) 백필. product-level 누락은 146건뿐 → product 는 대부분 LATEST_VERSION UPDATE.

(ORIGINAL_INFO={groupId,artifactId,version}, SORT_ORDER=0, FIXED_SORT_ORDER=0, PROCESSED=88, 나머지 NULL). 네트워크 없음, 빠름.

<latest>(없으면 <release>/정렬 max) → PRODUCT upsert, UPDATE 는 LATEST_VERSION(기존 PROCESSED 보존). rate limit 3/s → ~13h.

채움 → 성공 시 PROCESSED=1(정상 파이프라인 완료 코드와 합류), 일시오류는 88 유지(재시도), POM 404 별도 마킹. rate limit 3/s → ~4일. PROCESSED 컬럼 자체가 worklist(파일 done-set 불필요, resumable).

Java master crawler — 라이선스 버그 수정 + 누락 감사 (2026-06-18)

운영 로그(/data/logs/crawler-lib-java/logs/all.log)에 ~3초마다 두 에러가 반복(myScheduler-2, maven 라이선스 분석 단계). 둘 다 master(운영 Java) 코드 문제. 수정 브랜치 fix/maven-license-omissions(master 기반, 미커밋) 에 반영.

수정한 2건

  1. LicenseDao.getAllLicenses() → MyBatis PersistenceException: LicenseDao.java가 statement

...LicenseDao.getAllLicenses를 호출하나 LicenseDao.xml에 해당 <select>가 없었음 (getLicenseIdFromName, getSPDXLicenseIdFromName만 존재). 463464a(version table split/ license upgrade)에서 Java 메서드만 추가하고 매퍼 누락. → 유사 라이선스(Levenshtein) 매칭 무력화. Fix: LicenseDao.xmlgetAllLicenses 추가(SELECT ID,NAME_SHORT,NAME,MAPPING,URL FROM TB_LICENSE_V2 WHERE MAPPING is Not NULLgetSPDXLicenseIdFromName 미러).

  1. licensee /tools/licensee/license.txt (No such file or directory): `license.detect.dir=

/tools/licensee인데 Dockerfile은 licensee를 **전역 gem**(gem install licensee)으로 깔고 /tools/licensee 디렉토리는 안 만듦. 게다가 코드가 bundle exec bin/licensee(번들 체크아웃 방식)를 기대 → 전역 gem과 불일치. new FileWriter가 부모 디렉토리 없어 실패. **Fix**(2곳): LicenseAnalysisComponent.analyzeLicenseWithLicensee + LicenseeUtil(:187, hardcoded licenseDir="/tools/licensee") — mkdirs()로 디렉토리 보장 + bundle exec bin/licensee → 전역 licensee` 명령으로 교정.

⚠️ master 자체가 컴파일 안 됨 (배포 차단)

./mvnw compile5개 파일 "cannot find symbol": php/crawler/PackagistCrawler.java, php/service/PackagistCrawlerService.java, utils/db/dao/StatusLicenseDao.java, utils/runtime/util/FileUtil.java, HttpUtil.java. version-table-split이 ProductVo/VersionVo 필드(@Getter/@Setter)를 바꿨는데 호출부 미갱신(예: ProductVo.setCreated/setProcessed, VersionVo.getVersion). 운영 jar는 이 breakage 이전 커밋에서 빌드된 것으로 추정. → 위 라이선스 수정을 배포하려면 이 컴파일 에러부터 해결 필요(내 수정 파일은 에러 목록 밖, 무관).

누락(omission) 감사 — 미수정, 권장순

예외든 versionVo.setProcessed(1) 후 insert → 재스캔 쿼리(selectVersionListNotProcessed*, PROCESSED IS NULL OR =0)가 다시 안 잡음 → name/license/deps 빈 채 영구 누락. 메인 maven 경로의 최대 omission. Python 마이그레이션은 이걸 의도적으로 고침(transient는 processed 안 함). 권장 Fix**: 에러 경로에서 processed=0 유지(또는 실패 전용 상태), 성공 후에만 1.

현재 호출자 없어 latent. 다른 DAO↔매퍼는 모두 정상 확인됨.

없음** → 한 행이 제약/폭 위반이면 그 배치 전체 드롭. (archive insert는 1/10 split-retry 있음.)

commons copyURLToFile가 부모 생성해줘 현재는 무해.

로테이트+config화 필요. (Python 이식은 이 토큰 미반영 — getAiLibraryLicense 노트 참고.)

SHA1 null 보정 백필 (2026-06-18, 진행 중)

동기: Java→Python 전환 분석 중 라이브 DB에서 VERSION_JAVA의 SHA1_VALUE 결손이 큼을 확인. java-era PROCESSED=1 859만 중 SHA1 null 4,086,790 (47.5%) / set 4,509,113. 일부는 pom-only (바이너리 없음, 정상)이나 상당수는 Java sha1 수집 누락/H1 에러-마킹 결과. Python은 "이미 존재하는 version 재크롤 skip"(createVersionFromArchiveselectVersionExists)이라 전환해도 기존 null sha1을 안 고침. sha1 중요 → 별도 1회 데이터 보정.

도구: ~/labrador/tool/maven_missing_products/backfill_sha1_from_index.py (+ sha1_chain.sh). 3단계: targets(null-sha1 행의 distinct product_key 덤프, 스트리밍 커서) → index (collect_maven_missing.build_sha1_index로 그 product들의 g:a:v→sha1 추출) → apply (매칭 UPDATE).

원칙 (사용자 요구):

Maven HTTP 요청 0. 진행 중 PROCESSED=88 fill 과 병행 안전.

매칭(lower(g:a:v)). pom-only/바이너리 없는 행은 인덱스에 없어 자동으로 null 유지(정상).

규모/결과 (2026-06-18 완료): distinct null-sha1 product_key ≈ 470k → 인덱스 sha1 수집 8,738,661 (sha1_backfill.tsv). null-sha1 행 스캔 6,999,679 중 매칭 2,622,179 → SHA1_VALUE 채움. 최종 java/MAVEN sha1 커버리지 set 17,240,577 / null 4,377,498 (총 21,618,075, ~79.7% set). 잔여 null 은 pom-only/바이너리 없음(정상) + 인덱스에 sha1 없는 것 + 아직 PROCESSED=88 시드(POM fill 이 채울 것). 신규 insert 0(ON DUP, 기존 PK 갱신만), 총행수 증가분은 fill 시드로 설명.

존재하므로 다중행 INSERT … ON DUPLICATE KEY UPDATE SHA1_VALUE(배치당 1왕복)로 바꿔 ~2700/s (전체 ~19분)로 단축. 또 스트리밍 SELECT 중 UPDATE 끼우면 2013 Lost connection(net_write_timeout) → read(매칭→파일) ↔ write(파일→UPDATE) 분리로 해결.

참고 — Python 런타임 sha1 경로 비판(mavenPomCrawler.crawlVersion):

RELEASE_DATE 백필 (2026-06-18 완료)

sha1 백필과 동일 패턴(backfill_releasedate_from_index.py, targets→index→apply, HTTP 0, 포맷 불변). 소스 = Maven 인덱스 i 필드 lastModified(ms, classifier=NA, g:a:v당 최소) → YYYY-MM-DD HH:MM:SS(UTC). 크롤러의 archive 경로가 쓰는 값(archive.LAST_MODIFIED=같은 인덱스 lastModified)과 동일 소스 → 일관.

read↔write 분리(2013 회피). 신규 insert 0.

Python 컷오버 수정 (2026-06-18, master 푸시됨)

Java 정지 → Python(master) 전환 중 발견·수정한 버그/구성 (origin/master 98440ec 시점):

AtlassianExternal + GoogleMavenCrawler 14KB 그룹맵)는 labradordb.TB_CRAWLER_STATUS, Python 은 gatheringdb 를 읽음 → copy_status_anchors.py 로 7개 복사(컷오버 시 1회). 그래야 Python 이 Java 마지막 인덱스 timestamp 에서 증분 재개(안 하면 전 repo full 재인입).

Python 이미지 빌드에 maven jar 불필요. 인덱서는 jar(maven-index-helper.jar)가 아니라 순수 파이썬 MavenIndexFetcher(HTTP+gzip, JVM 불필요)로 동작 — jar/openjdk-11 은 orphan(레거시).

Dockerfile/compose 는 /workspace/log(단수)였음 → 핸들러 init 크래시/모니터링 누락. Dockerfile mkdir /workspace/logs + compose ./logs:/workspace/logs. logger when='D''midnight'(자정 분할).

(리다이렉트 따라가도 404). MAVEN_REPOS"index": False + startCrawl skip. POM 폴백/EtcPom 은 유지(기존 archive 접근 손실 없음).

baseUrl(search.maven.org)로 잘못된 인덱스 URL. "indexUrl": "https://repo1.maven.org/maven2" 추가.

while limit==LIMIT 루프가 행수=LIMIT 배수일 때 빈 배치로 한 번 더 돌아 executeSql(stmt, []) → 에러. while datas: 로 교정.

RELEASE_DATE/SORT_ORDER/FIXED_SORT_ORDER/SHA1_VALUE 추가 → 재크롤 시 백필값 유실 방지.

방지); sha1 HTTP 폴백 jar/aar→jar/aar/war/ear/zip. 보정잡 app/main_repair.py(불완전 행 재크롤, --recent-days/--limit, format 불변) — R1(에러로 processed=1+name null, ~60만) 등 점진 보정용.

MavenCentral 인입 정지 원인 + archive VERSION varchar(100) 하드닝 (2026-07-14)

증상: TB_CRAWLER_STATUS.MavenCentral 앵커가 2026-06-25 18:30 에 정지(같은 인덱서가 쓰는 Jenkins/Clojars/MavenIndexFullSweep 는 07-12~13 로 최신). 06-25 이후 Central 신규 g:a:v 가 MAVEN_ARCHIVE 에 안 들어와 다운스트림 전부 누락(관측: 75건 리포트 중 65 실누락).

근본원인 2중:

  1. 인덱서 미가동(labCrawl1 crawler-lib-maven-indexer): [indexer] 로그가

07-06~07-12 전무. 컨테이너 StartedAt 07-13T01:11(RestartCount=0) → 약 1주+ 정지.

  1. 1406 DataError: MAVEN_ARCHIVE.VERSION = varchar(100) (VERSION_JAVA 는 250).

Central 인덱스에 100자 초과 버전 1행이 있으면 1000행 배치 INSERT 가 통째로 (1406, "Data too long for column 'VERSION'") 로 실패 → startCrawl 의 repo별 예외격리가 WARNING(maven index ingest failed for MavenCentral)만 찍고 anchor 미전진 → 매 사이클 같은 지점 재정지. startCrawl(full=True)(full-sweep)도 같은 격리라 Central 만 실패해도 MavenIndexFullSweep 앵커는 갱신됨(오해 소지).

archive 컬럼 폭(2026-07-14 확인, gatheringdb.TB_COMP_LIB_MAVEN_ARCHIVE): LANGUAGE/REPOSITORY_NAME varchar(20), PRODUCT_KEY varchar(400), VERSION varchar(100), NAME varchar(200), DESCRIPTION longtext, SHA1_VALUE varchar(100). (VERSION_JAVA.VERSION 은 varchar(250) — 두 테이블 폭이 다르다.)

수정(스키마 불변, 코드만 — working tree, 배포 필요):

(좌표 절단 시 POM 경로 깨짐).

_ingestOversizedVersions 가 version_java(250) 스켈레톤으로 우회 적재(>250 하드 스킵). 앵커를 우회 적재보다 먼저 기록(우회 실패가 커서 전진을 막지 않게; 실패분은 다음 run overlap 60일이 회수).

스켈레톤(PROCESSED=90) 직접 적재(idempotent, archive 마킹 없음). 오버사이즈+백필 공용.

repo skip 반영으로 교정).

백필(2026-07-14 완료, host 165): 65 실누락 g:a 를 원본 case 로 repo1 maven-metadata → 전 버전 → insertVersionSkeletonDirect. new=207 버전 / product 생성, worker 가 POM 보강. 도구 scratchpad/backfill_missing_java.py(1회성).

남은 조치: (1) 코드 커밋 + labCrawl1 재배포(배포 전엔 앵커 06-25 고정·상시 인입 미재개), (2) 인덱서 liveness + 앵커 staleness 알람, (3) 1406 유발 100자 초과 버전 GAV 특정(선택).

Phase 2 Decisions (2026-06-09, user-confirmed)

  1. Version sort = pure Python reproducing Java VersionSortUtil.sortVersionUsingMaven

(Apache ComparableVersion + preprocessing _→-, +→-, pr→rc; insertion-sort ascending, incl. the equal-version drop quirk). No JPype / sort-gen jar at runtime. The existing jar/Java is used only as a test oracle (differential check, skipped when no JVM).

  1. License AI API = http://211.115.125.171/api/v1/licenses (POST), body

{"texts":[{"text": <str>}, ...]}, 30s timeout, no auth. Response {"NAMES":[{NAME,CONFIDENCE}], "AI_LICENSE":[ids]}; extract AI_LICENSE/ai_license (case-insensitive) → json.dumps(list) into LICENSE_AI (None if empty). Input priority: cloned license-file text → GitHub API license → POM <licenses> name. Independent of the TB_LICENSE SPDX match that fills STANDARD_LICENSE/LICENSE_IDS. Same client as crawler-lib-dotnet/golang (licenseeUtil.getAiLibraryLicense).

  1. DEPENDENCY table NOT used. Dependencies live only in the VERSION row DEPENDENCIES

(and DEPENDENCY_MANAGEMENT) JSON columns. baseCralwer.insertDependency / VERSION_DEPENDENCY_TABLE stay dormant (not called).

Details

Branch

python-migration-maven (working branch for all Python migration work).

Run tests

cd ~/labrador/crawler/crawler-lib-java
. .venv/bin/activate
python -m pytest tests/ -v

Column order: VERSION_JAVA (TB_COMP_LIB_VERSION_JAVA)

LANGUAGE, REPOSITORY, PRODUCT_KEY, VERSION, NAME, DESCRIPTION, LICENSE, LICENSE_AI, STANDARD_LICENSE, LICENSE_IDS, URL, SCM, ISSUE_MANAGEMENT, ORGANIZATION, PARENT, ORIGINAL_INFO, SHA1_VALUE, CREATED, RELEASE_DATE, PROCESSED, SORT_ORDER, FIXED_SORT_ORDER, DEPENDENCIES, DEPENDENCY_MANAGEMENT

Column order: PRODUCT (TB_COMP_LIB_PRODUCT)

LANGUAGE, REPOSITORY, PRODUCT_KEY, LATEST_VERSION, NAME, DESCRIPTION, LICENSE, CREATED, PROCESSED

Column order: MAVEN_ARCHIVE (TB_COMP_LIB_MAVEN_ARCHIVE)

LANGUAGE, REPOSITORY_NAME, PRODUCT_KEY, VERSION, NAME, DESCRIPTION, SHA1_VALUE, ORIGINAL_INFO, LAST_MODIFIED

Column order: DEPENDENCY (TB_COMP_LIB_VERSION_JAVA_DEPENDENCY)

ID, LANGUAGE, REPOSITORY, PRODUCT_KEY, VERSION, ORIGIN_PRODUCT_KEY, ORIGIN_VERSION_RANGE, RECORD_CREATED

Column order: STATUS (TB_CRAWLER_STATUS)

TYPE, RAW_DATA, TIMESTAMP