20,000 rows of 3,000 bits is only about 7 megabytes. Another idea might be a bit array in memory that you could search by and-ing with bit masks. PDL might make fast manipulations easier.
Comment on Re: Does Search::InvertedIndex module live up to billing?