Cookies op Tweakers

Tweakers is onderdeel van DPG Media en maakt gebruik van cookies, JavaScript en vergelijkbare technologie om je onder andere een optimale gebruikerservaring te bieden. Ook kan Tweakers hierdoor het gedrag van bezoekers vastleggen en analyseren. Door gebruik te maken van deze website, of door op 'Cookies accepteren' te klikken, geef je toestemming voor het gebruik van cookies. Wil je meer informatie over cookies en hoe ze worden gebruikt? Bekijk dan ons cookiebeleid.

Meer informatie
Toon posts:

[PHP7.3] Parallel / multi core processen van BSONDocument

Pagina: 1
Acties:

Vraag


Acties:
  • 0Henk 'm!

  • Matis
  • Registratie: januari 2007
  • Laatst online: 20:23

Matis

Rubber Rocket

Topicstarter
Beste DEVvers,

TL;DR: Is er een manier / library / composer-package om "eigen" klassen te kunnen gebruiken binnen closures in threads binnen PHP?

Ik heb een applicatie ontwikkeld in PHP 7.3 op basis van Symfony 4.4.
Deze applicatie kan gezien worden als een webbased Product Information Manager en bevat momenteel ruim 20 miljoen unieke producten. Deze producten hebben we opgeslagen in NoSQL (MongoDB) database.
Ieder product bevat, per leverancier, een collectie van attributen welke het product omschrijven. Denk hierbij aan gewicht, prijs, lengte, breedte, leverbaarheidsstatus etc..

Nu is de klantwens gekomen om statistieken over deze producten te draaien. Een van de wensen is het aantal leverbaarheidsstatussen per leverancier.

Om dit vraagstuk te beantwoorden, zijn we allereerst begonnen met het schrijven van een query in MongoDB. Dit bleek echter al snel, vanwege de enorme hoeveelheid data, onmogelijk te realiseren. We liepen tegen timeouts en/of out-of-memory exceptions aan.

Derhalve zijn we gedwongen om over de collectie van producten te intereren en van ieder BSONDocument weer een intern model te bouwen en daaruit de benodigde data te peuteren.
Deze data slaan we op in een tijdelijk array en schrijven we, nadat we alle producten hebben behandeld, weg in de statistieken database.

Dit proces werkt prima, maar heeft een heel lange doorlooptijd. We hebben getest met een subset van de data en als we dat getal extrapoleren komen we uit op ruim 9 uur. Gedurende deze tijd zit er 1 core continue op 100% te draaien.
Om een inzicht te krijgen waar de meeste tijd zit, heb ik de code door de profiler gehaald. Dit bleek, zoals verwacht, te zitten in het converteren van het BSONDocument naar het interne model. Dit betreft ruim 90% van de doorlooptijd.

Om het genereren van de statistieken (theoretisch) te versnellen, ben ik een poging gestart om dit proces parallel te gaan draaien. Om middels een pool van workers/threads meerdere cores aan het werk te zetten en de data aan het einde te combineren.

Om dit te bereiken heb ik de originele code welke het BSONDocument naar het interne model omzet in een Closure gegoten en deze Closure wordt middels een Future/Promise afgetrapt. Daarna wordt gecontroleerd of een taak is afgerond alvorens een nieuwe toe te voegen aan de threadpool.

In de vereenvoudigde test welke ik draaide, werkte dit allemaal vlekkeloos. Echter op het moment dat ik "eigen" types en/of klassen wil gebruiken binnen de Closure klapt de code er uit met een volgende Exception:
10:52:42 CRITICAL  [commandLogger] illegal parameter (MongoDB\Model\BSONDocument) passed to task at argument 2


De closure ziet er zo uit:
PHP:
1
2
3
4
5
6
7
8
9
        /**
         * @param int            $key
         * @param BSONDocument[] $documents
         */
        $closure = function (int $key, array $documents) {
            $this->logger->debug(sprintf('Starting %d', $key));
            /* ... */
            $this->logger->debug(sprintf('Stopped %d', $key));
        };

