Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vuCEz-006h3T-2c for pgsql-hackers@arkaria.postgresql.org; Sun, 22 Feb 2026 16:23:30 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vuCEx-00BIa8-0S for pgsql-hackers@arkaria.postgresql.org; Sun, 22 Feb 2026 16:23:27 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vuCEw-00BIZz-2X for pgsql-hackers@lists.postgresql.org; Sun, 22 Feb 2026 16:23:26 +0000 Received: from mail-ed1-x535.google.com ([2a00:1450:4864:20::535]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1vuCEt-00000000jP4-3zYn for pgsql-hackers@lists.postgresql.org; Sun, 22 Feb 2026 16:23:26 +0000 Received: by mail-ed1-x535.google.com with SMTP id 4fb4d7f45d1cf-65c01595082so5115995a12.3 for ; Sun, 22 Feb 2026 08:23:24 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1771777404; cv=none; d=google.com; s=arc-20240605; b=j5Z77j1ASNACXlDQAMKS2j8QPx968uWunepbWATtl+WBr8x4WhpWSFiOO/FVmaLGTv hCiWEwph8Q6QRrjpZp8sVyyPy0vAfbG8THiJPO+BVE8uZogE+U+UeIkU/Ede7Z+ZzrIP d6OzZL8XStB2X0eWmkLsc6dJ33lgjFAZTKbgm8PMO10wsxGvhW3OTB2W8KTa6++2XmPp DSx9esfFle/OiMERuQmCx5vHtfhhj77s8r/lqg/CDac6xXxezaAeGDkE0RIU9yPdwaV7 CDVSBLaCzh4kz8Z7aqynpu9Q5a8gk0QdH7VdIcmIW9XajZ4lwUQh1+DgBY+nZ4i13ij3 0vEA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=3B2Od912UxMdkRWiup7mQLwczAaFWsnApKY61NvvCGs=; fh=O545Ye6pCaBrSHjdePHm+oLX5TMNsbpRc69ysL5DZCE=; b=IlqI1ssvYZaZD10CRMjeXMqtDB1yoryzG/tNMtBS0+YC7KztgEdeWfxVgTN7SY59dS m8oEBtX4E3ueOXRNzPTWBbvRq5LNuHAch4EA3rYeQj5qRywuv6VErusuZb00qbhOpfUa le6B0ZtVBOpssHE3phLosvuAcV2hmQPBL5SiEDlAcxnR4wODv8ijzl8qtXNUIZmAiGcJ tFdbEe627Xc8i+Plk9HuqyGig5Yri+xM/J8K+i7WNZkKCuW7AUM3bhcerGACj0IxoDaS TTKxY+TOuKHc242OcXBRun+uF3uccavmIE/LSHc268BzLXx+4KTw/B3BS9ArKalG45jp yKiw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771777404; x=1772382204; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=3B2Od912UxMdkRWiup7mQLwczAaFWsnApKY61NvvCGs=; b=b2wPOdAjOykFC3MVzK7IJ98oXznfDvTp1OEE5sUQv20KzmrA5uVDDOio03vMQAKWaR 1N/Lhg/Hz1SwEsbqfyxnJM6WTajzA1Kw5RwONvoQ08s6gjrdYuP6FusBizC988by5Iq8 rynuEaJb4E8RwsYUorVOFoY9YoOPfJihO9gbVWYMQ+vw54PY3Y8RCpbBdcwnSUHGfuLR pmC60XzBlrRvJbdxjWnQ666QVbWwlx6lRgbROuiP+HnLZJHa+nFru6BEliy5JToMGdNI 0uNuncYHaFxxQUAfIhadeebSdTYBOSA1IsPymsEyF3K4BtKs60PnVG/e71ScKVcN6Y45 MN2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771777404; x=1772382204; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=3B2Od912UxMdkRWiup7mQLwczAaFWsnApKY61NvvCGs=; b=BMCty3VgCOKmZAylb3vHvFTt/r9gDIlTD7W35M+wGySFlmHPgcJXxFEPY6B4VvNfA6 lYrTJ6dObrvWlWr3wDilcAQOOQ9ClQt2nKF/orsd0dMk38iIoOQjIanzZx0nIfvO3nB6 HPinfjhPMnonYCcIDeHrMzaUAbD84RdO7gWD8uWI0Aq13lY68oe9DhD2j4MH1H1oC88H Ws7QcP7cH9Vbp61CkO6H5pNH4FqQ9eyiS/9ndm7XU29LXRqXET8hsjCuRxImW+Le0C3U mn5y71EWv4sqe0hQSU/29PbZSan8Vn4bhB2VJ7BTSST2vfDyEVQFJ7ZsnY/vDj1VocRP 3oEA== X-Forwarded-Encrypted: i=1; AJvYcCWE7rESkQvm4/PXHuVbZDEUZ08YF8UcKWqaWk0UypgNQDek5sgBhtP1IenoRTClzsf4gwsyqssTeQuYVtnx@lists.postgresql.org X-Gm-Message-State: AOJu0YwlZqA3nv96k7NhWX+GcSykZU+fAvKtxVHlMOPAXj8WM2+5mjzu EOTbm6v6ntCxAUjhBEKwH6dloO8sN0i0BwHBFYS/ukD0izmigphStZjXrKCZQ2EbZJUOvRMzKTZ 0GT5B48A5AsRGyn4RaqrBdTD25a8FqrheSA5x X-Gm-Gg: AZuq6aI/OCZ0g91SMw2ke2ZR54p391r3q/RH5+NlhNXMYzqJqGJP3kL5kQ3MZ7lr/j3 3FOgUbzopvCTfk7KVsqw3ycrIC+6OwQNSURJQoPyBEvCwV6UL1fhLvUNeoCrubzQ45qH1tYAd4J cFwgzCLKrwFq1VrTWgOkoTE/MRZ6/1CGr2V+varBIrNfHxziF2zwBoIEQqYADFfSuJIPFCF80NC NjeV0ig1r5SgJhw7vQ1wQnBPIgN0d/PR8WFNnRj1YHl0DQ3VNJiZj6sLdn4hZ86uQTQqJ5LnpsP VeaUdZRAINY+zrDUmhQeKUW1BKlMx8C+XZEaZQt8Wgk1CwWhXg== X-Received: by 2002:a05:6402:430c:b0:65b:edef:40b6 with SMTP id 4fb4d7f45d1cf-65ea4ee05eamr3349298a12.6.1771777403498; Sun, 22 Feb 2026 08:23:23 -0800 (PST) MIME-Version: 1.0 References: <64a2re223ajj4popowsyu4xekbuvvyfwkrihn5yzyrkwsmsuvp@2lls3tpww5dl> <52512325-b1f2-4fff-819e-f68122b2e427@vondra.me> <64mfcfv7iihc4pmqlxarii4esnmqry52ckz5m7lmwylnfnuxuz@oxh4ioxkjtep> <720560ca-97b9-40a1-ad40-9f9b8a6648e9@vondra.me> <2f7fdaa6-2855-4a49-884c-16b91db9a97b@vondra.me> In-Reply-To: <2f7fdaa6-2855-4a49-884c-16b91db9a97b@vondra.me> From: Alexandre Felipe Date: Sun, 22 Feb 2026 16:23:11 +0000 X-Gm-Features: AaiRm53gSVfBMibE9PE8PqXvqCu1eRCfqTN6gDXTXgFIMnxUFYreGaMJeP4Hcnc Message-ID: Subject: Re: index prefetching To: Tomas Vondra Cc: Andres Freund , Peter Geoghegan , Thomas Munro , Nazir Bilal Yavuz , Robert Haas , Melanie Plageman , PostgreSQL Hackers , Georgios , Konstantin Knizhnik , Dilip Kumar Content-Type: multipart/alternative; boundary="000000000000c1b3b1064b6c1243" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000c1b3b1064b6c1243 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi all, I did some experiments on a few questions that flew over this thread. In the hope to be useful. DISTANCE CONTROL I tested different strategies to increase distance. 2*d, 2*d+1, d+2, d+4, and so on. In my head, what would make sense is d + io_combine_limi, but in the end the 2*d gives the best results across different patterns, e.g. (h{200}m{200}) that seems to be a more reasonable pattern, as previous scans would have loaded in blocks. But these are fundamentally the same, as I posted about this a markov model, and the limit will be something like max_distance * sigmoid(h * (p - p0)), what changes is the transient when we go in and out of a cached region. LIMITING PREFETCH To avoid prefetch waste with a limit node wouldn't it make sense to send from the executor an estimate of how many rows will be required. A strict lower bound would be limit (+ offset) - returned, but a selectivity factor would be important if most rows are removed, a good start would be (limit - filtered) * selectivity, a simple model for the selectivity would be (unfiltered + 1) / (filtered + 1), the 1 here accounts for the uncertainty about the next row being filtered or not, and avoids a division by zero for the first row, and naturally will cap the distance to the limit when the query starts. I/O REORDERING I did an experiment reordering the heap accesses, following a zig-zag pattern, using constant memory like this fill heap-up with W blocks direction =3D up read-block =3D pop from heap-up for each new-block if new-block > read block push new-block into heap-up else push new-block into heap-down if heap-up is empty direction =3D down if heap-down is empty direction =3D up if direction =3D up read-block =3D pop from heap-up else read-block =3D pop from heap-down The heap-up and heap-down will contain at most W elements, so they can be stored in complementary regions of the same array. With a small adjustmentment (moving one element to the heap behind when the new-block is ahead) we ensure that the delay added to a block doesn't exceed the buffer length, and we can restore the index order with another heap, so this would give an algorithm with O(W) memory O(W * log(W)) complexity, asymptotically constant on the table size. And will reduce the total seek distance by roughly 2/W. Creating a ~400MB table CREATE TABLE zigzag_test ( id serial, random_key int, data text, pad1 char(2000), pad2 char(2000), pad3 char(2000) ); SELECT setseed(0.42); INSERT INTO zigzag_test (random_key, data, pad1, pad2, pad3) SELECT (random() * 100000)::int, 'data_' || g, 'x', 'x', 'x' FROM generate_series(1, 5000) g; Then accessing 5% of it using index scan (or the zigzag that breaks the index order) SELECT sum(id), sum(abs(blk - prev_blk)) as seek_dist FROM ( SELECT id, (ctid::text::point)[0] as blk, lag((ctid::text::point)[0]) over () as prev_blk FROM zigzag_test WHERE random_key < 50000 Measuring time +/- std-dev, using the I(ndex) order access and the Z(igzag) order (not so exactly as the algorithm described above) I order (ms) Z order (ms) Diff % Seek (I/Z) Disk Prefetch HDD on 250.4 +/- 227.8 168.7 +/- 83.0 -32.6% 78281/1242 off 185.8 +/- 269.9 141.9 +/- 77.4 -23.6% 78281/1242 SSD on 148.2 +/- 69.7 117.6 +/- 39.6 -20.7% 78281/1242 off 66.8 +/- 34.1 121.5 +/- 39.7 +81.8% 78281/1242 The tricky datapoint (and the one with the highest statistical significance) here is SSD without prefetch in index order. On small rows, and not-so-sparse indices this might help with pinning-unpinning overhead (??), but for that case we already have bitmap scans. Regards, Alexandre On Wed, Feb 18, 2026 at 3:39=E2=80=AFPM Tomas Vondra wrot= e: > > > On 2/18/26 05:21, Andres Freund wrote: > > Hi, > > > > On 2026-02-17 22:36:53 +0100, Tomas Vondra wrote: > >> On 2/17/26 21:16, Peter Geoghegan wrote: > >>> On Tue, Feb 17, 2026 at 2:27=E2=80=AFPM Andres Freund > wrote: > >>>> On 2026-02-17 12:16:23 -0500, Peter Geoghegan wrote: > >>>>> On Mon, Feb 16, 2026 at 11:48=E2=80=AFAM Andres Freund > wrote: > >>>>> I agree that the current heuristics (which were invented recently) > are > >>>>> too conservative. I overfit the heuristics to my current set of > >>>>> adversarial queries, as a stopgap measure. > >>>> > >>>> Are you doing any testing on higher latency storage? I found it to > be quite > >>>> valuable to use dm_delay to have a disk with reproducible (i.e. not > cloud) > >>>> higher latency (i.e. not just a local SSD). > >>> > >>> I sometimes use dm_delay (with the minimum 1ms delay) when testing, > >>> but don't do so regularly. Just because it's inconvenient to do so > >>> (perhaps not a great reason). > >>> > >>>> Low latency NVMe can reduce the > >>>> penalty of not enough readahead so much that it's hard to spot > problems... > >>> > >>> I'll keep that in mind. > >>> > >> > >> So, what counts as "higher latency" in this context? What delays shoul= d > >> we consider practical/relevant for testing? > > > > 0.5-4ms is the range I've seen in various clouds across their reasonabl= e > > storage products (i.e. not spinning disks or other ver bulk oriented > things). > > > > Unfortunately dm_delay doesn't support < 1ms delays, but it's still muc= h > > better than nothing. > > > > I've been wondering about teaching AIO to delay IOs (by adding a sleep = to > > workers and linking a IORING_OP_TIMEOUT submission with the actually > intended > > IO) to allow testing smaller delays. > > > > Could be useful testing facility, if it's done in a way that does not > limit the IO concurrency (i.e. the delay should probably be when > consuming the IO, depending on the timestamp of the IO start). > > > > >>> That would make sense. You can already tell when that's happened by > >>> comparing the details shown by EXPLAIN ANALYZE against the same query > >>> execution on master, but that approach is inconvenient. Automating my > >>> microbenchmarks has proven to be important with this project. There's > >>> quite a few competing considerations, and it's too easy to improve on= e > >>> query at the cost of regressing another. > >>> > >> > >> What counts as "unconsumed IO"? The IOs the stream already started, bu= t > >> then did not consume? That shouldn't be hard, I think. > > > > Yes, the number of IOs that were started but not consumed. Or, even > better, > > the number of IOs that completed but were not consumed - but that'd be > harder > > to get right now. > > > > I agree that started-but-not-consumed should be pretty easy. > > > > I'll try to add it to the EXPLAIN. > > > regards > > -- > Tomas Vondra > > --000000000000c1b3b1064b6c1243 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi all,

I did some experiments on a few quest= ions that flew over this thread. In the hope to be useful.

DISTANCE = CONTROL

I tested different strategies to increase distance. 2*d, 2*d= +1, d+2, d+4, and so on. In my head, what would make sense is d=C2=A0+ io_c= ombine_limi, but in the end the 2*d gives the best results=C2=A0across=C2= =A0different patterns, e.g. (h{200}m{200}) that seems to be a more reasonab= le pattern, as previous scans would have loaded in blocks. But these are fu= ndamentally the same, as I posted about this a markov model, and the limit = will be something like max_distance * sigmoid(h * (p - p0)), what changes i= s the transient when we go in and out of a cached region.

LIMITING PREFETCH

To avoid prefetch waste with a limit node w= ouldn't it make sense to send from the executor an estimate of how many= rows will be required. A strict lower bound would be limit (+ offset) - re= turned,=C2=A0but a selectivity factor would be important if most rows are r= emoved,=C2=A0a good start would be (limit - filtered) * selectivity, a simp= le model for the selectivity would be (unfiltered=C2=A0+ 1) / (filtered=C2= =A0+ 1),=C2=A0 the 1 here accounts for the uncertainty about the next row b= eing filtered or not, and avoids a division by zero for the first row, and = naturally will cap the distance to the limit when the query starts.

I/O REORDERING

I did an experiment reordering the = heap accesses, following a zig-zag pattern, using constant memory like this=

fill heap-up with W blocks
direction =3D up
read-bl= ock =3D pop from heap-up
for each= new-block
=C2=A0 if new-block &g= t; read block
=C2=A0 =C2=A0 push = new-block into heap-up
=C2=A0 els= e
=C2=A0 =C2=A0 push new-block in= to heap-down
=C2=A0 if heap-up is= empty
=C2=A0 =C2=A0 direction = =3D down
=C2=A0 if heap-down is e= mpty
=C2=A0 =C2=A0 direction =3D = up
=C2=A0 if direction =3D up
=C2=A0 =C2=A0 read-block =3D pop fro= m heap-up
=C2=A0 else
=C2=A0 =C2=A0 read-block =3D pop from heap-d= own

The heap-up and heap-down= will contain at most W elements, so they can be stored in complementary re= gions of the same array.
With a s= mall adjustmentment=C2=A0(moving one element to the heap behind when the ne= w-block is ahead) we ensure that the delay added to a block doesn't exc= eed the buffer length, and we can restore the index order with another heap= , so this would give an algorithm with O(W) memory O(W * log(W)) complexity= , asymptotically constant on the table size. And will reduce the total seek= distance by roughly 2/W.

Creating a ~400MB table

CREA= TE TABLE zigzag_test (
=C2=A0 =C2=A0 id serial, random_key int, data tex= t,
=C2=A0 =C2=A0 pad1 char(2000), pad2 char(2000), pad3 char(2000)
);=
SELECT setseed(0.42);
INSERT INTO zigzag_test (random_key, data, pad= 1, pad2, pad3)
SELECT (random() * 100000)::int, 'data_' || g, &#= 39;x', 'x', 'x'
FROM generate_series(1, 5000) g;

=
Then accessing 5% of it using index sca= n (or the zigzag that breaks the index order)

SELECT sum(id), sum(abs(blk - prev_blk= )) as seek_dist FROM (
=C2=A0 =C2=A0 SELECT id, (ctid::text::point)[0] a= s blk, lag((ctid::text::point)[0]) over () as prev_blk
=C2=A0 =C2=A0 FR= OM zigzag_test WHERE random_key < 50000<= /div>

Measuring time=C2=A0+/- std-dev, using the I(ndex)= order access and the Z(igzag) order (not so exactly as the algorithm descr= ibed above)

=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 I order (ms) =C2=A0 =C2=A0Z order (ms) =C2=A0Diff % =C2=A0Seek (I/Z)=
Disk Prefetch =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0
HDD =C2=A0on =C2= =A0 =C2=A0 =C2=A0 =C2=A0250.4 +/- 227.8 =C2=A0168.7 +/- 83.0 =C2=A0-32.6% = =C2=A078281/1242
=C2=A0 =C2=A0 =C2=A0off =C2=A0 =C2=A0 =C2=A0 185.8 +/- = 269.9 =C2=A0141.9 +/- 77.4 =C2=A0-23.6% =C2=A078281/1242
SSD =C2=A0on = =C2=A0 =C2=A0 =C2=A0 =C2=A0 148.2 +/- 69.7 =C2=A0117.6 +/- 39.6 =C2=A0-20.7= % =C2=A078281/1242
=C2=A0 =C2=A0 =C2=A0off =C2=A0 =C2=A0 =C2=A0 =C2=A0 6= 6.8 +/- 34.1 =C2=A0121.5 +/- 39.7 =C2=A0+81.8% =C2=A078281/1242

The tricky datapoint (and the one with the highest sta= tistical significance) here is SSD without prefetch in index order.

On small rows, and not-so-sparse indices this might help = with pinning-unpinning overhead (??), but for that case we already have bit= map scans.

Regards,
Alexandre
=


On Wed, Feb 18, 2026 at 3:39=E2=80=AFPM Tom= as Vondra <tomas@vo= ndra.me> wrote:


On 2/18/26 05:21, Andres Freund wrote:
> Hi,
>
> On 2026-02-17 22:36:53 +0100, Tomas Vondra wrote:
>> On 2/17/26 21:16, Peter Geoghegan wrote:
>>> On Tue, Feb 17, 2026 at 2:27=E2=80=AFPM Andres Freund <andres@anarazel.de&= gt; wrote:
>>>> On 2026-02-17 12:16:23 -0500, Peter Geoghegan wrote:
>>>>> On Mon, Feb 16, 2026 at 11:48=E2=80=AFAM Andres Freund= <andres@anaraze= l.de> wrote:
>>>>> I agree that the current heuristics (which were invent= ed recently) are
>>>>> too conservative. I overfit the heuristics to my curre= nt set of
>>>>> adversarial queries, as a stopgap measure.
>>>>
>>>> Are you doing any testing on higher latency storage?=C2=A0= I found it to be quite
>>>> valuable to use dm_delay to have a disk with reproducible = (i.e. not cloud)
>>>> higher latency (i.e. not just a local SSD).
>>>
>>> I sometimes use dm_delay (with the minimum 1ms delay) when tes= ting,
>>> but don't do so regularly. Just because it's inconveni= ent to do so
>>> (perhaps not a great reason).
>>>
>>>> Low latency NVMe can reduce the
>>>> penalty of not enough readahead so much that it's hard= to spot problems...
>>>
>>> I'll keep that in mind.
>>>
>>
>> So, what counts as "higher latency" in this context? Wha= t delays should
>> we consider practical/relevant for testing?
>
> 0.5-4ms is the range I've seen in various clouds across their reas= onable
> storage products (i.e. not spinning disks or other ver bulk oriented t= hings).
>
> Unfortunately dm_delay doesn't support < 1ms delays, but it'= ;s still much
> better than nothing.
>
> I've been wondering about teaching AIO to delay IOs (by adding a s= leep to
> workers and linking a IORING_OP_TIMEOUT submission with the actually i= ntended
> IO) to allow testing smaller delays.
>

Could be useful testing facility, if it's done in a way that does not limit the IO concurrency (i.e. the delay should probably be when
consuming the IO, depending on the timestamp of the IO start).

>
>>> That would make sense. You can already tell when that's ha= ppened by
>>> comparing the details shown by EXPLAIN ANALYZE against the sam= e query
>>> execution on master, but that approach is inconvenient. Automa= ting my
>>> microbenchmarks has proven to be important with this project. = There's
>>> quite a few competing considerations, and it's too easy to= improve one
>>> query at the cost of regressing another.
>>>
>>
>> What counts as "unconsumed IO"? The IOs the stream alrea= dy started, but
>> then did not consume? That shouldn't be hard, I think.
>
> Yes, the number of IOs that were started but not consumed. Or, even be= tter,
> the number of IOs that completed but were not consumed - but that'= d be harder
> to get right now.
>
> I agree that started-but-not-consumed should be pretty easy.
>

I'll try to add it to the EXPLAIN.


regards

--
Tomas Vondra

--000000000000c1b3b1064b6c1243--