ovsqlite: Add direct reader mode for nnrpd with WAL
expireover: Add bloom filter for fast history existence checks
expire: skip per-article SMretrieve via cancel tombstone log
The general theme of all three is to reduce random or sync I/O.-a In the case of expireover and expire, the general problem is somewhat similar
but the root cause and fix are fairly different.
On my system this is a 1100x improvement, 2 weeks down to 18 minutes.
On my system this will net a 2800x improvement, 2 days down to sub minute.
Both of these suppose integrity of the spool; if you 'rm' articles
instead of using 'sm -r' you'll need to do a patrol expire to clean up
your spool; expireover builds from history so it would see the new
history state the next time it runs.-a The first time expire runs it wont have a tombstone so it will 'upgrade' to a known state cleaning any
issues.. a gap is only if you decide to manually botch the spool after
that.
I'd be curious to hear any feedback or reviews from other inn admins
that can test the patch set.
Hi Kevin,
ovsqlite: Add direct reader mode for nnrpd with WAL
expireover: Add bloom filter for fast history existence checks
expire: skip per-article SMretrieve via cancel tombstone log
The general theme of all three is to reduce random or sync I/O.-a In
the case of expireover and expire, the general problem is somewhat
similar but the root cause and fix are fairly different.
Many thanks for these very useful improvements to expiration and
ovsqlite concurrent reading.
expire and expireover run way faster (x1100 for expire and x2800 for expireover on your system !!) thanks to your optimizations.-a Some people have been complaining with their slowness from a long time, and
hopefully you managed to find out how to improve that a lot and
contributed a patch.-a It will be integrated to the next release.
On my system this is a 1100x improvement, 2 weeks down to 18 minutes.
Gosh!
On my system this will net a 2800x improvement, 2 days down to sub
minute.
Re-gosh!
Both of these suppose integrity of the spool; if you 'rm' articles
instead of using 'sm -r' you'll need to do a patrol expire to clean up
your spool; expireover builds from history so it would see the new
history state the next time it runs.-a The first time expire runs it
wont have a tombstone so it will 'upgrade' to a known state cleaning
any issues.. a gap is only if you decide to manually botch the spool
after that.
Worth mentioning.
I'd be curious to hear any feedback or reviews from other inn admins
that can test the patch set.
If anyone could test the provided patches, do not hesitate to do so in
order to eventually catch possible nits before the next release (in June).
I've wondered a bit about what might be next.
network programming changed a lot once kqueue and epoll entered a couple years later.
nnrpd would be somewhat easy to make more efficient across a few dimensions.-a The least code churn might be integrating poll() directly
in the per process forks and adding some eventing to deal with NNTP pipelining and I/O and DNS.-a The main benefit would be building up some
I/O parallelism, all storage devices benefit from that and you smooth
out the latency of hot vs cold I/Os through the page cache (or ARC/
L2ARC) and actual storage devices.
Similar opportunities likely present in innd/innfeeder but I am notSure, there also are improvements to do for them!
familiar with this code so far.
File formats would be another consideration I am not familiar enough to evaluate right now but the network and disk I/O stuff interests me more.
Data movement could optionally use sendfile and even KTLS sendfile on Linux and FreeBSD.
On 5/10/2026 8:59 PM, Kevin Bowling wrote:
Data movement could optionally use sendfile and even KTLS sendfile on
Linux and FreeBSD.
So, this gave me an interesting thought earlier this evening, as I
actually do use sendfile() a lot (it's not very portable, so I have
wrappers around sendfile, copy_file_range, splice, and other fun
functions).
I was reading the requirement for :bytes earlier today and noted that it specifically does not include dot-stuffing characters, and I recalled
then that in SMTP and NNTP, I strip out any dot-stuffed lines as I
thought was convention. However, when *sending* the articles to a
client, I have just been using ~sendfile, for efficiency. I realize now
this is illegal, since I'm not adding the dot-stuffing back.
Taking a look at INN, I noticed it counts the number of dot-stuffed
lines and subtracts it from the value it provides for :bytes, which is
what the RFC says to do. But it looks like INN doesn't remove the dot- stuffing when receiving an article. This actually seems like the more elegant way to do things; if you never remove the dot-stuffing, you
don't need to add it back, and then you can do efficient things like use sendfile rather than going line by line. So in my case, I think I don't
need to care about receiving dot-stuffed lines since I can store them in
the spool and just have it be transparent.
On the other hand, this wouldn't work for SMTP, since with BDAT/
CHUNKING, you may not need to do dot-stuffing.
I guess what I'm trying to say is that for SMTP, because there are
multiple ways to send/receive messages, it makes sense to store the
messages in the "queue" normalized (without dot-stuffing), but since
there's only one way in NNTP, it makes more sense to just store the
stuffed lines in the spool directly. Is this about the gist of it?
(And implicitly, I'm assuming that in the future, it would be hard to support other extensions like SMTP has since I would assume most news
server are storing the dots in the spool; of course, such extensions are probably of little value to NNTP considering most articles are small).
On 5/12/26 19:46, InterLinked wrote:
I guess what I'm trying to say is that for SMTP, because there are
multiple ways to send/receive messages, it makes sense to store the
messages in the "queue" normalized (without dot-stuffing), but since
there's only one way in NNTP, it makes more sense to just store the
stuffed lines in the spool directly. Is this about the gist of it?
(And implicitly, I'm assuming that in the future, it would be hard to
support other extensions like SMTP has since I would assume most news
server are storing the dots in the spool; of course, such extensions
are probably of little value to NNTP considering most articles are
small).
Can you post some example transactions showing your thoughts?
One thing that comes to mind is probably to synthesize the header(s) and sendfile at an offset of the body.-a The synthesized part could be stack allocated.-a Likewise any trailer if 'wireformat' is off.
That is similar to what I'm talking about, though I think wireformat in
INN is just line endings (whether to store the CR or not). I was talking about storing the leading dot in dot-stuffed lines in the spool (same idea really).
I believe that INN does this unconditionally without any way to disable
it... because why would you want to?
Hi,
I've been looking at inn as I run it:-a tradspool/ovsqlite on FreeBSD+ZFS with unlimited retention and trying to fix some scaling issues.
ovsqlite: Add direct reader mode for nnrpd with WAL https://github.com/InterNetNews/inn/pull/338
expireover: Add bloom filter for fast history existence checks https://github.com/InterNetNews/inn/pull/339
expire: skip per-article SMretrieve via cancel tombstone log https://github.com/InterNetNews/inn/pull/340
The first two have been running on csiph.com for a few days without
issue.-a I haven't yet deployed the last one yet but will publish results
in the PR when I do.
The general theme of all three is to reduce random or sync I/O.-a In the case of expireover and expire, the general problem is somewhat similar
but the root cause and fix are fairly different.
expireover checks history for every article and this turns into a very
slow and random serial I/O for every article in the history DB.-a The new option is to use a bloom filter with a configurable false positive rate, chosen for some acceptable RAM vs accuracy tradeoff by the admin and
their spool size.-a The bloom filter is created with a much more
efficient streaming read of the entire history file.-a In the case of a false positive (bloom claims article in history, but expire removed it),
it gets cleaned up on the next expireover run (entropy introduced by
hash churn make this probabilistic reality).-a On my system this is a
1100x improvement, 2 weeks down to 18 minutes.
expire is a bit more involved.-a We don't want the same bloom tradeoff, exactness trades a bit of RAM (pending deletes) for totally eliminating unnecessary disk I/O for verification.-a If we keep a tombstone log of articles expireover deletes, and any cancels, and we trust the state of
the spool, we can eliminate all speculative work and only deal with the actual history cleanup needed.-a On my system this will net a 2800x improvement, 2 days down to sub minute.
With the expire tombstone, I can make 'nnrpdcheckart' an extremely
efficient hash lookup, so OVER and friends don't need to check every
single article's existence and only clean any pending tombstone entries.
Both of these suppose integrity of the spool; if you 'rm' articles
instead of using 'sm -r' you'll need to do a patrol expire to clean up
your spool; expireover builds from history so it would see the new
history state the next time it runs.-a The first time expire runs it wont have a tombstone so it will 'upgrade' to a known state cleaning any
issues.. a gap is only if you decide to manually botch the spool after
that.
You'd avoid the worst of some of this with CNFS, but the unlimited
retention case gets a little awkward, and tradspool should scale to a
text feed with over a billion articles with these changes on modern hardware.
The ovsqlite direct reader mode allows each nnrpd to open the sqlite DB directly, in read only mode.-a This eliminates costly IPC and exclusive locking to the ovsqlite-server which is now only concerned with innd.
The WAL file also helps with writes as they turn into streaming I/O and
the main DB does not create the same level of synchronous write load.
This is conceptually similar to how BDB used to work.
I have these gated on the FreeBSD news/inn-current port behind an EXPERIMENTAL flag (expire tombstones will come once I'm satisfied).
I'd be curious to hear any feedback or reviews from other inn admins
that can test the patch set.
Regards,
Kevin
I'd be curious to hear any feedback or reviews from other inn admins
that can test the patch set.
Regards,
Kevin
On May 20, 2026 at 1:15:51rC>AM CDT, "Kevin Bowling" <kevin.bowling@kev009.com>
wrote:
I'd be curious to hear any feedback or reviews from other inn admins
that can test the patch set.
Regards,
Kevin
Hey Kevin,
My news.blueworldhosting.com spool is tradspool with ovsqlite... I currently run news.daily with noexpire and noexpireover, since I don't wish to expire anything. That said, I do have over 350 million articles on news.blueworldhosting.com:
# wc -l history
350579886 history
I'm willing to try out all the new features you've help implement to provide more real life metrics.
Thanks for all of the effort you've put into improving INN.
Cheers,
Jesse
I think the ovsqlite changes Julien just landed would be most useful for
you, the WAL thing might really help with your writer benchmarks
especially on ZFS where the correctness guarantees add a fair bit of
latency.
I'm not sure how noexpire and noexpireover interact with the history dbz maintenance, presumably there is still some work in cancel handling? If
so the expire tombstones might be relevant, and it helps with readers if
you run with 'nnrpdcheckart'
On May 21, 2026 at 8:58:49rC>PM CDT, "Kevin Bowling" <kevin.bowling@kev009.com>
wrote:
I think the ovsqlite changes Julien just landed would be most useful for
you, the WAL thing might really help with your writer benchmarks
especially on ZFS where the correctness guarantees add a fair bit of
latency.
I'm lazy, so I'll probably wait for a development snapshot to be posted.
I'm not sure how noexpire and noexpireover interact with the history dbz
maintenance, presumably there is still some work in cancel handling? If
so the expire tombstones might be relevant, and it helps with readers if
you run with 'nnrpdcheckart'
Sorry, I should have clarified more, what I meant was that I typically run it that way, but for experimentation, I'd be open to running it prior to updating
and again after.
I'm also not sure how my current options interact with the history file. I thought cancels were handled when the cancel is processed, and the overview removal happens immediately with ovsqlite? I could be completely wrong, this is an area I've not done much more than cursing makehistory requiring a gigantic amount of memory for it to generate an empty and appropriately sized dbz, otherwise it always pukes. For my spool I had to increase RAM to 64GB to generate a 400,000,000 dbz. Under normal operation, my server doesn't need more than 16GB of RAM.
ovsqlite should be in the next snapshop whenever that fires today/tomorrow.
I'm also not sure how my current options interact with the history file. I >> thought cancels were handled when the cancel is processed, and the overview >> removal happens immediately with ovsqlite? I could be completely wrong, this >> is an area I've not done much more than cursing makehistory requiring a
gigantic amount of memory for it to generate an empty and appropriately sized
dbz, otherwise it always pukes. For my spool I had to increase RAM to 64GB to
generate a 400,000,000 dbz. Under normal operation, my server doesn't need >> more than 16GB of RAM.
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
On May 21, 2026 at 9:29:57rC>PM CDT, "Kevin Bowling" <kevin.bowling@kev009.com>
wrote:
ovsqlite should be in the next snapshop whenever that fires today/tomorrow.
Excellent!
I'm also not sure how my current options interact with the history file. I >>> thought cancels were handled when the cancel is processed, and the overview >>> removal happens immediately with ovsqlite? I could be completely wrong, this
is an area I've not done much more than cursing makehistory requiring a
gigantic amount of memory for it to generate an empty and appropriately sized
dbz, otherwise it always pukes. For my spool I had to increase RAM to 64GB to
generate a 400,000,000 dbz. Under normal operation, my server doesn't need >>> more than 16GB of RAM.
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
When you say how big is my history dbz, do you mean in file size?
$ ls -l history*
-rw-r--r-- 1 news news 34350282002 May 21 22:17 history
-rw-r--r-- 1 news news 52 May 21 22:16 history.dir
-rw-r--r-- 1 news news 4500000000 May 21 22:16 history.hash
-rw-r--r-- 1 news news 6000000000 May 21 22:16 history.index
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
On May 21, 2026 at 9:29:57rC>PM CDT, "Kevin Bowling" <kevin.bowling@kev009.com>
wrote:
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
I must have missed Russ's comment about using sqlite for the history file, but
my personal opinion is the history operations are the largest bottleneck when running a larger scale INN instance. With CNFS and ovsqlite expiration operations are not so terrible. Room for improvement sure, but the main thing I've noticed when feeding hundreds of millions of articles between servers is the history writes are the biggest overall bottleneck I encounter on a regular
basis. I have messed with the various knobs related to this and never got the throughput I wanted, or what Diablo is capable of.
Of course, I'm not doing regular expire operations, but if I recall correctly,
when I was doing them it could take days to complete. That did create issues for users.
Kevin Bowling <kevin.bowling@kev009.com> writes:
How big is your history dbz? I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
I would be very curious to see if that would work. The dbz code is fairly optimized, but it's also very old. I'm sure there are better algorithms,
but I don't know if a general-purpose database can go as fast. If it can, that would offload a lot of complexity.
History is one of those cases where there's probably some better algorithm that would be optimized for that specific purpose, but figuring out those algorithms is only fun for a particular set of people and then it's hardIt strikes me that LMDB would probably be a more direct fit for this,
to find people to maintain them. :)
anything. That said, I do have over 350 million articles on news.blueworldhosting.com:
# wc -l history
350579886 history
To get back to the topic, I'd also be interested in "plumping up" my
history with articles, so Jesse if you'd care to share with me what you find, it would be greatly appreciated. (Or I'll write to you privately)
They occupy two extremes in a sense; DBZ is eventually a perfect hashing
CDB.
The biggest justification is probably operational, all the quirks and oddities of history basically turn into simple SQL statements (remember, expire, etc).
It strikes me that LMDB would probably be a more direct fit for this,
and if you squint OpenLDAP and inn aren't that different (although inn eventually leafs down to the file server load that sendfile would
naturally fit where LDAP is more of a key/value store). But sqlite is
already well integrated, basically maintenance free, and there is a cost
to bringing in more dependencies.
On 5/22/26 10:43 AM, Ivo Gandolfo wrote:
To get back to the topic, I'd also be interested in "plumping up" my
history with articles, so Jesse if you'd care to share with me what you
find, it would be greatly appreciated. (Or I'll write to you privately)
On that subject, I've noticed that the Google groups .mbox files loaded
into Blueworld hosting are incomplete. It's not obvious unless you
actually load all headers, make a list of threads, and *look at them*,
but when you examine the threadlist you'll see there's a lot of
discussions missing.
And it's not just XNA articles or something similar. It looks like
whoever loaded these mbox files made a mistake that resulted in
thousands of messages being left out per newsgroup.
ovsqlite should be in the next snapshop whenever that fires today/tomorrow.
On May 22, 2026 at 11:44:23rC>AM CDT, "D Finnigan" <dog_cow@macgui.com> wrote:
On 5/22/26 10:43 AM, Ivo Gandolfo wrote:
To get back to the topic, I'd also be interested in "plumping up" my
history with articles, so Jesse if you'd care to share with me what you
find, it would be greatly appreciated. (Or I'll write to you privately)
On that subject, I've noticed that the Google groups .mbox files loaded
into Blueworld hosting are incomplete. It's not obvious unless you
actually load all headers, make a list of threads, and *look at them*,
but when you examine the threadlist you'll see there's a lot of
discussions missing.
And it's not just XNA articles or something similar. It looks like
whoever loaded these mbox files made a mistake that resulted in
thousands of messages being left out per newsgroup.
What's on the publicly available on news.blueworldhosting.com is minimal compared to what I have in totality. I worked with Billy G while he was still around to pump ~7TB of articles into another server. I have yet to combine everything together and re-order article numbering chronologically.
I cannot claim to have 'everything', but there are several terabytes of articles that aren't on news.blueworldhosting.com, yet.
Once I get the entire archive in a sane manner I will swing news.blueworldhosting.com to that server.
Check it out. If I pick a few Big-8 groups at random and look through
the threads from the mid- to late-90s, I find there are *only*
cross-posted articles. But maybe that's just an artifact of not having
loaded everything you've got.
On 5/21/26 20:24, Jesse Rehmer wrote:
On May 21, 2026 at 9:29:57rC>PM CDT, "Kevin Bowling"
<kevin.bowling@kev009.com>
wrote:
How big is your history dbz?-a I suspect the answer to all this is what
Russ was hinting at, and maybe use sqlite for history so it can do in
place maintenance.
I must have missed Russ's comment about using sqlite for the history
file, but
my personal opinion is the history operations are the largest
bottleneck when
running a larger scale INN instance. With CNFS and ovsqlite expiration
operations are not so terrible. Room for improvement sure, but the
main thing
I've noticed when feeding hundreds of millions of articles between
servers is
the history writes are the biggest overall bottleneck I encounter on a
regular
basis. I have messed with the various knobs related to this and never
got the
throughput I wanted, or what Diablo is capable of.
It was in an unrelated thread with InterLinked about maybe using
databases instead of home grown formats.
Of course, I'm not doing regular expire operations, but if I recall
correctly,
when I was doing them it could take days to complete. That did create
issues
for users.
It should be manageable with my current patch stack to run expire with
your current count but writing out a new history at this scale every
night is a bit wasteful.
I will take a look at history/dbz and see if there are any easy wins and
do some napkin math to see if sqlite would be viable for the use case.
Hi,
I've been looking at inn as I run it:-a tradspool/ovsqlite on FreeBSD+ZFS with unlimited retention and trying to fix some scaling issues.
ovsqlite: Add direct reader mode for nnrpd with WAL https://github.com/InterNetNews/inn/pull/338
expireover: Add bloom filter for fast history existence checks https://github.com/InterNetNews/inn/pull/339
expire: skip per-article SMretrieve via cancel tombstone log https://github.com/InterNetNews/inn/pull/340
The first two have been running on csiph.com for a few days without
issue.-a I haven't yet deployed the last one yet but will publish results
in the PR when I do.
The general theme of all three is to reduce random or sync I/O.-a In the case of expireover and expire, the general problem is somewhat similar
but the root cause and fix are fairly different.
expireover checks history for every article and this turns into a very
slow and random serial I/O for every article in the history DB.-a The new option is to use a bloom filter with a configurable false positive rate, chosen for some acceptable RAM vs accuracy tradeoff by the admin and
their spool size.-a The bloom filter is created with a much more
efficient streaming read of the entire history file.-a In the case of a false positive (bloom claims article in history, but expire removed it),
it gets cleaned up on the next expireover run (entropy introduced by
hash churn make this probabilistic reality).-a On my system this is a
1100x improvement, 2 weeks down to 18 minutes.
expire is a bit more involved.-a We don't want the same bloom tradeoff, exactness trades a bit of RAM (pending deletes) for totally eliminating unnecessary disk I/O for verification.-a If we keep a tombstone log of articles expireover deletes, and any cancels, and we trust the state of
the spool, we can eliminate all speculative work and only deal with the actual history cleanup needed.-a On my system this will net a 2800x improvement, 2 days down to sub minute.
On 5/9/2026 7:26 PM, Kevin Bowling wrote:
Hi,
I've been looking at inn as I run it:-a tradspool/ovsqlite on
FreeBSD+ZFS with unlimited retention and trying to fix some scaling
issues.
ovsqlite: Add direct reader mode for nnrpd with WAL
https://github.com/InterNetNews/inn/pull/338
expireover: Add bloom filter for fast history existence checks
https://github.com/InterNetNews/inn/pull/339
expire: skip per-article SMretrieve via cancel tombstone log
https://github.com/InterNetNews/inn/pull/340
The first two have been running on csiph.com for a few days without
issue.-a I haven't yet deployed the last one yet but will publish
results in the PR when I do.
The general theme of all three is to reduce random or sync I/O.-a In
the case of expireover and expire, the general problem is somewhat
similar but the root cause and fix are fairly different.
expireover checks history for every article and this turns into a very
slow and random serial I/O for every article in the history DB.-a The
new option is to use a bloom filter with a configurable false positive
rate, chosen for some acceptable RAM vs accuracy tradeoff by the admin
and their spool size.-a The bloom filter is created with a much more
efficient streaming read of the entire history file.-a In the case of a
false positive (bloom claims article in history, but expire removed
it), it gets cleaned up on the next expireover run (entropy introduced
by hash churn make this probabilistic reality).-a On my system this is
a 1100x improvement, 2 weeks down to 18 minutes.
expire is a bit more involved.-a We don't want the same bloom tradeoff,
exactness trades a bit of RAM (pending deletes) for totally
eliminating unnecessary disk I/O for verification.-a If we keep a
tombstone log of articles expireover deletes, and any cancels, and we
trust the state of the spool, we can eliminate all speculative work
and only deal with the actual history cleanup needed.-a On my system
this will net a 2800x improvement, 2 days down to sub minute.
I've been thinking about this recently as part of some of other work I
have been doing and so in the past few days, have had a renewed interest
in your optimizations.
A week or two ago, I was trying to figure out how to suck articles in a manner that preserves their ordering across all groups in which an
article appears. In the end, I think there is really only one solution.
Save all the message IDs + date header + Xref header for the articles
you want to suck, sort them by Xref header with a fallback to Date, and
then suck the articles in that order. (This is quite a bit slower than
just going group by group, since you need to change the GROUP quite
often, though I suspect faster (and nicer) to the server being sucked
from than requesting by Message ID.) This won't work if there are a lot
of articles being sucked by a wimpy machine, but fortunately I'm not operating at the scale.
I've been doing this in two passes, first saving the Message-IDs to a
file and then sucking those, so that operations can be resumed and are idempotent. I check if an article was already in history to avoid asking
for it again. I was surprised that even with a few hundred thousand articles, when resuming a partial suck, just weeding out all the
duplicates would take many minutes, due to all the history traversals. Naturally, this made me think of using a Bloom filter (and I was
reminded of the optimization you did here and its encouraging results)
and indeed, that did speed that up quite a bit. I chose to retain
accuracy in that case, so if the bloom filter answers yes, I check
history anyways to confirm, but if there are a lot of negative matches
then it goes much faster.
Although I added a Bloom filter for just this one operation, I'm now
toying with the concept of having one in the background to use for all operations. For example, for IHAVE/CHECK/TAKETHIS, at the moment I check history to see if the Message-ID is present. I don't see why I can't
just create a Bloom filter at startup, adding new articles to it as received, and recreating the filter on expire operations, and in the
case of accepting an article we don't have, then a history scan is no
longer necessary, even with a small Bloom filter (somewhat mediocre
false positive rate).
As I understand it, only recently with your change does INN use Bloom filters at all, but I'm curious if you have any thoughts about adopting
them for other purposes like this (either in INN, or news software in general). I'm thinking a lot of history related operations could benefit from them, but maybe there is something too good to be true here...
On 6/23/26 15:14, InterLinked wrote:
On 5/9/2026 7:26 PM, Kevin Bowling wrote:
Hi,
I've been looking at inn as I run it:-a tradspool/ovsqlite on
FreeBSD+ZFS with unlimited retention and trying to fix some scaling
issues.
ovsqlite: Add direct reader mode for nnrpd with WAL
https://github.com/InterNetNews/inn/pull/338
expireover: Add bloom filter for fast history existence checks
https://github.com/InterNetNews/inn/pull/339
expire: skip per-article SMretrieve via cancel tombstone log
https://github.com/InterNetNews/inn/pull/340
The first two have been running on csiph.com for a few days without
issue.-a I haven't yet deployed the last one yet but will publish
results in the PR when I do.
The general theme of all three is to reduce random or sync I/O.-a In
the case of expireover and expire, the general problem is somewhat
similar but the root cause and fix are fairly different.
expireover checks history for every article and this turns into a
very slow and random serial I/O for every article in the history DB.
The new option is to use a bloom filter with a configurable false
positive rate, chosen for some acceptable RAM vs accuracy tradeoff by
the admin and their spool size.-a The bloom filter is created with a
much more efficient streaming read of the entire history file.-a In
the case of a false positive (bloom claims article in history, but
expire removed it), it gets cleaned up on the next expireover run
(entropy introduced by hash churn make this probabilistic reality).
On my system this is a 1100x improvement, 2 weeks down to 18 minutes.
expire is a bit more involved.-a We don't want the same bloom
tradeoff, exactness trades a bit of RAM (pending deletes) for totally
eliminating unnecessary disk I/O for verification.-a If we keep a
tombstone log of articles expireover deletes, and any cancels, and we
trust the state of the spool, we can eliminate all speculative work
and only deal with the actual history cleanup needed.-a On my system
this will net a 2800x improvement, 2 days down to sub minute.
I've been thinking about this recently as part of some of other work I
have been doing and so in the past few days, have had a renewed
interest in your optimizations.
A week or two ago, I was trying to figure out how to suck articles in
a manner that preserves their ordering across all groups in which an
article appears. In the end, I think there is really only one
solution. Save all the message IDs + date header + Xref header for the
articles you want to suck, sort them by Xref header with a fallback to
Date, and then suck the articles in that order. (This is quite a bit
slower than just going group by group, since you need to change the
GROUP quite often, though I suspect faster (and nicer) to the server
being sucked from than requesting by Message ID.) This won't work if
there are a lot of articles being sucked by a wimpy machine, but
fortunately I'm not operating at the scale.
I've been doing this in two passes, first saving the Message-IDs to a
file and then sucking those, so that operations can be resumed and are
idempotent. I check if an article was already in history to avoid
asking for it again. I was surprised that even with a few hundred
thousand articles, when resuming a partial suck, just weeding out all
the duplicates would take many minutes, due to all the history
traversals. Naturally, this made me think of using a Bloom filter (and
I was reminded of the optimization you did here and its encouraging
results) and indeed, that did speed that up quite a bit. I chose to
retain accuracy in that case, so if the bloom filter answers yes, I
check history anyways to confirm, but if there are a lot of negative
matches then it goes much faster.
Although I added a Bloom filter for just this one operation, I'm now
toying with the concept of having one in the background to use for all
operations. For example, for IHAVE/CHECK/TAKETHIS, at the moment I
check history to see if the Message-ID is present. I don't see why I
can't just create a Bloom filter at startup, adding new articles to it
as received, and recreating the filter on expire operations, and in
the case of accepting an article we don't have, then a history scan is
no longer necessary, even with a small Bloom filter (somewhat mediocre
false positive rate).
As I understand it, only recently with your change does INN use Bloom
filters at all, but I'm curious if you have any thoughts about
adopting them for other purposes like this (either in INN, or news
software in general). I'm thinking a lot of history related operations
could benefit from them, but maybe there is something too good to be
true here...
It is in lib/, depending on the usage it may need to be tweaked slightly
to accept a comparator (different than the MD5-optimization) or offer different function signatures for different uses.
You may find the benchmarks in https://github.com/InterNetNews/inn/pull/349#issuecomment-4760898620 interesting.-a With that I wouldn't assume history read path is a big bottleneck in most cases without seeing some evidence.
Keep in mind hiscache sits in front of CHECK, which is a HASH variant of what you might be suggesting.
Lookup might benefit from a cache, but remember it is done by nnrpd so
it scales by processes (and inversely makes cache sharing harder).
I would default to HASH style of implementation (and windowing, like
LRU, to implement small bounded caches) without a good overriding
reason; the Bloom filter is a specific optimization where space
efficiency and totality (in one direction, as you noticed) are both required.
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 74 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 01:24:54 |
| Calls: | 1,102 |
| Calls today: | 2 |
| Files: | 1,339 |
| Messages: | 276,785 |