The main reason for the slowness is (was) that you aggregate over the big table from scratch for every iteration of the lateral sibquery. Compute earliest review & current total count per app in CTE *once* and base the lateral subquery on it. I discussed that and some other optizations under your predating related question on SO:

[Get apps with the highest review count since a dynamic series of days][1]

One difference: In this question you also return an additional attribute of the app (`slug`), so we need to join to table `app` after all. Literally: join to `apps` *after* aggregating `reviews`, much cheaper, on top of the CTE:

```sql
WITH cte AS (  -- MATERIALIZED
   SELECT a.id, a.slug,
   FROM   apps a
   JOIN    (
      SELECT app_id, min(review_date) AS earliest_review, count(*)::int AS total_ct
      FROM   reviews
      GROUP  BY 1
      ) r ON r.app_id = a.id
SELECT *
FROM  (
   SELECT generate_series(min(review_date)
                        , max(review_date)
                        , '1 day')::date
   FROM   reviews
   ) d(review_window_start)
LEFT  JOIN LATERAL (
   SELECT total_ct, array_agg(app_id) AS apps
   FROM  (
      SELECT app_id, total_ct
      FROM   cte c
      WHERE  c.earliest_review >= d.review_window_start
      ORDER  BY total_ct DESC
      FETCH  FIRST 1 ROWS WITH TIES  -- new & hot
      ) sub
   GROUP  BY 1
   ) a ON true;
```

`WITH TIES` makes it a bit cheaper. Added in Postgres 13 (currently beta). See:

- https://stackoverflow.com/questions/63178114/greater-than-or-equal-to-all-and-equal-to-max-speed/63184287#63184287

[1] https://stackoverflow.com/a/63320975/939860