Optimizing the query with 2 distinct counts in select
There is a many-to-many relationship between messages and order_items through products, as a product can appear in many order_items and many messages and also a message (or an order_item) can have many products.

This results in the query producing a variation of a CROSS JOIN, and thus the need for using DISTINCT in the counts and consequently the poor performance.

I'd suggest you try adding composite indexes on both tables, on (product_id, order_item_id), (product_id, message_id) and see if they are used. 

Another approach would be to rewrite the query so it doesn't use DISTINCT:

WITH 
  cm AS
    ( SELECT COUNT(*) AS count_messages
      FROM messages AS m
      WHERE EXISTS
            ( SELECT 1 
              FROM order_items AS o
              WHERE m.product_id = o.product_id
            )
    ),
  co AS
    ( SELECT COUNT(*) AS count_order_items
      FROM order_items AS o 
      WHERE EXISTS
            ( SELECT 1 
              FROM messages AS m
              WHERE m.product_id = o.product_id
            )
    )
SELECT cm.count_messages :: DECIMAL / co.count_order_items :: DECIMAL
         AS result
FROM cm, co ;