@@ -846,3 +846,148 @@ When configuring multiple keys, the first key will be used for reading and
846
846
writing, and the additional key(s) will only be used for reading. Once all
847
847
cache items encrypted with the old key have expired, you can completely remove
848
848
``OLD_CACHE_DECRYPTION_KEY ``.
849
+
850
+ Computing Cache Values Asynchronously
851
+ -------------------------------------
852
+
853
+ .. versionadded :: 5.2
854
+
855
+ The feature to compute cache values asynchronously was introduced in Symfony 5.2.
856
+
857
+ The Cache component uses the `probabilistic early expiration `_ algorithm to
858
+ protect against the :ref: `cache stampede <cache_stampede-prevention >` problem.
859
+ This means that some cache items are elected for early-expiration while they are
860
+ still fresh.
861
+
862
+ By default, expired cache items are computed synchronously. However, you can
863
+ compute them asynchronously by delegating the value computation to a background
864
+ worker using the :doc: `Messenger component </components/messenger >`. In this case,
865
+ when an item is queried, its cached value is immediately returned and a
866
+ :class: `Symfony\\ Component\\ Cache\\ Messenger\\ EarlyExpirationMessage ` is
867
+ dispatched through a Messenger bus.
868
+
869
+ When this message is handled by a message consumer, the refreshed cache value is
870
+ computed asynchronously. The next time the item is queried, the refreshed value
871
+ will be fresh and returned.
872
+
873
+ First, create a service that will compute the item's value::
874
+
875
+ // src/Cache/CacheComputation.php
876
+ namespace App\Cache;
877
+
878
+ use Symfony\Contracts\Cache\ItemInterface;
879
+
880
+ class CacheComputation
881
+ {
882
+ public function compute(ItemInterface $item): string
883
+ {
884
+ $item->expiresAfter(5);
885
+
886
+ // this is just a random example; here you must do your own calculation
887
+ return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
888
+ }
889
+ }
890
+
891
+ This cache value will be requested from a controller, another service, etc.
892
+ In the following example, the value is requested from a controller::
893
+
894
+ // src/Controller/CacheController.php
895
+ namespace App\Controller;
896
+
897
+ use App\Cache\CacheComputation;
898
+ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
899
+ use Symfony\Component\Routing\Annotation\Route;
900
+ use Symfony\Contracts\Cache\CacheInterface;
901
+ use Symfony\Contracts\Cache\ItemInterface;
902
+
903
+ class CacheController extends AbstractController
904
+ {
905
+ /**
906
+ * @Route("/cache", name="cache")
907
+ */
908
+ public function index(CacheInterface $asyncCache): Response
909
+ {
910
+ // pass to the cache the service method that refreshes the item
911
+ $cachedValue = $cache->get('my_value', [CacheComputation::class, 'compute'])
912
+
913
+ // ...
914
+ }
915
+ }
916
+
917
+ Finally, configure a new cache pool (e.g. called ``async.cache ``) that will use
918
+ a message bus to compute values in a worker:
919
+
920
+ .. configuration-block ::
921
+
922
+ .. code-block :: yaml
923
+
924
+ # config/packages/framework.yaml
925
+ framework :
926
+ cache :
927
+ pools :
928
+ async.cache :
929
+ messenger_bus : async_bus
930
+
931
+ messenger :
932
+ transports :
933
+ async_bus : ' %env(MESSENGER_TRANSPORT_DSN)%'
934
+ routing :
935
+ Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage : async_bus
936
+
937
+ .. code-block :: xml
938
+
939
+ <!-- config/packages/framework.xml -->
940
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
941
+ <container xmlns =" http://symfony.com/schema/dic/services"
942
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
943
+ xmlns : framework =" http://symfony.com/schema/dic/symfony"
944
+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
945
+ https://symfony.com/schema/dic/services/services-1.0.xsd
946
+ http://symfony.com/schema/dic/symfony
947
+ https://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
948
+ >
949
+ <framework : config >
950
+ <framework : cache >
951
+ <framework : pool name =" async.cache" early-expiration-message-bus =" async_bus" />
952
+ </framework : cache >
953
+
954
+ <framework : messenger >
955
+ <framework : transport name =" async_bus" >%env(MESSENGER_TRANSPORT_DSN)%</framework : transport >
956
+ <framework : routing message-class =" Symfony\Component\Cache\Messenger\Message\EarlyExpirationMessage" >
957
+ <framework : sender service =" async_bus" />
958
+ </framework : routing >
959
+ </framework : messenger >
960
+ </framework : config >
961
+ </container >
962
+
963
+ .. code-block :: php
964
+
965
+ // config/framework/framework.php
966
+ use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
967
+ use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;
968
+ use Symfony\Config\FrameworkConfig;
969
+
970
+ return static function (FrameworkConfig $framework): void {
971
+ $framework->cache()
972
+ ->pool('async.cache')
973
+ ->earlyExpirationMessageBus('async_bus');
974
+
975
+ $framework->messenger()
976
+ ->transport('async_bus')
977
+ ->dsn(env('MESSENGER_TRANSPORT_DSN'))
978
+ ->routing(EarlyExpirationMessage::class)
979
+ ->senders(['async_bus']);
980
+ };
981
+
982
+ You can now start the consumer:
983
+
984
+ .. code-block :: terminal
985
+
986
+ $ php bin/console messenger:consume async_bus
987
+
988
+ That's it! Now, whenever an item is queried from this cache pool, its cached
989
+ value will be returned immediately. If it is elected for early-expiration, a
990
+ message will be sent through to bus to schedule a background computation to refresh
991
+ the value.
992
+
993
+ .. _`probabilistic early expiration` : https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration
0 commit comments