En in de klasse welke de threadpool regelt, roep ik de closure zo aan
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    /**
     * @param BSONDocument[] $documents
     * @return bool
     */
    private function scheduleWork(array $documents): bool
    {
        foreach ($this->futures as $key => $future) {
            if ($future instanceof Future === true) {
                /* Already a taken slot, so no new work can be scheduled */
                continue;
            }
            $runtime = new Runtime();
            $future = $runtime->run($this->closure, [$key, $documents]);
            $this->futures[$key] = $future;

            return true;
        }

        return false;
    }

Runtime en Future komen uit PHP's parallel library.

Is er een manier / library / composer-package om toch "eigen" klassen te kunnen gebruiken binnen closures in threads binnen PHP?

Alvast bedankt _O_

Matis

If money talks then I'm a mime
If time is money then I'm out of time

Alle reacties


Acties:
  • 0Henk 'm!

  • Kappie
  • Registratie: oktober 2000
  • Laatst online: 12:47

Kappie

Tell me your secrets...

He does fit the profile perfectly. He's intelligent, but an under-achiever; alienated from his parents; has few friends. Classic case for recruitment by the Soviets.


Acties:
  • 0Henk 'm!

  • Gimmeabrake
  • Registratie: december 2008
  • Laatst online: 17-06 23:02
In de vereenvoudigde test welke ik draaide, werkte dit allemaal vlekkeloos. Echter op het moment dat ik "eigen" types en/of klassen wil gebruiken binnen de Closure klapt de code er uit met een volgende Exception:
Dat klopt, dat staat ook gewoon in de door jou gelinkte documentatie: https://www.php.net/manua....run-argv-characteristics.
Om dit vraagstuk te beantwoorden, zijn we allereerst begonnen met het schrijven van een query in MongoDB. Dit bleek echter al snel, vanwege de enorme hoeveelheid data, onmogelijk te realiseren. We liepen tegen timeouts en/of out-of-memory exceptions aan.
20 mil. producten met per product een paar leveranciers klinkt in eerste instantie niet superzwaar om een aggregatie op te draaien, daar kan een SQL server ook nog prima mee overweg. Hebben jullie ervaring met dat soort vraagstukken icm mongo? Hebben jullie eens gekeken naar de execution plan van de query? Misschien dat een kleine wijziging of de juiste index wonderen kan doen.
Is er een manier / library / composer-package om toch "eigen" klassen te kunnen gebruiken binnen closures in threads binnen PHP?
Er valt vast wel iets te hacken met serialization of iets dergelijks. Misschien dat door @Kappie genoemde libraries je het leven ook wat makkelijker gaan maken op dat gebied.

Los daarvan: je hebt het erover dat de resultaten wegschrijft in een statistiekentabel. Ik gok dus dat dit een los scriptje is dat je als cronjob of iets dergelijks hebt ingeregeld. Hoe zit het qua programmeerkennis van andere OO-talen bij jullie? Als er wat kennis van bijv. Java of C# is zou ik overwegen het daarin te bouwen, in het kader van "using the right tool for the right job". Die talen performen - ondanks de indrukwekkende snelheidstoenames van PHP de laatste tijd - toch nog stukken beter dan PHP over het algemeen. Daarnaast hebben ze ook nog eens ingebouwde support voor threading.

Succes in ieder geval! Klinkt als een leuke puzzel! :)

Acties:
  • 0Henk 'm!

  • Matis
  • Registratie: januari 2007
  • Laatst online: 20:23

Matis

Rubber Rocket

Topicstarter
Die eerste heb ik inderdaad ook gevonden. Daar heb ik gisterenmiddag naar gekeken. Ik weet niet of het kwam omdat ik al een aantal dagen aan het kloten ben, of omdat ik een verkeerde mind-set heb, maar ik krijg niet helder hoe ik die code moet gebruiken zodat het ook voor mij werkbaar is/wordt.
De voorbeelden zijn vrij summier en (logischerwijs) heel abstract.
Ik vind, van mij zelf althans, dat de situatie waarin ik zit niet zo triviaal is als de voorbeelden schetsen.

Ik heb echt een producer / consumer scenario, waar de database met een X-aantal BSONDocument van producten klaar zet om door de consumers parallel verwerkt te worden.

In de voorbeelden staan de vraagstukken (of het nu URLs zijn of arrays van integers) al klaar en worden middels een standaard PHP-functie verwerkt.

