Problem/Motivation

When content is fetched using the HTTP Fetcher, it is cached in the database (in the table "cache_feeds_http").

This is a problem because the fetched content can be very big. This can result into errors in the following cases:

  • The content is bigger than the MySQL max allowed packet. For example: if the content is 200 MB and max_allowed_packet = 128M, then it will result into a PDOException with the message "MySQL server has gone away".
  • When using an external cache system, like memcached. A 100M file will not fit in the 64M memcached bin.

Additional information

The cached content is used when all of the following conditions are true:

  • At the start of a second import. The cached content is not used when continuing an unfinished import.
  • When the source where the content comes from returns a 304. This means that the source reports that the content was not changed since the last time it was requested. http_request_get() sends the HTTP header "If-Modified-Since" to the source when there is cached content available locally.

The cached content may also be used by FeedsEnclosure::getContent(), when a file is retrieved for the file mapper.

How to reproduce

  1. Set the MySQL variable "max_allowed_packet" to a low value, but not too low or else other queries in Drupal may fail. Pick for example: 40 MB.
  2. Generate a large file, bigger than the MySQL max allowed packet, for example a file of 65 MB big.
  3. Create an importer using the HTTP Fetcher.
  4. Fetch the file.

Proposed resolution

Only cache the headers in the database. Cache the content on the file system, for example at private://feeds/cache. Use a cache class that extends DrupalDatabaseCache, so that content cached on the file system gets cleaned up when clearing caches.

Not caching content at all is not an option

Ideally, only the headers are cached because when a source has not changed, it isn't useful to import it again. Trying to do this introduces other problems though:

  1. If "Skip hash check" is checked - meaning someone wants to update everything - nothing will happen because the source won't be redownloaded.
  2. http_request_get() isn't used by the HTTP Fetcher only. It also used by FeedsEncloser::getContent(), which is used by the file mapper. If FeedsEncloser returns nothing, the file field will get erased (see FeedsProcessor::map()).

