From 0671b0a7d5467f7db928cf4a6dec436d9a969c3c Mon Sep 17 00:00:00 2001 From: Senthil Krishnamurthy Date: Thu, 14 May 2026 02:48:30 +0000 Subject: [PATCH] HLD for orchagent notifications queue policy Signed-off-by: Senthil Krishnamurthy --- .../notifications_redisgn/existing_design.png | Bin 0 -> 67916 bytes .../notification_queue_policy_hld.md | 612 ++++++++++++++++++ 2 files changed, 612 insertions(+) create mode 100644 doc/orchagent/images/notifications_redisgn/existing_design.png create mode 100644 doc/orchagent/notification_queue_policy_hld.md diff --git a/doc/orchagent/images/notifications_redisgn/existing_design.png b/doc/orchagent/images/notifications_redisgn/existing_design.png new file mode 100644 index 0000000000000000000000000000000000000000..9f326a29bcdef206e465217d90df8ac834c62607 GIT binary patch literal 67916 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

(ok23#nq-#pqvson)cY>J-*?~FxeKMdutg^9g9 zd^Wqo?WAZ%{7zrbK?Z4{D;CrED(NY z9z6}W{>(Z)W7l<%@=jGXGMXbS)hNP}G5_jRMvGn)Sh*Y0p8tZFS}vY~$@D7XT>=G@ z=2ej1ctUA5j}&3ZYI?vUwko`Y($JMKZz3D`5RI=`t?O>;tp60jSa5z?N#Ff;Nr$O5 z!931k?q>$fJNdyZ;RAcZgGX-Da23aKX;t5}&}E;w!wdjG_EDXx1ZUKk*dFPuK>R~u z^c6hxpcME4v}ETSI@n#kCM&3PgcV;+p{B}qL%BRP*D6p;2BC1 z+)-l3J4U8JfLjUt>;doz2YsV88GF!pkW{xE!(6=g{{H@lA8{ikEo-HT%McMBIok`W z;=(ykmh4~<&lk38C|CQs@~e+*&#l(RP_O%`{3X<7vFg_!OLDBr7>LAEjNb;uw7pZE z7+;=YA*D)Do4C^`PfNw5`LvZgC-7(^&*p){syA?w_wm*0 zE8==6xBPzVR^hVE2jOitvdTZ3gV@K}7j*Qb&vbTrJ_2{X<}!*`bCf3aV&x&;tk<=a zUy}9_m_jqDLSgkNgQYfqMt`eH<8VpTh53d^@R;sJPS@ZzsGn&kui%=BUc= zj+DT81IL~&pMK_z5+Lw;p{M`&&wCRbcC^lZIK;h18PzK4 zo1UjfM_Tkdyq|u3PnNV{(qg>--VJ(|2xNG4KDS6%xtDL>{_KRb1H(zE*HuizQ{A#p z`3*|1RlEy&`HUsrAB8ineByk_buf_QzYxDe1{s3~{!E#SS(D0!6cwkiYp8PUzujOl zO2n>Z1XWJ+RdkjFZG;Aa9IFPohmnLV$J@F&0mo5H-U&V-du87yyLZ&^3|c@l+NCH| z+P$x*iqCO;WfumtQA?{5b&yYnoV_O`H$UvqLfyUlR1SfH{`b1uGGs_}Ep77X7a>o7 zIEYfH_jLJDV%%N)#E?rkwbnUzBH^W00IKh}a+HvEKWdZIzXI|ud_%o}=L$cp zy0Zk{m0hwOorQn(Au;M?O1NKIO9^OYTIMS4H{RaNXxSO9E5NgwpiENp=*D zP?<&9_jAvt0d}4NJygn9rZ8i_k?5~PksOGzFUPY+oBrwh9HBp~Vr3zG5+xe+tqcgu zSIN&%QbP|`yg!`n03WZ3lIUYTIDX{?O$%30#vfaYnOa|ahMj)hmH{RYi@r8Bu|U$61A98us5ImyG5P?ngbGp4!+3h->H7rN4~c4LbZFCuyGkQtZHkcO!OKC1L}(vGjr-!QGB9iSd&{ z$e*;vqs^?V)bAvgvCi4NF0~h=h>Ch7Q%2cO>XYv#MJ19A&!e+#n0M7~j_A{pQD3FCIJ*_)AFx7GGmWzyBj|#LM?p`VH5PF>so3<)eU> z@#=L>5fv4eh7lPhqoUIvhF8W+#XS{7beKJ?-?7klMR@_AU;6O>XbZ@f)8r8R-;&?8 zkBD1ZT`EOaPJ}!gB)`vuw-SYW4J1Ym)=$U0Fqe-j+^azzTSly3smSOq!HCiH9Y!GK zQ&CsaGbw3meg*jEBFDRas#kY-h1xxP_5NSr2|kEt_@^NI2`#2);<|r<#N+!J(mKq# zeaxkhe1*?8rI-;jHAA|H^SCkZK$D3(6~&X)wl-nNBQ$l2D49>b z>v}D=?BW)G3H#iKIyxrJ96pPJWy@FeAzNl^xB~oc(5Cq4%wt&oi+=<*6$cS0TO@=0 zlgA(H-gak?cu+Gv2dR5y`l~u4HH6L(h;KCjw?cj@ZhJ@m!&S~znNz* zXQFFp2F#)g_cAzV7A|XRSb#jBGF;AJ+3`r3J0vU%f`MERsbJ z@jhw$q(sye3b-iMV}gM!iZ{Vee-n^z)l8I8p96`z==#x zrNb6=ZARt_ityD>o@sgR>_59NiyNzbSkyWE z4dB?9Ua6afjxD@|J%$#h0(9)UDOU0t-;idJDyONpONf6Pj3dT8_3WIhuTd6R_luOb z=8OdlVEv64@i_?b0G|o$dqpNqBI9%){DB5%^f^`&a3kgZ42da%Yi|kb&)TbXtU?wa zq-QwHM%aWC*e4QBeS2(AcTizkbXyk_+x9e1hb|p}pcSlTXfZtu&FAkCprQgYeCs*~ ztuLz;l?i#Q_g^&ud6rR*h3m)?u%ESlV$mCK&P~a*25EqMs&=Z9Y!9+B_C_Qp-5)%0 z^&V)9D8wsj1umcAQ~0&XM6eP7Pk`s@Ca0~Ufpp02XHfDvY9D&B{gBp)l8GeB~toiZxPx88q8QI zU}OWVtO=U^lOZJ3?y;Y!$EFx#Lr|%hQ9W0he?0fs<2Ck-JcjRHKfavZI=RT z3<1+19KeO5hsxl_b*PgI?AZkZf`wI43 zcouI1nHQp0juZ3yee<=&nz5k@d%)OPquaVwjSa6muZlC{GOiyfw`Id$I-)sI1-T1Vs8 z#7$12(un#}o)(s}rg5Lnzm_BuX!m-U{T=9uX#}UtHdnO9Dm%Ul%X&1N%yQUoye9j` zW7dl*g!zFt8<3sv6EV_bt)6$<0YP3I6X1s6lI9N|GtudDWUd`*DSuLqnBi5?t zXFbPHZjCwF%qcu!0cm&oE_f4rmSRhPogWK}hbSHB^No7N0+vntHW=f|eu%x0-Z4-! z8)5zVHgLa_RQ4_q=YSs~BH--Wha>mDJmEU(L!Y>CxVZ4M9)O6Y@nd9wJ_B)Uc=qO^ z9d{#87Ue@~1rUf|j_LlMG+HJOd4fRd!pl#5h3W0`8S`CwRCh($SOsuTC~NJvhdwvh zUL8@k(vsaGL?QIoT4Pzkzv9DYmv}8?v&hri=pZ_2VVXyz+K*r73>&}X1YTa~yHqQ1 z8Xc7p*h+lD+Y)zng_iy9=@3*xoZM@PM=5a<$^1)R6-X&8bAn*5Duo{{k47)4pC5r< z%@O`Y(Cmi%{O8!xC)S$Ycn#ZoHloSkaz5(yIHDIt2e*oxkNR9IhF_GT2&IFy1P{mw zS+^>Af>L989!+gNA9b}Uael0vA*j3N^nOg^C%zvJ5`r#I6~m+5t6zX7)w^)gn=0_4 ztDOho+3$sD92+c&Ula)zxGE@ByeCOIBAbp8h??4+ywFoKc;bnwZMVRx7~*Byw1unS zdv1IM9Itw=9@h^e7^sX#{LtAvycR|;i#v}4b|MU)4{6<8S@~nhPrPm%-e%3tnmp-7 zX~ozC(vpiaF{r8LBA$u3>nz%+v`et4=lqFb=f5|w;YWPb@Df&8iRifONqN-Cc}j51 zM?*wSm!iJ!uIi2|+@zjpDo|VW(!H`*-}c(q*FO1if@{H<&*-?2SlaWvj7!|LlVxOB zP$Tj=yy-PSO(O503VKnupA9coVeKLH;Z(qCDt3Yv6JwM8qATX6T61N*NtmY}$5(t< zZ&k0bBinfO<~CoJcYjy@cH{8Bn&4)zgeHIpEShrmtJevyogyrOU-8{@-- z#z$w5=&&aYVNDWoFf~Ud{()Y6nO09fR;l%1guv#x(Tu+V2`uP@_X zDbC1@S-wxM76@$Zr&}BGug#gMsLOGd$@$NXNpZAiea+^bLlFk=8HasA1v~;nmv}GH z8wBTGCgygfU04> zrwgvyGZrMUt3?7j8oCsWpxCw7mkXshbToT+XJKPlybB9ry7({9k?G|PQRxAu|LW0| z7IBnVff|+_36K$Q4xN45x>+%;hg%bjz{zW#Z%rR9ce;xg6@>A+u~Vx0^Han;fO$Tk z5AuJ`PkC-7@r7p&-*c=>?Fi@39tdqM61rDY-?+(R3eaFg?(u{q>r)eRE2zM}?rW#2F6sQT22`Ox;+ zzdj&c-FpuIetymSpq>POMzX`v1T&!eX?S=qbtKx@r|`F6ihXN(_E>WzbFb{}CaJ_n zmI1d=C%2xHa@@$kkjh~89K6=Umn(c1|MH?;fF+vy&TlXdBnRtQ_*Ojp%8I9HCz;L? z_3rBqTNPRgJP5t0WmGQ;QmPLC5I){3QD>f$+5z99a5a*1q2+)K5$-4AggImiL;b76c+t8>IFHmQ9LbiFg239hd1YkZhv@a0C` zw{i}t(GGtqt}db{4AHS;QfuTTmQX4~zl4zG5CO*$@C*ohuR_eKqJpVV%=7hk&_EI9 zPy#)yLsIbL#RTAM_OY3|WA0dO$@?=8V|Fdj?NHn|@Qvru57)atKHDF5(mQ{BDgK_{ zlR0M5WX!#@U%vH%Esfuid9UK3UANl&Cvm-LinXnW92>=`ozLZ=N=bd$3vNPohicRv z^SQ85a9h{~pbXcU3&GxO@rR3h;?#492c-8Or^$YbMK(nJ0vpx!yrx)rJiNo1HuM(# zQY`IcPmQ|UODM)5l)!vk4tf>Ww=6K`?yIz_M84v1{Xn2(qj3+={Ejlzq3Hn=1@IB- zG^a5|=%Q!>r}z64Y3342+1CfYM^}fHhbP~rOMPO+Z)%uE+phauOux$xYkfDgpC9;G z$)$gmsMsBs@FOf3(Dswqp8=PI%&Di_kstMOvMc7_bLXJrWxFX2%!v$;{akXB$FEJh zs{DC>7!&k#oQA#37ge5M;KwLd{kSoB4PO%*L}sIZt4f&lmbBdE4Nx%MEt=K{2@?eF zrsp@emX5g5qp7g%CTJI`s@=UQBe6ncIoV=G?cEg`T6x0@TO>Uye4EZxD*Pgt*t5T{ zQ_~9nnvb+9=w+`BVx(ILwH7zQ+&%F|$C+hyf=5{*ks8R@-pk3T{+nJifT0Fih_(`B@{_9sJT$$^1*q0gmnNHq6O&KU>H8g zS;kvQC~mgWU9G}6GSeX824oERmyAV+{)A18TYK+;^xBk38w!HcdT!f-_TzdPD(~bW z)rM`0pG`6@Yra|+k|{1z(m}fD*!+L%Aa$0%xEdRyCbo;Q^cGH`0SA>!UL9J z>gUjB@%;gC=c}t#SCb0sRCZZ4wM_+o0*y!;yH zw@tOoUI8s8Y;@oW2hm*O`^I3=bS#3mC$KTsf*>SWlNES3%>O96OtVNI3}8MSPO=&&&+Q7jgrtdvgYZ`;e{7h+MvSHT3~Jl14Gh!1 zp9|;kUrZ1)@Cf3joNT)B4&Ws%TEI>=6%2xVv2-Yk@#i$9jwfd}7S^!q8a+u|;JRDP zB?)drmv!cFrqilg8p|?e@O>aU1V3{6`cjeu#VhS^Q;SYcU%Cqj-Yht=Rx;9jv*1Ny zi;Pi#Q!KW91-0EZ`N2>>H>H{2(ICv)?pmkv+zn3G@s9bkC+kf|(sC|rWK&$9OjMnvmnrer6WE`XOCDoL|D-Vw_{tZdem z>Yk4Sapj8Rb8!aoR*NkE6E)4qy>aTZ0ln?`GEjjo zw_i=n8>gyKC<*>>B6$aGe94evxCv{AKmRed|2GpRXWtYrbATsI@HL5{AsMo|hXahzhCc)QdH74{U zq)rB3jUzRzL|?)}t*GY}olS+UymDq_G6Hwk2d|wkGM?n7u2p{$yELQ!A;Ueuvvsv7 zSVCyVIZtr&-P}~mbK!PJy(d7c8*UGL*CW|!^t&w<#Qh6v^FVLSjkqJ^TXt<|d`+1% zc4|eMzz7c^EP?|F_-65o6+TlH@yYYw2|_U;^1!l4){@#`up^~lsBrb@#eXQpczi+~ zAOdj!yueS9=hoi+xZEIJvAY}h53&F<987{O#cYVU zD-ddc8t};ARS5vqM**PZJkAEqe2oP!ywb~v1?vM36p(mJljJ_{cB}eH#~X8FmGJ=M zE6Q(Gmju01VjnL{xXZAsyN;NxY$&O`rxPCckE?3&ZR&gNeL5uS4P;fY2~K( zm4gxEDGRbEpsNr!A@|(4tpwsd{=-_7#$~sOSeN#x37HhzCWT61&MI@mYWR-m+h{fe zMb`{A#JR4Bs(7oFD>jXzX8U+0zRzM0TJnGx%PnABn|2`V>RQP|WO zRYEfJy7nfDY8Du82d+@~(iRO1puZ)mA@A0syRvCiHlhm{H|nm#v4tPhnflblTFK7rR{Dyhf%GBGuutax(jUXC zNt{*o)Zxc^O$>AYg z;RrsiKk8;&TGe!|zeA}ZP-`=BPHr-l5A~}3(F09&FYegAprKL8gOETyKTbizTxMaR zlbWppE7bZ<1w=;6u~73Zs$&~{4)@d<5%-uD0nl$Gr-2=ytqaUs!Bv6Mv7`x$lbdN6$G|d-|ppLkkuD$h)r zZ_K1YriKk~%ijx6_JTdIG#TfpU%?nT;vRaifT_j|;W`O$@OZfxuT~f}zuIbE>`oHR z(ts;^-M+rvzZZQhyfk3Gp3`wlDiJGbG}HQM5_f^-pc^0l)<;F?B-p$ioiUsR{f33J z{@G|K;*$dN{WFtv&%hH~oiIJQZ$Xwo5(!A2H_AF$Z`X9zp8QrtUEtoyi))vQvj1@m z3&8H*+XdId=eHM5IUnWxl<{u4zRF&P)aTCS=LNZzf0vcII)_JKORNpjymngo{z>{~ zPO5|Ve3WzGtjkTyJqkP`MSvX|tSG`$|0_8p%Lq~{#NNv(W_`Ni;2&yJE)q0rqOL>7 zE&vWqH(oDwONn4>da1){>NCAz7Clk3{oE>H+p}sMOGb1ev@fPJA3FQ8bj8bnQ16EQ zG_iaXVH*K)H=96{H2Q9$5eC`F>0pQ{uD_aEZ_S28O+Z8@!rHA4Td0l&L~Iuq00u~w zDtoOUxrp16TMA2Iy`GME>r=w$i<1n_V>Gmr7HvzE>~u$;8_p30yfU>WqvX}ks1nTn zN0ACX9acQ%`|n*;FpEc4=yJnf!(0~-wQfl_6bOpMkaT#tA`^dbzdMdj^xlusq z@I(KyS9Kl0L+R>9?jPWUSvzPyE*eN+-jWeLIaM1|q@j3at6BzzR7eS(a1`yU4ZQEE zTJgZ9;?vSGkRXS?C)O@r1tOAIiEDNIfOpBmsFkg5_UpmhU0pmxZU$YXxX+%);Z<$W zN2@m9x@g99M{lpWDgI-ml4N_tw@e@*00*w+ONH$8ts%HFK-sono~z|kRTkB~oIL6{A+Q9Z8|57MRn*7?L|dlqr%G=6d2JB+m24wcMRWAJ5Ima>Bii89(g_m z)qbcaRs>vQBGJ8&YgDd|x+`^;?*KW!I=L+$N?tX{=gKqT714SBS`ezmG`+|l($#$r znhKL9hunc6Au^q*k*;qDd>YelW*37YysKx~w5!t%`r%Ou%=+ddt0JmTeqp7%;6?+~ za?Z~D&+zwf#stwz!~KeMz^-2kDc~YvPLCIxOoGe|0?-*k=knY$3_d(Y5lfc~0|W1Z zO#@szJ70Fr!+pJWqosV+5_8{^x%1xF4jM$cbw1Yd&@fk83mqdLJC9#cwGTp0*k!JM zJ&Ny~F<6-Fxp5p2Dcr_DqAC9iSY;I>_+)d-mKykEph!#QC`VKK=0QyqPrAx=JSkkH zMZO;dd9uKaWj`M%@=c^7<j+RL9k^^|fMfX5+&cUst`N%l6Spd~x5+R0?GzsBDvT`G`~&yq6HP-x!y^OwC6sBtEggr5AMlgr2PDc|5|b#@cWV6^WUgjm z9ON}e^Y#nh9Qq&Z<`u?2|J0d6w{f9wbF;L#tazG5q(^kg{JrPcHrgUa>y{U9SiTcOL+C1Vp*g zPdw(@ED)L6`W3$n2%>G^?y~E)J$LNo!_mAuGKu6zYykZ9Y6{|HqFwa^H9Otg77aiP zf`~Bw`!YfhatN;O`+TRhsa5TlN#AR}rh;iaS86?>`v|i=k=7lC;wn+T?(RtxBcriJ zzm44FdEBNGJ*B;-lXJ&LPtPReBqY9$5sZ57HX#uDS01vjdxR9Gl}xOT z=eIo5_ohb4EliIKV|f+^%$=sxJZW;-555vmO-FXp{d8Af@)VNPcRR_u%DQf)gOpNv z=Li~X5LxFv$x63MJl$~mq$2s!5Y$TK@rrbLeVq(I4x8__# zoBTBzN#AGU(Up>2!6H0(BnD?+$cs1Ak7(OerkmrVUNjs9U)Uf8#=+Y=yPuu4r(e2jluG@<% z?nmUC<=!u;d?+FTR%x6hUF_J)lzRoH3H7MvXm+V*BgkD?#_fCONytrx22BrvScZ9Y zk~g;vu`+mdJ9mL-qxGdkCvXoOX*#;yBfFPqE<2g*QXcE%F2Pza&(|Pk*t7~VkpXAv6=dnfQf(%tcDm^uFwKOV>Zs?kzuPi-A$P4dPNF~r z-&K`vx=;h}@o|TqCynBXKY}#kF>gi~iDxxykiUJF>8uz_{eSu2wV76uNF=1a9a!bY zN&F?Y5@|)pxdg)w8kfM;xCeEGijUurW$4QpVJ=FLjY}k74t_uhQxEf)?_X>Bm8G8K zny^E^c{WZ1TDnpp`}y6P5e#VmGoG?lud_8Y*FkSGKjmSqNNi(3~YyL)(WqslEJmqrB z9UKU%e!FyQ5-_A)kCkto(Trqa=^L^EX@pSFs%1Z-Lx1|Iu|eZLDe-jncnsd1S@xNlT^gNQP{5WYEyWdYiSk>-mO3-OKCuPb1mc z<(V&3HLF6sH`?A^V!u2akKI+huoXH_xa=G3C#{PeBAhXXht{ zXG-(FPa zL$wY4g$P7Kb^_dVzP)p}3i83+LTSd_OBn{`F=N)g)X3+y4mo+VoA6E*wcU*3_@w#N z)0a|bd2%v${sq5WUcR)fBr*NwMb0DCaT~fPef~lWj9lp&B7Mh?K%Wu{=r22FiFp_LNj70-n-dJHS6AC-@#hfK;~EKGj68~MxtzTKfBu;B9`JRwDE=hLf6 zE$l?1gEC!@E36CmN~N0lAKeXyInIsQDs_{^SWyp6emH~Tdl;uKTh@wdH?^xB8m?^P zcb<>sIF5Xe(bDS7fERbltD3XUj|kW8aLD5G;nQ%QvmEN{H(%^Q zbDe$jn5>nFN|I~5cGVI2`ql?ucA$lmYgWi!Q+E$Q1{+~NWxYiGh^h|gOq9H#(*{AF zm-2OEPyA>OP@x1Q-?9N6OsKBYHLK$lR}Psm;otN6`F;yiY^<$S1A|!4I^b&h-GJ4+ zH7Zy|&=EK5nl|}V;*P%OFHD>dWS}fX{HYIQENd|7fLH4JkdGW0{MjNKSkH1s>bmnp z&g%U#yjeC+vAsJdL~Hfq7~uXTba;%{oxV=B`>XdhQ9&Qh2II-3yywi+z)afXj{2Rr zr*SIIwl)3N>Wwh0WlXg99Q6AeDj?s`y@S=dPYVVuc!t(O#aa8@9#c|ZlgaNzsn`8ji2oF__MH=T~p!ug? zcx^ap{TII&E6u$7plWK* z)Kt?E@|c?|a!ITqL}%VC-TaMDC=FP}C~?nvOV0aQCpmUc{?6sM-D-*0SbFjtFBfz$ zWSs!CL~-PG7^zIqi2?FB?XpuRg1_FYjM*8;#D8vvea(YU1I7(a*%MgyBro$CQvPjU zS>)j}qN+5fV_tQvhZtu%8{Dxq;&QR_%529eN%?W zea?)@Glr($jO}dmcI8a~T)<^bJ(#I*sE3dc@H>on-sBK2VHV@dsBIV<5Mvo)FMwTq zem3^#T~#-s0MFDI8?b>PO|bA3>hhV$Bf9g`V$tR=%fS~khA~!HV2GJWb_H_y8}Tfk zWS*6Eq0Y-X!_^H>)Rdjipnb%svR-7m8buq){#;?Wc zyugql*nVXrMWEV=-QV(g7+oJOG9Qp)9agar^@!!$8katJ=9PH?l z%SNsQXQguvVM^0y8(c3`(4uj@oxacSflB3^s|1*g@@a;2sjif0d!QY&*in04b!zpj z568^9@jHC{Pzc|YB?-&s)EZ^y37Ape_1AWjPHS4i;d7BU%Mmjyo4vbq{HJYx+2vgOA9=j{Xh)q#_Zf(bOK+#gRb4Dv*A zswd((G`9sqcgHvyzekfU(;Xj>HNOiLeJ9PdiM}i%b8VY`oJDoUPee0PV81$u`+5;l zQ-Kecj=2+Z6P{*hbbGT#cw1gZ>2D2*F;+x9JNdFOG9HcNt@`}jE~MX8$V+uigQKQt z>3Bv~>-j7P9w)vSC+J=mn{j%+q_JBvE4J}W8hKQ(H4;neYUNX>W9eQSB(w3mGIT zwV|=8=8!2RWThYG5co|g%yww1EDbLYRZU2p>bZ!5A>JSLx5v>wPC$#)&!aF)|<%DkHwuGno!8~UvPp}^V z(CtgO$4Nofy!Y39*OdEV=idP|3P3$sB6xV=fBo+^-loLjPZpn(sYxbZ{t%Uw_AUdU zAn4V2g9;JcmOC(iFE&_R{5PUIk(b2DpOJa_0qH-1mb~T^6goOdTl*5_$1TtTgY{W| zuW*AcXMmk-fwIE`!q1m9-{*5=75@RWZyzkXcV%1CEIDc=J#Qz?5R3oUM!tCt>1505 zw`}}uIIhAZ(Xwt=v~WyX?&ffdV1=1-K_<^^;|}TvTwo*XTt;^9P3BqGfP#O;U1 z&Wp#ZGyh5XKuT9RfrhtS_tw*y@3K}!EoUs{jTBNvX5%~nblkEl-$^<2t?hI9ifP|I zO0XBdnucWgBcYOsR#_V7kCyVZKqo`c`B=uZoiwMRFY`c8etk#q%-*gfF#5<;1eS}P zaecIPRgIeN%i`ig=e4@bVM8vwQ`h3US~S-5#8t%n#I9~np2FwQ{nN}9aABqpkqUJD zCa2SV_k>*#DPo8QSNINhqwcZb@Qg(XQlo-hZU060K+AL~bm6L6^!Qx``-T`|quEY# z<_T1P#Lg2|k#qxUR(47+n&+ECD3?Vq4?W8otK@TjIE8SdhG_<^fEf(4SRJnRu1wRhr0IA zada-_cje7rj;DC0b6q(+YktztL3N*!+qU`s_I1=4^`4mBxQ^rN87f>cyOWw(gPOYz ztDOUwy?gd!yHKe zU*&6vslP(VW}1MpDs`xWgt}MgfPl=?3J!1|!l&CNbhVDuGfFYrZoKMAb2uQhdTqM& zyiCB7i!veODcs*@CV304{W?4A`j?S%*$gIoV}6pPQf_OlHb0lB;KSu$ppQnncL0&K04gB zeq8^8@IU3s@)?+PxZ;!9DtnYAIU_xxTvh$$}6oKBOJ$BikuVk36?cFSyq zY=m$$yW=E$JZgew48MG__5ApA7(w{?dlKckxs>x^N50hPuzj!jxgO&1^DpP$N9->t~N*BHC_C4l^%`NL+bFfpE7k$IE7tZ+T4Oi zo@gUK=%T>&Tv}rW#y{)y?fU+S_Tck$-8TQ+U$5-G{ZZ=|!5(LyXLe0vz0TV=nx;sdt^VYpZ3!v)TT}7~i9iYuLio z(u4~>NM`=52^LGTb9O=HVU6JOqK-jyGKsnW()CK3P>*|x=UhMmx) z$r-eg-Unw1(kJK_rdGrWn9zm?$EvxMJsoC)f9E{EIEnKKbQw zt$kk$rz}`rogpXF97leYgaukiubdQ`Wk8>xTNwG@M5x5${_-f`DQuPeYl8GZhL)Vy z)4L(VEZBmrtB9E&FM1NaZaOrT;foSZKYAOs4pZMVZz8Gp^*v@?adfd@B`r?yf46mJ zMf5+~>k^vIpaxmKIx&~8>7#@1$U>BJ$G$Tto22nEq!5;L_1LbpQ&TVAa?Lam#+>kT zzYqTT3MR`&8CLQ}ZT3Z2^rI_wx&KrBE}=0fNRnhH#~5A@NG!#pI|&SQCBEXqB!@j4nQR^LBlup zv@5>hyo%*dA4S==$%8cZ-E$%6w1s5nRu_p5`q2=IW`**@ix6f;{2)WI_0bW4b?na&F1&xG z{?o}Y)-ehQF<@ELCX)kPFWE7?wQuyr-FqhSeaFq9n5cKJ0xwm5`^1R6W*^kv;;1YJ zLclyj;5|=;ZbkerFOds*^VlN)O+93lc?NnmYpVWYOpduhlk7G?h3+_}7;_0_Ko|!L zHG1X`1q;P0-Tx_gk_Gg>lxJK9?yZf1Wb!Gqql2(sAKqRBp;aQi!%^xLHt}8+!SbTb zcJS8s~E0;3{A>~h;Vs~ zTrG3?0&83!yLxxrUOHdDY)AcstmnCi6uI~8rsG-fbpiuvnM%Y>$|+mH_8d4I@gP`Q zj7u=;mnf>tlMl>x8|j?5leG*bGw@X1<`1-7C4}XzgkuhV#XV7u4iQ!(+5E2==}$wH zq#gbgXf07&^!k1Crrv=0WK5i=4nlE26D9BHMsa!DsGJ$GoJ2huI&y^{FU zRhU6Wo`&_Qby(kVHv_4K*$$piO(I#F+#Y=fN8%fgD>@>IHx$0Xn&Z&4*elc9#8uq= z`h;&DhoxsH4#?CF$T}R*5Ajs|1$i}CctJ~* zL2^cE6@c#=JyZJN*dL;xXs?33g%9aVW1e|**qj%TC>DP@K@niK04WI#5&Yj9f=Sp{ zj8~iv_2IB9A5xIu7!-G3unSrbf|g9;T}6roSC}#AkPY1hR%`cI1$& z%n!Kr*ihCeFyt(f>%r?hO+0-Gc=#Ao-e8EnITy)_^xX?P` z%zVWOL`t0GF*F@k@3v44_BS1*cP+Y8V!g-{=*^#1QGUG^(yZoft0KNnWz}euL42!rdLP~{x9l~}dApLZ z5c%8VH4!K(E1&A`C4P=4A3k#lCSw{%B9k;5T4YF+?}cr<5fq}r>VV%YxWs*d?^}M#D{KG@-UyzO0hWhfEuqZDvlUTwhgC@1z+<{zM|i zl$JXTQ;`YJ{zX0uo=g_MJ)FZhsLUF_OfLZXmS97s(s9xbrh`i=?LWs57)H)6T|B25 zu^1ot{J8?lpJd1j-T#y^z4A6|GREScH~kEqiI=-E_;8<>^j^V$<QwLVvvV;2j zCA#*5hENaV;PG4#q<~UV)UC3dZLc6ArPYi$WdxCN$vPPQVeuq&(4>4GG1f8=DtO9j0dII zk*+f!2hRt$N@p^Yu}^wbmV-`yisn-MNQ%1JF6E7s80B1=={>QxtRQ5Pl+Se5Tp6yf ztRJnv=s;N9rWxg>%q`RsIk`WJ=A(zs9_$+<6*+L-5jcT|Sv| z4L{)1{JJB&7%owBwNUN$+xV}B?CQ|2>In0^Wk|PW)l6 zFd9;S=zO52~Z9Y)?_&@m3!dOxJRThKT!HTR2%Q7_|eqjCp1nIGJ!eJkB_ zuOeZmGJh@mUm#zF{Hb~-QOaZ5`JEw= zK{*gjpWAW@?Jq9F{?y8|U~kOC@$kB1;<5R7FgMHS@6>QpXE-27&IeOzx%)q9K|^_m zl6K!9MDrgT!MVeA{GP`YneNP+p*A?t8-J*^H{c(I2^04Y-T*tMeo-&xOwyZ7X(0{w zAGv1F%whTJ%72h8r3Ah^%kSpRZzTOUWLpE(rLS0T(OO*V`m6b8A2{RMgu~y8XKKGk zJo(A_z3(Ol$3$VfZ|-6ye!k00GN2In58LiHyUf4&DmoUJBpS;}*+$4+ zMS*y@sTEI)`%kmnfEh{g&qZxkChmcr%-l9^-6sEMfad&2ha-^ZY%4Yx$M5Duq-P-* zeZdp*FIl8UIB!oTt($XAy5rw~Q9D%IVghLJ+-9=>@51dLFv(ujw?8 zyudn~PB!X1+W|M*`&x+{6WL_(>HpI*C0u*>d?ej`wPsVdgzk3>gX@ ze#d~upO!${;N@3CNR%TMR}0r|OW>LJ3MEs=-TV(`KnZH!`v#_H=uaky*r*K9!8k7TPC89ur$~$(J`B2&X0AO`d*1t#h+6fHy$p$Fus`oDJv_N&?No`Zn3BYonr4+rW>~TqZAm4}ieq0Y7o2 zMQinBbs+lRKCk||=_&sC*WQ3v@LwWiGEFiwDz}*PjR>ng!?Q)(Fit7tjl}JnqW=%A zGZ`?Zw%9ZQfw#}2zZ>EHl8N*_my)n32xtWYDv+(YFfRwTQQyQ)c>lIpbR$sAaVJ^^ z;!scyh49Y*pVmCSh{uN7=3~N#8L5zqTTI0L3&bQcisHYwDSqk1EU$t6GfaY0DB{e^`b}y3 zT@h$Z;6n$3SIA~ex^NG;edKi%)7RY2_A9<6sa010)iIH+ z0UfuM)3Fx*Pm6rG&Nrf;N^XP5CI|Yt4J8w_o09)no{ph0f_~#W7E@}#*0lqWD_Ub2 z5|hu($UfVn=qG<3Th(~_Y!NF_>7WJV%Y;NY^h<>S&tVoOLwM0Wfk2>E%b)3Q`f{ zUZSV-maH>krD% zHhF(B?v5JCuhPYil8?Z6;v8UC@Qlm6tfbx~m1d*=5tF#j9+$(&{n;E%QgNS}w?(?$ z83DMoB($*RU`_0Fn*^~>zBuHm55rDwj&unP*vUdz{C=x z8O2_HRAf#6*KT1mish?=`ctkdxm!Dmm+UcF43OR0kz6P*P0G6g{D+*>@M^q<03fH0 zzAsv&z9sWz?nQFOp>7JKK)>$8b@o+)`ARJR(A zNw$YjilTWPl>j5${jH-I9{QW9)}L9I62OAG8L_dja{qf!=0vQ1aFzs##ixk)VnZ=y zhawLLLgU*(rxFs_XS=)mV$8Yw^_$%{i2jJ>9(B5&h$AA~HfIFk$CfY_rSq zO(2V42Q3Oe-L+Js;q6J#c)i7AHipfMaw52O->pYy_bmVQJnVrYLOMTXTq}H=8^h z3c=!=ACv9su!@D<#8{xvQ7O+F*2GmjgD=nXRfGTwUhL+>*r8anHDfxaUf(s@uH4Ctam$Cw&bILf|3Hby5O{&u`& zmL3Ybm>)>smPt*j0=q}+$mg2mj=@{k^6@RfL%__E^3T-sh7SkKEIq$uzIp-t6mYur z(8iG$6!Ie;`9|6?NJ^z{bD^O-Y#E-XvprA+5UR#@WFKtkM;61n_iE$%#i352r+T#o zvP$T?Ab~ch$zKUHmIFgb_u6-&P6GRcj|%-DXAl|!v1)QbJ=13+oZT0fO@EbvotB-| zEO4U(X21@BN|MCK`Hv!*@BQKBh1S3PeK=c)7Q@}MN=1zWHjX$FPqSR~&vp8c=}sBL zICFoDk=?mAj?<{~ZG}evTZShos99cDkE2|nKHU^z%#(=y$kVbPy$7`3(vG$51Do{B z_x{8mFbw`L0V1YEe)tX;%E{csPCd%XDspZPxzi;1(;{#|(6H^m4zGDu>%oV&Zpe11bRGl2Anv_bYIk!JjY}W!YkyL^ zo6&xl_umu_jJuJ8L}7lv$JToQ+I?Y8rJ55Wlv|ZJ!N~70?=5S+V2e_05A=cdW>vV` zJognFP>qy0nIZ1{NvH0=hzOhr-~FPTZ_5xf1X{oUi?KHkhq8VD#|zmCiR`-)*|P7W z2rWv|lSG7U*|RT0_MN0GStb;EvhT7LLbl10-Hd(gV;h5E=64OfpLfsa^F6-5?>`QQ zW6XWu=XKrZdA?rf>pU-__c#u$@0xNTa*3@RQ(ye4J7NFhiOF?MPXG6^;v^x15*oyP zce>>~x?3*F7$$xz`vgo}eV7#Dk1eGFhb{#E5x4G4c_!FbYN~)|I^rj85_j+)`xT*q;8&-oy6F&;mAFZob#xn+3_{6Kl4jQoN;Q2gTFKAf%}Xc}=P zN%a$0)d2@2cAFhhh~t&*97Z6x_n|e8%RioeYW4y)#>##HjT}^Y_0<%e7#q81!3M>dOkg$ zn2l^YxnXHuKPm?GgJIAS41$$er(}%TMDExAR7;sShFAcs?{ugeQ(B8YZ9jZo4!O(; z?Wl3b?bmXbv~ha%jUXsY{LbH&5*;%bfcgIt-C>j8uI{hv=87;rnCf$&6cS6hxiR)VGK zg!H)AA%}}5bqVm=ajq~O3vZi|wJg6z^*GGk#)apG8M#k9-`1m@?l{r-ZlU~vgWlmDs?;9t%lX*>0{5HNn9s0z5%d{UXV?YpyM`3^x2>uX zsG&GVhczm6OtE|nB&7gHX9%hf%&_}9f0L;);2tN88zQ{)5i6^eNL~afyz#seq$3G( z&@{-q7Be7}k&tBMA6D;p&To;%1A~++s$Dis?di?UKtZ>gkv?+cxMYuYiQM7h)Pv;= z#jiU=?E?B_@P>AxO%9OFXgnl>1dz?2?y<*zHglwEiTT0ZEF5HPey6QvXO#T_nc4qq^|i0VD2VjA<yM< zJI(G)80m9|AL>^<#)b^N_s-XkSaC1eS1m&jk_`Hr-U1?=N{|aN?EoKR9~!y2EgMc> z^>wK1zKeLMwB`^u@&fmyfSZ1OcPY8Q2Rm>0louVI&_jJNH*_jCd*ha}hsyrzJKWbA zcVBl7lb2}A5x37ws;SEv?2gJTi(#11?OGK$i|~or4PJVNAPTxlzeg`p<_hA@hGx&d z8>^jMGF+8t37E{yjxncI!n^v^Pi(b(Mg)IMT1ML$Jg+&Q-+swAAcUzR{Y*0|t-p42 zaIwU-zj#7J1>>Em!BiIL^Yi^RgxPHo8zr4#P2V3g>RkTw8aw#!Ub|*f8a{Pl#=dR) z)Xq8-7sqrOHX>+d9*?;v)vuJm8xBR;Wmi8Hc{%#-=+)k7L*4pZuvh?!{*~D;y|+5r zOj?2I0`bZ0XC!Ggzs4Ts;rdwhdk82nNpp*w1h z@RMa=H(UWSvxHDeYM=Q4NsU887@^KT^)BaIw%_@w~i4&H0BLiRslZL?+5X$iv=J zrxh%Z73xzzUa0hw5Vv-3T8*z5#ru!&9gG-l3=Mlar7xR|9sl~A0xzrmCi{Z><6V%L z8)UX`n_Ry%?YBxJTauMwQy8w3BPjU<7E05<5dmwJZhCE>lSj)cS(~mHeQ$)YK4Wf^ zV^JIB%?7oTqRALxv@>xVgI11%w+FeuBfcWYlf5r}MS&@^ow0w9`EkoWsdG@CmA6Eh zSaL`A0#H&)KE7La)H{`apsJ&0kG6b;j~@JCD8`dEEM#s^T%qy?c{-iJ4z@ z_@o|I%U08_2N9U_^GsYfs2-B>%u}I~Mue9g%2QUmOl3R!p~&rrhCa|`=7X0-8&WoX zUb1ts6syis8D3I}N6UvoG|8$#7>Scg2O|TI)vF(@jDo6cfH2VQ(Cj zDHSR-;Of3Pdb&u^dG-FKMdDfTYe;Q)#_w_Gwa1?iKnvtrrzLK;X5OWSKDatS3Rez z>^(-txU@#=%*Fe)o86f)0~U%Ohv(hGgh8CF@;V<9d(O-EMlg5(VojU#1W!xx%+|u; zX~LDUKzqf(=P$22TR6u?#L>x%l#% zoZZmUvnj4FsYq`r^zL3kUM|DSMVvBSvkcJCYFuI;;>E-nPS0F(>3j2-vEW#%h1sqk%Rwv@dJ!uj#P%m z$K0Pfs`~Ph7x$aq4}xBVE8b_osUv@5#Y+1%4rTPv+>_inejVn?YTU7RzqH?F!bFpF z*xqOuZQ8aECgpD+-p*oHMv7LSmWUs-O6cO|)O5Ut0_$I7M&~j4&&8KXrFyg4 z?lI>vY#QnROZs}mlS*9~MEUh4GaQq#KNSii_wPQ>POIx*b;R_Fbgh$cLjL5|M6MS@CC66 ze_w3H^*hTZGIqaJ#zdl63pIHimheG>0+7G|k3i+kYI2G|yL$VARh1Z%De8KY*m+Yc z&8EV$reM%x;W^XX>rFY~#R+_-LlMO@{HAh|#pZmbRpG_i;Acc}Gry@vc(FS88FA&I zES+-^Whw_%txm88vZTQzc=bt1+2Je7v9fdIT=b^38ckfxrnT3bG8j#jG@CN$O~`Rtl+3@h>39o`tm@!6%{7!KpJ z>%K8Oq%ph{U#xjGT~UqA<%E|rtIejI9^32p|Hgy{ukv6bG+xC1He$P3WGN?5m`9G@ zBUHyhZ1Muje2oDT0U-s@@&6|P4}#EV;x~e%mffCTHt1+yX4|zfj%MezN0$Ox9>Rv{ zHHL7%ZRPlBy>G#L2hXPXKQK5DWFPG>u#jy;x9IlqkBY!rwk~%!+J)4n>tsxh39&~r z(?u3(iK3%Z+@EcfZn{%7It2sFWn#KtnNg8Plo6Yp4~Nrk5s|fYXMIcjt6jv$wfMOS zkyd!)WC0)oT{C@++SRkiv9GcTf}Khhbr_7{a6*Sb#FItz}6 z{-*oocJl^Z3W(}F_9ZnHMqTcszM&3x?M#)($S%QJKf$GJeYVGEhL*#LwWV_BnSa+K z00V7#^sn0>=KoZj7X<%0vMBsq{jlGl`WxzXUow5+5dF~B&}HkH)_)|&wQ<`ia;GPZ znIi)MzpPtn-f`DNmwgG=Ljog<_T#%k2i}0+HKxC19YX13Ky6!LuJu-0{jpOwa`5!$*_JyV7%ZW(O`EH4pgH~6% zT{tzxKNsW1_FkfqaVmq)e30Nj@(D!oZ2WNbr>|RU?MXzj=75F_cegZEOuNXy-J}6f z!5x-)b*$nHzQFyxL+)7JxP2>#kN&3HNg!@KF1>;fNm0FTQp;tbWLYXLn5H&@Q3qRP zcYzT$bjw^*I>C<>NT;Mx)v?cG*w5O&e|Dr2b<^$ED%tElR!;_k<+-pY_^^Rkdjuk) zuJSReKQEy%`}toxk(FB%$og%%!Y%v6Nsnu2x&_XpW;Ab(HIia~|L4o-HPr8M*TnsC z|3ICo11$A_`+7TWuw7LE*G&|E{w?EhH9z3>ks*i{`fZ3TYf4ZIi=WMYv}Z|tp#Ky} zFZ?=ncFXFxZqFEBL~M0_S;{GOhmG-W<_d7Faa=am62Sh2sG`5}2CGk&kl!MH__{7L z&<6@5kMEzCSekkrXEv8gysQl`;O38{ov)Wj!uOo85}4fy)sX3tsKK3|cEUsSGJ za1pcV0yV3n-TVJ|=Nti__b!wE*B*;cUHkp*g@Lav?bp9xo(v~Q1o%%jLStE-g0r4% zaKeOdnv+-i(d7O>YTGQFcwjsJ@)7Ap;!S1cMLmZ^zI8l$px(2fy2bzFKZ`6MXbiN! z|FzDCpx!%#ih_H`F9WCll>mR9>{@EM-ux4DhM2McTmpz7JHDQU8RCrkP@8<(S&wJP zTi66|j@!T9nCG;qqne4v;)FKV#T+JQL;kt;hub9?K-yXL_T*o$Mzsh^zdYGR?dwYT zqpSLwT5&_&8|DLA5?u~~KI8XuHZ+VAh=#uaaj)KJji&V7p5)zd3Onc*rg;)*ae2Ih z%q^Si*zOTZ1?QBv64@Nltg=!-xDt)u|r&+D6Uaa0O_?oXgLbKLd_8K;$f;e!|m*Rt7Nyr@nsZ zliL<&lR$n~SYiMb2}zO)h+E18YS+2c7I=>gVkwH%aqm<#wySr;38Qm{GN|+N*wEfM z&$)**pP<{Qc#K2Ra}i`?Q)A8lBHjc}S6k4?)~ zB@gx)&M$t-%3eScd-TwHn}l6>or0wtLas*wF}=lxZkC#E zlYhYdJTu()w|1NHm(v?KVQ23xsECDF7EN(6L~GB5SoS;09LYKgS`m=A*cGHTNq`B> zFE%-z!K?Al-Z(dZB)O(aFlxoE%50dAehBEld}hb1k&5h46H>&mp7h~v-y!wYAGr&^H#*+af45TGjtm>N?LKtG{mJPEpCMK^Wm!A zy-&#JdVRFG2$!orr@$bZ_4PM!FX1stoE)F&uU8M4uXe#+D{l>^ zo>ksrN*?nxsX(uk^KJh1yH5R}nN1*%o0+<{N%RerCJFe5>g2|~J3r+onow&3EWM|s zA9%``RfXHTFmULQ%8kB_b#wj$Uj|uy1)KGL&@($M(9QS zBqu^cgC)y3z8&ui(Mo=w$p+4)8J+1AT$8$%UCDG58E%gI(9Aq3&ECjRYW3{wVx~j$ z5Q-C9#Gx6cfgV$;TM9iWDoD|0%_k~NVISWNdyRuKs6P+e8xo6 z`EM%46HB~A&v<7#L5m#S50}c_;8GFcT6^U$IQCpn%H-F21684_@4*);W49?q=ucGZ zTN6cu?A9I)gX=oS^p?!s9p@nUM(9@6$Z%3z#b_=0{1F_A zw<+MVW8&Ip8}A!jrl4WlmYSi}#QEy8NxdX2NE&r<2P*I5*Tw{_ziP)0BP72uPRa4h z=446soy5irf3wfoX|6r?x<|oTwh05_miT6^L~~D*4YjdXPNk^BB^w}kjS@dhhF5MQ zOBahKb5X`e15g|t^%!IkkM;yM@IgWGlN$392phweW~Fvj5o!gMQvVRDmJ|H7yez*o zl&3#aoi}vtC^R~7dkw!GEJ~H)J8%Ysls?T#xA$&*xbYcUIa0Kt<6hbrYKTwPSZK7L zEW7GJ(>V+t5+X`<^FLvyg}ypbeDK=)G65qxQ)Y&7E&4gvj6j(?KO>sYuxT!+-*bGK zrqTSKp?_nU_mkL5piv|Ca&SgF>=)UGfRy{$KB7(aK-_}dZ~f4})vjay=94#X%dW3K zJ&dE`iuC&Wk&qaCBypE|FVXzG=cUl!(htS21Y9{8-e?6a1P67Wn?A{)En`_9#mVQc z$*~;s_>cJ5r+JklG<$ojCD<|8EL=vq&{12uT9HK`VO^Rq&k!N zXSpa6qO3RO<#26j>7A6#%hcJmy|y8DS#uCl{UJRPUomc(RF)5%pc>Afs0ypR+gGwR ziP@7ZNqYR0ExmB+`~LX535dv;#XZ;81y+H$$KhexSCm4(8)chZcwd$u4^9a9S9BI+ zH45v+A{{qt-m%J8U!}O5o{4>Du!mA$>3KB678j^*6U$I};r|rDBehip3g2`{-T=0e z(&~B>O8#|$4|c7SR0Ch7eAFvsLyq3_9Vg?jleRNB2*di>P?{K(@Cu|fb%alMzn$@N z!?y)c#4#fRSJJ4_kfrHgw zoKZOUh-N9f^=tn5HG|zC`d=jip7xZ^Vbv(Eumx0x=7>vNbM6#0dgxUXj`M2|w2=PK z3{{_vgPbI7ghlU;Q_SYAGxGgpQV#3TT}4(-IqmN-v%uJUiex9?-`4S+U&(M?n|Wa$ zzMKFJE5s*~50tiV@pi&Ax~p`!cY#SG^b*b=A0%E|%F0hu;JY&}Z7mny`sPN0pKv@; zd1b>`hr8WO{*4|g$9{!`Bi*t_C(B(WYS7((l>FXEed#gl zZ*6(!l!mlqU)0z4x{`Mh`K_vur=h2C(Py**oZioz3HX}JyO?J~wv@g9RUAl-vGR&-3s2{x zM5eN=UqY{QULEeeb`QRn8iT%v{IEYD#wc0pyQik&ih9Wosu#|+PCwCRrq$oH+|Gzc#`uac zaXkx+H_u5R(Y%!1A7y)-VI5`L`~yC}F0?e{?0-F}79Y!WP*y_u`4mPMTAa*TX2=#` zGzFm^y#6%P7*UdFR?KviUmP_8oz@jzh1dwPcHMtI%lci-v+9QVDc7!&9umw;84EUz z`%g{)Eh!Nya9pF`yG+!QE<9^%X?c6!fc#Tb>5KVrz8MEHi(C`B!Z|v6mWCNax@b03 z$iE&)WU{s+*W8eQy=|D0IW(@J0P5*6Ix<0;zKDb~>x3}hjU#dU& zCT_MJ8eRRA&pe@*paSyrqk_ems03J3BS1#7lUu#<6i{hA%_$NaL zhc^5R9h9FXiv5TZ(Es8PXEGH)7#STFJ&8MV%o5Ij$;ypc%K1#e+`rMd6Gp21X2B55 zhlmBQR`31{U$`N$ga2C)g^wI!%Xeiw-*>Lr#2Yj*u68S!y}>rss>Xx}NAZJ+F=fx} ztOLJ)*nDYAxYL9=sGtV^#&Y-GmLbC%J%{|3Pu8>`4YR#^v{;NZyuDg3Jq~_W=L&;F zIAmK6Mg{wL=Ci*Ab;0kbv06=yH+RFLQ+fO&PyC<2_mOL4n>(5|@PSy%t3{P~?5~2s zqn|548Fiw>1 zYEkF@T!k#X4HeU@8Vbq!>T-RG2hbhyi)}4`WuMVdSxG6*$F6x0ELU%4aRvCUa_Wrq zVyYkny~e||&K&ZXT<0yHDkS0E5O##SPRnF(+@Wz7p^ffMYP2{d+m`0FA?p74AUapI ze5zMyQ>T6ditb3|u5{m<*acG<`f4{5we0*%del3?xRDs*a(l+jo@aw z#sd$34p)egbHBQK&Q)oLon~JoVWkaGAZ1+Ak8NJRH&9fwCV(JMbwGhZjbRgwE1?v2 z6eOq~&v}0IA^Na66+eJGL`j&x-2AeLV6LlDC2YNvo`UK?K?~@rD;mrkjR{c!1CQ(s ze)gU_q+8z@YB29_uy6g02tyL|8`JG$8`qb7>_^(=Z-x1t}Ir1`ci z4vJ|U8YGP`bwhAJ@iWN?zln66vEqfjbwnEyQ;D$Qu7$R0$oUwq7GqyJk%@WdB1G7(axZEC?Zx5r=Qd~On{6m9&2L^P=kT$y$!YoQ$g-6E~QiuJHtdEf$ zGyUq^scrPjoz_Xj#?;X7`!8HGic2NO#4aogzh4WQ?m?M9b^>V=7gX@n$_LCf0N6sb zuk3@$eEDgI?a(tR^{NfJE-^a})i?!q&9HAB34^S!VBJ+C^(ODpmFRfVrts7uX1me0 zG!-7Z>07psBxsu@X}s2ZtwvlZ4Sj;TDS~xFi=c;cvc7mw)2#$$#(w-X_lnJERezyQc$3p0rdvw3`953&>Yik?Hble+&%V1fA z*DO=*!#S_TMtW;jr?}r8Mo@4%YPC?!q!VlI?2bwGbY-~g`HVKDm1>s)mN@XSVTHs) zl^xYlpByv=>Gi`mW3UBy^M;kt6mc4qyZ@Fwwm51vWZJWH6}!2kun>%oe{uaV2=3qK zfAB#@ZvCzbVLz`L)HE0O>;UD-hnQ-Ep+u{ip1X5r!b>vqP$~q5o-ZAdE;|OyFTs}O zc3JxPa6J?kwoi~@#5nDy@vqvRnvLk8*u3Lcg^*YnV3BwsCcGd-x|3w!cU~#RAM~iN zK%}??g)0}+V~; z7pk+%9cipmL$!20VF&68-N2>Krp@zqO80s@6>7J^G&6o>OR;@&~zG^JUN&E1t% z?$=e!674l#G;gsiwzj%DPU(W<6gSG?1I|0yS$;!M+N!Z=HHOx9$tGA& z!0p0Xcqng6bG3SWBsh9>$_}o&lZH1R4h!GE*J9$foGlyuX?3Ol<~M1=y#b{1;YIeI zaAn+q)=oBhAZ*3Jj1~PW&VNFD$>78*TH09E%@3ywVtV4PsAj%u*sp9`v4)IMx-_v! zpa`3t3#z8e4%Hu5d8i2N%XGfmL4;xb3&U0)U_I2vw|Qd0fyz0DsvKHjXE@2GgwMI2 z9VVb~Nrw~}xGxjAE-w%74FFxu>fzp<&RctoA^*g3K=b`iGTQRwWf4^H%S#@^ zhgWN%+d@e#n{BwM;DX8>&BIFKw6amQ^O&i2AHh+tp>(KbzkxarBW4P>>jCMr!X_Cz zSkwafKwqYEjyAN$dZ zu3ALCrH*`mC+nQ$w;IdqD*P>W;&sDh!mpbbmqfJN2K*~;#(mlgpLY7Re5nU2FMRD!7;4SL*QtLhuuPx!H;d(3Ct$P`s#Rkv;_ zkp{+|v*cdKD^Jy@qOFzKnsj|FuirDa>$hq0w~AEN+s7(xKQkP(b3vpc59;Oi!uwZB zIMfw`CBAT{L%)*vO}!)kTr^=zX!fo*9E0X8B_Qt&BxD@;cfa2DNu94$M-j)y z-b(pGo{e@+&5J|t_CVQl1JSdv$<&W{b&2`y?~Lknw+eAb z{X}&XJqu!|TQpX!o_MELNL4_}??Bhxl+MF!HaDg0qI*n;L-`;IBnj9d1=TdOy9X;_ z!i7?baKGKV@=;$yP{pGvYRb{6Leq6ocUkx`ltz^X55$y1b*eXN>h8%i&@NtiN=SDk zd_p&>n#pp;v&l235kov>D07jXix>*pCw-QeU+QJ2meB(Hj&2 zA()ex2#vQCLI@6uNUe$tU1;CWkMC&@Iv4UoAG=)?J*urKj!66gZ?oE~*{_W(4h0cm zLb5kZ>r!l%ys`idHHz3jF#+0&;1!QeCqj>6%5~^bTM;k;kmdi&1jHIjdaqG-+c?jml`c|+jWr}^> zI4#n&f5HYZmI^#ciaG7v7D_qo+vd}^K8XYkXc#DPeyh53<)idzqeDktNt9znh3tOf z_NP(~vBsw7=v#!B-IWuobeoSX6w<7^s)qxkUAiPt&`o&@h4JTYP*1pg=*7gxcw6EJ zjI4r08{Xh;NK#;4PqVu{c0;<7!1q+&fV=dmUgZDd;>@AH;iF=poUa34%$=rR1SSBG z$CAPdgk1(Khj>!U*;S{_!Lrc+v#O{(tTyyf(%7NWryQ`f{3DqV29X?9wgs zg!b2d!hrg@yu|r?Uf7Z7o+f->8eWm}9^9KH;~%4dvzYJ;F8RbS7#VT``vZ&%`;_PL z257W%BfYDL)5gHZf$*0c{3R0zDYNgj2|k}^Afsag&J;vh>fCiw*zKFyO+wo@)T4BFR6cM?qI5LsmHuqjIH_x5 zD-GF_q9dA%RRbCagdRpq$$b9DMX`ZaD~Nt9+dPYzpE*h6Tn}$hw|Gp}+eb*NtQ*fs zc6%(I^}v3cukv%AKbQdN4e-rja(zG1qOxH08ZY?nYMvz%{3J7CP7cCS=D{u%xzG3N zHmBY!5$zHy|GQsNqfPT(9*pYKOg2K1-(Nr)FM;)SgMIvgeCc9Dk7eU!5)pb-VJ zX!fKjLt@54y8E{ngl6jMWKJ0d-1y%E3Qaw?|ep(_% zn*eOuqSNUhgqtGyXTFFnIo6@}@XOY?2>~DR2D$!Pul?JISG;9D`iRXN1ZTSXbsED$ zVpfj;OhU_;JL+&nAt6qF!Ur|xAMstdrmN+zdItCxMN9EivB-S@#L7g+E;a6Gh=|Z2 z59ZPJDwnuzXU4sX)IS^B>N?aM8t*)^5f1{ZQY8K1T1FPWl$~p#ao)3Xpw_QCPON^s z1JT`YT;7QGM{|=l)u2K)rmDh>ccq!M*brO8N(hkkbC-oFFU>~Ty&GHgepz5X4CZE< z+tzN*hk=af60-Af1PnFq{ZDD1e^F^(LL!4X!tle#yttIUpJbqy;uc$K@G|6fg6buW zLjc&P3rc!g5v$ln%Twc0P}Ox=q&uA>n2Nc{Wb7K@r`u?Z9^COjs^2XQ%vjg(Nh~FC z27~P}OpmN3&;^c%z0{rkV%6Vjr*p#cUyXxvR_ZxDRNH+v4q+pqLz}{d4(v6xt3Ds> zT{{onTw?}fIL+DJ8hALAJ&WyGjQxkC2I-Vm{NA6Bsp|3kt~9x>XK&Rtbt%me?PiFs zuCh;iafS~c8-`ft^1+NDeww;?5=UZsba)MUxx})}J(0F}=P@PIPn>z%GGKBB0kEEBgK~@d}c&Fb{uNZbC0+TZw_f3?&(jg#&F8PJjSB+>ocf2AIJB&$zT^G z9 z(2%`4J3Fo(tI~%3_8kFarl$TS1&tfB2qOAzFt%Tfse3C>V2`%_rh?Y=p}U9xv#vVa z19zZ16vDJMISaeWy9;G?L>55=R$zAQ+cd#m3N9-wg}=8&dv>tPAp;RVP{y`ONmT%2 zFL(NI`w!t3CHUtHd zkn7PB!93jav)JXW)g(ZFR{cbnR+rGz(CUE1v>VE%Rp-f#>wF?r)@ za$`&UnUZ}aNpO;lOMm_9(DY=4w9Gn(q z>ogZ0q0RCGxVYf```CV}Wy{Wd_K5D2Xlk#}Q0LD4=Ij-tX7#Z%3*V!)Uk3)q6M;LK@U&2^daS~jd-c7_-P0v) zn5GKZ!%x^%2exl?9(FiMfee$tE@Z7UHvVm~@vn z7U>3St_Q5j1Xoxw3UY^{`F>~NGjxS(UF}j? zcvTt2!j1QLUt2Bl!!0N@{uFnGwE3=y{jp<8JM&kzmqF@Bo;LG3FPAr1(3Dy(J8Wtj zvBzxm!DJvz??wu3tbFE4^=2w|G|d(vS+ZR`#=3=kEIO?Yzlh$~T2er^&YafMjJ5FV zcn?fn491|+D6$1huP0x;7IYTL!?;e`*1x`0W`{(!#z{-F*7Ie!h@~#Yqa>O>5QpsI3V(-Q*gq5 zQ)ys^Flo@;K)HsEx118FFK1@VUDLN>YiX;9GP`oLajc4X2nY7dPs%%fe`R*S-%g zJbGHG#lnr^TUi{K!GAkLzr=(kx5P!ivn@{-%E~g<6(DLhlh?LRm_~s~_&3J+L^Ysq zo>&s=2q{}AuzXXjYf|S^Lq%zfH96FD<9j69`39;*$gG4rcq~$kj5vEM&xPjGW2{t6a7W1 zKak$IgQKzU=GGv}zcj|cIQJ^!v`Nk^|58e8_nx$-&6~cFK-EQ#^{B6r-Yt=l`=e*H zmTdC)rIRzyMechv;^dTE0?AeMwuJN%pK`O$&`a8M)7b58Dw2T%9#p1%x8K6;)idbT z$u1Nb2BQZ)=M}+$3Hdrj7%h218mkA@tGjzG>}0xCT60B<$jb#KOmu5yNnX+U!7H0gn9jm0pZpU$Z=(t0JuRU1 zRD@ksibeWMwR? zTwZbnl&23z`tVfQi`l%I)o_&|LAUUs6ARa$KFhdErpT~xW72bNZ2WX}Zo(j-MN8WH z7CTgoHR`jpkBTktov5pD%4)-AW_4WL3SFMe4vuv`wBcbIH;)z+q^i_sUWW=yet}D( z;OBMyvDuDT9ib3yf;pQ|3?&(PQy@0jCV3sfJvLPm+*>{M2=U~)b#M%46TR391D$D| zc=Xi^(Jt0bKaMj9PW~#@^@@|SInVVn!f88F$ptLfL;I_*vy|RZ*eaBbDk*^rsziy= z$DUZ!nZXb3!?mO(Ni@_GSWkZVUWKsX<%w0V zc6KyN9SU7PKOKk7Nl}a2Jw5Zx^3=u^_6Vq^OM$9z*;8Y1xX7T%6g1XH82hL)MdGWi zsN`|%293+@2VJCIk=}*kIn8C=Qc=r+%e3+EvPd^I%9(e~adqdKG0iFxY0lTe;9&Z- z!!jwPC23b$SymZlzVOu)h7%l88Y-^W^4nKh_Sn2C&zUt4u%LLsF3c@Z*~O6-mf%Wu zmd@6(82(PcRK>KC!nB1IF}7u$ z*3TO-IabFX8?C8O+4C6M84Z}>ZF2^v2`ivFo&;@>{~#o#I|E@-d>`u5Q~nn;1Q$@s zY(t5?wa>ZjIOl@G|9n>bWzPp97c+c!TS2DOYFk@0XuA){X8<^q|z^4-cWn z_eV67Raaf!8hne8??OFks(b_IrZ$whg2@ot)op3UvO2OEhAfMfE-OVbxp265jq~@!x*OkEu43Wrpi$eSG_W9 zCJJN6rF||{t%c8MU$Ji(B6D^^Jc4!Z9~4rN;id)T<){&l8DJ`t*OqhHn^7Ki5sNW+hrEZkJ{)+AXK@vPu;425DalNJ(?&jO@xI{}L6%L9|=hqc#7n_*C z9g1^|iT^Yow_e=cxV$8lxgsh2{|uTr!}WX{I7YMtRUeLM{&ZxfQbM4jJ9b=QxQCUj zZiF(psQ^of)_T}(CH$B0nAM(gZYlH&TuOLM_evykVH-EmonIFk9^w4MG2N<;bSVA1 zXd)dMZdbRp)(mxgH(G4>U#9TbOc5R2|3>4;OEmP){N_)B-FpQD+xSq9Kag_4S8yNK zcUV6*{z3ta%v=)0sl8sP{Pd0A$BvzI0xSU(KKpe=r8JMW*Q{Y%dk2YB!K&3`D2J(Q z^mdNq4WCU;kG*>udX2c72`raoBmQ|Ils`>fq!+J{M-wB zJVGw=UTy-#DIhOK#=qj5YCP@Nzxy2J+mELqdJOLaxM*vtSIvf>&mCovTSw@h0%IR} zd_MyIq&)_95f*FU)PXy&hl%i0Opm2EIypOw>RPjj;)yjhb8r?(u$VWL}L=q*x)I%aqWwhr4f?Ilv|QJ5lAfCm%J za=Fa{;mA}Tgy8FkGTe@VpRgZ#DFX}n{FXC=FGTEerOuUa??_c3bTZ$v zJYVk47ubQ3wpI^diwC+_*k1x~F>V`J&35YamIe|@@n1-kZ{hv{zWkwt>e-z}>!T?> zjMU)j)~B{vf3cLOMG6HSpHQWZ3kunNeVsLX(_}9#w^WDX)^3Ae_GIdg>ei%aY`cCTs#DCVsf%V==wTxdF z=5?*xX1T4@n)NlhK2OZK|HIH zhT*rXUa0Rqmj(9;PQ7p)O5Wf+W^dHnpY_&fz7(Chh!z(>&qQ@xnZ{!UY_C=AaN9C? z7^;__){eU|6#ZBT{F5k3zf)F?lJ`2owKeuyG-5918n)v7?BVxWsBf>-nZL2BGY$MX z2!`*^j8^i_<=j>lbANcPmk?&F(D=BL9{l6GYgQ{~&-d2)PFl3wT4-Q;Q`eZRTcXYw z`ZoG@z1A@D-1qCwEDMAyWtTV@l|Dax-{vS?k!B(~25$9i_f>QrJ`*>P?DI1m8mahR zTywjXd9XpsLCi&0dlS#ygWHAbcJBQujX4Vrz&_*cqZJdw8I+7z>wlU_k9|*%V|2S} z61@-;6yu|r7(tqKW%f`t4ZDHMT0mV221jkHJEAKPW!BPUqCmsjf4#9~m|{Wn>;Nbo{TFEJ1XgbB>x<-j5wLb%W)oe1C?Meb!50 zQ0dLxAD!HI9`^V3>-Z`Qh=E&c8H;M#sc9G6vKFv{n-$mowHRqXeafNeYqiGuoe5ax z{`Q%q2;meFx*3CL?jA+SSMMfy2mOj=uW{rBY8o4=mGfQ_g~Y z%45-zA=u;=L49=juus}XKc0E9z|9Vv2IB)(d>-10xAk0RZQn@!@0F#jLp&Y`r{TS@rIFW=zDPH=BR{7eR3_*K~P14By(ArQLe=2~~R z-D877_<*g`)cJRWmaZ!KOIS?b66kF9WSR*pi}~L`myAw9JSGGpas_U{FSSS*^;tbY+m-5_y6n)3shF!R^$sV zulvD1Kk1d0uF@CYe!znk&xvmnXV9-0&MxvzG4h?r{xnQ`NNILJx#5{QoZ*dw|ClgW zy}{+TQ6`3bpr?}>S#J(!Nf6_9>zMm-ok8dI{+jpZ(2rdP?N@$}# z@SnGsiBt8v{HWd^oQ`|zP=8{_ZbY^u-D#&{@r!V*QEq2dGgp`ZXoG!txza{~TXt?> zQE}O>`mfC{al$*h;zjuDzlKKTy@+Q=uNFNvbbedW4+T%bzX{5>htHx`cSH=Um{Psp zE!H`?VV?(5z3#4>$hND0920qd=k~?x9JZ%A*KX7AjgFoHc8~P7&v@KZlxPojNqF62 zH0Pc9OM{whSgtwo&Y&3gMvBY)=ZPLeldBOZV$6&O%sE|pl9X{h$ou{xOEADK|LDy% zj;Ee`f>pR&vw`&{@MkW;xHt1|(p){8if7o`(z4vP))8F9`k z`TlJ)=#OoPMO4_>vQNG6nuf|4x&gURNKO5P&d6(7YO!*>?X!cwtioE?=YIS4vph=SEB1q4HMv7@`Vi4GQ}ahQ^XNLfob+`|~`U=uCY=!U9#=anwtxZIyt zEl<^MwjoxT+b&e8?BCU20p}Or!MebQ=@OcC>f~$YrmAYw^+gj^RAbDHRu(Qv8RFDe z@&o?2ZwN?k8b!wV#NkB^x$K;}kt`l`_;;cxjRK1n#68weMBgvHN%R%J%rf)Z{3@3N zK7j)mC=WG~j2gzg8MLolV1@1{&nAW;mgd{6p(fy%_CENBe0bI5^u#o=S|K?ytM>uM z?TMcmuuSu+bXe9_@(q39$rsCP^lZ^VLcElIcns}#qiVQaMs`K<($0?x;#t=}iiL8P zDiQ0fmUq{@ma?VqCXzIOqvdmiW4~G&otVaZy0Sm@0uz+)CSet}-#jDm_uNp0BP4W2 z0~S3^?pkfCqjmGB4cgZ+yVh0>567$cfUk#j(rD`Kp*AD~f|F zgw3pI2D}d}{|HEt_*n0bY8PLgO*#RD)qMEK<`<@K4j<`VIpczQ$Nmu; zRY*QwR8I#Hvd_F!Ia?~6I}KZ*PW)l{RMmJuYW|xyyJLa_w<^Q0$L@z*_jX`fGv?bQAIl^xh{1JPeONcKe z8JPZ>J^0M6{e97BODv7r^B{a*W(di5sLfW7boeqQl>;Fk{ydNi0h?79qGvielbL7o zEMl$?o_5>U?-}h`^?N=r=8ACd#2E-CGtYlx2>G#K7XPS)en*7A&|oJEkz35IykF<) zwaQJ9eiDlt&{6*JQ`$ZwnHdw!JqdTLaFe|WU1`G|K*Vaj?1uafc!E8YcO1Tqo9LoL z6y9Ehti^~?w^(vY=$FpE_?R9D<=S%HvE9?jv_IIF$%gs>KHKHA| zV6d|VEm?zNWwLr4c_%L`A}cD@Ijqlh;WuvEQfS~$=RdKtSDPX0E1YOno4tFK%yb93 zZhd>_jTY(v;d$#QZF+@eZa!V6`zGS^V-ZG-h!lxZC*mx|_fh3uqbg&)j>xJPMKy@|Cu1)-d`t3j|ANg`fAm}UkdWKi08GcENpa=%Rt8_6gV%r>!7I zcTV(LZxk>8V<8VuY1dT1{$Y34Zl( z|L1}&8jSuh@qeF~Zg{aZj#CDN<^7XwPgLCpZ|Dp?WQ_FlQ!78$#)eg`u;9r1n%;^* zCS7+s{CAU~S4w^}k)Ps1dRpyb+zXtK9_WOl0!R;`K68`toN{;xD#A@f7 zw+|aX8^|t)*C4{7G3E3w|6gz471u=fwW}aSL5fmBZ_;~5DWR*N6hWmUT@1yb(n1xG z2mwTj6lp;zQly0@O-ewj6hj9wuOOY!LJK()eE;9~{mwa8=i=mIE|SciHG9@x`+1(V zX752im!JXJSn1|_lfH2^uJaM@(L??rK?4P~a*p=qL1^96zrfAq*C&=(J@v8%`F;h+ zN(aN9n@texh|R&kpmEb4JaiIUFfH?xs}F8JGJ0F~v;LU;PmY}HQlh)32f#8^j_-cz zQIEdk%3B{Ahz%V1V_9%N<8-C62+yAxey(mdfasjDTwwfeSzoiM6*-45rZ0P2Dk&PrbRYuzgX6|*C;=Sdm7+nB?!5=uvVfDwe zdAA?_x`pke+7HFF(5BYOcQNhZEsxvl9|VFN*1;H~H7gysqZuws*=OJ~IId4<-PusG zt~V2>*j?KCt#-b98kIC4J4BX%9-rYA({SPs1t7rz9X;ru-$2(UgS1P&iYNg`qJejZ-y%7bN<4hf!TY9tjVpJXLo-&wd5z4gf$S8cxi{s~I~ zbz5uGzaSdUuhVxhfOhGgg?Q>fsH|a~^;t`b=1Y#g?pC7!R9@LNWE8o0YY`48rO}2}ptql#-o7j78rXHc{`1={=EYMNQvX zp7En|tU#cw5vuR{;#6B$_slGh>riG@F7)_6eu>6#BS zxZ~|kUg=MLnddPZ!lmy8zh_)ZSBhtXHBvFSH8wousngi*8ygCE6*5QzUiv1-d|4wa z%cY9KVtC*PlU%s9NV*sh3$}0hnM#Yrhf+eHzt1akoBm>XS$7lvpvE zTedeJckc?V%*%h=LEfLq>CUs{#2IycJ&(R@gHJ|nmfCu*D`E$OQ=Zd2Wc)cqc!8~* zmxz`0nDMp2D`GTyf7y+BCrf@#l7yWE^fMkt^~9Q1=1X5&p(+F3K2=>g*BX6B+5l2~ z%!J8WAz49k>Ji{Jj|_c$E7b@QjXZuBdu#^PKbY7#8ZQ8S#kTw^Iucsf19|k zZ3EhfeX>7G{ofQ#H*q0-ADO-DpE@*{>eiDQ+=`1rtXgXfPI)7)qHB_M?13L)zdFcy zc5jZEg}2G&59tV+hTdAlroHDuAF99WTJG(Z{CsGlZ_vWo&GRL%`3SEqQeXcE; zrFY*R3MqRjF+NHrR2UQO@7^98oelFOguyC{Hv3aUGbCXkm4uLE^Ttc27f$4DU&m~! zy*|*W(h8euB*Sd3%pnA;|4B$)v7nXMQDc(mYwdjmJ8Ak? zSQ+O2-k5;P42T>)7l8EtKL_ETSVrW*9rtxFtW}(AtYZL?eku5*<$tI$&CU5}G~Jt` zcSWBC&Qy*g$H>QAe}s>)J>%0v8Rl~*J;pp#E4RG6rO8Nc7s-HR4}^Wb|G#-|jAD#7 zTqCbl*i3p)VULSkQ&w(Q1DZSd4}oKZ6%nFtx5fmBa#qQIVNU=r@^qt;=bFG`nsNiS z(yz}^eRA^QOwxP*!M$$|!ZyjJ6}?t2xIk;5Cg@!MVsIx0!y6nQDri?kFcVB}nU*w8IDR z?8=H)cjPwNI+bLl?uR=6gfQt5c}~&w0i{uZ4B%V%OSM7SlsGmWYE<5ePx)uko}g|U za@rhvO?i%JCAUBD*-VSLI~{s^U+rhTc`3iHWyn9jrAJHhfXZJ$|nY2lkWfqLw)wI9++B+ZEVOGt41?}V>7cqQF2Zk0FHyNS40wL@2y zh&*j^3(1_>|D$T#m(Y52zp+0+{bXe68_KUa4FN#oe-MTnKj(x=oV#%@YFy*J6!E0( z>WB-bc{yBpWC&Qlu-p09?NgC^}z4wt}<|j+u z`~f`n@CVUjM{%W|YzJwWVoN4MXxjCfk}UM+KjYU(0wq%3SJN1HtLw(V^HJd)$r{}e z*`=W3qW9ikyOO>MyLB%2At z)_!dWn#KuP%lIes?O^w)3q>(n#v z$UPq^huAzLNZw-v6Wa|7kReStv|%-S(R&N0=7A9xT|%O;ZHC<_qPjo3wCRZQ8>6t@ zTXa2sCStiDM>O5%ll$A^Layu(dR)M6cgD39QDf8cD-_o@+aYt^tjHg1tg0Dz0C_q6 z`lGptNh-ZoAz&axyd%xcfminETMsP7YLzE;-M*O?+GN*C2`KRdohH@nO`0?8scr;V zOgVy253s`EIS+HOk-{{e3}=Al3X+&u@$%#f(>8Tl3?Fn*iG$|K#mFo!2wZR@1ly~Njec?IZ{?)SbfV)ezH z^(*^sNq}%9Y5C+Z(H*e^K;0BR^B5Dhuj+YK7fiw9O*DdiWi|R4UmqC@HV}sGKcGP~wke1`@j{zFaJ$4 z%ImGFPK|5tigWnRE*1R~g>RMoLRo5xJl)RBF`&D#_}+OouGXNMx_0_^nDbx&%bm}R zIJ?_po1vX-%fU#bGwx$(a?DXzstw=d)1CDsV5MBq$>7%E;;@{ciDdWCATi^Kcn?Y| z{HnxCNyvkvu)hIhh2TQNmti^>_Zt=;f}2|j?eTf6JAcJnXX0fqJPFY ztTpr2K4O9i!zHhD&Cxl=>QySgoOo-xhT^S#&IG#-mt^e%MYmTwGQp(aWlUY!bdCb~ z6BE1`wk~dZ$C8_s6;EqjnP4LDva4O()Q+n+Wzj#kWMcI}U&P^_oLw8#j)-wawAW?F z%$t>&5un+K_KC|{9;+h0o($7cF}7Tt`g)b(5fkyIYumiF&zQbkgRir6NnLV8SYJU8 z(mTTRDxbZaXfa(Y<*l`6QdNh`PTvNpD#E!qyNoV5M(b5xeL0b6x;DY<`<%&XvLny| zLGX`HaO9lt2#BiGc{w3!idfxy%F_z}uMh243mW0H7N_i;%5IONwJ_`~sxYa!uYxZ%fhaYn==3Kl!DyGcvz?g8MF&e+lN^EOwP4E8%Dss|En z0#t_ zKHX>Ejzd)d_(|bNk;o53;Tl!u`ETm4k9;fjUmrcF5_%ojYvv$WA;PeCT2=DVF%HRq zoPXXKFO-D$zGeU3y|L~bDM_?$(B)U9OUPy3tT+uFI; zfv=BlXa&Uhq=@hau=ea|v@nzn)=vyO$DD%ufBJ;Nvw$``9|4OB0yrk{9pisOC|LlI ziPJXM`#XlfGGc_WN;4oM1fsn~0)6&Fa_qm0o~H7c%B&oc2AlD}e^m2MmJ$Ns27&Eg z^iF3Ik5m)9WT4*aF+7Lx_B4Y)I>Uvk@}N87(TIv#-~IG}@`u!p5#de5=pBuaP?gCk z*|RG>mKSSD5?m2nSv~h#q}L3YJGRqT9tA)aepZH#{J@GAWtSB<0qb{(V-TjQ=|X0QGl3{@J5&q({E8zlmFli0~^N~yy>@W zML@JzwtJqD{IcDNv<}H#6 zw}5$|-w)u27_#~GAMm)znN10)VXhTC zkAFr5Y$C3vf*C=3)_qd;6PnjelSoa>X`86@esrpiPcg(wcp0*94Br>~%=uX-K zv(Q;f6&rcU*AcNpPkQwT9q70?lQ3;bLqS0}5%Y;_NSqdp!D<$rW9Js8%mFDCQ zM|fLw7vk!q#{T`qoYASJaH1-<^M?721d6H&T1?AjN21cKIAh?|y}ZPgxx_BjGJ-g~ zIp^@5{6PyIXG z^{_&s#jh6Ozadke!kHLj5i&c4L|+!ezw=;ol`BUbGGileO!F>=zhL-tz*@5(g<`Fm zI7DBx(p8&hg{n-|Kg(t2O#Je6aY^meVf`|8e`zSfC9SJ|T}p9d=KA=XIbFg!+OxU= zy7Z5pB4Oh-kw4ZfQHz4Qov04k0SRw0Zur&Tz1MT)(V*ec?vC2g#4YT;UkP;D=dwAw zzo~H)6^^UQB>}(5bU2+7NO&|(md+fMQ`RPj3E@&bgReIMVJ0NZBm~JRc!{>1F;BD? zhvbG{tT70Avi`$k5?FQC27lnGbTP=yt%VNq#WiP~=GXVcL$*Jj=B`dsHITI}jatCN?JO$wMx zoJFgi`ib*b_O9#!11M>zg_&ZJ9BZYecfGQRYSC>MoR*?XXh~0K!Dj2o@htzO{fS=c zicn$bFoxVXrjJWK(NxwqnreRgJD!Aev z9x8Qgb9+pn9vAG{Xn%G3s}vfwFR<9nKEASAeNjrz%esnA4N&C=>isRk$(-;1oCj~$ ziQ7Qz-nfhyfOk)bU9*a$M4@k*Jne%0^gkvTpN`5w-O%9aCf0$OHE9a2lMv(96dX&e7jfKHTSx#U>OeYyhTH4;z`B zG9v9v?2t3MhqswH_33XSe(NQM1_8=oPkqQQyj$P28_ock&GI5rN^-E}K0{^5U6w+6 zIa}_>P~rH4!uwoa@?NvD=#R@ej}6Gu4bBZZBts+ipfic+JqTE2=QwqJ0HK=TcNf3& zNK<|UpJ$2WhL5sl?_e82C*|_7v!3fPvqmMWwPuotEXP`OYk~dN5dHC(8KdS}0U(Q1 z^SQ>JAWuye+KGDNT7HQKY|OxXG8NAMu0(HDE8v@7H8aJM8ulJ~2iXJGup1lg=D^*G zl@nddUXL>r#FNhk6b)$q;nid--OwHlb`HQ4ZmJ3VkeA_XXGyDy-^9|Rx$sU{T^UX{ zgJS%y19#RTW5-zTf}83{f>4hmeCqhlvKqsWQ#FSE_o~wm$rjeSP|*_SYp)A!sk_>^ zTzJmTYF{D_Y)ew_zI6g8@??xOZSQUtI=&vpv4s=;Fj9k#ZM=b;5vCgrl)B&)P_{VY zOSBnfi+%dN)}UbvI)#mNh+vVLorWy)TPl}kweN$TWO6YAb?sC znRX2}>YHwT9BlZk#IG_IJv*U)46tau$AY&6kNfK{2>|BB=s4*2Pn=H- z$ZwKB#JZ-P3z_MiC&AnzJf=ohB++{XK`|&-StWp6SdkTQdi=gVUnt}C<~G`e)cRDo zmEQZMTab6IIq5eJ|5@7+f#6+poItq&q7ZSbOtqNAyOR!$ezDH`{6YPnF3@)N%RMl} z-$qk9S-Jf@3FYk6PSa@2e>ohy^8oVYBSbO2p|sh1hSt0-_)*256hvmjw*>kW<>c6Z zjTsbbWhn_iJ)q{>4rk*GxuR-KTktVR?F{}%_5wS+(T2fb`)t8m9{X&SCqDbvoddB2 z%BKk^qP$muKx7kDUhnm&QMb`D=J}~fxoVC|1a6XM_q5%@V97ZrJvc@`+Ti=m86M#p zmr}yWZj)^@Z$~0_AVYHV(YhH{OgUBFtM(KBo$fmg@a@FMT1D3#pLeK%{v;$YAP-GS z2!3YleYyAZUW6BOgtAkS9DwD6fUo&&krj7SeGf9^sU&2TOug;0QAJ}wrjErvQC z3qp9gC?ReynAzRCwbl8(EIu}RoW z4T?j7IUX+NiIXvt_t$qGo}i~n&=&(YC~_<}Rm}`O3hOM)W9*c@h(L-Mq`cJZU(0PW zxn)&hL;p!s;2zztW2DVBE6}OsP;A!S+%qPO%-~rIgeE{ygX`@sn8Eln;LDq@Y`QtR zS*Te4BHQPjWMdPUu?qg+LN!%*&^^Fqg1z<|fczN{K`*+{hQDTTOXa#C-_2Lz&x$?j z7f25N6qpvWsi$s9$?4SB&oh!#gkQ!dU(D{&5Lof<(I9us>L)+yc*gai|1(RcTdnVV z(OK~->heB~=w&>$o5twM=7LSJW|8oZWX4182H)Xd{SG!Uw5YqB^8?Co)^Mr|3KN&}*r4 z;%t6KJl4laP&}2zob)kgOkgAH)@0!(`Q*j<3xFWn3WP6ka)b!|Yo)NMYex}442V8G zeKbyEXY{aZP$?|rr3Q*rH~3hO(n_d)MkV~R`s=`Z-R|i;E(QV<w zZf6HSIoAOSj#fJniL8uz2_kMeNO0Ojib}VG6tlDG^DPb{G<)xXcEJ8JQZaD=Wp80a zAAH4qZxuoeFm)b$rE@y@4(mCgb{eISeInaoA@bod1@PSg?0}KZ$zke>VxdDn{V%en zAb?cMiKE&8Gp_0fJ_HHO_Uc@I4%*q`zeH&AS8KY^4(i^2YUF@m&5wTP3c_0LgzXec zTYlNEyYXWxxS&?_1u3dDUF;J;ReXlJBn{kcC@Q3V_4K1&hSr=&CPJy)Mg~(~CB7;n zIMr<_5eaNbQwcngT-v*n5E!#xT1T7xg%JOLK)FR~w>pypE>%K)>hPE%jHWPXVo^fwMZ%0+U6W-gm@#)p9Op=E1d zET=n@*>66!;q!ct7jn749s+-nvE8rCp7A&`U$12}`Io{})MfoYZ}h5$J`~FLs+G-u zXgFr^QJ|6Be@`z^dwjZQ$gYf3XC`|uJv~eX_Nd64S6$xbLwIcBWns0n<|dJGUNR_X zxAQ#Q*SHWmNt4#OT1xP&*o&yiSJ46KWBhw`#YK%2fXr$<6$zPB?F4&IvC=4r6NH=-){`vs>3DA zMMRAvGCdSN{-%=QodSK0_~;d!>_|M1~$u&1q4ioDjVrqjBCykeZFxI(p(d^#X-| zBUBW!;v`KP7Z1XHXnwQy%3j%xNJ~V2oO`tcK$@wX^|EHjjLqkEEgpq?lL}%jq;_NI zelzzhk90aR$oi7to+*+Shv1r+5G_ z(&`@u&rJnm3SK*&{d?fmHudi-y}}C~7qp*8gzsq7)LN4oS3@9=mCc9 zO4A!>=59y$^mL@o-?Y6P z+G7+}%jw;xG!TfsET1kemw4iY$*xj@;NKLf4LpWa5-9Zvs~Hh1_ie^pRTM$&r}j-PGJR>BHWZ4>N?|xL!oHmTIfk< zCg<8{9)|gl{if)>{DhjJL9>ML8Bt=H0S2y`LB;I31bSDFiZ)#hN=%!bTENWJ?T4e1 z5UcGU?g#^?J8reXa`52a=X%MLX+(HTj2QU+URg}ftH?s5QG#EhqZlrK=$Hz?oW3Re zP!R5NwS?rCse7(xu1TA{l2iRU_VLHrjsM!RTOtg?G=er0l*-k3b@}o(yEQNxie4X1 z_TsLdXv2>>3|~sgAYuZMP&4BgO#Apmj>kF$BvgLmIB0O{BZjV`HVHVD1#|@+Pc*RZ6x%sBj{oU3dQ_^tYWe{h20 zM(_YE6R*4pRjh^AB0HuMF(IBa@WcMP`h3*&m%vPxl9#|7QDEb)unCtvcaHk_wyu`> zdL8=mo!cd95ep-nQc*hP%=?9BQUq>gLI2X5f~d78m*0%)dod z4%=WqUcVvkT>CvZipYpAuAiLRz_2_zqhY{Zi(IOMS%wdxdCI<8}`Knl^}EKWWUnr@^2n2Ma_^jNn?zh0lnMkxim|2RJiyn=rlTfUI= zcLw4^0G+sqX3hYbRY}G!x7pS0H=r&Wlq|;oy=GF}adWT)-31S)nHx2KU`<8fxq5?z06FqG z>CU;z+Lx&GGD%O*REZ5e!d=W_jZzK@CO@A)d7};Ht_K-qdv`>v_TcJl{T#N%&snJ< zwqxwMUfmB6jpKu86wz{ea_Ru{pl;uG5zE=OnSj4WPUUMct-M2+uD?XHg*?S$Zjfh+ z#L}D@nx%KASnw;Wof=cvBQfkHnGb5OOw!sD4r^}S6Q6W*a}CQ)f^w;FdZFt5P|MOnsHxaq@y%WAvEk2> z&Te#Boo4NwCu)7q$*I}i9X1!u3s{GIfL!j5OAp-yM9ic47i9}u$NBu{#tQ97{dYI| z^c9n4MHe#7O&?x(KKK0Uxa%G!r2l~1b6v{plgyhUR&6+X z+YB4xO?)!r?JF1rQ6c{o03@12J}KXug(2S-3G2}EECvWz7i3OA)1lc1O+|S3<7n5u zY-sv?Y~Y-|M5~((K5QZlN&%cT1j(5cg!1)>Ev*ke$Me@O}H;YCz z*Ktpu9|Jtf(&EGNRcp1FfFpqw_m4|Ep=Ebh9#)-*?Z8Y!&1+8#&3ez}w+77=0_$K} z6m&H!ABnF7zf;=C`Wu7uS_K@_Q|ax)w#muDc`hENc%*-g63HDNg0}ZRyge3?Gc>_k z8)UC%^-v7@1&R~kZ#{c<^k4?gl&alIVj_wn?)dVl4hwAhInRZ1VN-fP5Np063x7jL zJwY<1>sVl=qc?o-#Gt@t%-Gw&@-eNIGA8+G*2!)xX>KUj{{X0PyD?6O72^hNc8%Jb z8oP3kFD(e{ly%g{JS*q~v`6T_zU-#ii|dF%~-L%#Z`Wkos zwZ&WsByQt-bdt(5vgk9Pe|G(A{R61-7iO_`0WQ*M{^-k|3?CaPX)W>m*Ny)VB{Iz*R`&wfjZ6&;Z`s6wcM=8 z?oL=U{Q4Lpg0VNHr>*G@3#d6bcH-Yk?*oC%;Who<5h_Z9Q%93GNI*fb0Ok7zfSa&x zU^0<%cE~w{@bKD~!?CJ}OvALVh`^T*d^-2`pt<^KZ;Lw2jXJ2T_EhBrqVAo=c+%m1 z#cSwjzpM&(@Jf27e>n;NkopWbAm7ONd8ehe`r<48egS+N(+}_e_vf?!{J$L@o1OAZ Y9!MWQ!WdjQ2Yhbp8R?d5KYIDU06Ffb_y7O^ literal 0 HcmV?d00001 diff --git a/doc/orchagent/notification_queue_policy_hld.md b/doc/orchagent/notification_queue_policy_hld.md new file mode 100644 index 00000000000..384dc1adce5 --- /dev/null +++ b/doc/orchagent/notification_queue_policy_hld.md @@ -0,0 +1,612 @@ +# Opt-in LRU-Dedup Queue Policy for swss::NotificationConsumer + +## Table of Content +- [1. Revision](#1-revision) +- [2. Scope](#2-scope) +- [3. Definitions/Abbreviations](#3-definitionsabbreviations) +- [4. Overview](#4-overview) +- [5. Requirements](#5-requirements) +- [6. Architecture Design](#6-architecture-design) + - [6.1. Existing design](#61-existing-design) + - [6.2. Issues with the existing design](#62-issues-with-the-existing-design) + - [6.3. New design overview](#63-new-design-overview) + - [6.4. LRU Dedup Example](#64-lru-dedup-example) + - [Scenario](#scenario) + - [Notificaition message — exactly what dedup hashes](#notificaition-message--exactly-what-dedup-hashes) + - [Queue state — first 6 publishes, FIFO vs LRU](#queue-state--first-6-publishes-fifo-vs-lru) + - [End-state equivalence — drain at t=7](#end-state-equivalence--drain-at-t7) + - [6.5. New design - Op-Allowlist Filter](#65-new-design---op-allowlist-filter) +- [7. High-Level Design](#7-high-level-design) + - [7.1. NotificationQueueBase strategy interface](#71-notificationqueuebase-strategy-interface) + - [7.2. FifoNotificationQueue](#72-fifonotificationqueue) + - [7.3. LruDedupNotificationQueue](#73-lrudedupnotificationqueue) + - [7.4. NotificationConsumer API](#74-notificationconsumer-api) + - [7.5. Telemetry](#75-telemetry) + - [Syslog — periodic stats (every 5 s per active consumer)](#syslog--periodic-stats-every-5-s-per-active-consumer) + - [COUNTERS_DB — `NOTIFICATION_CONSUMER_STATS:`](#counters_db--notification_consumer_statsname) + - [7.6. Modules and repositories affected](#76-modules-and-repositories-affected) + - [7.7 Future Enhancements](#77-future-enhancements) +- [8. SAI API](#8-sai-api) +- [9. Configuration and management](#9-configuration-and-management) + - [9.1. CLI/YANG model Enhancements](#91-cliyangmodel-enhancements) + - [9.2. Config DB Enhancements](#92-config-db-enhancements) +- [10. Warmboot and Fastboot Design Impact](#10-warmboot-and-fastboot-design-impact) +- [11. Memory Consumption](#11-memory-consumption) +- [12. Restrictions/Limitations](#12-restrictionslimitations) +- [13. Testing Requirements/Design](#13-testing-requirementsdesign) + - [13.1. Unit Test cases](#131-unit-test-cases) + - [13.2. System Test cases](#132-system-test-cases) +- [14. Open/Action items](#14-openaction-items) + +## 1. Revision + +| Rev | Date | Author | Change Description | +|-----|------|--------|--------------------| +| 0.1 | 2026-05-14 | Senthil Krishnamurthy | Initial version | + +## 2. Scope + +This document describes an opt-in queue policy mechanism for +`swss::NotificationConsumer` in `sonic-swss-common`, and the initial +opt-in by `FdbOrch` and `PortsOrch` in `sonic-swss`. The change is +internal to swss-common's NotificationConsumer and adds no new CLI, +YANG, or Config DB surfaces. + +## 3. Definitions/Abbreviations + +| Term | Definition | +|------|------------| +| FDB | Forwarding Database (MAC address table) | +| LRU | Least Recently Used | +| MAC move | A MAC address learned on a new port after being learned on a different port | +| `NotificationConsumer` | swss-common class that subscribes to a redis pub/sub channel and buffers received messages in an in-process queue | +| `FdbOrch` | The orch in orchagent that consumes FDB notifications from syncd | +| `PortsOrch` | The orch in orchagent that consumes port-state and port-host-tx-ready notifications from syncd | + +## 4. Overview + +`swss::NotificationConsumer` buffers incoming pub/sub messages in an +unbounded `std::queue` until the owning orch drains them +on its main-loop iteration. When the producer rate (syncd publishing +into the channel) exceeds the consumer drain rate, the queue grows +without bound, eventually OOM-killing orchagent. + +This is hit most visibly during sustained FDB-flap or link-flap storms +on the `NOTIFICATIONS` and `PORT_STATE_NOTIFICATIONS` channels. The +common observation is orchagent RSS climbing into multiple-GB range +under a continuous storm and the kernel OOM-killing the process. + +This HLD introduces an opt-in **LRU-dedup** queue policy for +`NotificationConsumer`. Channels carrying end-state-idempotent events +can opt in; their queue depth becomes bounded by the universe of +distinct payloads rather than by event rate. Channels that haven't +opted in keep strict FIFO semantics with no behavior change. + +## 5. Requirements + +- Provide a queue policy that bounds memory usage of + `NotificationConsumer` under a sustained producer/consumer rate + imbalance. +- The new policy must be **opt-in per consumer**. Existing call sites + must compile and run unchanged. +- Preserve `libswsscommon` ABI: existing consumers (including + `python3-swsscommon`'s `_swsscommon.so` and other linked components) + must not need rebuilding. +- Opt the following storming-prone consumers into the new policy: + - `FdbOrch::m_fdbNotificationConsumer` on `NOTIFICATIONS`. + - `PortsOrch::m_portStatusNotificationConsumer` on + `NOTIFICATIONS`. + - `PortsOrch::m_portHostTxReadyNotificationConsumer` on + `NOTIFICATIONS`. + - `RedisChannel::m_notificationConsumer` on `NOTIFICATIONS`. + +## 6. Architecture Design + +### 6.1. Existing design + +![existing_design](images/notifications_redisgn/existing_design.png) + +1. SAI Notifications for mac_move / link_flap / macsec etc +2. enqueue() to NotificationQueue(). m_queue is bounded: + 1. limit = 300,000 + 2. on reaching limit AND 1000 consecutive messages with same key, start dropping + 3. NO drop message propagation - syslog or counters +3. pop in FIFO order, RID-to-VID translate, ASIC_DB update +4. sendNotification() triggers a Redis PUBLISH to NOTIFICATIONS channel +5. redis pubsub, unbounded: client-output-buffer-limit pubsub 0 0 0 +6. SUBSCRIBE to NOTIFICATIONS channel, unbounded +7. multiple consumers, push to m_queue in parellel, unbounded +8. pop in FIFO order, doTask() process, update STATE_DB + +`NotificationConsumer` holds the internal queue as a value member: + +```cpp +class NotificationConsumer : public Selectable { + ... + std::queue m_queue; +}; +``` + +`readData()` calls `m_queue.push(msg)` for each pub/sub message +received from the subscribed channel. `pop()` and `pops()` drain in +strict FIFO order. + +### 6.2. Issues with the existing design + +1. **Unbounded growth under producer/consumer rate imbalance.** Every + message arriving at the consumer is queued unconditionally. There + is no upper bound on `m_queue`'s size and no backpressure to the + producer. + +``` +Total: 2102.1 MB + 2100.7 99.9% 99.9% 2100.7 99.9% malloc_usable_size + 0.0 0.0% 99.9% 2099.9 99.9% __libc_start_main@GLIBC_2.2.5 + 0.0 0.0% 99.9% 2099.9 99.9% std::__throw_regex_error + 0.0 0.0% 99.9% 2098.7 99.8% __libc_init_first@@GLIBC_2.2.5 + 0.0 0.0% 99.9% 2078.5 98.9% std::__detail::_Compiler::_M_assertion + 0.0 0.0% 99.9% 2057.4 97.9% swss::NotificationConsumer::processReply + 0.0 0.0% 99.9% 2057.4 97.9% swss::NotificationConsumer::readData + 0.0 0.0% 99.9% 2057.4 97.9% swss::Select::poll_descriptors + 0.0 0.0% 99.9% 2057.4 97.9% swss::Select::select + 0.0 0.0% 99.9% 1860.1 88.5% swss::NotificationConsumer::getFd + 0.0 0.0% 99.9% 310.6 14.8% std::deque::_M_push_back_aux + 0.0 0.0% 99.9% 32.1 1.5% swss::ZmqServer::bind +``` + +2. **Workload-shape sensitivity.** For end-state-idempotent channels + (FDB events, port-state events), the *meaningful* state is the + latest payload per logical key (e.g. `(vlan, mac)` or `port_id`). + A storm typically cycles through a small set of distinct payloads + many times; each repeat is kept in the queue and re-processed. + +3. **No mechanism to express per-channel policy.** All consumers + share the same FIFO semantics, even though correctness requirements + differ across channels. + +4. **No telemetry/visibility on notifications storm.** Syncd has protection + for tail dropping notifications, and raises syslog, but these are rate-limited. + +``` +m_queueSizeLimit = 300,000 +m_thresholdLimit = 1,000 +m_dropCount = 5,708,756 (sample 1) -> 5,708,824 (sample 2) +m_lastEventCount = 6,786,589 (sample 1) -> 6,786,885 (sample 2) +m_lastEvent = "fdb_event" + +2026 May 14 05:36:34.996024 gold210 INFO syncd#rsyslogd: imuxsock[pid: 51, name: /usr/bin/syncd] from : begin to drop messages due to rate-limiting +2026 May 14 05:37:26.307660 gold210 INFO swss#rsyslogd: imuxsock[pid: 57, name: /usr/bin/orchagent] from : begin to drop messages due to rate-limiting +2026 May 14 05:41:28.000183 gold210 INFO syncd#rsyslogd: imuxsock[pid: 51, name: /usr/bin/syncd]: 48814808 messages lost due to rate-limiting (20000 allowed within 300 seconds) +2026 May 14 05:41:28.000249 gold210 INFO swss#rsyslogd: imuxsock[pid: 57, name: /usr/bin/orchagent]: 1024472 messages lost due to rate-limiting (20000 allowed within 300 seconds) +``` + +### 6.3. New design overview + +Replace the concrete `std::queue` member with a pointer +to an abstract `NotificationQueueBase` strategy. Provide two concrete +strategies: + +- `FifoNotificationQueue` — preserves existing behavior, default. +- `LruDedupNotificationQueue` — opt-in; collapses duplicate payloads + on enqueue. + +Add a new constructor overload that takes an `NotificationQueuePolicy` enum. +Existing 4-argument constructors keep their original mangled symbol +and select `Fifo` automatically. + +``` ++----------------------------------+ +| NotificationConsumer | +| m_queue: unique_ptr | ++----------------------------------+ + | + v (strategy selected at construction) + +-----------+-----------+ + | | ++---v-------------+ +-----v---------------------+ +| Fifo | | LruDedup | +| std::queue | | std::list + | +| | | unordered_map | ++-----------------+ +---------------------------+ +``` + +Existing call sites: unchanged. Opt-in is one extra constructor argument: +``` +// FdbOrch +m_fdbNotificationConsumer = new NotificationConsumer( + db, "NOTIFICATIONS", 100, DEFAULT_NC_POP_BATCH_SIZE, + NotificationQueuePolicy::LruDedup); + +// PortsOrch port-state consumer +m_portStatusNotificationConsumer = new NotificationConsumer( + db, "PORT_STATE_NOTIFICATIONS", 100, DEFAULT_NC_POP_BATCH_SIZE, + NotificationQueuePolicy::LruDedup); + +// libsairedis RedisChannel consume +m_notificationConsumer = std::make_shared( + m_dbNtf.get(), REDIS_TABLE_NOTIFICATIONS_PER_DB(dbAsic), 100, swss::DEFAULT_NC_POP_BATCH_SIZE, + swss::NotificationQueuePolicy::LruDedup); + +``` + +Each opt-in is one line right after the consumer is constructed. The allowlist is mechanically derived from that orch's `doTask(NotificationConsumer&)`: +``` +// fdborch.cpp +m_fdbNotificationConsumer->setOpAllowList({"fdb_event"}); +m_flushNotificationsConsumer->setOpAllowList( + {"ALL", "PORT", "VLAN", "PORTVLAN"}); + +// portsorch.cpp +m_portStatusNotificationConsumer->setOpAllowList({"port_state_change"}); +m_portHostTxReadyNotificationConsumer->setOpAllowList({"port_host_tx_ready"}); + +// bfdorch.cpp +m_bfdStateNotificationConsumer->setOpAllowList({"bfd_session_state_change"}); + +// icmporch.cpp +m_icmpStateNotificationConsumer->setOpAllowList({"icmp_echo_session_state_change"}); + +// twamporch.cpp +m_twampNotificationConsumer->setOpAllowList({"twamp_session_event"}); + +// p4orch/p4orch.cpp +m_portStatusNotificationConsumer->setOpAllowList({"port_state_change"}); +``` + +### 6.4. LRU Dedup Example + +This section spells out exactly what the dedup operates on and why the end state in STATE_DB matches the un-deduplicated case. + +#### Scenario + +A single MAC `08:00:27:11:22:33` in VLAN 100 (`bv_id=oid:0x26000000000064`) +flapping between two ports on a switch +(`switch_id=oid:0x21000000000000`): + +- Port A = `Ethernet0`, `bridge_port_id=oid:0x3a000000000001` +- Port B = `Ethernet4`, `bridge_port_id=oid:0x3a000000000002` + +ASIC sends `fdb_event` notifications at line rate, alternating LEARNED +and AGED between A and B. + +#### Notificaition message — exactly what dedup hashes + +`NotificationProducer::send(op, data, values)` produces a flat JSON +array of strings. An example message sent to the channel: + +**LEARNED on port A** +``` +["fdb_event","[{\"fdb_entry\":{\"switch_id\":\"oid:0x21000000000000\",\"bv_id\":\"oid:0x26000000000064\",\"mac\":\"08:00:27:11:22:33\"},\"fdb_event\":\"SAI_FDB_EVENT_LEARNED\",\"list\":[{\"id\":\"SAI_FDB_ENTRY_ATTR_BRIDGE_PORT_ID\",\"value\":{\"oid\":\"oid:0x3a000000000001\"}},{\"id\":\"SAI_FDB_ENTRY_ATTR_TYPE\",\"value\":\"SAI_FDB_ENTRY_TYPE_DYNAMIC\"}]}]"] +``` + +#### Queue state — first 7 publishes, FIFO vs LRU + +- **Msg A — LEARNED on port A** +- **Msg B — AGED on port A** +- **Msg C — LEARNED on port B** +- **Msg D — AGED on port B** + +Suppose the main thread is busy and doesn't `pop` for the first 7 +publishes. Arrival order: A, B, C, D, A, B, C + +| t | publish | FIFO queue (front → back) | LRU queue (front → back) | +|----|---------|----------------------------|----------------------------| +| 1 | A | `[A]` | `[A]` | +| 2 | B | `[A,B]` | `[A,B]` | +| 3 | C | `[A,B,C]` | `[A,B,C]` | +| 4 | D | `[A,B,C,D]` | `[A,B,C,D]` | +| 5 | A | `[A,B,C,D,A]` | A in idx → erase, append → **`[B,C,D,A]`** | +| 6 | B | `[A,B,C,D,A,B]` | B in idx → erase, append → **`[C,D,A,B]`** | +| 7 | C | `[A,B,C,D,A,B,C]` | C in idx → erase, append → **`[D,A,B,C]`** | + +FIFO grows linearly with arrival rate. LRU caps at 4 entries (the +distinct-payload count). The LRU ordering is **least-recently-seen +first** — that's the invariant on which drain correctness depends. + +#### End-state equivalence — drain at t=7 + +FIFO drain order = `[A, B, C, D, A, B, C]` (7 ops) + +LRU drain order = `[D, A, B, C]`: (4 ops) + +Drain: + +| pop | msg | effect | +|-----|-----|--------| +| 1 | D | AGED on port B → row didn't exist yet → skip | +| 2 | A | LEARNED port A → insert | +| 3 | B | AGED on port A → remove | +| 4 | C | LEARNED port B → insert | +| **end** | | **FDB entry: MAC on port B** ✓ matches ASIC | + +### 6.5. New design - Op-Allowlist Filter + +Redis pub/sub delivers a full copy of every PUBLISH to every SUBSCRIBE. A +single fdb_event from syncd therefore enters the queue of every one of these +consumers. Each consumer's doTask only acts on the match and silently +discards the rest — but the discard happens after the message has been +popped off the queue, not before it was pushed in. + +``` +syncd PUBLISH ["fdb_event", ...] on "NOTIFICATIONS" + | + | Redis fanout + v + +----------------------------+ + | BfdOrch:: | + | m_bfdStateNotification- | + | Consumer (FIFO) | + | | + | processReply(msg) | <-- peekOp(msg) == "bfd_..."; else DROP + | | + | | + | m_queue.push(msg) | + +----------------------------+ + | + | (main thread, eventually) + | doTask(consumer) + v + +----------------------------+ + | consumer.pop(op,data,..) | <-- parses JSON + | | + +----------------------------+ +``` +## 7. High-Level Design + +### 7.1. NotificationQueueBase strategy interface + +```cpp +class NotificationQueueBase { +public: + virtual ~NotificationQueueBase() = default; + virtual void push(const std::string& msg) = 0; + virtual const std::string& front() const = 0; + virtual void pop() = 0; + virtual bool empty() const = 0; + virtual size_t size() const = 0; +}; +``` + +Single-threaded; no locking. The owning `NotificationConsumer` already +guarantees single-threaded access via its main-loop usage pattern. + +### 7.2. FifoNotificationQueue + +A thin wrapper that delegates every method to `std::queue`. +Functionally identical to the pre-change member. This is the default +strategy and the one selected by every existing call site that doesn't +pass an `NotificationQueuePolicy`. + +### 7.3. LruDedupNotificationQueue + +Backed by a `std::list` and an +`std::unordered_map`: + +- **push(msg)**: if `msg` is already present in the index, erase its + current position from the list. Append `msg` at the tail and update + the index. +- **pop()**: remove the head of the list and erase the corresponding + index entry. + +Result: +- The list never contains duplicate payloads. +- Drain order is "least-recently-pushed unique payload first." +- Memory is bounded by `count(distinct payloads ever in flight)`. + +Both `push` and `pop` are O(1) amortized. + +### 7.4. NotificationConsumer API + +Add a new constructor overload (the original 4-arg form is preserved +verbatim — see [7.6](#76-abi-compatibility)): + +```cpp +enum class NotificationQueuePolicy { + Fifo, // default + LruDedup // opt-in +}; + +class NotificationConsumer : public Selectable { +public: + // Original 4-arg form, defaulted args, ABI-preserving. + NotificationConsumer(DBConnector *db, + const std::string &channel, + int pri = 100, + size_t popBatchSize = DEFAULT_NC_POP_BATCH_SIZE); + + // New 5-arg form with explicit policy. + NotificationConsumer(DBConnector *db, + const std::string &channel, + int pri, + size_t popBatchSize, + NotificationQueuePolicy policy); + ... +}; +``` + +The public `pop()`, `pops()`, `peek()`, `hasData()`, `hasCachedData()`, +`getFd()`, `readData()` API is unchanged. + +### 7.5. Telemetry + +Storm: 1000 MACs flapping between two ports, sustained at +~50 K `fdb_event`/sec from syncd. + +#### Syslog — periodic stats (every 5 s per active consumer) + +``` +NOTICE swss#orchagent: LruDedupNotificationQueue[FdbOrch:fdb_event]: + stats pushed=15176725 dedup_hits=14049124 dedup_ratio=92.57% + depth=3999 hwm=4000 (dedup=on) + +NOTICE swss#orchagent: LruDedupNotificationQueue[libsairedis:RedisChannel:NOTIFICATIONS]: + stats pushed=15176717 dedup_hits=4959602 dedup_ratio=32.68% + depth=1 hwm=4000 (dedup=on) + +NOTICE swss#orchagent: NotificationConsumer[FdbOrch:fdb_event]: + stats received=15176727 dropped_allowlist=2 admit_ratio=100.00% allowlist_entries=1 + +NOTICE swss#orchagent: NotificationConsumer[libsairedis:RedisChannel:NOTIFICATIONS]: + stats received=15176717 dropped_allowlist=0 admit_ratio=100.00% allowlist_entries=0 + +NOTICE swss#orchagent: NotificationConsumer[P4Orch:port_state_change]: + stats received=15176727 dropped_allowlist=15176725 admit_ratio=0.00% allowlist_entries=1 + +NOTICE swss#orchagent: NotificationConsumer[BfdOrch:bfd_session_state_change]: + stats received=15176727 dropped_allowlist=15176727 admit_ratio=0.00% allowlist_entries=1 + +NOTICE swss#orchagent: NotificationConsumer[PortsOrch:port_state_change]: + stats received=15176727 dropped_allowlist=15176725 admit_ratio=0.00% allowlist_entries=1 +``` + +#### COUNTERS_DB — `NOTIFICATION_CONSUMER_STATS:` + +Snapshot via `redis-cli -n 2 hgetall NOTIFICATION_CONSUMER_STATS:`: + +| Consumer | policy | allowlist | received | dropped@adm | admitted | admit % | lru_pushed | lru_dedup_hits | lru_dedup % | depth | HWM | +|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| `FdbOrch:fdb_event` | LruDedup | `{fdb_event}` | 9,386,892 | 2 | 9,386,890 | 99.999979 | 9,386,890 | 8,719,102 | **92.89 %** | **4000** | **4000** | +| `PortsOrch:port_state_change` | LruDedup | `{port_state_change}` | 9,386,893 | 9,386,891 | 2 | 0.000021 | 2 | 0 | 0 | 0 | 1 | +| `P4Orch:port_state_change` | Fifo | `{port_state_change}` | 9,386,891 | 9,386,889 | 2 | 0.000021 | — | — | — | — | — | +| `BfdOrch:bfd_session_state_change` | Fifo | `{bfd_session_state_change}` | 9,386,892 | 9,386,892 | 0 | 0 | — | — | — | — | — | +| `FdbOrch:flush` | Fifo | `{ALL,PORT,VLAN,PORTVLAN}` | 2 | 0 | 2 | 100 | — | — | — | — | — | +| `libsairedis:RedisChannel:NOTIFICATIONS` *(syslog-only)* | LruDedup | *(none)* | 15,176,717 | 0 | 15,176,717 | 100 | 15,176,717 | 4,959,602 | 32.68 % | 1 | 4000 | + + +### 7.6. Modules and repositories affected + +| Repo | File | Change | +|------|------|--------| +| `sonic-swss-common` | `common/notificationconsumer.{h,cpp}` | Add `NotificationQueueBase`, `FifoNotificationQueue`, `LruDedupNotificationQueue`, `NotificationQueuePolicy`, new constructor overload | +| `sonic-swss-common` | `tests/notification_queue_ut.cpp` | New unit tests for both queue strategies | +| `sonic-swss` | `orchagent/fdborch.cpp` | Opt `m_fdbNotificationConsumer` into `LruDedup` | +| `sonic-swss` | `orchagent/portsorch.cpp` | Opt `m_portStatusNotificationConsumer` and `m_portHostTxReadyNotificationConsumer` into `LruDedup` | + +### 7.7 Future Enhancements + +The following are **explicitly out of scope** for this HLD; they will +be addressed in a follow-on (Phase 2) HLD covering the producer side +of the same notification pipeline: + +- **Producer-side queue policy in `sonic-sairedis/syncd`.** + `syncd::NotificationQueue` (`sonic-sairedis/syncd/NotificationQueue.h`) + today uses a destructive FIFO + drop-on-full policy. When syncd's + internal queue fills up (default 300,000 entries), all subsequent + FDB events are unconditionally discarded at enqueue, biasing + toward stale state. + +- **SAI/ASIC-level rate limiting or station-move suppression.** + Some SAI implementations expose per-MAC station-move detection or + FDB-learn-rate caps that bound the producer rate at hardware. Phase 2 + may recommend enabling these where available; this HLD does not + require them. + +- **Redis pubsub backpressure / `client-output-buffer-limit`.** + In observed SONiC configurations the redis pubsub output buffer + is unbounded (`pubsub 0 0 0`), which is a separate system-wide + reliability concern. Tightening it requires coordinated changes to + consumer reconnect-and-resync logic, which is its own work item + and is out of scope here. + +- **Per-channel telemetry counters in `COUNTERS_DB`.** + Operator-facing dashboards for "how often is dedup collapsing + duplicates" / "what is the queue high watermark" are useful follow-ons + but are not required for the correctness of this change. The + in-process `m_high_watermark` counter and the existing + `SWSS_LOG_NOTICE`-on-new-max are debug-grade only. + +## 8. SAI API + +No SAI API changes. + +## 9. Configuration and management + +### 9.1. CLI/YANG model Enhancements + +No CLI or YANG changes. The policy is selected at compile time by the +owning orch, not exposed as a runtime configuration knob. + +### 9.2. Config DB Enhancements + +No Config DB schema changes. + +## 10. Warmboot and Fastboot Design Impact + +None. The change is internal to in-process queue management and does +not affect any persisted state or warmboot/fastboot handoff. + +## 11. Memory Consumption + +For an opted-in consumer under a sustained storm, peak `m_queue` +memory is bounded by `count(distinct payloads) × bytes(payload)`. +For typical FDB and port-state storms this is on the order of +hundreds of entries × ~500 bytes ≈ a few hundred kilobytes. Prior to +this change the same workload could drive `m_queue` into the millions +of entries. + +Consumers that don't opt in retain the existing `std::queue` +memory profile. + +## 12. Restrictions/Limitations + +- LRU-dedup applies only to channels whose downstream consumers are + **end-state-idempotent** — i.e. only the most-recent payload per + logical key needs to be acted on. Channels with order-sensitive or + cumulative semantics (e.g. `FLUSHFDBREQUEST`, sequence-numbered + commands) must remain `Fifo` and are not opted in by this change. +- Drain order under `LruDedup` is not strict arrival order; it is + last-seen-time per unique payload. Opt-in callers are responsible + for verifying their downstream logic tolerates this re-ordering. + For `FdbOrch::update()` and the relevant `PortsOrch` handlers, + audit has confirmed this is safe: each event independently encodes + current state, and the existing implicit-move detection in + `FdbOrch` correctly handles cases where the consumer-side queue + collapses `AGED+LEARNED` pairs. +- The dedup key is the entire serialized payload string. If a future + notification format embeds a per-event nonce (timestamp, sequence + number) into the payload, dedup will not collapse otherwise- + equivalent events. No such field exists today in the affected + channels. +- This design addresses the consumer side only. It does not bound + producer-side queue growth in syncd. A separate producer-side + policy is the subject of a follow-on HLD. + +## 13. Testing Requirements/Design + +### 13.1. Unit Test cases + +`sonic-swss-common/tests/notification_queue_ut.cpp` covers, without +requiring a redis instance: + +- `FifoNotificationQueue` preserves arrival order across mixed + push/pop sequences. +- `LruDedupNotificationQueue` collapses byte-identical pushes: + `push("a"); push("b"); push("a")` → `size()==2`, drain yields + `["b", "a"]`. +- `LruDedupNotificationQueue` is robust to many repeated identical + pushes (size remains 1). +- `LruDedupNotificationQueue.pop()` on an empty queue is a no-op. + +### 13.2. System Test cases + +- FDB-flap reproduction: inject a MAC-move storm involving multiple + distinct source MACs across two ports; verify orchagent RSS stays + bounded over a sustained run, and that STATE_DB FDB_TABLE settles + to the correct final state once the storm ends. +- Port-flap reproduction: simulate a flapping transceiver (rapid + link up/down on the same port); verify orchagent RSS stays bounded + and STATE_DB PORT_TABLE oper-status reflects the final state. +- Soak test: run the storm workload for ≥ 24 hours and verify no + RSS growth attributable to `m_queue`. + +## 14. Open/Action items + +- A separate producer-side queue policy in `sonic-sairedis/syncd` + (`syncd::NotificationQueue`) will be proposed in a follow-on HLD. + The producer side currently uses a destructive drop-on-full policy + that biases toward stale state; a non-destructive opt-in coalescer + there would close the remaining gap. +- Telemetry surface (per-channel push/collapse counters published to + `COUNTERS_DB`) is left as a follow-on. Operators can use existing + process-memory metrics and syslog patterns to monitor in the + interim.