Zoals eerder aangeven, vind ik mijn situatie net iets complexer en ik krijg het niet goed werkend. Misschien ga ik het vandaag, met frisse tegenzin, een tweede kans geven :P

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0Henk 'm!

  • Matis
  • Registratie: januari 2007
  • Laatst online: 20:23

Matis

Rubber Rocket

Topicstarter
Gimmeabrake schreef op dinsdag 19 mei 2020 @ 00:08:
20 mil. producten met per product een paar leveranciers klinkt in eerste instantie niet superzwaar om een aggregatie op te draaien, daar kan een SQL server ook nog prima mee overweg. Hebben jullie ervaring met dat soort vraagstukken icm mongo? Hebben jullie eens gekeken naar de execution plan van de query? Misschien dat een kleine wijziging of de juiste index wonderen kan doen.
Op productie hebben we momenteel 29 miljoen supplierAttributeBags in de collectie staan. Volgens MongoDB kost dat ruim 23GB aan data op disk en representeert dat bijna 80GB aan ruwe/tekstuele data.

Wij hebben, binnen ons team, vrijwel geen MongoDB ervaring. Niet in als het gaat over het schrijven van queries en niet over de werking van achterliggende technieken.
De architect welke het applicatielandschap ooit opgezet heeft, werkt niet meer bij ons.

Naar een execution plan hebben we nog nooit gekeken, simpelweg omdat we niet eens weten hoe we een query moeten schrijven welke het gewenste resultaat geeft. Misschien zit daar ook wel een deel van het probleem.

Een gemiddelde supplierAttributeBag ziet er zo uit:
JSON:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
{
    "_id" : ObjectId("5b97d14b6bc836000f4375d2"),
    "uuid" : "5c9bd142-a3cd-41f6-9557-432d18be12b6",
    "class" : "DareIt\\Product\\ProductManagement\\Models\\AttributeBag\\SupplierAttributeBag",
    "supplier" : {
        "uuid" : "69749ac1-b5cd-11e8-aec5-0230b4ec2220"
    },
    "product" : {
        "uuid" : "b6723748-b8de-4590-b29b-5e183a68b538"
    },
    "attributes" : [ 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\MeasurementAttribute",
            "code" : "height",
            "amount" : 225.0,
            "unitOfMeasurement" : "mm"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\MeasurementAttribute",
            "code" : "width",
            "amount" : 165.0,
            "unitOfMeasurement" : "mm"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\MeasurementAttribute",
            "code" : "depth",
            "amount" : 4.0,
            "unitOfMeasurement" : "mm"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\VatCodeAttribute",
            "code" : "vatCode",
            "vatCode" : "Reduced"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "productFormOnix",
            "value" : "BC"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "bic",
            "value" : "YBC"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "bicVersion",
            "value" : "2"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "publisher",
            "value" : "HarperCollins Publishers"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\IntegerAttribute",
            "code" : "numberOfPages",
            "value" : 32
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\DateAttribute",
            "code" : "publishingDate",
            "date" : 363484800
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "title",
            "value" : "Bears in the Night"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\AttributeCollectionAttribute",
            "code" : "author",
            "attributes" : [ 
                {
                    "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
                    "code" : "author",
                    "value" : "Stan Berenstain"
                }, 
                {
                    "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
                    "code" : "author",
                    "value" : "Jan Berenstain"
                }
            ]
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\MeasurementAttribute",
            "code" : "weight",
            "amount" : 80.0,
            "unitOfMeasurement" : "gr"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StatusAttribute",
            "code" : "status",
            "status_class" : "DareIt\\Product\\ProductManagement\\Models\\ProductStatus\\TemporarilyUnavailableProductStatus"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "availabilityStatus",
            "value" : "30"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\CloudVpsImageAttribute",
            "code" : "frontCover",
            "position" : 1,
            "url" : "https://8faae6ef03274e56a83928e5c027d247.objectstore.eu/VDMProduction/9780001712713.jpg",
            "adapter" : "products.importers.images.gardners.target_location_cloudvps_adapter"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\StringAttribute",
            "code" : "language",
            "value" : "eng"
        }, 
        {
            "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\AttributeCollectionAttribute",
            "code" : "textContent",
            "attributes" : [ 
                {
                    "class" : "DareIt\\Product\\ProductManagement\\Models\\Attribute\\TextContentAttribute",
                    "code" : "textContent",
                    "text_type" : "05",
                    "content_audience" : "03",
                    "text" : "This classic children's book is perfect for young and reluctant readers, thanks to its clever repetition and use of only 24 words!",
                    "text_date_time" : null,
                    "text_author" : null,
                    "source_title" : null,
                    "content_date_role" : null,
                    "content_date" : null
                }
            ]
        }
    ]
}