Remaining tasks

  1. Implement a cache class called FeedsHTTPCache that extends DrupalDatabaseCache. The class should make sure that headers are saved in the DB, but contents are saved on the file system.
  2. Write a test class for FeedsHTTPCache.
  3. Integrate cache class FeedsHTTPCache with http_request_get().
  4. Write a test that ensures that the source is not refetched on a second import when the source did not change. (start: #2032333-20: [Meta] Store remote files in the file system instead of in the database)
  5. Write a test that ensures that the source is refetched on a second import when the source changed. (start: #2032333-20: [Meta] Store remote files in the file system instead of in the database)
  6. Write a test that ensures cached files get cleaned up even when a different cache class is used.
  7. Review!

User interface changes

None.

API changes

API addition: new cache class FeedsHTTPCache.

Data model changes

  • Content fetched via HTTP is saved on the file system.
  • A cache class called FeedsHTTPCache is introduced.
CommentFileSizeAuthor
#29 interdiff-2829096-28-29.txt3.86 KBmegachriz
#29 feeds-fetcher-cache-2829096-29.patch54.11 KBmegachriz
#28 interdiff-2829096-25-28.txt546 bytesmegachriz
#28 feeds-fetcher-cache-2829096-28.patch51.69 KBmegachriz
#26 interdiff-2829096-24-25.txt3.03 KBmegachriz
#26 feeds-fetcher-cache-2829096-25.patch51.63 KBmegachriz
#24 interdiff-2829096-22-24.txt6.26 KBmegachriz
#24 feeds-fetcher-cache-2829096-24.patch49.34 KBmegachriz
#2 feeds-fetcher-cache-2829096-2.patch20.1 KBmegachriz
#4 feeds-fetcher-cache-2829096-4.patch20.12 KBmegachriz
#4 interdiff-2829096-2-4.txt2.91 KBmegachriz
#5 feeds-fetcher-cache-2829096-5.patch23.68 KBmegachriz
#5 interdiff-2829096-4-5.txt6.99 KBmegachriz
#6 feeds-fetcher-cache-2829096-6.patch37 KBmegachriz
#6 interdiff-2829096-5-6.txt13.32 KBmegachriz
#8 feeds-fetcher-cache-2829096-8.patch36.99 KBmegachriz
#8 interdiff-2829096-6-8.txt3 KBmegachriz
#10 feeds-fetcher-cache-2829096-10.patch37.37 KBmegachriz
#10 interdiff-2829096-8-10.txt1.98 KBmegachriz
#12 feeds-fetcher-cache-2829096-12.patch37.41 KBmegachriz
#12 interdiff-2829096-10-12.txt532 bytesmegachriz
#13 feeds-fetcher-cache-2829096-13.patch40.79 KBmegachriz
#13 interdiff-2829096-12-13.txt4.26 KBmegachriz
#14 feeds-fetcher-cache-2829096-14.patch40.62 KBmegachriz
#14 interdiff-2829096-13-14.txt549 bytesmegachriz
#17 feeds-fetcher-cache-2829096-17.patch41.01 KBmegachriz
#17 interdiff-2829096-14-17.txt2.08 KBmegachriz
#19 feeds-fetcher-cache-2829096-19.patch43.99 KBmegachriz
#19 interdiff-2829096-17-19.txt6.18 KBmegachriz
#21 feeds-fetcher-cache-2829096-21.patch44.01 KBmegachriz
#21 interdiff-2829096-19-21.txt1.12 KBmegachriz
#22 feeds-fetcher-cache-2829096-22.patch45.42 KBmegachriz
#22 interdiff-2829096-21-22.txt2.36 KBmegachriz

Comments

MegaChriz created an issue. See original summary.

megachriz’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new20.1 KB

This patch implements the cache class FeedsHTTPCache and also include a test that purely tests this class.

The cache class is not integrated with the rest of the Feeds code yet. I just first wanted to focus on the class itself.

Setting to "Needs review" for the testbot, but the work here is not done yet.

Status: Needs review » Needs work

The last submitted patch, 2: feeds-fetcher-cache-2829096-2.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new20.12 KB
new2.91 KB

Try again.

megachriz’s picture

StatusFileSize
new23.68 KB
new6.99 KB

This implements the case of expired cache entries.

megachriz’s picture

Great, the cache class seems to do everything now that it is supposed to do.

Now adding all the stuff from #2032333-20: [Meta] Store remote files in the file system instead of in the database back, though without the changes to the fetchers that focussed on solving #2829097: Don't store raw source in feeds_source table. Should fail, as I haven't updated the tests in FeedsFileHTTPTestCase yet (that still assume that the complete cache entry is cached on the file system). I wonder though if changes are needed in the fetcher classes and/or http_request_get() to fix this issue. Lets find out.

Status: Needs review » Needs work

The last submitted patch, 6: feeds-fetcher-cache-2829096-6.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new36.99 KB
new3 KB

Hm. Response code is expected to be cached as well. Made changes for this in FeedsHTTPCacheItem class.
Also updated the tests, though not checked yet if I did that properly.

Status: Needs review » Needs work

The last submitted patch, 8: feeds-fetcher-cache-2829096-8.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new37.37 KB
new1.98 KB

I remember the reason why I decided to convert the HTTP response to a classed object when caching it: so that whenever $result->data was requested, the class could return the data from the cached file (by implementing a magic getter).
Now we are going to see if that works.

Status: Needs review » Needs work

The last submitted patch, 10: feeds-fetcher-cache-2829096-10.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new37.41 KB
new532 bytes

Almost no test failures!

I see that in #8 I had removed one line too much. That may be just everything?

+++ b/tests/feeds_fetcher_http.test
@@ -167,15 +159,11 @@ class FeedsFileHTTPTestCase extends FeedsWebTestCase {
-    $csv = implode("\n", $lines);
megachriz’s picture

Issue summary: View changes
StatusFileSize
new40.79 KB
new4.26 KB

I have been testing the patch myself and I found the following issues:

  1. When deleting a cached file manually, the content didn't get refetched when the HTTP source returned a 304. Even worse, files mapped to a file mapper lead to a broken link in this case.
  2. I realized that when a different cache class is used, Memcache or Redis for example, the raw data would still be cached, as now purely the cache class 'FeedsHTTPCache' takes care that the raw data isn't saved to cache. But when a different cache system is used, FeedsHTTPCache is bypassed. So saving the raw data to a file should happen one step earlier.
  3. When cache entries are deleted manually from the database, the files will remain and wouldn't get cleaned up. This would also be an issue when a different cache class is used (and the previous noted issue is fixed).

Attached a patch that add tests for the first two issues found. I'm already working on the fixes for these two, but I want to make sure that these tests are failing now. For the third issue I still need to think about how that fix and test that.

megachriz’s picture

StatusFileSize
new40.62 KB
new549 bytes

Oops. There was still debug code in the test.

The last submitted patch, 13: feeds-fetcher-cache-2829096-13.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 14: feeds-fetcher-cache-2829096-14.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new41.01 KB
new2.08 KB

Hm. The test FeedsFileHTTPTestCase::testHTTPCacheOverride() is still passing, while it was meant to fail.
New try.

Status: Needs review » Needs work

The last submitted patch, 17: feeds-fetcher-cache-2829096-17.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new43.99 KB
new6.18 KB

Great, now FeedsFileHTTPTestCase::testHTTPCacheOverride() does fail. The attached patch should fix the following cases:

  1. A manually removed cached file will not lead to errors: the source will be refetched.
  2. When using an other cache class, the raw data fetched via HTTP will still be cached on the file system.

I changed the FeedsHTTPCacheItem class quite a bit. Now it knows the cache ID it will be saved under and it talks with the FeedsHTTPCache class about where to store the cached file (even when a different cache class is used).

The following problem still needs to be fixed: when cache entries are deleted manually from the database or when a different cache class is used, the files will remain and wouldn't get cleaned up.

Status: Needs review » Needs work

The last submitted patch, 19: feeds-fetcher-cache-2829096-19.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new44.01 KB
new1.12 KB

Oops. The method FeedsHTTPCacheItem::saveResponseData() should call FeedsHTTPCacheItem::getCacheObject() to receive the cache object.

megachriz’s picture

Here is a test that checks if orphaned cached files are cleaned up. Cache files can get orphaned when cache entries are deleted manually from the database or when a different cache class is used for the cache bin 'cache_feeds_http'.

Test should fail.

The last submitted patch, 22: feeds-fetcher-cache-2829096-22.patch, failed testing.

megachriz’s picture

And here is a possible fix for the test failure. Adds quite a lot of code though.

In short:

  • A queue called 'feeds_sync_cache_feeds_http' is added.
  • Every 6 hours a job is started to scan the cache file directory for orphaned files. The job is not started if there are still items in the 'feeds_sync_cache_feeds_http' queue. The interval is configurable via a variable called 'feeds_sync_cache_feeds_http_interval' (documented in the README-file).
  • For at max 50 files per queue item it is checked if there are still cache entries for these files. Files for which no cache entry exist are removed.

Let's see if this passes tests.

megachriz’s picture

Issue summary: View changes

I think this is ready.

megachriz’s picture

I found a small bug when manually testing cleaning up orphaned cache files. If no feeds needed be rescheduled, the cleanup task was not started.

Also added a test for this cleanup task.

Status: Needs review » Needs work

The last submitted patch, 26: feeds-fetcher-cache-2829096-25.patch, failed testing.

megachriz’s picture

Status: Needs work » Needs review
StatusFileSize
new51.69 KB
new546 bytes

I think the test FeedsFileHTTPTestCase::testCachedFilesCleanupOnHTTPCacheOverride() failed because cron already ran earlier, causing the variable 'feeds_sync_cache_feeds_http_last_check' to be set and not scheduling the cleanup task on the next cron run (as the cleanup task is only scheduled every six hours). The fix for this: empty the 'feeds_sync_cache_feeds_http_last_check' before running cron.

megachriz’s picture

This patch adds a check for if the Feeds cache directory is writable. Also included tests that test the case of a non-writable cache directory. I have set the non-writable dir to 'file://non-writeable-dir/feeds' as that resulted into something non-writable locally. Not sure if that counts too for the testbot.

If the passes tests, I commit the patch.

  • MegaChriz committed 0bc3832 on 7.x-2.x
    Issue #2829096 by MegaChriz: Cache result of HTTP source on file system.
    
megachriz’s picture

Status: Needs review » Fixed

Committed #29!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.