jnioche commented on PR #2052:
URL: https://github.com/apache/stormcrawler/pull/2052#issuecomment-5315368549

   thanks @akash-manna-sky 
   
   a quick AI assisted review
   
   Thanks for tackling #315 — the PR hygiene here is good (formatting, unit 
tests, docs, issue linkage all present). Most of my comments are about how the 
filter interacts with the topol
   ogy, which is hard to see without tracing through several bolts.
   
   ## Blocker: the filter can't learn anything in a standard topology
   
   `learn()` reads the canonical from `sourceMetadata`, but in both parsing 
bolts the URL filters are applied to the outlinks **before** the parse filters 
that produce the canonical:
   
   * `JSoupParserBolt`: `toOutlinks(url, metadata, slinks)` (line 452) → 
`filterOutlink` → `urlFilters.filter` (line 604), whereas `jsoupFilters.filter` 
is line 464 and `parseFilters.fil
   ter` is line 478.
   * `external/tika` `ParserBolt`: outlink filtering at line 406 vs 
`parseFilters.filter` at line 282.
   
   `canonical` is extracted by `XPathFilter`, a parse filter. So at filtering 
time `sourceMetadata.getFirstValue("canonical")` is always null and `learn()` 
returns early. The unit tests
   pass because they inject the metadata by hand.
   
   The feature only does anything if the user adds `canonical` to 
`metadata.persist` (so it is learnt from the *previous* fetch), or places the 
filtering inside `LinkParseFilter` ordered
    after the XPath filter — neither of which is documented. The docs hunk says 
the filter "must therefore be placed in a parsing bolt", which isn't sufficient.
   
   This needs a design decision rather than a patch: either learn from 
somewhere that runs after the parse filters, or explicitly require `canonical` 
in `metadata.persist` and document t
   hat rules are learnt one fetch cycle late.
   
   ## Also structural
   
   **Thread safety.** `stats.get(k, ...)` returns a plain `HashMap` that is 
mutated outside any lock, plus non-volatile `int` counters and 
`lastLearnedSource`. `URLFilter` instances *are
   * used concurrently: `StatusEmitterBolt.urlFilters` is a single instance 
shared by all `FetcherThread`s, and `FetcherBolt` calls `emitOutlink` from 
those threads (lines 635 and 857).
   Every other built-in filter is stateless, so this contract has never been 
exercised. Two fetcher threads handling redirects for the same host can corrupt 
the scope map or lose counter
    increments. `ConcurrentHashMap` + `AtomicInteger`/`LongAdder` would cover 
it.
   
   **Non-deterministic normalisation.** The learned evidence is per-instance, 
and `URLFilters.fromConf` builds a fresh instance per bolt 
(`URLFilters.java:86`) — one per parser task, one
    per fetcher, one in `URLFilterBolt` (which calls `filter(null, tempMed, 
url)` and so never learns), plus a second one inside `LinkParseFilter`. 
`urlfilters.config.file` is global, so
    there's no way to load the filter only in the parsing bolt as the docs 
suggest. The same outlink then gets emitted as `?id=1&sid=x` by one task and 
`?id=1` by another, so both forms
   land in the status store and both keep being fetched — the opposite of the 
intended dedup. URLs discovered before a rule was learnt are never reconciled 
either.
   
   **Content-bearing parameters can be learnt.** Self-referencing canonicals 
are a common misconfiguration: plenty of sites serve `/list?page=2..N` with 
`<link rel="canonical" href="/lis
   t">`. `sameResource()` accepts that (same host/port/path), so after 
`minObservations` such pages `page` hits a 1.0 drop ratio and becomes 
removable. Since the rule is applied to outli
   nks before they're emitted as DISCOVERED, `?page=2..N` are all rewritten to 
`/list`, deduplicated away and never fetched — the crawl silently loses all 
paginated content, with no evid
   ence decay and no recovery path. A denylist of protected parameter names 
(`page`, `p`, `offset`, `start`, `q`, …), or requiring the canonical to drop 
the parameter across several dist
   inct paths, would limit the blast radius.
   
   ## Smaller things
   
   * **`minObservations` counts observations, not distinct pages.** 
`lastLearnedSource` only suppresses *consecutive* re-observations of the same 
source. With `fetchInterval.default` at
   1440 min, a host whose only query-string URL is `/x?sid=1` accumulates 
`dropped=5, total=5` over five days and the rule is applied on the strength of 
one page. The docs say "number of
    pages a parameter must have been seen on" — nothing tracks distinct source 
URLs.
   * **URL reconstruction changes unrelated parts, and only when something was 
removed.** `url.getPath()` is `""` for `http://example.com?sid=1&id=2`, so the 
result is `http://example.co
   m?id=2` — a different status-store key from the `http://example.com/?id=2` 
that `BasicURLNormaliser` produces. Similarly, if `URLUtil.toURL` had to 
sanitize the input (space, `|`, `\`
   , `{`, `}`), the sanitized form is returned, so `/a b?sid=1&id=2` → 
`/a%20b?out a removable parameter is returned verbatim. Appending `/` for an 
empty pat
   h and rebuilding only the query portion of the original string would avoid 
both.
   * **`maxParams` has no eviction.** The size check counts every parameter 
evevant ones, and entries are never removed. A site with per-page tokens in the 
p
   arameter *name* (cache-busters, `utm_term_<hash>`, CMS facets like 
`f[123]=`) fills the 100 slots with single-observation entries early in the 
crawl, after which the genuinely removab
   le parameter can never be tracked for that host. A size-bounded cache (Caffe 
or dropping single-observation entries when full, would be self-healing.
   * **`testNumberOfTrackedSitesIsBounded` doesn't exercise eviction.** It sets 
`maxScopes: 1` but only ever observes `example.com`, so the bound is never 
reached. Observing two distinct
    hosts and asserting the first host's evidence was discarded would actually
   
   Things that do look correct: null handling, `MalformedURLException` handling 
correctly leaves `mailto:`/`file:` URLs alone), default-port comparison, and
   the raw-vs-decoded parameter-name handling.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to