Ik heb in dit voorbeeld de prijs- en kortinginformatie verwijderd.
We kennen op dit moment 11 verschillende StatusAttribute en hebben 6 suppliers, ieder met hun eigen UUID.

Als ik uit zou moeten schrijven wat de query zou moeten doen is dat het volgende:

* Controleer of een SupplierAttributeBag in de attributes array een Attribute object heeft waarvan de "code" == "status".
* Bestaat deze niet, sla dan deze SupplierAttributeBag over
* Als deze bestaat, haal dan de value van dat specifieke Attribute object op.
* Bepaal per supplier.uuid het totaal aantal per status.

Het (fictieve) resultaat kan er zo uit komen te zien:
JSON:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[
    {
        "supplier.uuid" : "ABC"
        "statuses" : [
            {
                "status_class" : "status1",
                "count" : 123
            },
            {
                "status_class" : "status3",
                "count" : 456
            },
            {
                "status_class" : "status4",
                "count" : 7
            },
            {
                "status_class" : "status6",
                "count" : 89
            },
            {
                "status_class" : "status9",
                "count" : 1011
            },
            {
                "status_class" : "status10",
                "count" : 121
            }
        ]
    },
    {
        "supplier.uuid" : "DEF"
        "statuses" : [
            {
                "status_class" : "status7",
                "count" : 44
            },
            {
                "status_class" : "status3",
                "count" : 11
            }
        ]
    },
    {
        "supplier.uuid" : "GHI"
        "statuses" : [
            {
                "status_class" : "status9",
                "count" : 3545
            },
            {
                "status_class" : "status3",
                "count" : 11
            },
            {
                "status_class" : "status6",
                "count" : 77
            }
        ]
    }
]


Momenteel kent deze collectie de volgende indexen:
JSON:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "mongo.attributeBag"
    },
    {
        "v" : 2,
        "unique" : true,
        "key" : {
            "uuid" : 1.0
        },
        "name" : "idx_uuid",
        "ns" : "mongo.attributeBag",
        "background" : true
    },
    {
        "v" : 2,
        "key" : {
            "supplier.uuid" : 1.0
        },
        "name" : "idx_supplier_uuid",
        "ns" : "mongo.attributeBag",
        "background" : true
    },
    {
        "v" : 2,
        "key" : {
            "product.uuid" : 1.0
        },
        "name" : "idx_product_uuid",
        "ns" : "mongo.attributeBag",
        "background" : true
    },
    {
        "v" : 2,
        "unique" : true,
        "key" : {
            "supplier.uuid" : 1.0,
            "product.uuid" : 1.0
        },
        "name" : "idx_supplier_uuid_product_uuid",
        "ns" : "mongo.attributeBag",
        "background" : true
    }
]


Misschien dat ik het schrijven van een query en het optimaliseren er van ook nog maar eens moet gaan proberen. Zoals eerder aangegeven, is de kennis van MongoDB binnen het team echt minimaal. Doordat (naar mijn gevoel) MongoDB ook een stuk minder vaak gebruikt wordt dan een SQL-database, is de documentatie er over ook een stuk schaarser.
Er valt vast wel iets te hacken met serialization of iets dergelijks. Misschien dat door @Kappie genoemde libraries je het leven ook wat makkelijker gaan maken op dat gebied.
Ja, bedankt voor de motivatie. Ik moet dat ook een tweede kans geven.
Los daarvan: je hebt het erover dat de resultaten wegschrijft in een statistiekentabel. Ik gok dus dat dit een los scriptje is dat je als cronjob of iets dergelijks hebt ingeregeld. Hoe zit het qua programmeerkennis van andere OO-talen bij jullie? Als er wat kennis van bijv. Java of C# is zou ik overwegen het daarin te bouwen, in het kader van "using the right tool for the right job". Die talen performen - ondanks de indrukwekkende snelheidstoenames van PHP de laatste tijd - toch nog stukken beter dan PHP over het algemeen. Daarnaast hebben ze ook nog eens ingebouwde support voor threading.

