Uploaded image for project: 'phpBB'
  1. phpBB
  2. PHPBB-17731

Redis cache: per-table SQL cache index keys expire immediately, so destroy('sql', $table) never invalidates cached queries (regression from PHPBB-17358)

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: Major Major
    • 4.0.0-b1, 3.3.20-RC1
    • 3.3.13, 3.3.14, 3.3.15, 3.3.16, 3.3.17, 3.3.18, 3.3.19
    • Caching (ACM)
    • None

      Since 3.3.13 (PHPBB-17358), phpbb\cache\driver\redis::_write() always sets an absolute expiry:

          return $this->redis->set($var, $data, ['EXAT' => time() + $ttl]);

      With PHPBB-17358 a TTL of 0 now means "expire immediately" (consistent with the file driver).
      However, phpbb\cache\driver\memory::sql_save(), which is shared by the Redis driver, still
      writes the per-table query index with a TTL of 0, expecting it to never expire:

          // This must never expire
          $this->write('sql' . $table_name, $temp, 0);

      With Redis this becomes SET ... EXAT <current second>. Because that timestamp is already in the
      past (sub-second), Redis replies OK but does not store the key. As a result, the
      sql_<table_name> index keys never exist, and memory::destroy('sql', $table) finds no index and
      deletes nothing. Cached query results (sql_<md5>) are only dropped when their own TTL runs out.

      Impact: after any change that relies on destroy('sql', TABLE) for invalidation (styles, forums,
      moderator cache, teampage, bbcodes, banlist, groups, smilies, extensions using the same pattern),
      stale data keeps being served until the query cache TTL expires. That is up to 1 hour for several
      core queries (e.g. user.php style query, functions_display.php moderators, memberlist.php
      teampage) and 10 minutes for the jumpbox.

      Steps to reproduce:
      1. Configure $acm_type = 'phpbb\\cache\\driver
      redis'.
      2. Browse the board so cached queries are stored.
      3. Run "redis-cli --scan --pattern '<prefix>sql_*'": only sql_<md5> keys exist and no
         sql_<table_name> index keys are present.
      4. Or run "redis-cli MONITOR" while a cached query is saved. It shows:
           "SET" "<prefix>sql_phpbb_forums" "..." "EXAT" "1790591323"
         issued at 1790591323.53, so the key is discarded on write.
      5. Change something in the ACP whose queries are cached with a TTL (e.g. forum/moderator data):
         the old result is still served until the TTL expires.

      Expected: the per-table index keys persist (at least as long as the cached queries they
      reference), so destroy('sql', $table) invalidates those queries immediately.
      Actual: the index keys are never stored and per-table invalidation is a no-op on Redis.

      Environment: phpBB 3.3.17, Redis 7.0.15, phpredis 6.3.0, PHP 8.3. The same _write() code is
      present on the current 3.3.x and master branches.

      Suggested fix: do not use 0 for the index in memory::sql_save(). Write it with a long TTL, e.g.
      the default:
          $this->write('sql' . $table_name, $temp);
      Alternatively, keep a dedicated "no expiry" path for these keys in the Redis driver. Treating
      ttl <= 0 as "no expiry" in redis::_write() would also work, but it reverts the semantics
      introduced by PHPBB-17358.

      Related: PHPBB-17358, PHPBB-15320.

            Marc Marc
            DSR! DSR!
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: