From 907c83e4cbaf38300bb96c804ccd9e9dff860a2e Mon Sep 17 00:00:00 2001
From: Rahul Rampure What are docValues? In the index mapping, there is an option to enable or disable docValues for a specific field mapping. However, what does it actually mean to activate or deactivate docValues, and how does it impact the end user? This document aims to address these questions. Enabling docValues will always result in an increase in the size of your Bleve index, leading to a corresponding increase in disk usage. But what advantages can you expect in return? This document also quantitatively assesses this trade-off with a test case. In a more general sense, we recommend enabling docValues on a field mapping if you anticipate queries that involve sorting and/or facet operations on that field. It's important to note, though, that sorting and faceting will work irrespective of whether docValues are enabled or not. This may lead you to wonder if there's any real benefit to enabling docValues since you're allocating extra disk space without an apparent return. The real advantage, however, becomes evident in enhanced query response times and reduced memory consumption during active usage. By accepting a minor increase in the disk space used by your Full-Text Search (FTS) index, you can anticipate better performance in handling search requests that involve sorting and faceting. The initial use of docValues comes into play when sorting is involved. In the search request JSON, there is a field named "sort." This optional "sort" field can have a slice of JSON objects as its value. Each JSON object must belong to one of the following types:
+ Purpose of Docvalues
+
+Background
+
+
+ "default_mapping": {
+ "dynamic": true,
+ "enabled": true,
+ "properties": {
+ "loremIpsum": {
+ "enabled": true,
+ "dynamic": false,
+ "fields": [
+ {
+ "name": "loremIpsum",
+ "type": "text",
+ "store": false,
+ "index": true,
+ "include_term_vectors": false,
+ "include_in_all": false,
+ "docvalues": true
+ }
+ ]
+ }
+ }
+
+Usage
+
+
+
+
DocValues are relevant only when any of the JSON objects in the "sort" field are of type SortGeoDistance or SortField. This means that if you expect queries on a field F, where the queries either do not specify a value for the "sort" field or provide a JSON object of type SortDocID or SortScore, enabling docValues will not improve sorting operations, and as a result, query latency will remain unchanged. It's worth noting that the default sorting object, SortScore, does not require docValues to be enabled for any of the field mappings. Therefore, a search request without a sorting operation will not utilize docValues at all.
+No Sort Objects | +SortDocID | +SortScore | +SortField | +SortGeoDistance | +
---|---|---|---|---|
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field":"dolor" + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field":"sit_amet" + }, + "sort":[ + { + "by":"id", + "desc":true + } + ], + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field":"sit_amet" + }, + "sort":[ + { + "by":"score", + } + ], + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field":"sit_amet" + }, + "sort":[ + { + "by":"field", + "field":"dolor", + "type":"auto", + "mode":"min", + "missing":"last" + } + ], + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "dolor" + }, + "sort": [ + { + "by": "geo_distance", + "field": "sit_amet", + "location": [ + 123.223, + 34.33 + ], + "unit": "km" + } + ], + "size": 10, + "from": 0 +} ++ |
+
No DocValues used | +No DocValues used | +No DocValues used | +DocValues used for field "dolor". Field Mapping for "dolor" may enable docValues. | +DocValues used, for field "sit_amet". +Field Mapping for "sit_amet" may enable docValues. | +
Now, let's consider faceting. The search request object also includes another field called "facets," where you can specify a collection of facet requests, with each request being associated with a unique name. Each of these facet requests can fall into one of three types: +
No Facet Request | +Date Range Facet | +Numeric Range Facet | +Term Facet | +
---|---|---|---|
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "dolor" + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "sit_amet" + }, + "facet": { + "facetA": { + "size": 1, + "field": "dolor", + "date_ranges": [ + { + "name": "lorem", + "start": "20/August/2001", + "end": "22/August/2002", + "datetime_parser": "custDT" + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "sit_amet" + }, + "facet": { + "facetA": { + "size": 1, + "field": "dolor", + "numeric_ranges":[ + { + "name":"lorem", + "min":22, + "max":34 + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "sit_amet" + }, + "facet": { + "facetA": { + "size": 1, + "field": "dolor" + } + }, + "size": 10, + "from": 0 +} ++ |
+
No DocValues used | +DocValues used for field "dolor". Field Mapping for "dolor" may enable docValues. | +
In summary, when a search request is received by the Bleve index, it extracts all the fields from the sort objects and facet objects. To potentially benefit from docValues, you should consider enabling docValues for the fields mentioned in SortField and SortGeoDistance sort objects, as well as the fields associated with all the facet objects. By doing so, you can optimize sorting and faceting operations in your search queries.
+ +Combo A | +Combo B | +
---|---|
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "sit_amet" + }, + "facet": { + "facetA": { + "size": 1, + "field": "dolor", + "date_ranges": [ + { + "name": "lorem", + "start": "20/August/2001", + "end": "22/August/2002", + "datetime_parser": "custDT" + } + ] + } + }, + "sort":[ + { + "by":"field", + "field":"lorem", + "type":"auto", + "mode":"min", + "missing":"last" + } + ], + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "lorem ipsum", + "field": "sit_amet" + }, + "facet": { + "facetA": { + "size": 1, + "field": "dolor", + "numeric_ranges":[ + { + "name":"lorem", + "min":22, + "max":34 + } + ] + } + }, + "sort": [ + { + "by": "geo_distance", + "field": "ipsum", + "location": [ + 123.223, + 34.33 + ], + "unit": "km" + } + ], + "size": 10, + "from": 0 +} ++ |
+
DocValues used for field "dolor" and "lorem". Field Mapping for "dolor" and "lorem" may enable docValues. | +DocValues used for field "dolor" and "ipsum". Field Mapping for "dolor" and "ipsum" may enable docValues. | +
To evaluate our hypothesis, I've set up a sample dataset on my personal computer and I've created two Bleve indexes: one with docvalues enabled for three fields (dummyDate
, dummyNumber
, and dummyTerm
), and another where I've disabled docValues for the same three fields. These field mappings were incorporated into the Default Mapping. It's important to mention that for both indexes, DocValues for dynamic fields were enabled, as the default mapping is dynamic.
The values for dummyDate
and dummyNumber
were configured to increase monotonically, with dummyDate
representing a date value and `dummyNumber` representing a numeric value. This setup was intentional to ensure that facet aggregation would consistently result in cache hits and misses, providing a useful testing scenario.
Index A | +Index B | +
---|---|
+ + "default_mapping": { + "dynamic": true, + "enabled": true, + "properties": { + "dummyNumber": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyNumber", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": true + } + ] + }, + "dummyTerm": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyTerm", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": true + } + ] + }, + "dummyDate": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyDate", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": true + } + ] + } + } + } ++ |
+
+ + "default_mapping": { + "dynamic": true, + "enabled": true, + "properties": { + "dummyNumber": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyNumber", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": false + } + ] + }, + "dummyTerm": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyTerm", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": false + } + ] + }, + "dummyDate": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "name": "dummyDate", + "type": "text", + "store": false, + "index": true, + "include_term_vectors": false, + "include_in_all": false, + "docvalues": false + } + ] + } + } + } ++ |
+
Docvalues enabled across all three field mappings | +Docvalues disabled across all three field mappings | +
Document 1 | +Document 2 | +... Document i | +Document 5000 | +
---|---|---|---|
+ +{ + "dummyTerm":"Term", + "dummyDate":"2000-01-01T00:00:00, + "dummyNumber:1 +} ++ |
+
+ +{ + "dummyTerm":"Term", + "dummyDate":"2000-01-01T01:00:00, + "dummyNumber:2 +} ++ |
+
+ +{ + "dummyTerm":"Term", + "dummyDate":"2000-01-01T01:00:00"+(i hours), + "dummyNumber:i +} ++ |
+
+ +{ + "dummyTerm":"Term", + "dummyDate":2000-01-01T01:00:00 + (5000 hours), + "dummyNumber:5000 +} ++ |
+
Now I ran the following set of search requests across both the indexes, while increasing the number of documents indexed from 2000 to 4000.
+ +Request 1 | +Request 2 | +... Request i | +Request 1000 | +
---|---|---|---|
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "term", + "field":"dummyTerm" + }, + "facets":{ + "myDate":{ + "field":"dummyDate", + "size":100000, + "date_ranges":[ + { + "start":"2000-01-01T00:00:00", + "end":"2000-01-01T01:00:00" + } + ] + }, + "myNum":{ + "field":"dummyNumber", + "size":100000, + "numeric_ranges":[ + { + "min": 1000, + "max": 1001 + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "term", + "field":"dummyTerm" + }, + "facets":{ + "myDate":{ + "field":"dummyDate", + "size":100000, + "date_ranges":[ + { + "start":"2000-01-01T01:00:00", + "end":"2000-01-01T02:00:00" + } + ] + }, + "myNum":{ + "field":"dummyNumber", + "size":100000, + "numeric_ranges":[ + { + "min": 999, + "max": 1000 + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "term", + "field":"dummyTerm" + }, + "facets":{ + "myDate":{ + "field":"dummyDate", + "size":100000, + "date_ranges":[ + { + "start":"2000-01-01T00:00:00" + i hour + "end":"2000-01-01T00:00:00" + (i+1) hour + } + ] + }, + "myNum":{ + "field":"dummyNumber", + "size":100000, + "numeric_ranges":[ + { + "min": 1000-i, + "max": 1000-i+1 + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
+ +{ + "explain": true, + "fields": [ + "*" + ], + "highlight": {}, + "query": { + "match": "term", + "field":"dummyTerm" + }, + "facets":{ + "myDate":{ + "field":"dummyDate", + "size":100000, + "date_ranges":[ + { + "start":"2000-01-01T01:00:00" + 1000 hour, + "end":"2000-01-01T02:00:00" + 1001 hour + } + ] + }, + "myNum":{ + "field":"dummyNumber", + "size":100000, + "numeric_ranges":[ + { + "min": 0, + "max": 1 + } + ] + } + }, + "size": 10, + "from": 0 +} ++ |
+
Bleve index size growth with increase in indexed documents | +Total query time for 1000 queries with increase in number of indexed documents | ++ | + + |
---|
Average increase in index size (in bytes) by enabling DocValues | +Average reduction in time taken to perform 1000 queries (in milliseconds) by enabling DocValues | +
---|---|
7762.47 |
+ 27.034 |
+
When a search request involves facet or sorting operations on a field F, these operations occur after the main search query is executed. For instance, if the main query yields a result of 200 documents, the sorting and faceting processes will be applied to these 200 documents. However, the main query result only provides a set of document IDs, not the actual document contents.
+ +Here's where docValues become essential. If the field mapping for F is docValue enabled, the system can directly access the values for the field from the stored docValue part in the index file. This means that for each document ID returned in the search result, the field values are readily available.
+ +However, if docValues are not enabled for field F, the system must take a different approach. It needs to "fetch the document" from the index file, read the value for field F, and cache this field-document pair in memory for further processing. The issue becomes apparent in the latter scenario. By not enabling docValues for field F, you essentially retrieve all the documents in the search result (at the worst case), which can be a substantial amount of data. Moreover, you have to cache this information in memory, leading to increased memory usage. As a result, query latency significantly suffers because you're essentially fetching and processing all documents, which can be both time-consuming and resource-intensive. Enabling docValues for the relevant fields is, therefore, a crucial optimization to enhance query performance and reduce memory overhead in such situations.
diff --git a/docs/sort_facet_supporting_docs/indexSizeVsNumDocs.png b/docs/sort_facet_supporting_docs/indexSizeVsNumDocs.png new file mode 100644 index 0000000000000000000000000000000000000000..11211709d5faa1e6d27e8a493f49ef532cc56de3 GIT binary patch literal 30918 zcmd?RWmuJMw>CNf0TmDl2`QbFC=Jpf0wPi>N=UazcZVWf0!o*nqN23YA>Ang(m6r8 zC;g4v_xRRYd#~U7*YI3!>M^3p&*6^M`e_(wuDMCLG=%%?Zu`&8=KY&$gcy(1)1Q>3hPs?pzwXRRqCy6?- z!4akt)jss#vH*$&oEPrb^2NIUDYjf5t4M%*+yu&Ga&1iwV!GY_AM~_mJU1UbX $f_6n5gx^58)=Sn!|anAC;l4cw{<;R^2b&C2(-5l>sW?Atw-Mr8vrqeIL|0Pxr1| zG>KRoF3LupBJAf);mghsK0o|G8DQtSHf{qfx#7{#kurcA74VTxAOO;qD9Fi`A>97^ z$JNm?m8VY^Tmo)Eg<*VM JXE5Mae}TT~EV6QtRC6fChQM||Fs?`=@{ z{|3OB47es4U>ROwem<{B$4 z2mzVn9;P)q3SJ7#Ja~eRVr PuCd|>U TjhvP} zkqwW(JUeCk3!f93M{UzHZ%q`iQ-rAHxf`2xAG(uViV1d>>RV!d$Fu*wPlU9pbHc&{ zG``VKbvF}kt$H1mY`U5=e3MC+SRZrCOgSxOuUa8ZB+w4e>6tK#A)Y_-c-o8m!`fl= zrQy`=j|^4~+jh0}^<*cTvjydM3&L $Fp8RvoZ*_8g6*&~1OF|B4+S(+6p7-aL z$4Q0@e{Rk#Z~DBJ;-b%)@F4s $$#XQVHXNA-d&-g)7UBG|Mm;;z-bkw|dB-n4(~VHXv7ulgCy!pQc6yu=#dIa5 z^UipsM=Nklz@2L(Fgp%bR#gpbvd50EjGj|T5t~1_p&nE;kvlyQgO8o3YyO)mYBVm< zl0No_pVj9+N*NERpo2|lXAxTTvo-riFFaL#^Tm5(`@Fu@tMEjc{NYRmQW}9fQ)(v} zNU#6yo!yz}o*!Jg)ym2r#ES1e>1YxTKz-66kvb%XBCPa|qT9dzAO9DRt;sUX6~34; z{Nx8SgC4qU6vKt0Pmapn2wiTqMlSH*2Non^p`C|sWkl8(y;U`$zOsi8-_olPYkIN0 z{CS;SIQw6BvDX`W)p;qX)7Ikrm _~)|GD0IlhUJuvoyRY z|2_A}=h{-3`p|Uii3{JKY3pW~()=@E7QB4+VbS%58xqCy%&4IC3)x~9*fxDqY!}(w zlo7cDOWMeH$x!K((}a0UHLjs^y4q7Q@t2-EoZQO%od0N^_v*d9yjNJ#O$IVP?3d-; zt&1}|O=``IJMZUc_wPRXXicEj&%F0pBpv(plFd2h-uhr7bxiM31@-#wkMi5C3;zr~ zHfni{=<3ycuVat+z86odueS0Cj4M&a7Cm6jX{VqDwhvav`N~xseLsflnn4r4GmO%{ zNqrxB(VBcIZ+#$hoqaFn3F32{yL%Jsztr<>87QaE= r$fyZ1?}_b+*4 z<3Ig|*21LB(_!)p3@@%HF%K`CiX@$g;Rh1c{~fsh?&Sb$7|}y&sIf!j>+0X$AyL3C z#aod{?@P>|^)qX$ZHheaw^{q2(?C)CR*@n 1`G$xiT+!yrSaWsM( z*G2Ps13uf+UehKw@963DX8n)q 4ziViC1sZR^ zzG}w-G@5YCD09L9N~47of%N(So+xEKlb35q dMLKzTxA&y3MaJLTqT}t#{A FB*=#$^Blt9^H?Ej=r>E_WdqqRr zORpps%2z183QSwcF@-=t-!F4CgLZT3-@%a3(5u(3g)A&Q1y*iNX$%;#4OX@~FEzWY zv3Fgsuacv@5^~*@v8Wd>ICE9Ek5|xK{)6{aRm~wA!Wkbx&Ze_1GHa&-?mX348_GgR z_U&BJ3`|UF%F4=N!N6yJ@5+d>ux hhSK#|9fwY8~D*Z8vvuK@U14d7yl}y{~Q5c(F+xt=AtxfvTJ; z5(9 HzU*;5twgU9g%jOVPXnJYM>oklCqLba8?TPI`QXRV+PQKWoz$2|w zT6xAcOB+3O$s|9QKSI!iMn~67l>-(94k($j9RRgUzZC&EW^UZ;mp %R!MI9-bqYp@W3iOSeOQS~HG9H`9ikZw*+6{_5r zz6T^L^aSDoCPKSe{{H=oz BINRZ_O<47n)!%RI&%Ft$U zoKyQ*mb*hvI7OLL`9m4-X!D5Ypr?`6AcSwxe-ybneEZSJMIpNf*tiHKBudZT>B?vx zFXyV u~bPvfB(c~J<`#6U#Po0@L*!ke4O;W-?Z4jZ{XUWNKpT&zWduH z>Or-Wd(ek(*_&gpuQ+0UlnTWhDsw>)o8BcCvYzqoy>6;k^2*)YU2-dXsQNTLkLub{ zsMSLRu9Whai0uEB+f;Q=vu2B_r^JLeV|dp`zRb3nxj3LnqmZOp`T^UI5q-EP-^KuU z!oBT7u(kiF$@M~EDSr$hs7wG_b)77Zob~)HIY-Wr aGutvM&2!Uh4mzHuZ+4q1?n)>gR#@pB4r`gXK;>?GXR0IlZkB zC{6b7M!70nW%e9X+&5;f7n@%VWt3{DI^@_F_9d11yLk!c-{ytiRSnvBdu~V}!ZgT- zGkqwOi2KgGnbSt@7Y*qcD-LL1+zoQuzDDeGAWX3Ao&IbYHF@u_%QStH> !@qD~1MD-_985{C-xA+~BZX^}Wh#xy0rF;DOyD8VlrH2P*K-&jEuWZqBbLZ(# z!gD??4QAZZyCS}Xdxq**iZWKDS8_L~$PkBffxd~5Z>+U-g2iKhdSko-ySIYJ_-y22 z83XcIt-Qv@hV~iZkM<8Pne&qPw|}@Xb6PLF-Xlw;$sX%~q|duSpw{umE>n$*A4@cf zhB46X4(d1iZQotr`Ccv&LoAPMQr~y1yZch79Dk%=N?Bz9yKC+l>bkYVB==uA7A_uW z+E~P1-s^bn)~0mRn?9Z0lBnxOu^eIWkJ$XaDyGS=KL7Ux@m8qF2!mz ^!eb!O95_8vlkw4wtSlM=TTvq3S_UtGa#XAQH`P*K+feGtZ$-FAEH}=g^>M}y` zA*cog1wE|tbU|2jC?hTVKav1nRWfD=%n@*SmqAZ(o{F>OMqXvzn;V*anZ{&fdj&># zf;m`1=D~q1LUleNx(J5w=1S3v7Y$)kZChE({RhA4DBQh!b|6QS9a`8|WrGm4&hFZT z8YpAzO1nX};XV^7jNNSK`3wOYSnBt{ Pl1p)@82)b^6e}D6;_YtxiSB4tbW3>!}x(00- z=w};3@FcC&CnM9OW+2fMqub<8ZFqJtr9!?Ne0{QvmpF_XjEJlRL#|hQO2xoHatm;U zUAYQSC_pL`N*`}iAc9ac02P9E<3}G4p)CfSR^0SeG@4`O5B|*@-M0gEw%&Knj | Q#f^I`8uXm^E kwMuGccKcVIZDK1(=?2#=B;e7anUf1 znDaGg1rq{i`wniYa;r;xZ$$#6o4px+b$%y!`gFew11um=C#H%PbfyuV9=K`A^<@jz z5Uhi?aW=ugcM9|iS#ap19F5N5dxTMoT0p$jx=r{r=hd9FbtDOR0N2)E*zl ?E{xK$sl2c{Vdg ?d{gllMQjD`-0{e|+os;VD>6@SgC9SdC4 z!)kAjC?;uJc}p}JcX{;OO{= zXe0qvo&vvl547j #|1HGFGqxK-nOe{1Rt 6a=1d|m2krt 4@#E5ztW uzqvp2jk0 buGLL*Hex-b_Je#vM=l5^=>hSgKg&;l?dS;)SGSwkU1@DPOL`6j# z1$jYz$gx@tUAwm<1)(7lhMKvxEipa$N7{dKms?f8^d#P-F)*N!7>XgTaD9xWaFB@~ zJHHB;ngtvNRWXKi$c8H1cu>%p&;P;+lK=c?m$ug~sTDCZ6(y9MLpK#n4_g#O(q$iF z!%9Vz=5Z=_ HNa| UFO#;Iq3SZ}*Sp8AFx`MiQiS &2-9lmOh T#S0ci-*Z=r1t)iN_;|E)t2&+V#(h_CTy_^P+@Mai6TjA>{aFr2w#J<%Oh7x;}^ zU5@kh{2*#|q9y|(03UG2>0Ya%u$%ITYJF>K283BhyCZfm5YYF-Mn@x8R}tvezH&K` zpe~h6Oa@T9ZjSD1CU%LTrk&>N-Mp`jcT4Bx>XYGcUdHzxfcE}ysRJ{+YU(76OU!m8 z#ep~g >R+ybiVZyC-DWr3elgb_*Z^BCd8GZB?>&Rt(z9|ULl?UpkbM? ztGM^*GuEp3@67%pCtaG)mz~%s?pMSQd
@fU*s1+{(_>3WDn+i)ao711$ z*UQ7P|0M4hW_?89?ai61EXN<_Oo!q@g!OQ>Xm4avkAJXVlPvpKZZ& (D7f=3gKH0gIKX!PRNNipITI 7MbDf-)e6u&&Qf`%W}tC$#U}LD98QHcxd2I5bQ+Pc?GC zF@ls7vbq6OrrJjgq3#<`((M+umvSh%tzC@J(_c7A{6WuS3US>T+FojEYM4!M=nVi` z+FD5=cQ1=N1-0M&8|P7E@&Yp+7{B?aD+ =* zG{xo1_dr8|03#$NK~oYsi{q{}AUr@stZ >#Hit&F=ah#mtuPY)JV< zF+BRPR-ckoP;h3rNJlR*q?AYr&k)ox+Li8nFqgmv+8{(UgGYEFA7Bw9xZfit3m_4} zsPkQxG?4x*q;BwptK8hH9}ihrSk|#K(N0HL2gsNT(8C0gFDp<*do#a)a&p_}kGLFI zC9fpG(_9eNMEs56=kmJYix*$HRpmw%%aaJ#eWC40507CE*ZB2KXD?_3+F3~m s;L%Hd%`uI z2a)LGcsC!8~&Ne^=BhZ1sfwu^%XZ)Nb@ceUZ9j =`Apsdj3NqoRf|k%)NaxFsV@TzzSnU5O1rm7t`d6YDCEH+NZV@sH5VkYW za@<}WOKE73yAwg IyK>JoS5$Vr{u Tm<=8ovz3@g14ZD%Z 7W`ac{^ zn!OJ4Drjb|ePSbXV9wDrIoPTH;U_I*Nd0D^S?(zLm&pbp@Ddu-*U;03i#Pod%25p_ zL0sB1KqRb@r=RwBhQ?%}KbsWPBVY9@;z1)j?wSEB?}S}9T6V2)8ipad))e72lKPC0 z78M3jT$?|&Gtnl%3DmU6sIhHikc-olmNMSZ02b`-fw3j3BZip}{H4-%A1N^$2z^+t zUcKrKSg|AdW;myIp*_;f1{oDtPTGj`-(R6{0r8AIAb(*XE*Kuy0l*