Succes in ieder geval! Klinkt als een leuke puzzel! :)
Ik heb, vanuit mijn vorige werkgevers, wel ervaring met OO programmeertalen. Voornamelijk Java en C++.
Dat heb ik (nog) niet overwogen en zie ik echt al aller-aller-allerlaatste redmiddel :+

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0Henk 'm!

  • Gimmeabrake
  • Registratie: december 2008
  • Laatst online: 17-06 23:02
Ik ga niet de hele voorbeelddata en indexen helemaal doorpluizen, dat is volgens mij ook niet de bedoeling in dit subforum. Als ik er zo doorheen scroll heb ik wel de indruk dat er wat vreemde designkeuzes zijn gemaakt in het "schema". Ik zie bijv. dat er PHP FQCNs worden opgeslagen, dat soort taalspecifieke waardes zou ik zelf liever niet in een database stoppen.
Wij hebben, binnen ons team, vrijwel geen MongoDB ervaring. Niet in als het gaat over het schrijven van queries en niet over de werking van achterliggende technieken.
De architect welke het applicatielandschap ooit opgezet heeft, werkt niet meer bij ons.
Dan is het dus zaak dat iemand zich even goed verdiept in wat er is opgezet, en hoe mongo werkt. Als je onverwacht een storing krijgt in dit systeem, sta je dan ook wat minder met de rug tegen de muur. Ik kan me bijna niet voorstellen dat je zo'n aggregatie niet in een goed performende mongo query voor elkaar kunt krijgen, maar ik weet te weinig van Mongo om dat met zekerheid te zeggen. Op gevoel zou ik zeggen dat zo'n aggregatie secondenwerk zou moeten zijn.

Acties:
  • 0Henk 'm!

  • Voutloos
  • Registratie: januari 2002
  • Niet online
Die dump gaat nergens over, dat stuur je maar naar iemand als je iemand inhuurt. :D

Je issue is met Mongo, dus daar zit je oplossing. Meer kennis opdoen, het beter doorgronden, etc.

Als dat niet gaat zou ik nog eerder tijd stoppen in een migratie naar iets dat beter matcht voor het team en de gevraagde features dan het in applicatiecode gaan ploeteren. Als je dat laatste doet, moet je er ook op voorbereid zijn dat het bij vervolgvragen ook zo gaat. Ik zou het wel weten in ieder geval. :p

Talkin.nl daily photoblog


Acties:
  • 0Henk 'm!

  • DJMaze
  • Registratie: juni 2002
  • Niet online
Matis schreef op maandag 18 mei 2020 @ 13:16:
Om een inzicht te krijgen waar de meeste tijd zit, heb ik de code door de profiler gehaald. Dit bleek, zoals verwacht, te zitten in het converteren van het BSONDocument naar het interne model. Dit betreft ruim 90% van de doorlooptijd.
En als je het nou eens niet converteert?

Ik moest ooit 1GB XML bestanden inlezen in PHP.
Dan weet je dat bijna elke oplossing niet de juiste is, dan zoek je uit wat wel kan.

Het zelfde geld voor (no)SQL queries waarvan het resultaat in een PHP array wordt ingeladen 8)7

[Voor 8% gewijzigd door DJMaze op 19-05-2020 15:32]

Maak je niet druk, dat doet de compressor maar

Pagina: 1


Apple iPad Pro (2021) 11" Wi-Fi, 8GB ram Microsoft Xbox Series X LG CX Google Pixel 5a 5G Sony XH90 / XH92 Samsung Galaxy S21 5G Sony PlayStation 5 Nintendo Switch Lite

Tweakers vormt samen met Hardware Info, AutoTrack, Gaspedaal.nl, Nationale Vacaturebank, Intermediair en Independer DPG Online Services B.V.
Alle rechten voorbehouden © 1998 - 2021 Hosting door True