[ 
https://issues.apache.org/jira/browse/CAMEL-26635?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Federico Mariani resolved CAMEL-26635.
--------------------------------------
    Resolution: Fixed

> camel-mina-sftp, camel-sftp - stop listing the directory once 
> maxMessagesPerPoll files are accepted
> ---------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26635
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26635
>             Project: Camel
>          Issue Type: Improvement
>          Components: camel-mina-sftp, camel-sftp
>            Reporter: Federico Mariani
>            Assignee: Federico Mariani
>            Priority: Major
>
> With {{maxMessagesPerPoll}} set (and {{eagerMaxMessagesPerPoll=true}}, the 
> default), the SFTP consumers still list the whole directory on every poll, 
> and only then stop picking files:
> * {{AbstractSftpConsumer.doPollDirectory}} gets the full array from 
> {{operations.listFiles(dir)}}; {{canPollMoreFiles}} only stops the loop over 
> it.
> * {{MinaSftpOperations.listFiles}} drains {{sftpClient.readDir(path)}}; 
> {{SftpOperations.listFiles}} calls {{channel.ls(path)}}.
> The option description says "If eager then the limit is during the scanning 
> of files", so users expect the listing itself to stop at the limit.
> h3. Impact
> Measured with the embedded MINA SFTP server, 30,000 files, 
> {{maxMessagesPerPoll=1000}}, {{stepwise=false}}:
> ||Listing||Entries read in one poll||READDIR requests||
> |today (whole directory)|30,002|145|
> |stop once 1,000 files are accepted|about 1,040|5|
> 30,002 is the files plus {{.}} and {{..}}; about 1,040 because the server 
> sends entries in batches, so the last batch goes past 1,000.
> With {{delete=true}} and {{greedy=true}}, every poll re-reads what is left: 
> draining 25,000 files reads about 325,000 entries (25k + 24k + ... + 1k) 
> instead of about 25,000.
> A user hit this with camel-mina-sftp and camel-sftp on Azure Blob Storage 
> SFTP (a folder of 25k+ files, {{maxMessagesPerPoll=1000}}, 
> {{stepwise=false}}): the listing gets cut, and Azure closes connections that 
> are idle for two minutes. It is not known yet whether a shorter listing 
> avoids the cut there (it depends on where Azure spends the time; keep-alive 
> with {{serverAliveInterval}} is being tried first), but reading only what a 
> poll needs is worth it regardless.
> h3. Proposal
> Stop reading the directory once {{maxMessagesPerPoll}} files are *accepted* 
> (as counted by {{canPollMoreFiles}}, after include/exclude, filters, 
> idempotent and in-progress checks), rather than capping the raw listing at 
> {{maxMessagesPerPoll}} entries.
> A raw cap (for example {{listFiles(path, maxMessages)}}) starves when 
> rejected entries stay in the directory, because they keep filling the cap: 
> files not matching {{include}}, files already in the idempotent repository, 
> {{noop=true}}. Reproduced: with {{noop=true}}, 300 files and 
> {{maxMessagesPerPoll=100}}, a raw cap consumes 98 files and then nothing more 
> ({{.}} and {{..}} count against the cap too); stopping on accepted files 
> consumes all 300.
> No new option: use it whenever the outcome is the same as today, and keep the 
> full listing otherwise:
> * {{maxMessagesPerPoll > 0}} and {{eagerMaxMessagesPerPoll=true}}
> * no {{preSort}} (it sorts the whole listing, CAMEL-23931)
> * no {{doneFileName}} ({{isMatched}} looks the done file up in the listing; 
> see also CAMEL-25728 and CAMEL-25772)
> * both {{stepwise=true}} (the default) and {{stepwise=false}}
> Nothing is kept between polls: each poll lists from the start, as today. With 
> {{delete}} or {{move}} a poll reads about {{maxMessagesPerPoll}} entries; 
> with {{noop}} or idempotent it reads further on each poll, up to the whole 
> directory.
> h3. Implementation notes
> * camel-mina-sftp: use {{sftpClient.openDir(path)}} and {{listDir(handle)}} 
> in try-with-resources. {{readDir(path)}} closes the handle only at the end of 
> the directory, so leaving a loop over it early leaves the handle open.
> * camel-sftp (JSch): {{ChannelSftp.ls(path, LsEntrySelector)}} can stop with 
> {{BREAK}}, but an SFTP request sent from inside the selector corrupts the 
> channel (reproduced: a {{stat}} in the selector makes {{ls}} fail with 
> {{ArrayIndexOutOfBoundsException}}). The consumer sends requests while it 
> walks the listing (resolving symbolic links, recursion), so camel-sftp needs 
> either a two-step approach or only the cases that send none. Suggest doing 
> camel-mina-sftp first.
> * Test: count the READDIR requests on the embedded server (with an 
> {{SftpEventListener}}) instead of measuring time.
> h3. Same pattern elsewhere (not in scope)
> * camel-smb: {{SmbOperations.listFiles}} returns {{share.list(path, 
> searchPattern)}}, the whole directory; smbj 0.15.0 also offers a lazy 
> {{Directory.iterator(...)}}.
> * camel-file: {{File.listFiles()}} reads the whole directory, while 
> {{Files.newDirectoryStream}} is lazy. Mostly matters on network file systems.
> * camel-ftp: {{LIST}} is a single data transfer, so there is little to gain.
> _Claude Code on behalf of Federico Mariani_



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to