Slide 1

Slide 1 text

About me Name: Alexander Schranz Workplace: Sulu (sulu.io) Tools: PHP, Symfony, Twig, Elasticsearch, Redis ReactJS, MobX, React Native (Expo) Experience: Web Developer since 2012 Certified Symfony 5 Expert X / Twitter: @alex_s_ Github: @alexander-schranz Mastodon: [email protected]

Slide 2

Slide 2 text

Efficient JSON API

Slide 3

Slide 3 text

The Problem 4K images JSON API with more than 100 items

Slide 4

Slide 4 text

Example Object for the API: class Article { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: Types::INTEGER)] public readonly int $id; #[ORM\Column(type: Types::STRING)] public string $title = ‘’; #[ORM\Column(type: Types::TEXT)] public string $description = ‘’; #[ORM\Column(type: Types::TEXT)] #[Serializer\Ignore()] public string $note = ‘’; }

Slide 5

Slide 5 text

Our JSON (JSON-HAL) { "_embedded": { "articles": [ { "id": 1, "title": "Article 1", "description": "Description 1\nMore description text ..." }, ... ] } }

Slide 6

Slide 6 text

Typical Controller

Slide 7

Slide 7 text

Finding Bottlenecks What want we reduce: ❏ I/O usage ❏ CPU usage ❏ Memory usage

Slide 8

Slide 8 text

The Basic Bottlenecks - ORM Hydration / Serializer Object Hydration Serialization

Slide 9

Slide 9 text

Understanding Hydration The Hydration process of the ORM will convert the SQL result (associative array) to an Object (Article) Understanding Serialization The Serialization process of the Serializer will convert (normalize) the Object (Article) into an associative array and encode it then to JSON https://symfony.com/doc/current/components/serializer.html

Slide 10

Slide 10 text

Reduce CPU and Memory usage by avoid Hydration and Normalization process

Slide 11

Slide 11 text

Reduce I/O usage by avoid overfetching and select only required columns

Slide 12

Slide 12 text

Is that all? Here most devs stops with the optimization and to avoid that many articles need to be kept in memory limit there APIs to never return more as 100 items. How we can go further with the optimization?

Slide 13

Slide 13 text

Static Image / File Streaming

Slide 14

Slide 14 text

Current State our API

Slide 15

Slide 15 text

Generators A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over. https://www.php.net/manual/en/language.generators.overview.php

Slide 16

Slide 16 text

Generators in PHP 1 10 1_ ---- 2 20 2_ ---- 3 30 3_ ----

Slide 17

Slide 17 text

Generators in JavaScript 1 10 1_ ---- 2 20 2_ ---- 3 30 3_ ----

Slide 18

Slide 18 text

Use Generators to read from Database

Slide 19

Slide 19 text

Use Generators with Doctrine

Slide 20

Slide 20 text

Split JSON in Static and Dynamic Part { "_embedded": { "articles": [ {"id": 1, "title": "Article 1", …}, {"id": 2, "title": "Article 2", …}, {"id": 3, "title": "Article 3", …}, … ] } }

Slide 21

Slide 21 text

Replace JsonResponse with StreamedResponse Get Static Parts

Slide 22

Slide 22 text

Output Static Before Dynamic Part Output Static After

Slide 23

Slide 23 text

Whole Code

Slide 24

Slide 24 text

Make it reusable

Slide 25

Slide 25 text

Efficient JSON API

Slide 26

Slide 26 text

Contribute to Symfony 124 Comments from 11 different people over 3 month we improved it to even a better version where the Generator can directly be set in the Structure. https://github.com/symfony/symfony/pull/47709

Slide 27

Slide 27 text

Adopted by Laravel Part of Laravel since v10.43: https://github.com/laravel/framework/pull/49873

Slide 28

Slide 28 text

Efficient JSON API

Slide 29

Slide 29 text

Stats Old: New:

Slide 30

Slide 30 text

Buuuuut What about the client?

Slide 31

Slide 31 text

Alternatives: Line-delimited JSON {"id": 1, "title": "Article 1", …} {"id": 2, "title": "Article 2", …} {"id": 3, "title": "Article 3", …} Use HTTP Headers to provide more data like total or other numbers. Line-delimited JSON Streams can be even ready efficiently by clients while structured nested JSON objects are harder to read object by object. https://en.wikipedia.org/wiki/JSON_streaming

Slide 32

Slide 32 text

Time for your questions https://github.com/alexander-schranz/efficient-json-streaming-with-symfony-doctrine X / Twitter: @alex_s_ Github: @alexander-schranz Mastodon: [email protected]