I dealt with this exact pattern at my last gig. We had "dynamic" content that changed every 30 minutes but TTL was 0 because "real-time." Cost was 40 lakh INR in overages before we fixed headers.
Joy, use surrogate-control for the shield layer, cache-control for the edge. Different directives per tier. S-maxage=300 for shield, max-age=30 for edge, stale-while-revalidate=86400. Layered caching, not layered missing.
Also: OVHcloud Singapore to Gravelines would hurt. But Gravelines to Gravelines should not. Verify your shield zone location.