mirror of
https://github.com/ceph/ceph
synced 2026-08-01 22:45:39 +00:00
Compare commits
159 Commits
8b525616b0
...
d735481a93
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d735481a93 | ||
|
|
a8eaf2e9d2 | ||
|
|
3804ef0dd1 | ||
|
|
0c4cba1010 | ||
|
|
f7bff3598d | ||
|
|
365f478dae | ||
|
|
8482cb11bc | ||
|
|
ac9a88d3ed | ||
|
|
3434180fc5 | ||
|
|
2caabd7a28 | ||
|
|
2c452108d3 | ||
|
|
0152782bfe | ||
|
|
b1c466c2eb | ||
|
|
cdbd4dabe9 | ||
|
|
0323ff2141 | ||
|
|
3d9aa9f329 | ||
|
|
218983d0cf | ||
|
|
31519526f7 | ||
|
|
c2afa96e8e | ||
|
|
beccebe2e8 | ||
|
|
fd39161c7f | ||
|
|
6beaf71d96 | ||
|
|
660ba02205 | ||
|
|
4e380d304b | ||
|
|
10292cc86f | ||
|
|
ab4a964a1c | ||
|
|
b8f9beb9c1 | ||
|
|
6b6c8d4da3 | ||
|
|
e89e2104a8 | ||
|
|
528bba37b9 | ||
|
|
953710c5e6 | ||
|
|
ba0655657c | ||
|
|
64263d609d | ||
|
|
acdb292361 | ||
|
|
f56b2a5f42 | ||
|
|
aad3524f7a | ||
|
|
92a569aa4a | ||
|
|
aba5a77872 | ||
|
|
b899ade928 | ||
|
|
f18fe72baa | ||
|
|
43f048c81e | ||
|
|
0a0a7e3da0 | ||
|
|
a4f338e77f | ||
|
|
6db2d97c74 | ||
|
|
e141ac3f38 | ||
|
|
a5693801a6 | ||
|
|
a9220d9470 | ||
|
|
e3c1baf8c8 | ||
|
|
eeba1411a2 | ||
|
|
e4979f98ed | ||
|
|
c8769a0f34 | ||
|
|
660c7d86f4 | ||
|
|
c48848ba9f | ||
|
|
5314e38d39 | ||
|
|
d86fb5fd80 | ||
|
|
eb9e9b9d06 | ||
|
|
2a5257a04c | ||
|
|
a8b0baca91 | ||
|
|
c969a8b9b5 | ||
|
|
afde4c6dcb | ||
|
|
43d0b3316a | ||
|
|
3e1c41c193 | ||
|
|
386cd71173 | ||
|
|
de09de6004 | ||
|
|
1066090245 | ||
|
|
413f3e7590 | ||
|
|
2569c8bd8b | ||
|
|
0b3995d9cc | ||
|
|
d0add82594 | ||
|
|
35c5193cd3 | ||
|
|
35060603bc | ||
|
|
92cef49cde | ||
|
|
b9fccdffd2 | ||
|
|
5bab45c606 | ||
|
|
4adc02dffb | ||
|
|
98f1892fb6 | ||
|
|
223dde6bab | ||
|
|
eca32e1fa9 | ||
|
|
051db088b3 | ||
|
|
91eb5d2a9c | ||
|
|
c7b3fd1350 | ||
|
|
3f2b958478 | ||
|
|
d088cfc46d | ||
|
|
1af4a97792 | ||
|
|
c3e971a84c | ||
|
|
ad7469ca59 | ||
|
|
671b1b4ffc | ||
|
|
2d629af107 | ||
|
|
6c526c811b | ||
|
|
ece1a68497 | ||
|
|
f17f12b4a1 | ||
|
|
9336dcba48 | ||
|
|
71ddab8e32 | ||
|
|
144c6e8f76 | ||
|
|
4705abd056 | ||
|
|
0a6d0cb043 | ||
|
|
8026e6941c | ||
|
|
e82ccd0873 | ||
|
|
2dcf7a1267 | ||
|
|
bd3a488a1f | ||
|
|
c4c937d81c | ||
|
|
25187a4389 | ||
|
|
51131eb3d9 | ||
|
|
0bb0d59a85 | ||
|
|
3fe15c1f71 | ||
|
|
872d3bd14c | ||
|
|
66a1de3eff | ||
|
|
a5803a5ed5 | ||
|
|
c748ba8b79 | ||
|
|
4d26ec1395 | ||
|
|
851ca4c75c | ||
|
|
58ef0cd984 | ||
|
|
070e0ca298 | ||
|
|
bfebe2ea25 | ||
|
|
9da521bd82 | ||
|
|
97cc3c8d93 | ||
|
|
44075f25c8 | ||
|
|
86c46693c2 | ||
|
|
3a081d5cd0 | ||
|
|
03df721ac7 | ||
|
|
a11f0e3d4e | ||
|
|
405b99064f | ||
|
|
cbc41b07d8 | ||
|
|
90098a7027 | ||
|
|
9020d1dccd | ||
|
|
ad980c6b65 | ||
|
|
78b7fa265b | ||
|
|
10e008aaca | ||
|
|
f6d85aad99 | ||
|
|
18da8c4b90 | ||
|
|
e63f8d1aff | ||
|
|
517f9ce8f4 | ||
|
|
e25810d879 | ||
|
|
8926e0cce3 | ||
|
|
0bb27c7de1 | ||
|
|
4af78378a9 | ||
|
|
c4472c0f9c | ||
|
|
f988cd91e3 | ||
|
|
0ea4eb19b0 | ||
|
|
ef0b9f339f | ||
|
|
18179111ba | ||
|
|
eb107111d6 | ||
|
|
9c0f43e7da | ||
|
|
c8d115ee5d | ||
|
|
d9dbfde2b7 | ||
|
|
d96e5b148f | ||
|
|
51f9de8d34 | ||
|
|
47f7475545 | ||
|
|
f6b2f5fee9 | ||
|
|
50fc43d006 | ||
|
|
75b0084f52 | ||
|
|
e565a2b06d | ||
|
|
71cd5806a8 | ||
|
|
c1ef8e868a | ||
|
|
2601725fbf | ||
|
|
a4b7a658c1 | ||
|
|
aed96f7a2f | ||
|
|
ec59888070 | ||
|
|
dcd9c5bac5 |
8
.github/workflows/check-license.yml
vendored
8
.github/workflows/check-license.yml
vendored
@ -1,6 +1,12 @@
|
||||
---
|
||||
name: "Check for Incompatible Licenses"
|
||||
on: [pull_request]
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
- umbrella
|
||||
- tentacle
|
||||
- squid
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
9
.github/workflows/pr-checklist.yml
vendored
9
.github/workflows/pr-checklist.yml
vendored
@ -1,17 +1,22 @@
|
||||
---
|
||||
name: "Pull Request Checklist"
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- edited
|
||||
- opened
|
||||
- reopened
|
||||
|
||||
# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#defining-access-for-the-github_token-scopes
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
pr_checklist:
|
||||
runs-on: ubuntu-latest
|
||||
name: Verify
|
||||
name: Verify Checklist
|
||||
steps:
|
||||
- name: Sleep for 30 seconds
|
||||
run: sleep 30s
|
||||
|
||||
10
.github/workflows/qa-symlink.yml
vendored
10
.github/workflows/qa-symlink.yml
vendored
@ -1,8 +1,7 @@
|
||||
---
|
||||
name: "Check for missing .qa links"
|
||||
on:
|
||||
pull_request:
|
||||
# Run CI check only for branches which are active and a target for a PR
|
||||
pull_request_target:
|
||||
branches:
|
||||
- main
|
||||
- umbrella
|
||||
@ -11,7 +10,6 @@ on:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- edited
|
||||
- reopened
|
||||
permissions:
|
||||
contents: read
|
||||
@ -36,6 +34,6 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: pull_request
|
||||
|
||||
- name: verify .qa links
|
||||
run: ../main/src/script/verify-qa
|
||||
working-directory: pull_request/
|
||||
- name: Run verification script
|
||||
run: |
|
||||
./main/src/script/verify-qa ./pull_request
|
||||
|
||||
@ -38,6 +38,7 @@ baergj Joshua Baergen <jbaergen@digitalocean.com>
|
||||
baruza Barbora Ančincová <bara@redhat.com>
|
||||
bassamtabbara Bassam Tabbara <bassam.tabbara@quantum.com>
|
||||
batrick Patrick Donnelly <pdonnell@ibm.com>
|
||||
baum Alexander Indenbaum <aindenba@redhat.com>
|
||||
BBoozmen Oguzhan Ozmen <oozmen@bloomberg.net>
|
||||
benhanokh Gabriel Benhanokh <gbenhano@redhat.com>
|
||||
bigjust Justin Caratzas <jcaratza@redhat.com>
|
||||
|
||||
@ -36,7 +36,9 @@ RUN DISTRO=$DISTRO \
|
||||
FOR_MAKE_CHECK=${FOR_MAKE_CHECK} \
|
||||
bash -x ${CEPH_CTR_SRC}/buildcontainer-setup.sh
|
||||
RUN \
|
||||
if [ $(uname -m) != ppc64le ]; then \
|
||||
if rpm --quiet --query sccache 2>/dev/null || { command -v dnf >/dev/null 2>&1 && dnf install -y sccache 2>/dev/null; }; then \
|
||||
echo "sccache provided by distro packages"; \
|
||||
elif [ $(uname -m) != ppc64le ]; then \
|
||||
SCCACHE_ARCH="$(uname -m)"; \
|
||||
if [ "$SCCACHE_ARCH" = riscv64 ]; then SCCACHE_ARCH=riscv64gc; fi; \
|
||||
SCCACHE_URL="${SCCACHE_REPO}/releases/download/${SCCACHE_VERSION}/sccache-${SCCACHE_VERSION}-${SCCACHE_ARCH}-unknown-linux-musl.tar.gz"; \
|
||||
|
||||
@ -269,6 +269,10 @@
|
||||
rather rigid and also challenging to disseminate in some environments. The
|
||||
key can be embedded in the migration spec or just referenced from there while
|
||||
stored in the MON config-key store.
|
||||
* RBD: `Group::list_images` python API will now correctly raise an ObjectNotFound
|
||||
error when invoked on a non-existent group. The C APIs `rbd_group_image_list`,
|
||||
`rbd_snap_list`, `rbd_group_snap_list` and C++ API `group_image_list` will now
|
||||
return ENOENT instead of masking the error and returning 0.
|
||||
* BlueStore: The experimental PMEM device backend has been removed, together
|
||||
with the ``pmem`` value of the ``bdev_type`` option and the DML/DSA offload
|
||||
path. The hardware it targeted, Intel Optane DC persistent memory, was
|
||||
|
||||
4
debian/ceph-common.install
vendored
4
debian/ceph-common.install
vendored
@ -10,7 +10,6 @@ usr/bin/ceph
|
||||
usr/bin/ceph-authtool
|
||||
usr/bin/ceph-conf
|
||||
usr/bin/ceph-dencoder
|
||||
usr/bin/ceph-diff-sorted
|
||||
usr/bin/ceph-rbdnamer
|
||||
usr/bin/ceph-syn
|
||||
usr/bin/cephfs-data-scan
|
||||
@ -37,7 +36,6 @@ usr/lib/ceph/crypto/* [amd64]
|
||||
usr/share/man/man8/ceph-authtool.8
|
||||
usr/share/man/man8/ceph-conf.8
|
||||
usr/share/man/man8/ceph-dencoder.8
|
||||
usr/share/man/man8/ceph-diff-sorted.8
|
||||
usr/share/man/man8/ceph-rbdnamer.8
|
||||
usr/share/man/man8/ceph-syn.8
|
||||
usr/share/man/man8/ceph-post-file.8
|
||||
@ -49,8 +47,6 @@ usr/share/man/man8/radosgw-admin.8
|
||||
usr/share/man/man8/rgw-policy-check.8
|
||||
usr/share/man/man8/rgw-policy-test.8
|
||||
usr/share/man/man8/rgw-gap-list.8
|
||||
usr/share/man/man8/rgw-orphan-list.8
|
||||
usr/share/man/man8/rgw-restore-bucket-index.8
|
||||
usr/share/man/man8/rbd.8
|
||||
usr/share/man/man8/rbdmap.8
|
||||
usr/share/man/man8/rbd-replay*.8
|
||||
|
||||
4
debian/radosgw.install
vendored
4
debian/radosgw.install
vendored
@ -4,11 +4,7 @@ usr/bin/radosgw
|
||||
usr/bin/radosgw-es
|
||||
usr/bin/radosgw-object-expirer
|
||||
usr/bin/radosgw-token
|
||||
usr/bin/rgw-gap-list
|
||||
usr/bin/rgw-gap-list-comparator
|
||||
usr/bin/rgw-orphan-list
|
||||
usr/share/man/man8/ceph-diff-sorted.8
|
||||
usr/share/man/man8/radosgw.8
|
||||
usr/share/man/man8/rgw-gap-list.8
|
||||
usr/share/man/man8/rgw-orphan-list.8
|
||||
usr/share/man/man8/rgw-restore-bucket-index.8
|
||||
|
||||
@ -149,6 +149,7 @@ packages that provide the ``cephadm`` command, run the following commands:
|
||||
#. Add the repository:
|
||||
|
||||
.. prompt:: bash #
|
||||
:substitutions:
|
||||
|
||||
./cephadm add-repo --release |stable-release|
|
||||
|
||||
|
||||
@ -376,7 +376,7 @@ values until the new owning daemon writes an updated entry.
|
||||
* - ``metrics_updated_at``
|
||||
- Yes
|
||||
- No
|
||||
- Wall-clock time of the last omap write; exposed only by
|
||||
- Wall-clock time of the last omap write (ISO-8601 in mgr CLI); exposed only by
|
||||
``ceph fs snapshot mirror status`` (not ``fs mirror peer status``)
|
||||
|
||||
**Omap cleanup**
|
||||
@ -660,7 +660,7 @@ command parameter is of format ``filesystem-name@filesystem-id peer-uuid``::
|
||||
"crawl_duration": "2s",
|
||||
"datasync_queue_wait_duration": "1s",
|
||||
"sync_duration": "33s",
|
||||
"sync_time_stamp": "274900.558797s",
|
||||
"sync_time_stamp": "2026-07-15T12:00:00.558797+0530",
|
||||
"sync_bytes": "149.94 MiB",
|
||||
"sync_files": 5000
|
||||
},
|
||||
@ -750,11 +750,14 @@ Plain unsigned integers with no unit suffix.
|
||||
Timestamp
|
||||
---------
|
||||
|
||||
Field: ``sync_time_stamp``.
|
||||
Field: ``sync_time_stamp`` (and ``metrics_updated_at`` in mgr status).
|
||||
|
||||
Monotonic clock time in seconds (since daemon startup) when the snapshot sync finished,
|
||||
printed with sub-second precision and an ``s`` suffix (for example, ``274900.558797s``). This
|
||||
is not a wall-clock or epoch timestamp.
|
||||
Wall-clock time printed as an ISO-8601 local timestamp with sub-second precision
|
||||
and timezone offset (for example, ``2026-07-15T12:00:00.558797+0530``).
|
||||
``sync_time_stamp`` is when the snapshot sync finished;
|
||||
``metrics_updated_at`` is when omap was last written for that directory and peer.
|
||||
Omap persistence and ``counter dump`` (``last_sync_timestamp``) still store Unix
|
||||
epoch seconds.
|
||||
|
||||
``snaps_synced``, ``snaps_deleted``, and ``snaps_renamed`` are
|
||||
:ref:`per-session counters<cephfs_mirroring_sync_metric_fields>`: they count
|
||||
@ -923,7 +926,7 @@ E.g., adding a regular file for synchronization would result in failed status::
|
||||
"crawl_duration": "2s",
|
||||
"datasync_queue_wait_duration": "1s",
|
||||
"sync_duration": "44s",
|
||||
"sync_time_stamp": "500900.600797s",
|
||||
"sync_time_stamp": "2026-07-15T12:05:33.600797+0530",
|
||||
"sync_bytes": "149.94 MiB",
|
||||
"sync_files": 5000
|
||||
},
|
||||
@ -972,7 +975,7 @@ In the remote filesystem::
|
||||
"crawl_duration": "2s",
|
||||
"datasync_queue_wait_duration": "1s",
|
||||
"sync_duration": "33s",
|
||||
"sync_time_stamp": "274900.558797s",
|
||||
"sync_time_stamp": "2026-07-15T12:00:00.558797+0530",
|
||||
"sync_bytes": "149.94 MiB",
|
||||
"sync_files": 5000
|
||||
},
|
||||
@ -1227,7 +1230,7 @@ Counters are not updated on every file read or write. Behavior differs by field
|
||||
- Durations in seconds; bytes are raw counts
|
||||
* - ``last_sync_timestamp``
|
||||
- ``last_synced_snap.sync_time_stamp``
|
||||
- ``utime_t`` (seconds since epoch), not the monotonic string shown in admin JSON
|
||||
- Perf counter is Unix epoch seconds; admin/mgr JSON show ISO-8601 local time
|
||||
* - ``snaps_synced`` / ``snaps_deleted`` / ``snaps_renamed``
|
||||
- Same field names at directory level
|
||||
- Reset on daemon restart or directory reassignment (same as admin socket)
|
||||
|
||||
@ -15,6 +15,10 @@ Key features include:
|
||||
* Configurable block sizes, file counts, and fsync intervals.
|
||||
* Detailed statistical reporting (Mean, Std Dev, Min/Max) for throughput and IOPS.
|
||||
* Support for specific CephFS user/group impersonation (UID/GID) via ``ceph_mount_perms_set``.
|
||||
* Optional JSON output and libcephfs perf counter dumps.
|
||||
* Optional progress reporting during benchmark phases.
|
||||
* Optional asynchronous I/O benchmarking with configurable queue depth.
|
||||
* Optional overrides for selected client configuration settings such as object cache and messenger worker threads.
|
||||
|
||||
Building
|
||||
========
|
||||
@ -60,7 +64,7 @@ General Options
|
||||
|
||||
Path to keyring file
|
||||
|
||||
.. option:: --filesystem <name>
|
||||
.. option:: --filesystem, --fs <name>
|
||||
|
||||
CephFS filesystem name to mount
|
||||
|
||||
@ -72,6 +76,19 @@ General Options
|
||||
|
||||
Group ID to mount as (default: ``-1``)
|
||||
|
||||
.. option:: --client-oc <0|1>
|
||||
|
||||
Override the ``client_oc`` setting for the benchmark mount
|
||||
|
||||
.. option:: --client-oc-size <value>
|
||||
|
||||
Override the ``client_oc_size`` setting for the benchmark mount
|
||||
|
||||
.. option:: --msgr-workers <n>
|
||||
|
||||
Override ``ms_async_op_threads`` for the benchmark mount. Valid values are
|
||||
``1`` to ``24``; ``0`` keeps the configured default.
|
||||
|
||||
Benchmark Options
|
||||
-----------------
|
||||
|
||||
@ -121,6 +138,40 @@ These options are used with the ``bench`` command.
|
||||
|
||||
Disable cleanup of files
|
||||
|
||||
.. option:: --json <path>
|
||||
|
||||
Write structured benchmark results to a JSON file. Use ``-`` to output to stdout.
|
||||
|
||||
.. option:: --duration <seconds>
|
||||
|
||||
Limit each write and read phase to ``N`` seconds. A value of ``0`` means the
|
||||
benchmark processes all files.
|
||||
|
||||
.. option:: --perf-dump <path>
|
||||
|
||||
Dump libcephfs performance counters to the specified file after the benchmark
|
||||
|
||||
.. option:: --progress
|
||||
|
||||
Show live progress, bandwidth, file rate, and ETA during benchmark phases
|
||||
|
||||
.. option:: --progress-interval <percent>
|
||||
|
||||
Minimum percentage increment between progress updates (default: ``10``;
|
||||
valid range: ``1`` to ``100``)
|
||||
|
||||
.. option:: --async-io
|
||||
|
||||
Use asynchronous I/O via ``ceph_ll_nonblocking_readv_writev`` for read and
|
||||
write phases
|
||||
|
||||
.. option:: --queue-depth <n>
|
||||
|
||||
Maximum number of outstanding async I/Os per worker thread (default:
|
||||
``16``; valid range: ``1`` to ``1024``). This option is only meaningful with
|
||||
``--async-io`` and ``--async-io`` cannot be combined with
|
||||
``--per-thread-mount``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
|
||||
@ -444,7 +444,7 @@ status. Commands of this kind take the form ``filesystem-name@filesystem-id peer
|
||||
"crawl_duration": "2s",
|
||||
"datasync_queue_wait_duration": "1s",
|
||||
"sync_duration": "33s",
|
||||
"sync_time_stamp": "274900.558797s",
|
||||
"sync_time_stamp": "2026-07-15T12:00:00.558797+0530",
|
||||
"sync_bytes": "149.94 MiB",
|
||||
"sync_files": 5000
|
||||
},
|
||||
@ -548,7 +548,7 @@ status:
|
||||
"crawl_duration": "2s",
|
||||
"datasync_queue_wait_duration": "1s",
|
||||
"sync_duration": "33s",
|
||||
"sync_time_stamp": "274900.558797s",
|
||||
"sync_time_stamp": "2026-07-15T12:00:00.558797+0530",
|
||||
"sync_bytes": "149.94 MiB",
|
||||
"sync_files": 5000
|
||||
},
|
||||
|
||||
@ -101,7 +101,7 @@ run::
|
||||
$ MDS=0 MON=1 OSD=1 MGR=1 taskset -ac '0-95' /ceph/src/vstart.sh --new -x \
|
||||
--localhost --without-dashboard --redirect-output --seastore --osd-args \
|
||||
"--seastore_max_concurrent_transactions=128 --seastore_cachepin_type=LRU \
|
||||
--seastore_main_device_type=RANDOM_BLOCK_SSD" --seastore-devs /dev/nvme0n1 \
|
||||
--seastore_hot_device_type=RANDOM_BLOCK_SSD" --seastore-devs /dev/nvme0n1 \
|
||||
--crimson --crimson-smp 1 --no-restart
|
||||
|
||||
Another SeaStore example::
|
||||
|
||||
@ -204,7 +204,7 @@ Metadata Structures
|
||||
Device Types
|
||||
------------
|
||||
|
||||
Configured via the ``seastore_main_device_type`` option, the device types are
|
||||
Configured via the ``seastore_hot_device_type`` option, the device types are
|
||||
separated into **Segmented** and **RBM** backend types as follows:
|
||||
|
||||
|
||||
|
||||
85
doc/dev/crimson/seastore_logical_bucket_cache.rst
Normal file
85
doc/dev/crimson/seastore_logical_bucket_cache.rst
Normal file
@ -0,0 +1,85 @@
|
||||
=====================================
|
||||
The Logical Bucket Cache in SeaStore
|
||||
=====================================
|
||||
|
||||
This document introduces the LogicalBucketCache machinery which is
|
||||
mainly for using SSDs as non-volatile cache for the relatively slow
|
||||
HDD devices. Specifically, it serves to:
|
||||
|
||||
#. Load frequently accessed data from the cold tier to the Logical
|
||||
Bucket Cache.
|
||||
#. Evict relatively cold data from the Logical Bucket Cache to the
|
||||
cold tier based on the heat of both data read and write.
|
||||
|
||||
The Logical Bucket Cache is enabled when there is at least one cold
|
||||
tier.
|
||||
|
||||
General Approach
|
||||
================
|
||||
|
||||
#. Cache line: a range of continuous laddr address space, which we
|
||||
call logical bucket. For now, that continuous laddr range is the
|
||||
scope of a RADOS object.
|
||||
#. Extents are loaded to the cache device when: 1) they are newly
|
||||
written by clients and don't trigger the write_through machinery.
|
||||
For now, the write_through machinery is triggered when the newly
|
||||
written data is larger than a specific threshold; 2) they are on
|
||||
the slow cold device and read to or evicted from the in-memory Cache.
|
||||
#. Extents are evicted from the cache device in units of logical buckets,
|
||||
that is, extents of the same logical bucket are evicted from the cache
|
||||
device altogether.
|
||||
#. The process that write extents evicted from Cache down to the cache
|
||||
device is called promotion.
|
||||
#. The process that evict extents from the cache device to the cold device
|
||||
is called demotion.
|
||||
#. Extents that are loaded from the cold tier to the Logical Bucket Cache
|
||||
are NOT removed from the cold tier, which means they'd have two paddrs,
|
||||
one in the Logical Bucket Cache, the other in the cold tier, and the
|
||||
promoted data is stored twice. This is designed to make the demote process
|
||||
cheap. We call paddrs corresponding to the cold tier shadow paddrs.
|
||||
So lba mappings have two paddrs now, the primary paddr and the shadow
|
||||
paddr.
|
||||
|
||||
Promotion
|
||||
=========
|
||||
|
||||
Promotion is the process that load the extents in the cold tier to the
|
||||
Logical Bucket Cache.
|
||||
|
||||
Extents that are read from the cold tier are first added to the in-memory
|
||||
cache; when evicted from the in-memory cache, they are added to the Promoter.
|
||||
|
||||
The Promoter write its extents down to the Logical Bucket Cache when the size
|
||||
of its extents grows larger than ``seastore_cache_promotion_size``.
|
||||
|
||||
Demotion
|
||||
========
|
||||
|
||||
Demotion happens when:
|
||||
|
||||
#. The usage of the Logical Bucket Cache reaches ``seastore_multiple_tiers_fast_evict_ratio``.
|
||||
#. The in-memory data index of the Logical Bucket Cache reaches ``seastore_logical_bucket_capacity``.
|
||||
|
||||
Demoting extents that are live in both the Logical Bucket Cache and the
|
||||
cold tier only involves setting the shadow paddr to the primary paddr and
|
||||
removing the shadow paddr itself.
|
||||
|
||||
Demoting extents that are only live in the Logical Bucket Cache would involve
|
||||
rewriting the extents to the cold tier.
|
||||
|
||||
WriteThrough
|
||||
============
|
||||
|
||||
When the newly written data is larger than ``seastore_write_through_size``,
|
||||
it would be written directly to the cold tier.
|
||||
|
||||
Internal Test Workload
|
||||
=======================
|
||||
|
||||
To test the correctness of the whole Logical Bucket Cache machinery, we also
|
||||
implemented an internal stress test workload. When turned on, SeaStore would
|
||||
aggressively load the extents from the cold tier to the Logical Bucket Cache,
|
||||
and demote extents back to the cold tier. The newly written data may also be
|
||||
written directly to the cold tier based on ``seastore_test_workload_write_through_probability``.
|
||||
|
||||
The internal test workload is turned on by ``crimson_test_workload``.
|
||||
@ -41,8 +41,7 @@ Make sure X (and, ideally, X+1) is defined:
|
||||
Github Actions
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
- [ ] .github/workflows/redmine-upkeep.yml add release branch to pull_request_target trigger
|
||||
- [ ] .github/workflows/releng-audit.yml add release branch to pull_request_target trigger
|
||||
- [ ] `.github/workflows/*.yml' add release branch to pull_request_target trigger
|
||||
|
||||
Scripts
|
||||
~~~~~~~
|
||||
@ -137,6 +136,8 @@ After dev freeze
|
||||
- [ ] open the Branch Protection settings of the Ceph repo. Duplicate settings to the new release branch.
|
||||
- [ ] create vX.3.0 annotated tag on ``main`` so upgrades from new release to main are not wrongly considered downgrades.
|
||||
- [ ] remove ``doc/releases/*.rst``. This should leave behind ``doc/releases/releases.yml`` which is used for doc building purposes. See also commit 33d63c3 ("doc: remove release notes for release branch") for details.
|
||||
- [ ] remove ``.github/workflows``. ``main`` is authoritative for workflows; avoid possible confusion/conflicts.
|
||||
- [ ] remove ``.github/pull_request_template.md``. Backports do not use it.
|
||||
- [ ] cherry-pick 8cf9ad62949516666ad0f2c0bb7726ef68e4d666 ("doc: add releases links to toc"). There will be trivial conflicts.
|
||||
- [ ] add redirect for new major release at `RTD <https://readthedocs.org/dashboard/ceph/redirects/>`_.
|
||||
- [ ] add release name to redmine (using https://tracker.ceph.com/custom_fields/16/edit)
|
||||
|
||||
@ -42,13 +42,13 @@ repository and execute the following::
|
||||
cd build
|
||||
ninja
|
||||
|
||||
See `Installing a Build`_ to install a build in user space and `Ceph README.md`_
|
||||
See :ref:`install-storage-cluster-build` to install a build in user space and `Ceph README.md`_
|
||||
doc for more details on build.
|
||||
|
||||
Build Ceph Packages
|
||||
===================
|
||||
|
||||
To build packages, you must clone the `Ceph`_ repository. You can create
|
||||
To build packages, you must clone the :ref:`Ceph repository <install-clone-source>`. You can create
|
||||
installation packages from the latest code using ``dpkg-buildpackage`` for
|
||||
Debian/Ubuntu or ``rpmbuild`` for the RPM Package Manager.
|
||||
|
||||
@ -61,7 +61,7 @@ Advanced Package Tool (APT)
|
||||
---------------------------
|
||||
|
||||
To create ``.deb`` packages for Debian/Ubuntu, ensure that you have cloned the
|
||||
`Ceph`_ repository, installed the `Build Prerequisites`_ and installed
|
||||
:ref:`Ceph repository <install-clone-source>`, installed the `Build Prerequisites`_ and installed
|
||||
``debhelper``::
|
||||
|
||||
sudo apt-get install debhelper
|
||||
@ -76,7 +76,7 @@ For multi-processor CPUs use the ``-j`` option to accelerate the build.
|
||||
RPM Package Manager
|
||||
-------------------
|
||||
|
||||
To create ``.rpm`` packages, ensure that you have cloned the `Ceph`_ repository,
|
||||
To create ``.rpm`` packages, ensure that you have cloned the :ref:`Ceph repository <install-clone-source>`,
|
||||
installed the `Build Prerequisites`_ and installed ``rpm-build`` and
|
||||
``rpmdevtools``::
|
||||
|
||||
@ -104,6 +104,4 @@ Build the RPM packages::
|
||||
|
||||
For multi-processor CPUs use the ``-j`` option to accelerate the build.
|
||||
|
||||
.. _Ceph: ../clone-source
|
||||
.. _Installing a Build: ../install-storage-cluster#installing-a-build
|
||||
.. _Ceph README.md: https://github.com/ceph/ceph#building-ceph
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
.. _install-clone-source:
|
||||
|
||||
=========================================
|
||||
Cloning the Ceph Source Code Repository
|
||||
=========================================
|
||||
|
||||
@ -123,7 +123,7 @@ For RPMs::
|
||||
The major releases of Ceph are summarized at: :ref:`Releases <ceph-releases-index>`
|
||||
|
||||
.. tip:: For non-US users: There might be a mirror close to you where
|
||||
to download Ceph from. For more information see: `Ceph Mirrors`_.
|
||||
to download Ceph from. For more information see: :ref:`install-mirrors`.
|
||||
|
||||
Debian Packages
|
||||
~~~~~~~~~~~~~~~
|
||||
@ -168,7 +168,7 @@ of Debian and Ubuntu releases supported.
|
||||
echo deb https://download.ceph.com/debian-testing/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list
|
||||
|
||||
.. tip:: For non-US users: There might be a mirror close to you where
|
||||
to download Ceph from. For more information see: `Ceph Mirrors`_.
|
||||
to download Ceph from. For more information see: :ref:`install-mirrors`.
|
||||
|
||||
|
||||
RPM Packages
|
||||
@ -234,7 +234,7 @@ You can download the RPMs directly from
|
||||
https://download.ceph.com/rpm-testing
|
||||
|
||||
.. tip:: For non-US users: There might be a mirror close to you where
|
||||
to download Ceph from. For more information see: `Ceph Mirrors`_.
|
||||
to download Ceph from. For more information see: :ref:`install-mirrors`.
|
||||
|
||||
openSUSE Leap 15.1
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
@ -389,4 +389,3 @@ line to get the short codename.
|
||||
|
||||
.. _the testing Debian repository: https://download.ceph.com/debian-testing/dists
|
||||
.. _the shaman page: https://shaman.ceph.com
|
||||
.. _Ceph Mirrors: ../mirrors
|
||||
|
||||
@ -7,8 +7,7 @@ source code. You may download source code tarballs for Ceph releases here:
|
||||
|
||||
`Ceph Release Tarballs`_
|
||||
|
||||
.. tip:: For international users: There might be a mirror close to you where download Ceph from. For more information see: `Ceph Mirrors`_.
|
||||
.. tip:: For international users: There might be a mirror close to you where download Ceph from. For more information see: :ref:`install-mirrors`.
|
||||
|
||||
|
||||
.. _Ceph Release Tarballs: https://download.ceph.com/tarballs/
|
||||
.. _Ceph Mirrors: ../mirrors
|
||||
|
||||
@ -53,6 +53,4 @@ Windows
|
||||
~~~~~~~
|
||||
|
||||
For Windows installations, consult this document:
|
||||
`Windows installation guide`_.
|
||||
|
||||
.. _Windows installation guide: ./windows-install
|
||||
:ref:`install-windows`.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
.. _install_storage_cluster:
|
||||
|
||||
==============================
|
||||
Install Ceph Storage Cluster
|
||||
==============================
|
||||
@ -79,6 +81,7 @@ Once you have added either release or development packages, or added a
|
||||
|
||||
sudo yum install ceph
|
||||
|
||||
.. _install-storage-cluster-build:
|
||||
|
||||
Installing a Build
|
||||
==================
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
.. _install-mirrors:
|
||||
|
||||
=============
|
||||
Ceph Mirrors
|
||||
=============
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
:orphan:
|
||||
|
||||
.. _install-windows-basic-config:
|
||||
|
||||
===========================
|
||||
Windows basic configuration
|
||||
===========================
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
:orphan:
|
||||
|
||||
.. _install-windows:
|
||||
|
||||
==========================
|
||||
Installing Ceph on Windows
|
||||
==========================
|
||||
@ -15,7 +17,7 @@ Supported platforms
|
||||
-------------------
|
||||
.. note::
|
||||
|
||||
Please see the `OS recommendations`_ regarding client package support.
|
||||
Please see the :ref:`os-recommendations` regarding client package support.
|
||||
|
||||
Windows Server 2019 and Windows Server 2016 are supported. Previous Windows
|
||||
Server versions, including Windows client versions such as Windows 10, might
|
||||
@ -66,7 +68,7 @@ https://github.com/ceph/ceph/blob/master/README.windows.rst
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Please check the `Windows configuration sample`_ to get started.
|
||||
Please check the :ref:`Windows configuration sample <install-windows-basic-config>` to get started.
|
||||
|
||||
You'll also need a keyring file. The `General CephFS Prerequisites`_ page provides a
|
||||
simple example, showing how a new CephX user can be created and how its secret
|
||||
@ -80,12 +82,8 @@ Further reading
|
||||
|
||||
* `RBD Windows documentation`_
|
||||
* :ref:`CephFS Windows documentation <ceph-dokan>`
|
||||
* `Windows troubleshooting`_
|
||||
* :ref:`install-windows-troubleshooting`
|
||||
|
||||
.. _Windows configuration sample: ../windows-basic-config
|
||||
.. _RBD Windows documentation: ../../rbd/rbd-windows/
|
||||
.. _Windows troubleshooting: ../windows-troubleshooting
|
||||
.. _General CephFS Prerequisites: ../../cephfs/mount-prerequisites
|
||||
.. _Client Authentication: ../../cephfs/client-auth
|
||||
.. _Windows testing: ../dev/tests-windows
|
||||
.. _OS recommendations: ../../start/os-recommendations
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
:orphan:
|
||||
|
||||
.. _install-windows-troubleshooting:
|
||||
|
||||
===============================
|
||||
Troubleshooting Ceph on Windows
|
||||
===============================
|
||||
|
||||
@ -80,21 +80,66 @@ Example output:
|
||||
{
|
||||
"Files Migrated": [
|
||||
...
|
||||
"db.wal/000052.log size=0x24cd21 migrated=0x1200000 from dev=2->1 ts=2026-06-02T12:44:25.488250+0000",
|
||||
"db.wal/000053.log size=0x3696ad migrated=0x1200000 from dev=2->1 ts=2026-06-02T12:44:25.833014+0000",
|
||||
"db.wal/000054.log size=0x1571f61 migrated=0x2400000 from dev=2->1 ts=2026-06-02T12:44:26.134245+0000",
|
||||
"db.wal/000055.log size=0x1b2b508 migrated=0x2400000 from dev=2->1 ts=2026-06-02T12:44:26.550633+0000"
|
||||
"db/000084.sst size=0xf8cd6 migrated=0x100000 from dev=2->1 ts=2026-07-22T18:49:42.555723+0000",
|
||||
"db/000086.sst size=0xfdd34 migrated=0x100000 from dev=2->1 ts=2026-07-22T18:49:42.595349+0000",
|
||||
"db.wal/000078.log size=0x19de2e2 migrated=0x2400000 from dev=2->1 ts=2026-07-22T18:49:42.694700+0000",
|
||||
"db.wal/000079.log size=0x19d32f7 migrated=0x2400000 from dev=2->1 ts=2026-07-22T18:49:43.424059+0000",
|
||||
"db.wal/000080.log size=0xd1a637 migrated=0x1200000 from dev=2->1 ts=2026-07-22T18:49:44.204353+0000",
|
||||
"db.wal/000081.log size=0xcbeaf5 migrated=0x1200000 from dev=2->1 ts=2026-07-22T18:49:44.618457+0000",
|
||||
"db.wal/000082.log size=0xd6737 migrated=0x1200000 from dev=2->1 ts=2026-07-22T18:49:44.892949+0000",
|
||||
"db.wal/000083.log size=0x696fe8 migrated=0x1200000 from dev=2->1 ts=2026-07-22T18:49:45.279432+0000"
|
||||
],
|
||||
"pending_files": [
|
||||
"db.wal/000056.log",
|
||||
"db.wal/000057.log",
|
||||
"db.wal/000058.log",
|
||||
"db.wal/000059.log",
|
||||
"db.wal/000060.log",
|
||||
"db.wal/000091.log",
|
||||
"db.wal/000092.log",
|
||||
"db.wal/000093.log",
|
||||
"db.wal/000094.log",
|
||||
"db.wal/000095.log",
|
||||
...
|
||||
]
|
||||
],
|
||||
"active_files": [
|
||||
"db.wal/000085.log num_writers=1",
|
||||
"db.wal/000087.log num_writers=1",
|
||||
"db.wal/000088.log num_writers=1",
|
||||
"db.wal/000089.log num_writers=1",
|
||||
"db.wal/000090.log num_writers=1"
|
||||
],
|
||||
"last_scan_time": "2026-07-22T18:51:15.895644+0000"
|
||||
}
|
||||
|
||||
Interpreting Cleaner Stats
|
||||
==========================
|
||||
|
||||
The spillover cleaner operates in repeated scan cycles of alternating Active and Idle phases.
|
||||
The output of ``ceph tell osd.N bluefs spillover cleaner stats`` shows the
|
||||
current cycle state and migration history of spillover cleaner.
|
||||
|
||||
``Files Migrated``
|
||||
Contains a list of entries for files that have been recently migrated from the slow device
|
||||
to the DB device.
|
||||
|
||||
Example entry::
|
||||
|
||||
db/000084.sst size=0xf8cd6 migrated=0x100000 from dev=2->1 ts=2026-07-22T18:49:42.555723+0000
|
||||
|
||||
``pending_files``
|
||||
At the beginning of each cycle, the spillover cleaner scans for files on the slow device and adds them to ``pending_files``.
|
||||
These files are later processed one by one.
|
||||
|
||||
- Successfully migrated files are removed from ``pending_files`` and added
|
||||
to ``Files Migrated``.
|
||||
- Files open for write are removed from ``pending_files`` and reported
|
||||
in ``active_files`` for that cycle.
|
||||
|
||||
A new scan is performed at the beginning of the next cycle.
|
||||
|
||||
``active_files``
|
||||
Contains files that have active writers when the cleaner attempted to
|
||||
migrate them. Migration of these files are skipped during the current cycle.
|
||||
|
||||
``last_scan_time``
|
||||
Shows the time of the most recent cleaner scan.
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
|
||||
5
qa/config/crimson_logical_bucket_cache.yaml
Normal file
5
qa/config/crimson_logical_bucket_cache.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
overrides:
|
||||
ceph:
|
||||
conf:
|
||||
osd:
|
||||
seastore logical bucket cache test stress: true
|
||||
@ -14,6 +14,8 @@ overrides:
|
||||
debug ms: 20
|
||||
seastore cachepin size pershard: 64M
|
||||
seastore max concurrent transactions: 8
|
||||
client:
|
||||
debug_objecter: 20
|
||||
crimson_compat: true
|
||||
workunit:
|
||||
env:
|
||||
|
||||
@ -4,7 +4,10 @@ overrides:
|
||||
osd:
|
||||
# crimson's osd objectstore option
|
||||
osd objectstore: seastore
|
||||
seastore main device type: RANDOM_BLOCK_SSD
|
||||
seastore hot device type: RANDOM_BLOCK_SSD
|
||||
seastore hot backend type: RANDOM_BLOCK
|
||||
seastore cold device type: RANDOM_BLOCK_HDD
|
||||
seastore cold backend type: RANDOM_BLOCK
|
||||
debug seastore: 20
|
||||
debug seastore onode: 20
|
||||
debug seastore odata: 20
|
||||
|
||||
@ -5,7 +5,10 @@ overrides:
|
||||
# crimson's osd objectstore option
|
||||
osd objectstore: seastore
|
||||
seastore cachepin type: 2Q
|
||||
seastore main device type: RANDOM_BLOCK_SSD
|
||||
seastore hot device type: RANDOM_BLOCK_SSD
|
||||
seastore hot backend type: RANDOM_BLOCK
|
||||
seastore cold device type: RANDOM_BLOCK_HDD
|
||||
seastore cold backend type: RANDOM_BLOCK
|
||||
debug seastore: 20
|
||||
debug seastore onode: 20
|
||||
debug seastore odata: 20
|
||||
|
||||
@ -4,6 +4,8 @@ overrides:
|
||||
osd:
|
||||
# crimson's osd objectstore option
|
||||
osd objectstore: seastore
|
||||
seastore cold device type: RANDOM_BLOCK_HDD
|
||||
seastore cold backend type: RANDOM_BLOCK
|
||||
debug seastore: 20
|
||||
debug seastore onode: 20
|
||||
debug seastore odata: 20
|
||||
|
||||
@ -5,6 +5,8 @@ overrides:
|
||||
# crimson's osd objectstore option
|
||||
osd objectstore: seastore
|
||||
seastore cachepin type: 2Q
|
||||
seastore cold device type: RANDOM_BLOCK_HDD
|
||||
seastore cold backend type: RANDOM_BLOCK
|
||||
debug seastore: 20
|
||||
debug seastore onode: 20
|
||||
debug seastore odata: 20
|
||||
|
||||
@ -11,3 +11,8 @@ tasks:
|
||||
mon osdmap full prune min: 15
|
||||
mon osdmap full prune interval: 2
|
||||
mon osdmap full prune txsize: 2
|
||||
overrides:
|
||||
ceph:
|
||||
conf:
|
||||
osd:
|
||||
seastore logical bucket cache test stress: true
|
||||
|
||||
1
qa/suites/crimson-rados/cephfs/crimson_logical_bucket_cache.yaml
Symbolic link
1
qa/suites/crimson-rados/cephfs/crimson_logical_bucket_cache.yaml
Symbolic link
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
1
qa/suites/crimson-rados/rbd/crimson_logical_bucket_cache.yaml
Symbolic link
1
qa/suites/crimson-rados/rbd/crimson_logical_bucket_cache.yaml
Symbolic link
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
1
qa/suites/crimson-rados/rgw/crimson_logical_bucket_cache.yaml
Symbolic link
1
qa/suites/crimson-rados/rgw/crimson_logical_bucket_cache.yaml
Symbolic link
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
1
qa/suites/crimson-rados/thrash/crimson_logical_bucket_cache.yaml
Symbolic link
1
qa/suites/crimson-rados/thrash/crimson_logical_bucket_cache.yaml
Symbolic link
@ -0,0 +1 @@
|
||||
.qa/config/crimson_logical_bucket_cache.yaml
|
||||
@ -29,6 +29,7 @@ overrides:
|
||||
- POOL_FULL
|
||||
- pool full
|
||||
- pool\(s\) full
|
||||
- telemetry module includes new collections
|
||||
tasks:
|
||||
- install:
|
||||
branch: squid
|
||||
|
||||
@ -29,6 +29,7 @@ overrides:
|
||||
- POOL_FULL
|
||||
- pool full
|
||||
- pool\(s\) full
|
||||
- telemetry module includes new collections
|
||||
tasks:
|
||||
- install:
|
||||
branch: tentacle
|
||||
|
||||
@ -209,8 +209,8 @@ def task(ctx, config):
|
||||
manager.raw_cluster_cmd('osd', 'set', 'nodown')
|
||||
|
||||
if CRIMSON:
|
||||
CRIMSON_DEVICE_TYPE = manager.get_config('osd', 0, 'seastore_main_device_type')
|
||||
log.info('seastore_main_device_type is {}...'.format(CRIMSON_DEVICE_TYPE))
|
||||
CRIMSON_DEVICE_TYPE = manager.get_config('osd', 0, 'seastore_hot_device_type')
|
||||
log.info('seastore_hot_device_type is {}...'.format(CRIMSON_DEVICE_TYPE))
|
||||
|
||||
PGNUM = config.get('pgnum', 12)
|
||||
log.info("pgnum: {num}".format(num=PGNUM))
|
||||
|
||||
@ -11,6 +11,7 @@ import functools
|
||||
|
||||
from io import StringIO
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
|
||||
from tasks.cephfs.cephfs_test_case import CephFSTestCase
|
||||
from teuthology.exceptions import CommandFailedError
|
||||
@ -19,6 +20,16 @@ from teuthology.orchestra import run
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# ISO-8601 local time with offset, as dumped by peer_status / mgr status
|
||||
# (e.g. 2026-07-15T12:00:00.558797+0530)
|
||||
SYNC_TIME_STAMP_RE = re.compile(
|
||||
r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}[+-]\d{4}$')
|
||||
|
||||
|
||||
def parse_sync_time_stamp(ts):
|
||||
"""Parse sync_time_stamp / metrics_updated_at ISO-8601 display strings."""
|
||||
return datetime.strptime(ts, '%Y-%m-%dT%H:%M:%S.%f%z')
|
||||
|
||||
|
||||
# Exceptions to retry in test assertions
|
||||
RETRY_EXCEPTIONS = (AssertionError, KeyError, IndexError, CommandFailedError)
|
||||
@ -410,6 +421,7 @@ class TestMirroring(CephFSTestCase):
|
||||
self.assertRegex(
|
||||
last_synced_snap['sync_bytes'],
|
||||
r'^\d+(\.\d+)?\s+(B|KiB|MiB|GiB|TiB|PiB)$')
|
||||
self.assertRegex(last_synced_snap['sync_time_stamp'], SYNC_TIME_STAMP_RE)
|
||||
self.assertIsInstance(last_synced_snap['sync_files'], int)
|
||||
self.assertGreaterEqual(last_synced_snap['sync_files'], 0)
|
||||
|
||||
@ -2765,12 +2777,12 @@ class TestMirroring(CephFSTestCase):
|
||||
'fs', 'mirror', 'peer', 'status',
|
||||
f'{self.primary_fs_name}@{self.primary_fs_id}',
|
||||
peer_uuid)
|
||||
d0_sync_time_stamp = float(self.peer_dir_status(res, '/d0', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d1_sync_time_stamp = float(self.peer_dir_status(res, '/d1', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d2_sync_time_stamp = float(self.peer_dir_status(res, '/d2', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d0_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d0', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
d1_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d1', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
d2_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d2', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
|
||||
self.assertGreaterEqual(d1_sync_time_stamp, d0_sync_time_stamp)
|
||||
self.assertGreaterEqual(d2_sync_time_stamp, d0_sync_time_stamp)
|
||||
@ -2837,12 +2849,12 @@ class TestMirroring(CephFSTestCase):
|
||||
'fs', 'mirror', 'peer', 'status',
|
||||
f'{self.primary_fs_name}@{self.primary_fs_id}',
|
||||
peer_uuid)
|
||||
d0_sync_time_stamp = float(self.peer_dir_status(res, '/d0', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d1_sync_time_stamp = float(self.peer_dir_status(res, '/d1', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d2_sync_time_stamp = float(self.peer_dir_status(res, '/d2', peer_uuid)
|
||||
['last_synced_snap']['sync_time_stamp'].rstrip('s'))
|
||||
d0_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d0', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
d1_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d1', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
d2_sync_time_stamp = parse_sync_time_stamp(
|
||||
self.peer_dir_status(res, '/d2', peer_uuid)['last_synced_snap']['sync_time_stamp'])
|
||||
|
||||
self.assertLess(d1_sync_time_stamp, d0_sync_time_stamp)
|
||||
self.assertLess(d2_sync_time_stamp, d0_sync_time_stamp)
|
||||
|
||||
@ -36,6 +36,9 @@ class DashboardTestCase(MgrTestCase):
|
||||
REQUIRE_FILESYSTEM = True
|
||||
CLIENTS_REQUIRED = 1
|
||||
CEPHFS = False
|
||||
# assigned by setup_mgrs() while the mgrs are down, so setUpClass
|
||||
# bounces the mgrs once instead of twice
|
||||
MODULE_PORTS = [("dashboard", "ssl_server_port", 7789)]
|
||||
ORCHESTRATOR = False
|
||||
ORCHESTRATOR_TEST_DATA = {
|
||||
'inventory': [
|
||||
@ -212,8 +215,10 @@ class DashboardTestCase(MgrTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# setup_mgrs() assigns the dashboard port (MODULE_PORTS) while
|
||||
# the mgrs are down, so no second _assign_ports() bounce is
|
||||
# needed here
|
||||
super(DashboardTestCase, cls).setUpClass()
|
||||
cls._assign_ports("dashboard", "ssl_server_port")
|
||||
cls._load_module("dashboard")
|
||||
cls.update_base_uri()
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import json
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from typing import List, Tuple
|
||||
from unittest import SkipTest
|
||||
|
||||
from teuthology import misc
|
||||
@ -95,8 +96,20 @@ MgrCluster = MgrClusterBase
|
||||
class MgrTestCase(CephTestCase):
|
||||
MGRS_REQUIRED = 1
|
||||
|
||||
# (module_name, config_name, min_port) tuples whose per-mgr port
|
||||
# options are assigned inside setup_mgrs(), while the daemons are
|
||||
# stopped anyway. A subclass that needs a module port should prefer
|
||||
# this over calling _assign_ports() after setup_mgrs(): the latter
|
||||
# stops, fails and restarts every mgr a second time.
|
||||
MODULE_PORTS: List[Tuple[str, str, int]] = []
|
||||
|
||||
@classmethod
|
||||
def setup_mgrs(cls):
|
||||
# for the port-in-use probes below; the map keeps the address
|
||||
# usable even with no active mgr
|
||||
mgr_map = cls.mgr_cluster.get_mgr_map()
|
||||
ip_addr = mgr_map.get('active_addr', '').split(':')[0]
|
||||
|
||||
# Stop all the daemons
|
||||
for daemon in cls.mgr_cluster.mgr_daemons.values():
|
||||
daemon.stop()
|
||||
@ -115,6 +128,12 @@ class MgrTestCase(CephTestCase):
|
||||
cls.mgr_cluster.mon_manager.raw_cluster_cmd(
|
||||
"mgr", "module", "disable", m)
|
||||
|
||||
# Assign module ports while the daemons are down, so the single
|
||||
# restart below picks them up
|
||||
for module_name, config_name, min_port in cls.MODULE_PORTS:
|
||||
cls._assign_module_ports(ip_addr, module_name, config_name,
|
||||
min_port)
|
||||
|
||||
# Start all the daemons
|
||||
for daemon in cls.mgr_cluster.mgr_daemons.values():
|
||||
daemon.restart()
|
||||
@ -219,29 +238,20 @@ class MgrTestCase(CephTestCase):
|
||||
return uri
|
||||
|
||||
@classmethod
|
||||
def _assign_ports(cls, module_name, config_name, min_port=7789):
|
||||
def _assign_module_ports(cls, ip_addr, module_name, config_name,
|
||||
min_port=7789):
|
||||
"""
|
||||
To avoid the need to run lots of hosts in teuthology tests to
|
||||
get different URLs per mgr, we will hand out different ports
|
||||
to each mgr here.
|
||||
|
||||
This is already taken care of for us when running in a vstart
|
||||
environment.
|
||||
Hand out a distinct port per mgr for the given module option.
|
||||
Call with the mgr daemons stopped, so the probes below don't
|
||||
mistake a previous instance of the module for a foreign user
|
||||
of the port.
|
||||
"""
|
||||
# Start handing out ports well above Ceph's range.
|
||||
assign_port = min_port
|
||||
ip_addr = cls.mgr_cluster.get_mgr_map()['active_addr'].split(':')[0]
|
||||
|
||||
for mgr_id in cls.mgr_cluster.mgr_ids:
|
||||
cls.mgr_cluster.mgr_stop(mgr_id)
|
||||
cls.mgr_cluster.mgr_fail(mgr_id)
|
||||
|
||||
|
||||
for mgr_id in cls.mgr_cluster.mgr_ids:
|
||||
# Find a port that isn't in use
|
||||
while True:
|
||||
if not cls.is_port_in_use(ip_addr, assign_port):
|
||||
break
|
||||
while ip_addr and cls.is_port_in_use(ip_addr, assign_port):
|
||||
log.debug(f"Port {assign_port} in use, trying next")
|
||||
assign_port += 1
|
||||
|
||||
@ -252,6 +262,24 @@ class MgrTestCase(CephTestCase):
|
||||
force=True)
|
||||
assign_port += 1
|
||||
|
||||
@classmethod
|
||||
def _assign_ports(cls, module_name, config_name, min_port=7789):
|
||||
"""
|
||||
To avoid the need to run lots of hosts in teuthology tests to
|
||||
get different URLs per mgr, we will hand out different ports
|
||||
to each mgr here.
|
||||
|
||||
This is already taken care of for us when running in a vstart
|
||||
environment.
|
||||
"""
|
||||
ip_addr = cls.mgr_cluster.get_mgr_map()['active_addr'].split(':')[0]
|
||||
|
||||
for mgr_id in cls.mgr_cluster.mgr_ids:
|
||||
cls.mgr_cluster.mgr_stop(mgr_id)
|
||||
cls.mgr_cluster.mgr_fail(mgr_id)
|
||||
|
||||
cls._assign_module_ports(ip_addr, module_name, config_name, min_port)
|
||||
|
||||
for mgr_id in cls.mgr_cluster.mgr_ids:
|
||||
cls.mgr_cluster.mgr_restart(mgr_id)
|
||||
|
||||
|
||||
@ -86,7 +86,10 @@ class Lvm(BaseObjectStore):
|
||||
if disk.is_partition(device) or disk.is_device(device):
|
||||
if device_type == 'block' and self.objectstore == 'bluestore':
|
||||
# NVMe preformat already discards, skip mkfs discard.
|
||||
if nvme_utils.preformat(device):
|
||||
# Only preformat when no ceph VG already exists on the device.
|
||||
# With --osds-per-device > 1, the first OSD creates the VG;
|
||||
# subsequent OSDs reuse it, so reformatting would destroy it.
|
||||
if not api.get_device_vgs(device, 'ceph') and nvme_utils.preformat(device):
|
||||
self.skip_mkfs_discard = True
|
||||
# we must create a vg, and then a single lv
|
||||
lv_name_prefix = "osd-{}".format(device_type)
|
||||
|
||||
@ -150,11 +150,13 @@ class TestLvm:
|
||||
assert self.lvm.args.data_size == 102400
|
||||
|
||||
@patch('ceph_volume.objectstore.lvm.nvme_utils.preformat', Mock(return_value=True))
|
||||
@patch('ceph_volume.objectstore.lvm.api.get_device_vgs', Mock(return_value=[]))
|
||||
@patch('ceph_volume.util.disk.is_device', Mock(return_value=True))
|
||||
@patch('ceph_volume.api.lvm.create_lv')
|
||||
def test_prepare_data_device_preformats_nvme_and_skips_mkfs_discard(self,
|
||||
m_create_lv: MagicMock,
|
||||
factory: Callable[..., Namespace]) -> None:
|
||||
"""No existing VG on a fresh device: preformat runs, discard flag is set."""
|
||||
args = factory(data='/dev/nvme0n1',
|
||||
data_slots=1,
|
||||
data_size=0)
|
||||
@ -168,6 +170,30 @@ class TestLvm:
|
||||
self.lvm.prepare_data_device('block', 'abcd')
|
||||
assert self.lvm.skip_mkfs_discard is True
|
||||
|
||||
@patch('ceph_volume.util.disk.is_device', Mock(return_value=True))
|
||||
@patch('ceph_volume.api.lvm.create_lv')
|
||||
def test_prepare_data_device_skips_preformat_when_vg_exists(self,
|
||||
m_create_lv: MagicMock,
|
||||
factory: Callable[..., Namespace]) -> None:
|
||||
"""Existing ceph VG on device (--osds-per-device > 1): preformat must not run."""
|
||||
m_preformat = Mock(return_value=True)
|
||||
fake_vg = Mock()
|
||||
with patch('ceph_volume.objectstore.lvm.nvme_utils.preformat', m_preformat), \
|
||||
patch('ceph_volume.objectstore.lvm.api.get_device_vgs', return_value=[fake_vg]):
|
||||
args = factory(data='/dev/nvme0n1',
|
||||
data_slots=1,
|
||||
data_size=0)
|
||||
self.lvm.args = args
|
||||
self.lvm.objectstore = 'bluestore'
|
||||
m_create_lv.return_value = Volume(lv_name='lv_foo',
|
||||
lv_path='/fake-path',
|
||||
vg_name='vg_foo',
|
||||
lv_tags='',
|
||||
lv_uuid='abcd')
|
||||
self.lvm.prepare_data_device('block', 'abcd')
|
||||
m_preformat.assert_not_called()
|
||||
assert self.lvm.skip_mkfs_discard is False
|
||||
|
||||
@patch('ceph_volume.util.disk.is_device', Mock(return_value=False))
|
||||
@patch('ceph_volume.util.disk.is_partition', Mock(return_value=False))
|
||||
def test_prepare_data_device_fails(self, factory):
|
||||
|
||||
@ -327,6 +327,12 @@ class SambaContainerCommon(ContainerCommon):
|
||||
# variable. This can be dropped once sambacc no longer needs it,
|
||||
# possibly after the next sambacc release.
|
||||
environ['SAMBACC_CTDB'] = 'ctdb-is-experimental'
|
||||
environ[
|
||||
'SAMBACC_PASSWD_LOCATION'
|
||||
] = '/etc/passwd:/var/lib/samba/passwd:/lib/passwd'
|
||||
environ[
|
||||
'SAMBACC_GROUP_LOCATION'
|
||||
] = '/etc/group:/var/lib/samba/group:/lib/group'
|
||||
if self.cfg.ceph_config_entity:
|
||||
environ['SAMBACC_CEPH_ID'] = f'name={self.cfg.ceph_config_entity}'
|
||||
if self.cfg.rank >= 0:
|
||||
@ -368,8 +374,11 @@ class SMBDContainer(SambaContainerCommon):
|
||||
args = super().args()
|
||||
args.append('run')
|
||||
if self.cfg.clustered:
|
||||
auth_kind = 'nsswitch' if self.cfg.domain_member else 'users'
|
||||
args.append(f'--setup={auth_kind}')
|
||||
if self.cfg.domain_member:
|
||||
args.append('--setup=nsswitch')
|
||||
else:
|
||||
args.append('--setup=nsswitch_auto')
|
||||
args.append('--setup=users')
|
||||
args.append('--setup=smb_ctdb')
|
||||
args.append('--wait-for=ctdb')
|
||||
args.append('smbd')
|
||||
@ -445,11 +454,17 @@ class ConfigWatchContainer(SambaContainerCommon):
|
||||
return 'configwatch'
|
||||
|
||||
def args(self) -> List[str]:
|
||||
return super().args() + [
|
||||
'update-config',
|
||||
'--watch',
|
||||
f'--signal-pids-dir={_WANT_SIGNAL_DIR}',
|
||||
]
|
||||
args = super().args()
|
||||
args.append('run')
|
||||
if self.cfg.clustered:
|
||||
if self.cfg.domain_member:
|
||||
args.append('--setup=nsswitch')
|
||||
else:
|
||||
args.append('--setup=nsswitch_auto')
|
||||
args.append('--wait-for=ctdb')
|
||||
args.append(f'--config-watch-signal-pids-dir={_WANT_SIGNAL_DIR}')
|
||||
args.append('configwatch')
|
||||
return args
|
||||
|
||||
|
||||
class SMBMetricsContainer(ContainerCommon):
|
||||
|
||||
@ -60,6 +60,12 @@ public:
|
||||
static bool is_meta_pool(int64_t pool) {
|
||||
return pool == POOL_META;
|
||||
}
|
||||
static int64_t get_logical_pool(int64_t pool) {
|
||||
if (is_temp_pool(pool))
|
||||
return get_temp_pool(pool); // it's reversible
|
||||
else
|
||||
return pool;
|
||||
}
|
||||
|
||||
public:
|
||||
object_t oid;
|
||||
@ -114,10 +120,7 @@ public:
|
||||
return is_meta_pool(pool);
|
||||
}
|
||||
int64_t get_logical_pool() const {
|
||||
if (is_temp_pool(pool))
|
||||
return get_temp_pool(pool); // it's reversible
|
||||
else
|
||||
return pool;
|
||||
return get_logical_pool(pool);
|
||||
}
|
||||
|
||||
hobject_t() : snap(0), hash(0), max(false), pool(INT64_MIN) {
|
||||
|
||||
@ -154,6 +154,13 @@ options:
|
||||
desc: CPU cores on which POSIX threads alienized to seastar will run in cpuset(7) format
|
||||
flags:
|
||||
- startup
|
||||
- name: seastore_logical_bucket_cache_test_stress
|
||||
type: bool
|
||||
level: dev
|
||||
desc: Enable SeaStore Logical Bucket Cache stress mode. When enabled, SeaStore exercises cold-tier
|
||||
cache paths by forcing promotion/demotion activity and probabilistic write-through behavior.
|
||||
Intended for testing only.
|
||||
default: false
|
||||
|
||||
# Seastore options
|
||||
|
||||
@ -245,15 +252,46 @@ options:
|
||||
level: advanced
|
||||
desc: maximum concurrent transactions that seastore allows (per reactor)
|
||||
default: 128
|
||||
- name: seastore_main_device_type
|
||||
- name: seastore_hot_device_type
|
||||
type: str
|
||||
level: dev
|
||||
desc: The main device type seastore uses (SSD or RANDOM_BLOCK_SSD)
|
||||
desc: The type of the devices in the hot (or only) tier, valid values are HDD, SSD, RANDOM_BLOCK_SSD and RANDOM_BLOCK_HDD
|
||||
default: SSD
|
||||
- name: seastore_hot_backend_type
|
||||
type: str
|
||||
level: dev
|
||||
desc: The back end used by the devices in the hot (or only) tier, valid values are SEGMENTED and RANDOM_BLOCK
|
||||
default: SEGMENTED
|
||||
- name: seastore_hot_backend_bw_throttle
|
||||
type: size
|
||||
level: advanced
|
||||
desc: Size in bytes per second written to the hot devices, 0 indicates no limit
|
||||
default: 0
|
||||
- name: seastore_cold_devices_count
|
||||
type: uint
|
||||
level: dev
|
||||
desc: create cold devices during mkfs
|
||||
default: 0
|
||||
- name: seastore_cold_device_type
|
||||
type: str
|
||||
level: dev
|
||||
desc: The cold device type SeaStore uses, valid values are RANDOM_BLOCK_SSD, RANDOM_BLOCK_HDD, SSD and HDD.
|
||||
This is only effective when there are two tiers, a hot one and a cold one.
|
||||
default: RANDOM_BLOCK_HDD
|
||||
- name: seastore_cold_backend_type
|
||||
type: str
|
||||
level: dev
|
||||
desc: The backend used by cold devices (SEGMENTED or RANDOM_BLOCK)
|
||||
default: RANDOM_BLOCK
|
||||
- name: seastore_cold_backend_bw_throttle
|
||||
type: size
|
||||
level: advanced
|
||||
desc: Size in bytes per second written to cold devices, 0 indicates no limit
|
||||
default: 100_M
|
||||
- name: seastore_cbjournal_size
|
||||
type: size
|
||||
level: dev
|
||||
desc: Total size to use for CircularBoundedJournal if created, it is valid only if seastore_main_device_type is RANDOM_BLOCK
|
||||
desc: Total size to use for CircularBoundedJournal if created, it is valid only if seastore_hot_device_type is RANDOM_BLOCK
|
||||
default: 5_G
|
||||
- name: seastore_multiple_tiers_stop_evict_ratio
|
||||
type: float
|
||||
@ -342,3 +380,38 @@ options:
|
||||
desc: disable osd shards changes upon restart.
|
||||
flags:
|
||||
- startup
|
||||
- name: seastore_cache_promotion_size
|
||||
type: size
|
||||
level: advanced
|
||||
desc: Size in bytes of extents to be promoted from cold devices, set 0 to disable promotion
|
||||
default: 2_M
|
||||
- name: seastore_logical_bucket_capacity
|
||||
type: size
|
||||
level: advanced
|
||||
desc: Size in bytes of memory used by logical bucket
|
||||
default: 200_M
|
||||
- name: seastore_logical_bucket_proceed_size_per_cycle
|
||||
type: size
|
||||
level: advanced
|
||||
desc: Size in bytes of extents to be demoted from logical bucket
|
||||
default: 2_M
|
||||
- name: seastore_write_through_size
|
||||
type: size
|
||||
level: dev
|
||||
desc: Select write through policy when data length is greater than this value.
|
||||
default: 512_K
|
||||
- name: seastore_test_workload_write_through_probability
|
||||
type: float
|
||||
level: dev
|
||||
desc: The percentage of writes that are applied directly to slow devices
|
||||
default: 0.5
|
||||
- name: seastore_test_workload_2Q_promote_probability
|
||||
type: float
|
||||
level: dev
|
||||
desc: The probability of promoting the extents evicted from the warm_in queue to faster devices
|
||||
default: 0.5
|
||||
- name: seastore_test_workload_force_process_background_tasks_period
|
||||
type: uint
|
||||
level: dev
|
||||
desc: Seconds of the period to force process background tasks
|
||||
default: 5
|
||||
|
||||
@ -52,6 +52,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true when the local store is full (failsafe limit); checked at
|
||||
// OSDOp boundary, where data-allocating ops are dropped with -EAGAIN so
|
||||
// the client resends. Default false.
|
||||
virtual bool is_storage_full() const { return false; }
|
||||
|
||||
using CollectionRef = boost::intrusive_ptr<FuturizedCollection>;
|
||||
using base_errorator = crimson::errorator<crimson::ct_error::input_output_error>;
|
||||
using read_errorator = crimson::errorator<crimson::ct_error::enoent,
|
||||
|
||||
@ -9,6 +9,7 @@ set(crimson_seastore_srcs
|
||||
transaction_manager.cc
|
||||
cache.cc
|
||||
extent_pinboard.cc
|
||||
logical_bucket.cc
|
||||
root_block.cc
|
||||
lba_manager.cc
|
||||
async_cleaner.cc
|
||||
@ -45,6 +46,7 @@ set(crimson_seastore_srcs
|
||||
random_block_manager/block_rb_manager.cc
|
||||
random_block_manager/rbm_device.cc
|
||||
random_block_manager/nvme_block_device.cc
|
||||
random_block_manager/hdd_device.cc
|
||||
random_block_manager/avlallocator.cc
|
||||
journal/segmented_journal.cc
|
||||
journal/segment_allocator.cc
|
||||
|
||||
@ -603,10 +603,10 @@ std::size_t JournalTrimmerImpl::get_alloc_journal_size() const
|
||||
return static_cast<std::size_t>(ret);
|
||||
}
|
||||
|
||||
seastar::future<> JournalTrimmerImpl::trim() {
|
||||
seastar::future<> JournalTrimmerImpl::trim(bool force) {
|
||||
return seastar::when_all(
|
||||
[this] {
|
||||
if (should_trim_alloc()) {
|
||||
[this, force] {
|
||||
if (force || should_trim_alloc()) {
|
||||
return trim_alloc(
|
||||
).handle_error(
|
||||
crimson::ct_error::assert_all(
|
||||
@ -617,8 +617,8 @@ seastar::future<> JournalTrimmerImpl::trim() {
|
||||
return seastar::now();
|
||||
}
|
||||
},
|
||||
[this] {
|
||||
if (should_start_trim_dirty()) {
|
||||
[this, force] {
|
||||
if (force || should_start_trim_dirty()) {
|
||||
return trim_dirty(
|
||||
).handle_error(
|
||||
crimson::ct_error::assert_all(
|
||||
@ -717,8 +717,11 @@ JournalTrimmerImpl::trim_dirty()
|
||||
return trans_intr::do_for_each(
|
||||
dirty_list,
|
||||
[this, &t](auto &e) {
|
||||
return extent_callback->rewrite_extent(
|
||||
return extent_callback->maybe_remove_shadow(t, *e
|
||||
).si_then([this, &t, e] {
|
||||
return extent_callback->rewrite_extent(
|
||||
t, e, INIT_GENERATION, NULL_TIME);
|
||||
});
|
||||
});
|
||||
});
|
||||
}).si_then([this, &t] {
|
||||
@ -1388,15 +1391,11 @@ do_reclaim_space_ret do_reclaim_space(
|
||||
&reclaimed, &t, modify_time, target_generation] {
|
||||
DEBUGT("reclaim {} extents", t, extents.size());
|
||||
// rewrite live extents
|
||||
return trans_intr::do_for_each(
|
||||
extents,
|
||||
[&extent_callback, modify_time, &t,
|
||||
&reclaimed, target_generation](auto ext)
|
||||
{
|
||||
reclaimed += ext->get_length();
|
||||
return extent_callback.rewrite_extent(
|
||||
t, ext, target_generation, modify_time);
|
||||
});
|
||||
for (auto &ext : extents) {
|
||||
reclaimed += ext->get_length();
|
||||
}
|
||||
return extent_callback.rewrite_extents(
|
||||
t, extents, target_generation, PLACEMENT_HINT_NULL, modify_time);
|
||||
});
|
||||
}).si_then([&extent_callback, &t] {
|
||||
return extent_callback.submit_transaction_direct(t);
|
||||
@ -1962,9 +1961,11 @@ RBMCleaner::RBMCleaner(
|
||||
RBMDeviceGroupRef&& rb_group,
|
||||
BackrefManager &backref_manager,
|
||||
LBAManager &lba_manager,
|
||||
bool detailed)
|
||||
bool detailed,
|
||||
bool is_cold)
|
||||
: store_index(store_index),
|
||||
detailed(detailed),
|
||||
is_cold(is_cold),
|
||||
rb_group(std::move(rb_group)),
|
||||
backref_manager(backref_manager),
|
||||
lba_manager(lba_manager)
|
||||
@ -2009,12 +2010,41 @@ void RBMCleaner::mark_space_free(
|
||||
ceph_assert(stats.used_bytes >= len);
|
||||
stats.used_bytes -= len;
|
||||
rbm->mark_space_free(addr, len);
|
||||
background_callback->maybe_wake_blocked_io();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RBMCleaner::account_conflict_pending_free(std::size_t bytes)
|
||||
{
|
||||
conflict_pending_free_bytes += bytes;
|
||||
}
|
||||
|
||||
void RBMCleaner::deaccount_conflict_pending_free(std::size_t bytes)
|
||||
{
|
||||
ceph_assert(conflict_pending_free_bytes >= bytes);
|
||||
conflict_pending_free_bytes -= bytes;
|
||||
conflict_drained.broadcast();
|
||||
// Pending conflict frees are a release source try_reserve_projected_usage
|
||||
// blocks on; without this wake a blocked IO whose only release source was
|
||||
// a draining conflict free would miss its wakeup.
|
||||
background_callback->maybe_wake_blocked_io();
|
||||
}
|
||||
|
||||
seastar::future<> RBMCleaner::wait_for_conflict_drain()
|
||||
{
|
||||
auto data_capacity = get_total_bytes() - get_journal_bytes();
|
||||
if (data_capacity == 0) {
|
||||
return seastar::now();
|
||||
}
|
||||
auto threshold = data_capacity / 20; // 5% of data capacity
|
||||
return conflict_drained.wait([this, threshold] {
|
||||
return conflict_pending_free_bytes <= threshold;
|
||||
});
|
||||
}
|
||||
|
||||
void RBMCleaner::commit_space_used(paddr_t addr, extent_len_t len)
|
||||
{
|
||||
auto rbms = rb_group->get_rb_managers();
|
||||
@ -2032,25 +2062,36 @@ bool RBMCleaner::try_reserve_projected_usage(std::size_t projected_usage)
|
||||
{
|
||||
assert(background_callback->is_ready());
|
||||
|
||||
// Capacity check. Without this, concurrent transactions over-commit the
|
||||
// RBM device: each reserves but the cleaner has no clean_space() yet, so
|
||||
// a write that physically can't be served reaches the allocator and
|
||||
// surfaces as `unexpected enospc` asserts in the data path (object_data
|
||||
// _handler.cc et al.). Return false so the EPM BackgroundProcess blocks
|
||||
// the IO until committed transactions release space.
|
||||
//
|
||||
// Headroom carves out room for metadata writes (LBA btree, backref) and
|
||||
// for fragmentation slack the allocator can't pack into. 5% is a starting
|
||||
// point; until RBMCleaner::clean_space() exists we cannot reclaim from
|
||||
// fragmented free space, so headroom doubles as a fragmentation guard.
|
||||
// Use the allocator's authoritative view instead of stats.used_bytes.
|
||||
// alloc_paddr and mark_space_used from existing_block_list are unmetered
|
||||
// paths that push stats.used_bytes past the actual allocator state, causing
|
||||
// premature rejection under sustained overwrite.
|
||||
// Headroom is 1% (down from 5%) because the AVL view already accounts for
|
||||
// metadata extents; 1% covers in-flight surge before alloc_paddr commits.
|
||||
assert(get_total_bytes() > get_journal_bytes());
|
||||
auto data_capacity = get_total_bytes() - get_journal_bytes();
|
||||
auto headroom = data_capacity / 20;
|
||||
auto committed_and_projected = stats.used_bytes
|
||||
auto headroom = data_capacity / 100;
|
||||
auto committed_and_projected = get_allocator_used_bytes()
|
||||
+ stats.projected_used_bytes
|
||||
+ projected_usage;
|
||||
if (committed_and_projected + headroom > data_capacity) {
|
||||
return false;
|
||||
// Gating is only sound while waiting can help. RBM has no cleaner
|
||||
// (can_clean_space() == false): space is only released when in-flight
|
||||
// transactions complete and release what they hold. If no release
|
||||
// source exists, blocking would deadlock (e.g. a sole writer on a
|
||||
// full device waits forever). Admit instead and let the block
|
||||
// allocator stay the final authority: a genuinely full device fails
|
||||
// the allocation with ENOSPC through the existing alloc-failure path
|
||||
// rather than wedging the IO path.
|
||||
//
|
||||
// A zero-usage reservation can never overcommit, and may belong to a
|
||||
// space-releasing transaction (deletes are metadata-only); blocking
|
||||
// those would wedge the escape hatch, so admit them unconditionally.
|
||||
bool has_release_source = stats.projected_used_bytes > 0 ||
|
||||
conflict_pending_free_bytes > 0;
|
||||
if (projected_usage != 0 && has_release_source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
stats.projected_used_bytes += projected_usage;
|
||||
return true;
|
||||
@ -2194,16 +2235,23 @@ void RBMCleaner::register_metrics()
|
||||
{
|
||||
namespace sm = seastar::metrics;
|
||||
|
||||
metrics.add_group("rbm_cleaner", {
|
||||
std::string prefix;
|
||||
if (is_cold) {
|
||||
prefix.append("cold_");
|
||||
}
|
||||
prefix.append("rbm_cleaner");
|
||||
|
||||
metrics.add_group(prefix, {
|
||||
sm::make_counter("total_bytes",
|
||||
[this] { return get_total_bytes(); },
|
||||
sm::description("the size of the space"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter("available_bytes",
|
||||
[this] { return get_total_bytes() - get_journal_bytes() - stats.used_bytes; },
|
||||
[this] { return get_total_bytes() - get_journal_bytes() - get_used_bytes(); },
|
||||
sm::description("the size of the space is available"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter("used_bytes", stats.used_bytes,
|
||||
sm::make_counter("used_bytes",
|
||||
[this] { return get_used_bytes(); },
|
||||
sm::description("the size of the space occupied by live extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))})
|
||||
});
|
||||
|
||||
@ -4,12 +4,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/intrusive/set.hpp>
|
||||
#include <seastar/core/condition-variable.hh>
|
||||
#include <seastar/core/metrics_types.hh>
|
||||
|
||||
#include "common/ceph_time.h"
|
||||
|
||||
#include "osd/osd_types.h"
|
||||
|
||||
#include "crimson/common/config_proxy.h"
|
||||
#include "crimson/os/seastore/cached_extent.h"
|
||||
#include "crimson/os/seastore/seastore_types.h"
|
||||
#include "crimson/os/seastore/segment_manager.h"
|
||||
@ -351,6 +353,66 @@ public:
|
||||
rewrite_gen_t target_generation,
|
||||
sea_time_point modify_time) = 0;
|
||||
|
||||
using rewrite_extents_iertr = base_iertr;
|
||||
using rewrite_extents_ret = rewrite_extents_iertr::future<>;
|
||||
virtual rewrite_extents_ret rewrite_extents(
|
||||
Transaction &t,
|
||||
std::vector<CachedExtentRef> &extents,
|
||||
rewrite_gen_t target_generation,
|
||||
placement_hint_t hint,
|
||||
sea_time_point modify_time) = 0;
|
||||
|
||||
/**
|
||||
* promote_extent
|
||||
*
|
||||
* Promote an extent located from slow devices to the faster devices.
|
||||
* When the type of extent is ObjectDataBlock, the original extent won't
|
||||
* be retired, so that this extent is located in two differenct devices
|
||||
* at the same time, which is helpful to reduce the cost of cleaner process.
|
||||
* The others follow the normal rewrite process but its rewrite generation
|
||||
* will be INIT_GENERATION(XXX: or the maximum generation of the hot tier?).
|
||||
*/
|
||||
using promote_extent_iertr = base_iertr;
|
||||
using promote_extent_ret = promote_extent_iertr::future<>;
|
||||
virtual promote_extent_ret promote_extent(
|
||||
Transaction &t,
|
||||
CachedExtentRef extent) = 0;
|
||||
|
||||
virtual promote_extent_ret promote_extents_from_disk(
|
||||
Transaction &t,
|
||||
paddr_t paddr) = 0;
|
||||
|
||||
/**
|
||||
* demote_region
|
||||
*
|
||||
* Demote the logical extents promoted from the slower device and evict
|
||||
* the extents to the cold tier under the given laddr prefix.
|
||||
*/
|
||||
struct demote_region_res_t {
|
||||
std::size_t demoted_size = 0;
|
||||
std::size_t evicted_size = 0;
|
||||
bool complete = false;
|
||||
};
|
||||
using demote_region_iertr = base_iertr;
|
||||
using demote_region_ret = demote_region_iertr::future<
|
||||
demote_region_res_t>;
|
||||
virtual demote_region_ret demote_region(
|
||||
Transaction &t,
|
||||
laddr_t prefix,
|
||||
std::size_t max_proceed_size) = 0;
|
||||
|
||||
/**
|
||||
* maybe_remove_shadow
|
||||
*
|
||||
* Remove the shadow of the extent if it exists
|
||||
*/
|
||||
using maybe_remove_shadow_iertr = base_iertr;
|
||||
using maybe_remove_shadow_ret =
|
||||
maybe_remove_shadow_iertr::future<>;
|
||||
virtual maybe_remove_shadow_ret maybe_remove_shadow(
|
||||
Transaction &t,
|
||||
CachedExtent &e) = 0;
|
||||
|
||||
/**
|
||||
* get_extents_if_live
|
||||
*
|
||||
@ -418,6 +480,7 @@ struct BackgroundListener {
|
||||
virtual ~BackgroundListener() = default;
|
||||
virtual void maybe_wake_background() = 0;
|
||||
virtual void maybe_wake_blocked_io() = 0;
|
||||
virtual void maybe_wake_promote() = 0;
|
||||
virtual state_t get_state() const = 0;
|
||||
|
||||
bool is_ready() const {
|
||||
@ -622,7 +685,7 @@ public:
|
||||
reserved_usage -= usage;
|
||||
}
|
||||
|
||||
seastar::future<> trim();
|
||||
seastar::future<> trim(bool force);
|
||||
|
||||
static JournalTrimmerImplRef create(
|
||||
store_index_t store_index,
|
||||
@ -654,7 +717,7 @@ private:
|
||||
}
|
||||
|
||||
bool can_drop_backref() const {
|
||||
return get_backend_type() == backend_type_t::RANDOM_BLOCK;
|
||||
return !tail_include_alloc;
|
||||
}
|
||||
|
||||
bool should_trim_alloc() const {
|
||||
@ -1231,18 +1294,34 @@ public:
|
||||
|
||||
virtual void release_projected_usage(std::size_t) = 0;
|
||||
|
||||
// Returns true when the allocator is full (failsafe limit) and writes should
|
||||
// be refused. Only RBMCleaner overrides this; SegmentCleaner returns false.
|
||||
virtual bool is_storage_full() const { return false; }
|
||||
|
||||
// Backpressure gate for deferred-free accumulation (RBM only).
|
||||
// Base-class no-ops so SegmentCleaner callers compile without change.
|
||||
virtual void account_conflict_pending_free(std::size_t) {}
|
||||
virtual void deaccount_conflict_pending_free(std::size_t) {}
|
||||
virtual seastar::future<> wait_for_conflict_drain() {
|
||||
return seastar::now();
|
||||
}
|
||||
|
||||
virtual bool should_block_io_on_clean() const = 0;
|
||||
|
||||
virtual bool can_clean_space() const = 0;
|
||||
|
||||
virtual bool should_clean_space() const = 0;
|
||||
|
||||
virtual double get_alive_ratio() const = 0;
|
||||
|
||||
using clean_space_ertr = base_ertr;
|
||||
using clean_space_ret = clean_space_ertr::future<>;
|
||||
virtual clean_space_ret clean_space() = 0;
|
||||
|
||||
virtual const std::set<device_id_t>& get_device_ids() const = 0;
|
||||
|
||||
virtual backend_type_t get_backend_type() const = 0;
|
||||
|
||||
virtual std::size_t get_reclaim_size_per_cycle() const = 0;
|
||||
|
||||
// Periodic hook for adaptive threshold control. Default: no-op.
|
||||
@ -1467,6 +1546,10 @@ public:
|
||||
return sm_group->get_device_ids();
|
||||
}
|
||||
|
||||
backend_type_t get_backend_type() const final {
|
||||
return backend_type_t::SEGMENTED;
|
||||
}
|
||||
|
||||
std::size_t get_reclaim_size_per_cycle() const final {
|
||||
return config.reclaim_bytes_per_cycle;
|
||||
}
|
||||
@ -1609,7 +1692,7 @@ private:
|
||||
if (segments.get_unavailable_bytes() == 0) return 0;
|
||||
return (double)get_unavailable_unused_bytes() / (double)segments.get_unavailable_bytes();
|
||||
}
|
||||
double get_alive_ratio() const {
|
||||
double get_alive_ratio() const final {
|
||||
return stats.used_bytes / (double)segments.get_total_bytes();
|
||||
}
|
||||
|
||||
@ -1741,17 +1824,19 @@ public:
|
||||
RBMDeviceGroupRef&& rb_group,
|
||||
BackrefManager &backref_manager,
|
||||
LBAManager &lba_manager,
|
||||
bool detailed);
|
||||
bool detailed,
|
||||
bool is_cold);
|
||||
|
||||
static RBMCleanerRef create(
|
||||
store_index_t store_index,
|
||||
RBMDeviceGroupRef&& rb_group,
|
||||
BackrefManager &backref_manager,
|
||||
LBAManager &lba_manager,
|
||||
bool detailed) {
|
||||
bool detailed,
|
||||
bool is_cold) {
|
||||
return std::make_unique<RBMCleaner>(
|
||||
store_index,
|
||||
std::move(rb_group), backref_manager, lba_manager, detailed);
|
||||
std::move(rb_group), backref_manager, lba_manager, detailed, is_cold);
|
||||
}
|
||||
|
||||
RBMDeviceGroup* get_rb_group() {
|
||||
@ -1776,13 +1861,18 @@ public:
|
||||
|
||||
store_statfs_t get_stat() const final {
|
||||
store_statfs_t st;
|
||||
auto used = get_used_bytes();
|
||||
st.total = get_total_bytes();
|
||||
st.available = get_total_bytes() - get_journal_bytes() - stats.used_bytes;
|
||||
st.allocated = get_journal_bytes() + stats.used_bytes;
|
||||
st.data_stored = get_journal_bytes() + stats.used_bytes;
|
||||
st.available = get_total_bytes() - get_journal_bytes() - used;
|
||||
st.allocated = get_journal_bytes() + used;
|
||||
st.data_stored = get_journal_bytes() + used;
|
||||
return st;
|
||||
}
|
||||
|
||||
double get_alive_ratio() const final {
|
||||
return stats.used_bytes / (double)get_total_bytes();
|
||||
}
|
||||
|
||||
void print(std::ostream &, bool is_detailed) const final;
|
||||
|
||||
mount_ret mount() final;
|
||||
@ -1791,6 +1881,10 @@ public:
|
||||
|
||||
void mark_space_free(paddr_t, extent_len_t) final;
|
||||
|
||||
void account_conflict_pending_free(std::size_t bytes) final;
|
||||
void deaccount_conflict_pending_free(std::size_t bytes) final;
|
||||
seastar::future<> wait_for_conflict_drain() final;
|
||||
|
||||
void commit_space_used(paddr_t, extent_len_t) final;
|
||||
|
||||
bool try_reserve_projected_usage(std::size_t) final;
|
||||
@ -1815,6 +1909,10 @@ public:
|
||||
return rb_group->get_device_ids();
|
||||
}
|
||||
|
||||
backend_type_t get_backend_type() const final {
|
||||
return backend_type_t::RANDOM_BLOCK;
|
||||
}
|
||||
|
||||
std::size_t get_reclaim_size_per_cycle() const final {
|
||||
return 0;
|
||||
}
|
||||
@ -1850,10 +1948,11 @@ public:
|
||||
return paddr;
|
||||
}
|
||||
|
||||
std::list<alloc_paddr_result> alloc_paddrs(extent_len_t length) {
|
||||
std::list<alloc_paddr_result> alloc_paddrs(
|
||||
extent_len_t length, paddr_t hint) {
|
||||
// TODO: implement allocation strategy (dirty metadata and multiple devices)
|
||||
auto rbs = rb_group->get_rb_managers();
|
||||
auto ret = rbs[0]->alloc_extents(length);
|
||||
auto ret = rbs[0]->alloc_extents(length, hint);
|
||||
if (!ret.empty()) {
|
||||
stats.used_bytes += length;
|
||||
}
|
||||
@ -1879,6 +1978,43 @@ public:
|
||||
return total;
|
||||
}
|
||||
|
||||
// Bytes allocated in the AVL allocator across all RBM devices.
|
||||
// get_size() returns the data section only (journal already excluded).
|
||||
uint64_t get_allocator_used_bytes() const {
|
||||
uint64_t used = 0;
|
||||
for (auto *rbm : rb_group->get_rb_managers()) {
|
||||
uint64_t data = rbm->get_size();
|
||||
uint64_t free = static_cast<uint64_t>(rbm->get_free_blocks())
|
||||
* rbm->get_block_size();
|
||||
used += (data > free) ? (data - free) : 0;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
// Used bytes as reported to statfs and metrics: the allocator's
|
||||
// authoritative view, matching the admission gating in
|
||||
// try_reserve_projected_usage (stats.used_bytes drifts on unmetered
|
||||
// paths). Falls back to stats.used_bytes until the allocator is
|
||||
// populated during mount (mirrors is_storage_full).
|
||||
uint64_t get_used_bytes() const {
|
||||
return background_callback->is_ready()
|
||||
? get_allocator_used_bytes() : stats.used_bytes;
|
||||
}
|
||||
|
||||
bool is_storage_full() const final {
|
||||
// The allocator view is not authoritative until the background process
|
||||
// has finished scanning space at mount; never report full before then
|
||||
// (mirrors the is_ready assertion in try_reserve_projected_usage).
|
||||
if (!background_callback->is_ready()) {
|
||||
return false;
|
||||
}
|
||||
auto data_capacity = get_total_bytes() - get_journal_bytes();
|
||||
if (data_capacity == 0) return false;
|
||||
auto headroom = data_capacity / 100;
|
||||
return get_allocator_used_bytes() + stats.projected_used_bytes
|
||||
+ headroom > data_capacity;
|
||||
}
|
||||
|
||||
// Testing interfaces
|
||||
|
||||
bool check_usage(bool has_cold_tier) final;
|
||||
@ -1893,6 +2029,7 @@ private:
|
||||
|
||||
store_index_t store_index;
|
||||
const bool detailed;
|
||||
const bool is_cold;
|
||||
RBMDeviceGroupRef rb_group;
|
||||
BackrefManager &backref_manager;
|
||||
LBAManager &lba_manager;
|
||||
@ -1913,6 +2050,12 @@ private:
|
||||
*/
|
||||
uint64_t projected_used_bytes = 0;
|
||||
} stats;
|
||||
|
||||
// Bytes held in deferred-free state (conflicted OOL writes still in flight).
|
||||
// Gate: new OOL writes wait when this exceeds 5% of data_capacity.
|
||||
std::size_t conflict_pending_free_bytes = 0;
|
||||
seastar::condition_variable conflict_drained;
|
||||
|
||||
seastar::metrics::metric_group metrics;
|
||||
void register_metrics();
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
|
||||
// vim: ts=8 sw=2 sts=2 expandtab
|
||||
|
||||
#include "crimson/common/coroutine.h"
|
||||
#include "crimson/os/seastore/backref/btree_backref_manager.h"
|
||||
|
||||
SET_SUBSYS(seastore_backref);
|
||||
@ -71,16 +72,16 @@ BtreeBackrefManager::mkfs(
|
||||
{
|
||||
LOG_PREFIX(BtreeBackrefManager::mkfs);
|
||||
INFOT("start", t);
|
||||
return cache.get_root(t).si_then([this, &t](auto croot) {
|
||||
assert(croot->is_mutation_pending());
|
||||
croot->get_root().backref_root = BackrefBtree::mkfs(croot, get_context(t));
|
||||
return mkfs_iertr::now();
|
||||
}).handle_error_interruptible(
|
||||
mkfs_iertr::pass_further{},
|
||||
crimson::ct_error::assert_all(
|
||||
"Invalid error in BtreeBackrefManager::mkfs"
|
||||
)
|
||||
);
|
||||
|
||||
auto croot = co_await cache.get_root(t);
|
||||
croot->get_root().backref_root =
|
||||
co_await BackrefBtree::mkfs(croot, get_context(t)
|
||||
).handle_error_interruptible(
|
||||
mkfs_iertr::pass_further{},
|
||||
crimson::ct_error::assert_all(
|
||||
"Invalid error in BtreeBackrefManager::mkfs"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
BtreeBackrefManager::get_mapping_ret
|
||||
@ -456,6 +457,50 @@ BtreeBackrefManager::scan_mapped_space(
|
||||
});
|
||||
}
|
||||
|
||||
BtreeBackrefManager::scan_device_ret
|
||||
BtreeBackrefManager::scan_device(
|
||||
Transaction &t,
|
||||
paddr_t paddr,
|
||||
scan_device_func_t &f)
|
||||
{
|
||||
LOG_PREFIX(BtreeBackrefManager::scan_device);
|
||||
auto c = get_context(t);
|
||||
auto croot = co_await cache.get_root(t);
|
||||
auto btree = BackrefBtree(croot);
|
||||
auto iter = co_await btree.lower_bound(c, paddr);
|
||||
while (!iter.is_end()) {
|
||||
auto key = iter.get_key();
|
||||
auto bentry = cache.get_cached_backref_entry(key);
|
||||
if (bentry) {
|
||||
assert(bentry->paddr == key);
|
||||
DEBUGT("found in cache: {} {}", t, bentry->paddr, bentry->laddr);
|
||||
}
|
||||
if (bentry && bentry->laddr == L_ADDR_NULL) {
|
||||
DEBUGT("{} is removed", t, bentry->paddr);
|
||||
iter = co_await iter.next(c);
|
||||
continue;
|
||||
}
|
||||
if (key.get_device_id() == paddr.get_device_id()) {
|
||||
auto val = iter.get_val();
|
||||
if (bentry && bentry->laddr != val.laddr) {
|
||||
DEBUGT("{} changed from {} to {}",
|
||||
t, bentry->paddr, val.laddr, bentry->laddr);
|
||||
iter = co_await iter.next(c);
|
||||
continue;
|
||||
}
|
||||
DEBUGT("scanned {}, {}", t, key, val.laddr);
|
||||
auto ret = co_await f(key, val.len, val.type, val.laddr);
|
||||
if (ret == seastar::stop_iteration::yes) {
|
||||
break;
|
||||
}
|
||||
} else if (key.get_device_id() > paddr.get_device_id()) {
|
||||
break;
|
||||
}
|
||||
iter = co_await iter.next(c);
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
base_iertr::future<> _init_cached_extent(
|
||||
op_context_t c,
|
||||
const CachedExtentRef &e,
|
||||
|
||||
@ -55,6 +55,11 @@ public:
|
||||
Transaction &t,
|
||||
scan_mapped_space_func_t &&f) final;
|
||||
|
||||
scan_device_ret scan_device(
|
||||
Transaction &t,
|
||||
paddr_t paddr,
|
||||
scan_device_func_t &f) final;
|
||||
|
||||
init_cached_extent_ret init_cached_extent(
|
||||
Transaction &t,
|
||||
CachedExtentRef e) final;
|
||||
|
||||
@ -139,6 +139,15 @@ public:
|
||||
Transaction &t,
|
||||
scan_mapped_space_func_t &&f) = 0;
|
||||
|
||||
using scan_device_ret = base_iertr::future<>;
|
||||
using scan_device_func_t = std::function<
|
||||
base_iertr::future<seastar::stop_iteration>(
|
||||
paddr_t, extent_len_t, extent_types_t, laddr_t)>;
|
||||
virtual scan_device_ret scan_device(
|
||||
Transaction &t,
|
||||
paddr_t start,
|
||||
scan_device_func_t &f) = 0;
|
||||
|
||||
virtual ~BackrefManager() {}
|
||||
};
|
||||
|
||||
|
||||
@ -12,9 +12,13 @@ namespace lba {
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const lba_map_val_t& v)
|
||||
{
|
||||
return out << "lba_map_val_t("
|
||||
<< v.pladdr
|
||||
<< "~0x" << std::hex << v.len
|
||||
out << "lba_map_val_t(";
|
||||
if (v.shadow_paddr != P_ADDR_NULL) {
|
||||
out << '[' << v.pladdr << ',' << v.shadow_paddr << ']';
|
||||
} else {
|
||||
out << v.pladdr;
|
||||
}
|
||||
return out << "~0x" << std::hex << v.len
|
||||
<< ", type=" << (extent_types_t)v.type
|
||||
<< ", checksum=0x" << v.checksum
|
||||
<< ", refcount=" << std::dec << v.refcount
|
||||
|
||||
@ -110,6 +110,7 @@ struct lba_map_val_t {
|
||||
extent_len_t len = 0; ///< length of mapping
|
||||
pladdr_t pladdr; ///< direct addr of mapping or
|
||||
// laddr of a direct lba mapping(see btree_lba_manager.h)
|
||||
paddr_t shadow_paddr;
|
||||
extent_ref_count_t refcount = 0; ///< refcount
|
||||
checksum_t checksum = 0; ///< checksum of original block written at paddr (TODO)
|
||||
extent_types_t type = extent_types_t::NONE;
|
||||
@ -118,11 +119,12 @@ struct lba_map_val_t {
|
||||
lba_map_val_t(
|
||||
extent_len_t len,
|
||||
pladdr_t pladdr,
|
||||
paddr_t shadow_paddr,
|
||||
extent_ref_count_t refcount,
|
||||
checksum_t checksum,
|
||||
extent_types_t type)
|
||||
: len(len), pladdr(pladdr), refcount(refcount),
|
||||
checksum(checksum), type(type) {}
|
||||
: len(len), pladdr(pladdr), shadow_paddr(shadow_paddr),
|
||||
refcount(refcount), checksum(checksum), type(type) {}
|
||||
bool operator==(const lba_map_val_t&) const = default;
|
||||
};
|
||||
|
||||
@ -136,6 +138,7 @@ std::ostream& operator<<(std::ostream& out, const lba_map_val_t&);
|
||||
struct __attribute__((packed)) lba_map_val_le_t {
|
||||
extent_len_le_t len = init_extent_len_le(0);
|
||||
pladdr_le_t pladdr;
|
||||
paddr_le_t shadow_paddr;
|
||||
extent_ref_count_le_t refcount{0};
|
||||
checksum_le_t checksum{0};
|
||||
extent_types_le_t type{EXTENT_TYPES_MAX};
|
||||
@ -145,6 +148,7 @@ struct __attribute__((packed)) lba_map_val_le_t {
|
||||
explicit lba_map_val_le_t(const lba_map_val_t &val)
|
||||
: len(init_extent_len_le(val.len)),
|
||||
pladdr(pladdr_le_t(val.pladdr)),
|
||||
shadow_paddr(paddr_le_t(val.shadow_paddr)),
|
||||
refcount(val.refcount),
|
||||
checksum(val.checksum),
|
||||
type(static_cast<extent_types_le_t>(val.type)) {}
|
||||
@ -153,6 +157,7 @@ struct __attribute__((packed)) lba_map_val_le_t {
|
||||
return lba_map_val_t{
|
||||
len,
|
||||
pladdr,
|
||||
shadow_paddr,
|
||||
refcount,
|
||||
checksum,
|
||||
static_cast<extent_types_t>(type)};
|
||||
@ -293,3 +298,7 @@ std::ostream &operator<<(
|
||||
}
|
||||
|
||||
} // namespace crimson::os::seastore
|
||||
|
||||
#if FMT_VERSION >= 90000
|
||||
template <> struct fmt::formatter<crimson::os::seastore::lba::lba_map_val_t> : fmt::ostream_formatter {};
|
||||
#endif
|
||||
|
||||
@ -216,6 +216,21 @@ public:
|
||||
|
||||
}
|
||||
|
||||
// return true if reached the next mapping and false otherwise
|
||||
bool next_sync(op_context_t c) {
|
||||
#ifndef NDEBUG
|
||||
assert_valid();
|
||||
#endif
|
||||
assert(is_full());
|
||||
assert(!is_end());
|
||||
leaf.pos++;
|
||||
if (at_boundary()) {
|
||||
return handle_boundary_sync(c);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to the previous entry. If already at position 0 in the current
|
||||
* leaf, walks up the internal stack (ensure_internal_bottom_up) to find
|
||||
@ -520,6 +535,50 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
// return true if reached the next mapping and false otherwise
|
||||
bool handle_boundary_sync(op_context_t c) {
|
||||
assert(at_boundary());
|
||||
assert(is_full());
|
||||
depth_t depth = 2;
|
||||
// find the first level (from the leaf's parent up) with a right sibling
|
||||
for (; depth <= get_depth(); depth++) {
|
||||
auto &internal = get_internal(depth);
|
||||
auto it = internal.node->iter_idx(internal.pos + 1);
|
||||
if (it != internal.node->end()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (depth > get_depth()) {
|
||||
return false; // no next leaf (end)
|
||||
}
|
||||
// step to the right sibling at that level, then descend leftmost to the leaf
|
||||
get_internal(depth).pos++;
|
||||
while (depth > 2) {
|
||||
auto &internal = get_internal(depth);
|
||||
auto it = get_internal(depth).node->iter_idx(internal.pos);
|
||||
auto child = get_internal(depth).node->template get_child_sync<
|
||||
internal_node_t>(c.trans, c.cache, internal.pos, it.get_key());
|
||||
if (!is_valid_child_ptr(child.get())) {
|
||||
return false;
|
||||
}
|
||||
depth--;
|
||||
get_internal(depth).node = child;
|
||||
get_internal(depth).pos = 0;
|
||||
}
|
||||
assert(depth == 2);
|
||||
// position `leaf` at the next leaf
|
||||
auto &parent = get_internal(depth);
|
||||
auto it = parent.node->begin();
|
||||
auto child = parent.node->template get_child_sync<
|
||||
leaf_node_t>(c.trans, c.cache, parent.pos, it.get_key());
|
||||
if (!is_valid_child_ptr(child.get())) {
|
||||
return false;
|
||||
}
|
||||
leaf.node = child;
|
||||
leaf.pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when leaf.pos has advanced past the end of the current leaf
|
||||
* (at_boundary() == true). Walks up the internal stack to find the
|
||||
@ -649,14 +708,25 @@ public:
|
||||
* leaf node as the root, links it to the RootBlock, and returns the
|
||||
* tree's root descriptor.
|
||||
*/
|
||||
using mkfs_ret = phy_tree_root_t;
|
||||
using mkfs_iertr = base_iertr;
|
||||
using mkfs_ret = mkfs_iertr::future<phy_tree_root_t>;
|
||||
static mkfs_ret mkfs(RootBlockRef &root_block, op_context_t c) {
|
||||
assert(root_block->is_mutation_pending());
|
||||
auto root_leaf = c.cache.template alloc_new_non_data_extent<leaf_node_t>(
|
||||
c.trans,
|
||||
node_size,
|
||||
placement_hint_t::HOT,
|
||||
INIT_GENERATION);
|
||||
LeafNodeRef root_leaf;
|
||||
while (!root_leaf) {
|
||||
try {
|
||||
root_leaf = c.cache.template alloc_new_non_data_extent<leaf_node_t>(
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
} catch (crimson::ct_error::eagain&) {
|
||||
} catch (crimson::ct_error::enospc&) {
|
||||
ceph_abort("shouldn't have no space");
|
||||
}
|
||||
if (!root_leaf) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
root_leaf->set_size(0);
|
||||
fixed_kv_node_meta_t<node_key_t> meta{min_max_t<node_key_t>::min, min_max_t<node_key_t>::max, 1};
|
||||
root_leaf->set_meta(meta);
|
||||
@ -664,7 +734,7 @@ public:
|
||||
get_tree_stats<self_type>(c.trans).depth = 1u;
|
||||
get_tree_stats<self_type>(c.trans).extents_num_delta++;
|
||||
TreeRootLinker<RootBlock, leaf_node_t>::link_root(root_block, root_leaf.get());
|
||||
return phy_tree_root_t{root_leaf->get_paddr(), 1u};
|
||||
co_return phy_tree_root_t{root_leaf->get_paddr(), 1u};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -752,10 +822,12 @@ public:
|
||||
if (depth > 1) {
|
||||
auto child = entry.node->template get_child_sync<internal_node_t>(
|
||||
c.trans, c.cache, entry.pos, riter.get_key());
|
||||
ceph_assert(is_valid_child_ptr(child.get()));
|
||||
iter.get_internal(depth).node = child;
|
||||
} else {
|
||||
auto child = entry.node->template get_child_sync<leaf_node_t>(
|
||||
c.trans, c.cache, entry.pos, riter.get_key());
|
||||
ceph_assert(is_valid_child_ptr(child.get()));
|
||||
iter.leaf.node = child;
|
||||
}
|
||||
}
|
||||
@ -1730,15 +1802,29 @@ public:
|
||||
LOG_PREFIX(FixedKVBtree::rewrite_extent);
|
||||
assert(is_lba_backref_node(e->get_type()));
|
||||
|
||||
auto do_rewrite = [&](auto &fixed_kv_extent) {
|
||||
auto n_fixed_kv_extent = c.cache.template alloc_new_non_data_extent<
|
||||
std::remove_reference_t<decltype(fixed_kv_extent)>
|
||||
>(
|
||||
c.trans,
|
||||
fixed_kv_extent.get_length(),
|
||||
auto do_rewrite = [&](auto &fixed_kv_extent) -> rewrite_extent_ret {
|
||||
auto opt = Cache::alloc_option_t {
|
||||
fixed_kv_extent.get_user_hint(),
|
||||
// get target rewrite generation
|
||||
fixed_kv_extent.get_rewrite_generation());
|
||||
fixed_kv_extent.get_rewrite_generation()
|
||||
};
|
||||
TCachedExtentRef<std::remove_reference_t<
|
||||
decltype(fixed_kv_extent)>> n_fixed_kv_extent;
|
||||
while (!n_fixed_kv_extent) {
|
||||
try {
|
||||
n_fixed_kv_extent = c.cache.template alloc_new_non_data_extent<
|
||||
std::remove_reference_t<decltype(fixed_kv_extent)>
|
||||
>(
|
||||
c.trans,
|
||||
fixed_kv_extent.get_length(),
|
||||
opt);
|
||||
} catch (crimson::ct_error::eagain&) {}
|
||||
if (!n_fixed_kv_extent) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
n_fixed_kv_extent->rewrite(c.trans, fixed_kv_extent, 0);
|
||||
|
||||
SUBTRACET(
|
||||
@ -1747,26 +1833,24 @@ public:
|
||||
c.trans,
|
||||
fixed_kv_extent,
|
||||
*n_fixed_kv_extent);
|
||||
|
||||
return update_internal_mapping(
|
||||
|
||||
co_await update_internal_mapping(
|
||||
c,
|
||||
n_fixed_kv_extent->get_node_meta().depth,
|
||||
n_fixed_kv_extent->get_node_meta().begin,
|
||||
e->get_paddr(),
|
||||
n_fixed_kv_extent->get_paddr(),
|
||||
n_fixed_kv_extent
|
||||
).si_then([c, e] {
|
||||
c.cache.retire_extent(c.trans, e);
|
||||
});
|
||||
n_fixed_kv_extent);
|
||||
c.cache.retire_extent(c.trans, e);
|
||||
};
|
||||
|
||||
if (e->get_type() == internal_node_t::TYPE) {
|
||||
auto lint = e->cast<internal_node_t>();
|
||||
return do_rewrite(*lint);
|
||||
co_await do_rewrite(*lint);
|
||||
} else {
|
||||
assert(e->get_type() == leaf_node_t::TYPE);
|
||||
auto lleaf = e->cast<leaf_node_t>();
|
||||
return do_rewrite(*lleaf);
|
||||
co_await do_rewrite(*lleaf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2601,126 +2685,150 @@ private:
|
||||
{
|
||||
LOG_PREFIX(FixedKVBtree::handle_split);
|
||||
|
||||
return iter.check_split(c
|
||||
).si_then([FNAME, this, c, &iter](auto split_from) {
|
||||
SUBTRACET(seastore_fixedkv_tree,
|
||||
"split_from {}, depth {}", c.trans, split_from, iter.get_depth());
|
||||
auto split_from = co_await iter.check_split(c);
|
||||
SUBTRACET(seastore_fixedkv_tree,
|
||||
"split_from {}, depth {}", c.trans, split_from, iter.get_depth());
|
||||
|
||||
if (split_from == iter.get_depth()) {
|
||||
assert(iter.is_full());
|
||||
auto nroot = c.cache.template alloc_new_non_data_extent<internal_node_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
fixed_kv_node_meta_t<node_key_t> meta{
|
||||
min_max_t<node_key_t>::min, min_max_t<node_key_t>::max, iter.get_depth() + 1};
|
||||
nroot->set_meta(meta);
|
||||
nroot->range = meta;
|
||||
nroot->journal_insert(
|
||||
nroot->begin(),
|
||||
min_max_t<node_key_t>::min,
|
||||
get_root().get_location(),
|
||||
nullptr);
|
||||
iter.internal.push_back({nroot, 0});
|
||||
if (split_from == iter.get_depth()) {
|
||||
assert(iter.is_full());
|
||||
InternalNodeRef nroot;
|
||||
while (!nroot) {
|
||||
try {
|
||||
nroot = c.cache.template alloc_new_non_data_extent<internal_node_t>(
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
} catch (crimson::ct_error::eagain&) {}
|
||||
if (!nroot) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
fixed_kv_node_meta_t<node_key_t> meta{
|
||||
min_max_t<node_key_t>::min, min_max_t<node_key_t>::max, iter.get_depth() + 1};
|
||||
nroot->set_meta(meta);
|
||||
nroot->range = meta;
|
||||
nroot->journal_insert(
|
||||
nroot->begin(),
|
||||
min_max_t<node_key_t>::min,
|
||||
get_root().get_location(),
|
||||
nullptr);
|
||||
iter.internal.push_back({nroot, 0});
|
||||
|
||||
get_tree_stats<self_type>(c.trans).depth = iter.get_depth();
|
||||
get_tree_stats<self_type>(c.trans).extents_num_delta++;
|
||||
get_tree_stats<self_type>(c.trans).depth = iter.get_depth();
|
||||
get_tree_stats<self_type>(c.trans).extents_num_delta++;
|
||||
|
||||
root_block = c.cache.duplicate_for_write(
|
||||
c.trans, root_block)->template cast<RootBlock>();
|
||||
get_root().set_location(nroot->get_paddr());
|
||||
get_root().set_depth(iter.get_depth());
|
||||
ceph_assert(get_root().get_depth() <= MAX_DEPTH);
|
||||
set_root_node(nroot);
|
||||
root_block = c.cache.duplicate_for_write(
|
||||
c.trans, root_block)->template cast<RootBlock>();
|
||||
get_root().set_location(nroot->get_paddr());
|
||||
get_root().set_depth(iter.get_depth());
|
||||
ceph_assert(get_root().get_depth() <= MAX_DEPTH);
|
||||
set_root_node(nroot);
|
||||
}
|
||||
|
||||
/* pos may be either node_position_t<leaf_node_t> or
|
||||
* node_position_t<internal_node_t> */
|
||||
auto split_level = [&, c, FNAME](auto &parent_pos, auto &pos)
|
||||
-> handle_split_iertr::future<std::pair<
|
||||
decltype(pos.node), decltype(pos.node)>> {
|
||||
decltype(pos.node) left, right;
|
||||
node_key_t pivot = min_max_t<node_key_t>::null;
|
||||
while (!left) {
|
||||
assert(!right);
|
||||
assert(pivot == min_max_t<node_key_t>::null);
|
||||
try {
|
||||
auto t = pos.node->make_split_children(c);
|
||||
left = std::get<0>(t);
|
||||
right = std::get<1>(t);
|
||||
pivot = std::get<2>(t);
|
||||
} catch (crimson::ct_error::eagain&) {}
|
||||
if (!left) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
|
||||
/* pos may be either node_position_t<leaf_node_t> or
|
||||
* node_position_t<internal_node_t> */
|
||||
auto split_level = [&, c, FNAME](auto &parent_pos, auto &pos) {
|
||||
auto [left, right, pivot] = pos.node->make_split_children(c);
|
||||
auto parent_node = parent_pos.node;
|
||||
auto parent_iter = parent_pos.get_iter();
|
||||
|
||||
auto parent_node = parent_pos.node;
|
||||
auto parent_iter = parent_pos.get_iter();
|
||||
parent_node->update(
|
||||
parent_iter,
|
||||
left->get_paddr(),
|
||||
left.get());
|
||||
parent_node->insert(
|
||||
parent_iter + 1,
|
||||
pivot,
|
||||
right->get_paddr(),
|
||||
right.get());
|
||||
|
||||
parent_node->update(
|
||||
parent_iter,
|
||||
left->get_paddr(),
|
||||
left.get());
|
||||
parent_node->insert(
|
||||
parent_iter + 1,
|
||||
pivot,
|
||||
right->get_paddr(),
|
||||
right.get());
|
||||
SUBTRACET(
|
||||
seastore_fixedkv_tree,
|
||||
"splitted {} into left: {}, right: {}",
|
||||
c.trans,
|
||||
*pos.node,
|
||||
*left,
|
||||
*right);
|
||||
c.cache.retire_extent(c.trans, pos.node);
|
||||
|
||||
get_tree_stats<self_type>(c.trans).extents_num_delta++;
|
||||
co_return std::make_pair(left, right);
|
||||
};
|
||||
|
||||
for (; split_from > 0; --split_from) {
|
||||
auto &parent_pos = iter.get_internal(split_from + 1);
|
||||
if (!parent_pos.node->is_mutable()) {
|
||||
parent_pos.node = c.cache.duplicate_for_write(
|
||||
c.trans, parent_pos.node
|
||||
)->template cast<internal_node_t>();
|
||||
}
|
||||
|
||||
if (split_from > 1) {
|
||||
auto &pos = iter.get_internal(split_from);
|
||||
SUBTRACET(
|
||||
seastore_fixedkv_tree,
|
||||
"splitted {} into left: {}, right: {}",
|
||||
"splitting internal {} at depth {}, parent: {} at pos: {}",
|
||||
c.trans,
|
||||
*pos.node,
|
||||
*left,
|
||||
*right);
|
||||
c.cache.retire_extent(c.trans, pos.node);
|
||||
split_from,
|
||||
*parent_pos.node,
|
||||
parent_pos.pos);
|
||||
auto [left, right] = co_await split_level(parent_pos, pos);
|
||||
|
||||
get_tree_stats<self_type>(c.trans).extents_num_delta++;
|
||||
return std::make_pair(left, right);
|
||||
};
|
||||
|
||||
for (; split_from > 0; --split_from) {
|
||||
auto &parent_pos = iter.get_internal(split_from + 1);
|
||||
if (!parent_pos.node->is_mutable()) {
|
||||
parent_pos.node = c.cache.duplicate_for_write(
|
||||
c.trans, parent_pos.node
|
||||
)->template cast<internal_node_t>();
|
||||
}
|
||||
|
||||
if (split_from > 1) {
|
||||
auto &pos = iter.get_internal(split_from);
|
||||
SUBTRACET(
|
||||
seastore_fixedkv_tree,
|
||||
"splitting internal {} at depth {}, parent: {} at pos: {}",
|
||||
c.trans,
|
||||
*pos.node,
|
||||
split_from,
|
||||
*parent_pos.node,
|
||||
parent_pos.pos);
|
||||
auto [left, right] = split_level(parent_pos, pos);
|
||||
|
||||
if (pos.pos < left->get_size()) {
|
||||
pos.node = left;
|
||||
} else {
|
||||
pos.node = right;
|
||||
pos.pos -= left->get_size();
|
||||
|
||||
parent_pos.pos += 1;
|
||||
}
|
||||
if (pos.pos < left->get_size()) {
|
||||
pos.node = left;
|
||||
} else {
|
||||
auto &pos = iter.leaf;
|
||||
SUBTRACET(
|
||||
seastore_fixedkv_tree,
|
||||
"splitting leaf {}, parent: {} at pos: {}",
|
||||
c.trans,
|
||||
*pos.node,
|
||||
*parent_pos.node,
|
||||
parent_pos.pos);
|
||||
auto [left, right] = split_level(parent_pos, pos);
|
||||
pos.node = right;
|
||||
pos.pos -= left->get_size();
|
||||
|
||||
/* right->get_node_meta().begin == pivot == right->begin()->get_key()
|
||||
* Thus, if pos.pos == left->get_size(), we want iter to point to
|
||||
* left with pos.pos at the end rather than right with pos.pos = 0
|
||||
* since the insertion would be to the left of the first element
|
||||
* of right and thus necessarily less than right->get_node_meta().begin.
|
||||
*/
|
||||
if (pos.pos <= left->get_size()) {
|
||||
pos.node = left;
|
||||
} else {
|
||||
pos.node = right;
|
||||
pos.pos -= left->get_size();
|
||||
parent_pos.pos += 1;
|
||||
}
|
||||
} else {
|
||||
auto &pos = iter.leaf;
|
||||
SUBTRACET(
|
||||
seastore_fixedkv_tree,
|
||||
"splitting leaf {}, parent: {} at pos: {}",
|
||||
c.trans,
|
||||
*pos.node,
|
||||
*parent_pos.node,
|
||||
parent_pos.pos);
|
||||
auto [left, right] = co_await split_level(parent_pos, pos);
|
||||
|
||||
parent_pos.pos += 1;
|
||||
}
|
||||
/* right->get_node_meta().begin == pivot == right->begin()->get_key()
|
||||
* Thus, if pos.pos == left->get_size(), we want iter to point to
|
||||
* left with pos.pos at the end rather than right with pos.pos = 0
|
||||
* since the insertion would be to the left of the first element
|
||||
* of right and thus necessarily less than right->get_node_meta().begin.
|
||||
*/
|
||||
if (pos.pos <= left->get_size()) {
|
||||
pos.node = left;
|
||||
} else {
|
||||
pos.node = right;
|
||||
pos.pos -= left->get_size();
|
||||
|
||||
parent_pos.pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return seastar::now();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2896,7 +3004,7 @@ private:
|
||||
|
||||
SUBTRACET(seastore_fixedkv_tree, "parent: {}, node: {}", c.trans, *parent_pos.node, *pos.node);
|
||||
auto do_merge = [c, iter, donor_iter, donor_is_left, &parent_pos, &pos](
|
||||
typename NodeType::Ref donor) {
|
||||
typename NodeType::Ref donor) -> handle_merge_ret {
|
||||
LOG_PREFIX(FixedKVBtree::merge_level);
|
||||
auto [l, r] = donor_is_left ?
|
||||
std::make_pair(donor, pos.node) : std::make_pair(pos.node, donor);
|
||||
@ -2905,7 +3013,17 @@ private:
|
||||
std::make_pair(donor_iter, iter) : std::make_pair(iter, donor_iter);
|
||||
|
||||
if (donor->at_min_capacity()) {
|
||||
auto replacement = l->make_full_merge(c, r);
|
||||
typename NodeType::Ref replacement;
|
||||
while (!replacement) {
|
||||
try {
|
||||
replacement = l->make_full_merge(c, r);
|
||||
} catch (crimson::ct_error::eagain&) {}
|
||||
if (!replacement) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
|
||||
parent_pos.node->update(
|
||||
liter,
|
||||
@ -2926,11 +3044,23 @@ private:
|
||||
} else {
|
||||
auto pivot_idx = l->get_balance_pivot_idx(*l, *r);
|
||||
LOG_PREFIX(FixedKVBtree::merge_level);
|
||||
auto [replacement_l, replacement_r, pivot] =
|
||||
l->make_balanced(
|
||||
c,
|
||||
r,
|
||||
pivot_idx);
|
||||
typename NodeType::Ref replacement_l, replacement_r;
|
||||
node_key_t pivot = min_max_t<node_key_t>::null;
|
||||
while (!replacement_l) {
|
||||
assert(!replacement_r);
|
||||
assert(pivot == min_max_t<node_key_t>::null);
|
||||
try {
|
||||
auto t = l->make_balanced(c, r, pivot_idx);
|
||||
replacement_l = std::get<0>(t);
|
||||
replacement_r = std::get<1>(t);
|
||||
pivot = std::get<2>(t);
|
||||
} catch (crimson::ct_error::eagain&) {}
|
||||
if (!replacement_l) {
|
||||
c.cache.get_epm().maybe_wake_background();
|
||||
co_await trans_intr::make_interruptible(
|
||||
c.cache.get_epm().wait_background());
|
||||
}
|
||||
}
|
||||
|
||||
parent_pos.node->update(
|
||||
liter,
|
||||
@ -2966,8 +3096,6 @@ private:
|
||||
c.cache.retire_extent(c.trans, l);
|
||||
c.cache.retire_extent(c.trans, r);
|
||||
}
|
||||
|
||||
return seastar::now();
|
||||
};
|
||||
|
||||
auto v = parent_pos.node->template get_child<NodeType>(
|
||||
@ -2975,30 +3103,26 @@ private:
|
||||
// checking the lba child must be atomic with creating
|
||||
// and linking the absent child
|
||||
if (v.has_child()) {
|
||||
return std::move(v.get_child_fut()
|
||||
).si_then([do_merge=std::move(do_merge), &pos,
|
||||
donor_iter, donor_is_left, c, parent_pos](auto child) {
|
||||
LOG_PREFIX(FixedKVBtree::merge_level);
|
||||
SUBTRACET(seastore_fixedkv_tree,
|
||||
"got child on {}, pos: {}, res: {}",
|
||||
c.trans,
|
||||
*parent_pos.node,
|
||||
donor_iter.get_offset(),
|
||||
*child);
|
||||
std::ignore = donor_is_left;
|
||||
std::ignore = pos;
|
||||
[[maybe_unused]] auto &node = (typename internal_node_t::base_t&)*child;
|
||||
assert(donor_is_left ?
|
||||
node.get_node_meta().end == pos.node->get_node_meta().begin :
|
||||
node.get_node_meta().begin == pos.node->get_node_meta().end);
|
||||
assert(node.get_node_meta().begin == donor_iter.get_key());
|
||||
assert(node.get_node_meta().end > donor_iter.get_key());
|
||||
return do_merge(child->template cast<NodeType>());
|
||||
});
|
||||
auto child = co_await std::move(v.get_child_fut());
|
||||
SUBTRACET(seastore_fixedkv_tree,
|
||||
"got child on {}, pos: {}, res: {}",
|
||||
c.trans,
|
||||
*parent_pos.node,
|
||||
donor_iter.get_offset(),
|
||||
*child);
|
||||
std::ignore = donor_is_left;
|
||||
std::ignore = pos;
|
||||
[[maybe_unused]] auto &node = (typename internal_node_t::base_t&)*child;
|
||||
assert(donor_is_left ?
|
||||
node.get_node_meta().end == pos.node->get_node_meta().begin :
|
||||
node.get_node_meta().begin == pos.node->get_node_meta().end);
|
||||
assert(node.get_node_meta().begin == donor_iter.get_key());
|
||||
assert(node.get_node_meta().end > donor_iter.get_key());
|
||||
co_return co_await do_merge(child->template cast<NodeType>());
|
||||
}
|
||||
|
||||
auto child_pos = v.get_child_pos();
|
||||
return get_node<NodeType>(
|
||||
auto donor = co_await get_node<NodeType>(
|
||||
c,
|
||||
depth,
|
||||
donor_iter.get_val().maybe_relative_to(parent_pos.node->get_paddr()),
|
||||
@ -3006,10 +3130,8 @@ private:
|
||||
end,
|
||||
std::make_optional<node_position_t<internal_node_t>>(
|
||||
child_pos.get_parent(),
|
||||
child_pos.get_pos())
|
||||
).si_then([do_merge=std::move(do_merge)](typename NodeType::Ref donor) {
|
||||
return do_merge(donor);
|
||||
});
|
||||
child_pos.get_pos()));
|
||||
co_await do_merge(donor);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ struct FixedKVNode : CachedExtent {
|
||||
void on_delta_write(paddr_t record_block_offset) final {
|
||||
// All in-memory relative addrs are necessarily record-relative
|
||||
assert(get_prior_instance());
|
||||
assert(pending_for_transaction);
|
||||
assert(t != nullptr);
|
||||
resolve_relative_addrs(record_block_offset);
|
||||
}
|
||||
|
||||
@ -264,8 +264,9 @@ struct FixedKVInternalNode
|
||||
paddr_t addr,
|
||||
base_child_t* nextent) {
|
||||
LOG_PREFIX(FixedKVInternalNode::update);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
(void*)nextent);
|
||||
this->update_child_ptr(iter.get_offset(), nextent);
|
||||
@ -281,8 +282,9 @@ struct FixedKVInternalNode
|
||||
paddr_t addr,
|
||||
base_child_t* nextent) {
|
||||
LOG_PREFIX(FixedKVInternalNode::insert);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, key {}, {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
pivot,
|
||||
(void*)nextent);
|
||||
@ -296,8 +298,9 @@ struct FixedKVInternalNode
|
||||
|
||||
void remove(internal_const_iterator_t iter) {
|
||||
LOG_PREFIX(FixedKVInternalNode::remove);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, key {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
iter.get_key());
|
||||
this->remove_child_ptr(iter.get_offset());
|
||||
@ -312,8 +315,9 @@ struct FixedKVInternalNode
|
||||
paddr_t addr,
|
||||
base_child_t* nextent) {
|
||||
LOG_PREFIX(FixedKVInternalNode::replace);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, old key {}, key {}, {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
iter.get_key(),
|
||||
pivot,
|
||||
@ -329,9 +333,9 @@ struct FixedKVInternalNode
|
||||
std::tuple<Ref, Ref, NODE_KEY>
|
||||
make_split_children(op_context_t c) {
|
||||
auto left = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
auto right = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
this->split_child_ptrs(c.trans, *left, *right);
|
||||
auto pivot = this->split_into(*left, *right);
|
||||
left->range = left->get_meta();
|
||||
@ -347,7 +351,7 @@ struct FixedKVInternalNode
|
||||
op_context_t c,
|
||||
Ref &right) {
|
||||
auto replacement = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
replacement->merge_child_ptrs(
|
||||
c.trans, static_cast<node_type_t&>(*this), *right);
|
||||
replacement->merge_from(*this, *right->template cast<node_type_t>());
|
||||
@ -365,9 +369,9 @@ struct FixedKVInternalNode
|
||||
ceph_assert(_right->get_type() == this->get_type());
|
||||
auto &right = *_right->template cast<node_type_t>();
|
||||
auto replacement_left = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
auto replacement_right = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
|
||||
// We should do full merge if pivot_idx == right.get_size().
|
||||
ceph_assert(pivot_idx != right.get_size());
|
||||
@ -750,9 +754,9 @@ struct FixedKVLeafNode
|
||||
std::tuple<Ref, Ref, NODE_KEY>
|
||||
make_split_children(op_context_t c) {
|
||||
auto left = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
auto right = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
this->on_split(c.trans, *left, *right);
|
||||
auto pivot = this->split_into(*left, *right);
|
||||
left->range = left->get_meta();
|
||||
@ -777,7 +781,7 @@ struct FixedKVLeafNode
|
||||
op_context_t c,
|
||||
Ref &right) {
|
||||
auto replacement = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
replacement->on_merge(c.trans, static_cast<node_type_t&>(*this), *right);
|
||||
replacement->merge_from(*this, *right->template cast<node_type_t>());
|
||||
replacement->range = replacement->get_meta();
|
||||
@ -809,9 +813,9 @@ struct FixedKVLeafNode
|
||||
ceph_assert(_right->get_type() == this->get_type());
|
||||
auto &right = *_right->template cast<node_type_t>();
|
||||
auto replacement_left = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
auto replacement_right = c.cache.template alloc_new_non_data_extent<node_type_t>(
|
||||
c.trans, node_size, placement_hint_t::HOT, INIT_GENERATION);
|
||||
c.trans, node_size, {placement_hint_t::HOT, INIT_GENERATION});
|
||||
|
||||
this->on_balance(
|
||||
c.trans,
|
||||
|
||||
@ -36,9 +36,12 @@ Cache::Cache(
|
||||
delta_based_overwrite_enabled(
|
||||
crimson::common::get_conf<Option::size_t>(
|
||||
"seastore_data_delta_based_overwrite") > 0),
|
||||
force_backref(crimson::common::get_conf<bool>(
|
||||
"seastore_logical_bucket_cache_test_stress")),
|
||||
pinboard(create_extent_pinboard(
|
||||
crimson::common::get_conf<Option::size_t>(
|
||||
"seastore_cachepin_size_pershard")))
|
||||
"seastore_cachepin_size_pershard"),
|
||||
&epm))
|
||||
{
|
||||
register_metrics(store_index);
|
||||
segment_providers_by_device_id.resize(DEVICE_ID_MAX, nullptr);
|
||||
@ -119,6 +122,8 @@ void Cache::register_metrics(store_index_t store_index)
|
||||
last_dirty_io_by_src_ext = {};
|
||||
last_trim_rewrites = {};
|
||||
last_reclaim_rewrites = {};
|
||||
last_promote_rewrites = {};;
|
||||
last_demote_rewrites = {};
|
||||
last_access = {};
|
||||
last_cache_absent_by_src = {};
|
||||
last_access_by_src_ext = {};
|
||||
@ -133,6 +138,8 @@ void Cache::register_metrics(store_index_t store_index)
|
||||
{src_t::TRIM_ALLOC, {sm::label_instance("src", "TRIM_ALLOC")}},
|
||||
{src_t::CLEANER_MAIN, {sm::label_instance("src", "CLEANER_MAIN")}},
|
||||
{src_t::CLEANER_COLD, {sm::label_instance("src", "CLEANER_COLD")}},
|
||||
{src_t::PROMOTE, {sm::label_instance("src", "PROMOTE")}},
|
||||
{src_t::DEMOTE, {sm::label_instance("src", "DEMOTE")}},
|
||||
};
|
||||
assert(labels_by_src.size() == (std::size_t)src_t::MAX);
|
||||
|
||||
@ -228,6 +235,29 @@ void Cache::register_metrics(store_index_t store_index)
|
||||
}
|
||||
);
|
||||
|
||||
metrics.add_group("cache", {
|
||||
sm::make_counter(
|
||||
"write_hit_hot",
|
||||
stats.write_hit_hot,
|
||||
sm::description("the number of the lbc hits of data writes"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"write_hit_cold",
|
||||
stats.write_hit_cold,
|
||||
sm::description("the number of the lbc misses of data writes"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"read_hit_hot",
|
||||
stats.read_hit_hot,
|
||||
sm::description("the number of the lbc hits of data reads"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"read_hit_cold",
|
||||
stats.read_hit_cold,
|
||||
sm::description("the number of the lbc misses of data reads"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
});
|
||||
|
||||
{
|
||||
/*
|
||||
* efforts discarded/committed
|
||||
@ -672,6 +702,10 @@ void Cache::register_metrics(store_index_t store_index)
|
||||
src2 == Transaction::src_t::CLEANER_MAIN) ||
|
||||
(src1 == Transaction::src_t::CLEANER_COLD &&
|
||||
src2 == Transaction::src_t::CLEANER_COLD) ||
|
||||
(src1 == Transaction::src_t::PROMOTE &&
|
||||
src2 == Transaction::src_t::PROMOTE) ||
|
||||
(src1 == Transaction::src_t::DEMOTE &&
|
||||
src2 == Transaction::src_t::DEMOTE) ||
|
||||
(src1 == Transaction::src_t::TRIM_ALLOC &&
|
||||
src2 == Transaction::src_t::TRIM_ALLOC)) {
|
||||
continue;
|
||||
@ -747,6 +781,34 @@ void Cache::register_metrics(store_index_t store_index)
|
||||
sm::description("sum of the version from rewrite-reclaim extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}
|
||||
),
|
||||
sm::make_counter(
|
||||
"version_count_promote",
|
||||
[this] {
|
||||
return stats.promote_rewrites.get_num_rewrites();
|
||||
},
|
||||
sm::description("total number of rewrite-promote extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}
|
||||
),
|
||||
sm::make_counter(
|
||||
"version_sum_promote",
|
||||
stats.promote_rewrites.dirty_version,
|
||||
sm::description("sum of the version from rewrite-promote extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}
|
||||
),
|
||||
sm::make_counter(
|
||||
"version_count_demote",
|
||||
[this] {
|
||||
return stats.demote_rewrites.get_num_rewrites();
|
||||
},
|
||||
sm::description("total number of rewrite-demote extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}
|
||||
),
|
||||
sm::make_counter(
|
||||
"version_sum_demote",
|
||||
stats.demote_rewrites.dirty_version,
|
||||
sm::description("sum of the version from rewrite-demote extents"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -1066,7 +1128,17 @@ void Cache::mark_transaction_conflicted(
|
||||
efforts.mutate_delta_bytes += delta_stat.bytes;
|
||||
|
||||
if (t.get_pending_ool()) {
|
||||
// Paddrs are still in-flight; mark_space_free happens in
|
||||
// do_write().finally() once the write completes (safe for all devices,
|
||||
// including NVMe MQ which may reorder overlapping writes).
|
||||
t.get_pending_ool()->is_conflicted = true;
|
||||
std::size_t pending_bytes = 0;
|
||||
for (auto &e : t.pre_alloc_list) {
|
||||
pending_bytes += e->get_length();
|
||||
}
|
||||
if (pending_bytes > 0) {
|
||||
epm.account_conflict_pending_free(pending_bytes);
|
||||
}
|
||||
} else {
|
||||
for (auto &i: t.pre_alloc_list) {
|
||||
epm.mark_space_free(i->get_paddr(), i->get_length());
|
||||
@ -1194,6 +1266,9 @@ CachedExtentRef Cache::alloc_remapped_extent_by_type(
|
||||
case extent_types_t::TEST_BLOCK:
|
||||
return alloc_remapped_extent<TestBlock>(
|
||||
t, remap_laddr, remap_paddr, remap_offset, remap_length, original_bptr);
|
||||
case extent_types_t::LOG_NODE:
|
||||
return alloc_remapped_extent<log_manager::LogNode>(
|
||||
t, remap_laddr, remap_paddr, remap_offset, remap_length, original_bptr);
|
||||
default:
|
||||
ceph_abort("invalid extent type");
|
||||
return CachedExtentRef();
|
||||
@ -1205,42 +1280,45 @@ CachedExtentRef Cache::alloc_new_non_data_extent_by_type(
|
||||
extent_types_t type, ///< [in] type tag
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
rewrite_gen_t gen ///< [in] rewrite generation
|
||||
rewrite_gen_t gen, ///< [in] rewrite generation
|
||||
paddr_t paddr_hint,
|
||||
bool is_tracked
|
||||
)
|
||||
{
|
||||
LOG_PREFIX(Cache::alloc_new_non_data_extent_by_type);
|
||||
SUBDEBUGT(seastore_cache, "allocate {} 0x{:x}B, hint={}, gen={}",
|
||||
t, type, length, hint, rewrite_gen_printer_t{gen});
|
||||
ceph_assert(get_extent_category(type) == data_category_t::METADATA);
|
||||
auto opt = alloc_option_t{hint, gen, is_tracked, paddr_hint};
|
||||
switch (type) {
|
||||
case extent_types_t::ROOT:
|
||||
ceph_assert(0 == "ROOT is never directly alloc'd");
|
||||
return CachedExtentRef();
|
||||
case extent_types_t::LADDR_INTERNAL:
|
||||
return alloc_new_non_data_extent<lba::LBAInternalNode>(t, length, hint, gen);
|
||||
return alloc_new_non_data_extent<lba::LBAInternalNode>(t, length, opt);
|
||||
case extent_types_t::LADDR_LEAF:
|
||||
return alloc_new_non_data_extent<lba::LBALeafNode>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::ROOT_META:
|
||||
return alloc_new_non_data_extent<RootMetaBlock>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::ONODE_BLOCK_STAGED:
|
||||
return alloc_new_non_data_extent<onode::SeastoreNodeExtent>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::OMAP_INNER:
|
||||
return alloc_new_non_data_extent<omap_manager::OMapInnerNode>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::OMAP_LEAF:
|
||||
return alloc_new_non_data_extent<omap_manager::OMapLeafNode>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::COLL_BLOCK:
|
||||
return alloc_new_non_data_extent<collection_manager::CollectionNode>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::TEST_BLOCK_PHYSICAL:
|
||||
return alloc_new_non_data_extent<TestBlockPhysical>(t, length, hint, gen);
|
||||
return alloc_new_non_data_extent<TestBlockPhysical>(t, length, opt);
|
||||
case extent_types_t::LOG_NODE:
|
||||
return alloc_new_non_data_extent<log_manager::LogNode>(
|
||||
t, length, hint, gen);
|
||||
t, length, opt);
|
||||
case extent_types_t::NONE: {
|
||||
ceph_assert(0 == "NONE is an invalid extent type");
|
||||
return CachedExtentRef();
|
||||
@ -1255,27 +1333,26 @@ std::vector<CachedExtentRef> Cache::alloc_new_data_extents_by_type(
|
||||
Transaction &t, ///< [in, out] current transaction
|
||||
extent_types_t type, ///< [in] type tag
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
rewrite_gen_t gen ///< [in] rewrite generation
|
||||
alloc_option_t opt ///< [in] allocation options
|
||||
)
|
||||
{
|
||||
LOG_PREFIX(Cache::alloc_new_data_extents_by_type);
|
||||
SUBDEBUGT(seastore_cache, "allocate {} 0x{:x}B, hint={}, gen={}",
|
||||
t, type, length, hint, rewrite_gen_printer_t{gen});
|
||||
t, type, length, opt.hint, rewrite_gen_printer_t{opt.gen});
|
||||
ceph_assert(get_extent_category(type) == data_category_t::DATA);
|
||||
std::vector<CachedExtentRef> res;
|
||||
switch (type) {
|
||||
case extent_types_t::OBJECT_DATA_BLOCK:
|
||||
{
|
||||
auto extents = alloc_new_data_extents<
|
||||
ObjectDataBlock>(t, length, hint, gen);
|
||||
ObjectDataBlock>(t, length, std::move(opt));
|
||||
res.insert(res.begin(), extents.begin(), extents.end());
|
||||
}
|
||||
return res;
|
||||
case extent_types_t::TEST_BLOCK:
|
||||
{
|
||||
auto extents = alloc_new_data_extents<
|
||||
TestBlock>(t, length, hint, gen);
|
||||
TestBlock>(t, length, std::move(opt));
|
||||
res.insert(res.begin(), extents.begin(), extents.end());
|
||||
}
|
||||
return res;
|
||||
@ -1322,7 +1399,7 @@ CachedExtentRef Cache::duplicate_for_write(
|
||||
}
|
||||
|
||||
auto ret = i->duplicate_for_write(t);
|
||||
ret->pending_for_transaction = t.get_trans_id();
|
||||
ret->t = &t;
|
||||
ret->set_prior_instance(i);
|
||||
if (!is_root_type(ret->get_type())) {
|
||||
assert(ret->get_paddr().is_absolute());
|
||||
@ -1723,7 +1800,7 @@ record_t Cache::prepare_record(
|
||||
}
|
||||
assert(i->state == CachedExtent::extent_state_t::DIRTY);
|
||||
assert(i->version > 0);
|
||||
assert(i->pending_for_transaction == TRANS_ID_NULL);
|
||||
assert(i->t == nullptr);
|
||||
assert(!i->prior_instance);
|
||||
remove_from_dirty(i, &trans_src);
|
||||
// set the version to zero because the extent state is now clean
|
||||
@ -1753,10 +1830,10 @@ record_t Cache::prepare_record(
|
||||
|
||||
if (i->is_exist_clean()) {
|
||||
assert(i->version == 0);
|
||||
assert(!i->prior_instance);
|
||||
assert(!i->prior_instance || t.get_src() == transaction_type_t::DEMOTE);
|
||||
// no set_io_wait(), skip complete_commit()
|
||||
assert(!i->is_pending_io());
|
||||
i->pending_for_transaction = TRANS_ID_NULL;
|
||||
i->t = nullptr;
|
||||
i->state = CachedExtent::extent_state_t::CLEAN;
|
||||
} else {
|
||||
assert(i->is_exist_mutation_pending());
|
||||
@ -1764,13 +1841,22 @@ record_t Cache::prepare_record(
|
||||
should_use_no_conflict_publish(t, i->get_type()));
|
||||
}
|
||||
|
||||
// exist mutation pending extents must be in t.mutated_block_list
|
||||
add_extent(i);
|
||||
const auto t_src = t.get_src();
|
||||
if (i->is_stable_dirty()) {
|
||||
add_to_dirty(i, &t_src);
|
||||
assert(i->is_logical());
|
||||
if (t.get_src() == transaction_type_t::DEMOTE) {
|
||||
assert(!i->committer);
|
||||
assert(!i->get_prior_instance()->committer);
|
||||
i->new_committer(t);
|
||||
assert(i->committer);
|
||||
i->get_prior_instance()->committer = i->committer;
|
||||
} else {
|
||||
touch_extent_fully(*i, &t_src, t.get_cache_hint());
|
||||
// exist mutation pending extents must be in t.mutated_block_list
|
||||
add_extent(i);
|
||||
const auto t_src = t.get_src();
|
||||
if (i->is_stable_dirty()) {
|
||||
add_to_dirty(i, &t_src);
|
||||
} else {
|
||||
touch_extent_fully(*i, &t_src, t.get_cache_hint());
|
||||
}
|
||||
}
|
||||
|
||||
alloc_delta.alloc_blk_ranges.emplace_back(
|
||||
@ -1945,6 +2031,10 @@ record_t Cache::prepare_record(
|
||||
} else if (trans_src == Transaction::src_t::CLEANER_MAIN ||
|
||||
trans_src == Transaction::src_t::CLEANER_COLD) {
|
||||
stats.reclaim_rewrites.add(rewrite_stats);
|
||||
} else if (trans_src == Transaction::src_t::PROMOTE) {
|
||||
stats.promote_rewrites.add(rewrite_stats);
|
||||
} else if (trans_src == Transaction::src_t::DEMOTE) {
|
||||
stats.demote_rewrites.add(rewrite_stats);
|
||||
} else {
|
||||
assert(rewrite_stats.is_clear());
|
||||
}
|
||||
@ -2022,7 +2112,7 @@ void Cache::complete_commit(
|
||||
assert(i->get_last_committed_crc() == CRC_NULL);
|
||||
}
|
||||
#endif
|
||||
i->pending_for_transaction = TRANS_ID_NULL;
|
||||
i->t = nullptr;
|
||||
i->on_initial_write();
|
||||
const auto t_src = t.get_src();
|
||||
if (should_use_no_conflict_publish(t, i->get_type())) {
|
||||
@ -2033,7 +2123,7 @@ void Cache::complete_commit(
|
||||
TRACET("committing rewritten extent into "
|
||||
"existing, inline={} -- {}, prior={}",
|
||||
t, is_inline, *i, prior);
|
||||
prior.pending_for_transaction = TRANS_ID_NULL;
|
||||
prior.t = nullptr;
|
||||
committer.commit_state();
|
||||
committer.sync_checksum();
|
||||
committer.commit_and_share_paddr();
|
||||
@ -2042,6 +2132,9 @@ void Cache::complete_commit(
|
||||
}
|
||||
if (i->is_logical()) {
|
||||
committer.maybe_sync_copied_lba_key();
|
||||
if (t.get_src() == transaction_type_t::PROMOTE) {
|
||||
committer.commit_shadow_promote(t);
|
||||
}
|
||||
}
|
||||
touch_extent_fully(prior, &t_src, t.get_cache_hint());
|
||||
committer.sync_version();
|
||||
@ -2126,7 +2219,7 @@ void Cache::complete_commit(
|
||||
committer.commit_state();
|
||||
committer.sync_checksum();
|
||||
auto &prior = *i->prior_instance;
|
||||
prior.pending_for_transaction = TRANS_ID_NULL;
|
||||
prior.t = nullptr;
|
||||
ceph_assert(prior.is_valid());
|
||||
if (is_lba_backref_node(i->get_type())) {
|
||||
committer.commit_data();
|
||||
@ -2138,7 +2231,7 @@ void Cache::complete_commit(
|
||||
prior.committer.reset();
|
||||
}
|
||||
|
||||
i->pending_for_transaction = TRANS_ID_NULL;
|
||||
i->t = nullptr;
|
||||
i->reset_prior_instance();
|
||||
assert(i->version > 0);
|
||||
i->complete_io();
|
||||
@ -2150,6 +2243,30 @@ void Cache::complete_commit(
|
||||
continue;
|
||||
}
|
||||
epm.mark_space_used(i->get_paddr(), i->get_length());
|
||||
assert(i->is_logical());
|
||||
auto t_src = t.get_src();
|
||||
if (t.get_src() == transaction_type_t::DEMOTE) {
|
||||
assert(i->committer);
|
||||
auto &committer = *i->committer;
|
||||
auto &prior = static_cast<LogicalChildNode&>(
|
||||
*i->get_prior_instance());
|
||||
ceph_assert(prior.is_valid());
|
||||
TRACET("committing rewritten extent into "
|
||||
"existing -- {}, prior={}",
|
||||
t, *i, prior);
|
||||
prior.t = nullptr;
|
||||
if (auto shadow = prior.get_shadow(); shadow) {
|
||||
committer.commit_shadow_demote(t);
|
||||
prior.reset_shadow();
|
||||
}
|
||||
committer.commit_state();
|
||||
committer.sync_checksum();
|
||||
committer.commit_and_share_paddr();
|
||||
touch_extent_fully(prior, &t_src, t.get_cache_hint());
|
||||
committer.sync_version();
|
||||
i->committer.reset();
|
||||
prior.committer.reset();
|
||||
}
|
||||
}
|
||||
for (auto &i: t.pre_alloc_list) {
|
||||
if (!i->is_valid()) {
|
||||
@ -2173,6 +2290,9 @@ void Cache::complete_commit(
|
||||
i->set_invalid(t);
|
||||
}
|
||||
}
|
||||
|
||||
stats.write_hit_hot += t.write_hit_hot;
|
||||
stats.write_hit_cold += t.write_hit_cold;
|
||||
}
|
||||
|
||||
void Cache::init()
|
||||
@ -2190,7 +2310,8 @@ void Cache::init()
|
||||
P_ADDR_ROOT,
|
||||
PLACEMENT_HINT_NULL,
|
||||
NULL_GENERATION,
|
||||
TRANS_ID_NULL);
|
||||
nullptr,
|
||||
write_policy_t::WRITE_BACK);
|
||||
root->set_modify_time(seastar::lowres_system_clock::now());
|
||||
INFO("init root -- {}", *root);
|
||||
add_extent(root);
|
||||
@ -2614,7 +2735,8 @@ Cache::_get_absent_extent_by_type(
|
||||
offset,
|
||||
PLACEMENT_HINT_NULL,
|
||||
NULL_GENERATION,
|
||||
TRANS_ID_NULL);
|
||||
nullptr,
|
||||
write_policy_t::WRITE_BACK);
|
||||
DEBUGT("{} length=0x{:x} is absent, add extent ... -- {}",
|
||||
t, type, length, *ret);
|
||||
add_extent(ret);
|
||||
@ -2837,6 +2959,10 @@ cache_stats_t Cache::get_stats(
|
||||
_trim_rewrites.minus(last_trim_rewrites);
|
||||
rewrite_stats_t _reclaim_rewrites = stats.reclaim_rewrites;
|
||||
_reclaim_rewrites.minus(last_reclaim_rewrites);
|
||||
rewrite_stats_t _promote_rewrites = stats.promote_rewrites;
|
||||
_promote_rewrites.minus(last_promote_rewrites);
|
||||
rewrite_stats_t _demote_rewrites = stats.demote_rewrites;
|
||||
_demote_rewrites.minus(last_demote_rewrites);
|
||||
oss << "\nrewrite trim ndirty="
|
||||
<< fmt::format(dfmt, _trim_rewrites.num_n_dirty/seconds)
|
||||
<< "ps, dirty="
|
||||
@ -2848,7 +2974,19 @@ cache_stats_t Cache::get_stats(
|
||||
<< "ps, dirty="
|
||||
<< fmt::format(dfmt, _reclaim_rewrites.num_dirty/seconds)
|
||||
<< "ps, dversion="
|
||||
<< fmt::format(dfmt, _reclaim_rewrites.get_avg_version());
|
||||
<< fmt::format(dfmt, _reclaim_rewrites.get_avg_version())
|
||||
<< "; promote ndirty="
|
||||
<< fmt::format(dfmt, _promote_rewrites.num_n_dirty/seconds)
|
||||
<< "ps, dirty="
|
||||
<< fmt::format(dfmt, _promote_rewrites.num_dirty/seconds)
|
||||
<< "ps, dversion="
|
||||
<< fmt::format(dfmt, _promote_rewrites.get_avg_version())
|
||||
<< "; demote ndirty="
|
||||
<< fmt::format(dfmt, _demote_rewrites.num_n_dirty/seconds)
|
||||
<< "ps, dirty="
|
||||
<< fmt::format(dfmt, _demote_rewrites.num_dirty/seconds)
|
||||
<< "ps, dversion="
|
||||
<< fmt::format(dfmt, _demote_rewrites.get_avg_version());
|
||||
|
||||
oss << "\ncache total"
|
||||
<< cache_size_stats_t{extents_index.get_bytes(), extents_index.size()};
|
||||
@ -2907,6 +3045,8 @@ cache_stats_t Cache::get_stats(
|
||||
last_dirty_io_by_src_ext = stats.dirty_io_by_src_ext;
|
||||
last_trim_rewrites = stats.trim_rewrites;
|
||||
last_reclaim_rewrites = stats.reclaim_rewrites;
|
||||
last_promote_rewrites = stats.promote_rewrites;
|
||||
last_demote_rewrites = stats.demote_rewrites;
|
||||
last_cache_absent_by_src = stats.cache_absent_by_src;
|
||||
last_access_by_src_ext = stats.access_by_src_ext;
|
||||
}
|
||||
|
||||
@ -742,10 +742,18 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ExtentPlacementManager& get_epm() {
|
||||
return epm;
|
||||
}
|
||||
|
||||
extent_len_t get_block_size() const {
|
||||
return epm.get_block_size();
|
||||
}
|
||||
|
||||
ExtentPinboard *get_extent_pinboard() {
|
||||
return pinboard.get();
|
||||
}
|
||||
|
||||
// Interfaces only for tests.
|
||||
public:
|
||||
CachedExtentRef test_query_cache(paddr_t offset) {
|
||||
@ -754,7 +762,12 @@ public:
|
||||
}
|
||||
|
||||
bool can_drop_backref() const {
|
||||
return epm.is_pure_rbm();
|
||||
return epm.is_pure_rbm() && !force_backref;
|
||||
}
|
||||
|
||||
void update_read_ratio(Transaction &t) {
|
||||
stats.read_hit_hot += t.read_hit_hot;
|
||||
stats.read_hit_cold += t.read_hit_cold;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -853,7 +866,8 @@ private:
|
||||
offset,
|
||||
PLACEMENT_HINT_NULL,
|
||||
NULL_GENERATION,
|
||||
TRANS_ID_NULL);
|
||||
nullptr,
|
||||
write_policy_t::WRITE_BACK);
|
||||
SUBDEBUGT(seastore_cache,
|
||||
"{} {}~0x{:x} is absent, add extent and reading range 0x{:x}~0x{:x} ... -- {}",
|
||||
t, T::TYPE, offset, length, partial_off, partial_len, *ret);
|
||||
@ -892,7 +906,8 @@ private:
|
||||
offset,
|
||||
PLACEMENT_HINT_NULL,
|
||||
NULL_GENERATION,
|
||||
TRANS_ID_NULL);
|
||||
nullptr,
|
||||
write_policy_t::WRITE_BACK);
|
||||
SUBDEBUG(seastore_cache,
|
||||
"{} {}~0x{:x} is absent, add extent and reading range 0x{:x}~0x{:x} ... -- {}",
|
||||
T::TYPE, offset, length, partial_off, partial_len, *ret);
|
||||
@ -1065,6 +1080,24 @@ private:
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<backref_entry_t> get_cached_backref_entry(paddr_t addr) {
|
||||
auto it = backref_entry_mset.lower_bound(
|
||||
addr,
|
||||
backref_entry_t::cmp_t());
|
||||
if (it == backref_entry_mset.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
while (it->paddr == addr) {
|
||||
auto &backref_entry = *it;
|
||||
++it;
|
||||
if (it == backref_entry_mset.end() ||
|
||||
it->paddr != addr) {
|
||||
return backref_entry;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const backref_entry_mset_t& get_backref_entry_mset() {
|
||||
return backref_entry_mset;
|
||||
}
|
||||
@ -1142,6 +1175,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
using alloc_option_t = ExtentPlacementManager::alloc_option_t;
|
||||
/**
|
||||
* alloc_new_non_data_extent
|
||||
*
|
||||
@ -1152,25 +1186,20 @@ public:
|
||||
TCachedExtentRef<T> alloc_new_non_data_extent(
|
||||
Transaction &t, ///< [in, out] current transaction
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
rewrite_gen_t gen, ///< [in] rewrite generation
|
||||
std::optional<paddr_t> epaddr = std::nullopt ///< [in] paddr fed by callers
|
||||
#else
|
||||
rewrite_gen_t gen
|
||||
#endif
|
||||
alloc_option_t opt ///< [in] allocation options
|
||||
) {
|
||||
LOG_PREFIX(Cache::alloc_new_non_data_extent);
|
||||
SUBTRACET(seastore_cache, "allocate {} 0x{:x}B, hint={}, gen={}",
|
||||
t, T::TYPE, length, hint, rewrite_gen_printer_t{gen});
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
auto result = epm.alloc_new_non_data_extent(t, T::TYPE, length, hint, gen, epaddr);
|
||||
#else
|
||||
auto result = epm.alloc_new_non_data_extent(t, T::TYPE, length, hint, gen);
|
||||
#endif
|
||||
SUBTRACET(seastore_cache, "allocate {} 0x{:x}B, opt.hint={}, gen={}",
|
||||
t, T::TYPE, length, opt.hint, rewrite_gen_printer_t{opt.gen});
|
||||
auto result = epm.alloc_new_non_data_extent(t, T::TYPE, length, opt);
|
||||
if (!result) {
|
||||
SUBERRORT(seastore_cache, "insufficient space", t);
|
||||
std::rethrow_exception(crimson::ct_error::enospc::exception_ptr());
|
||||
if (epm.is_full()) {
|
||||
SUBERRORT(seastore_cache, "insufficient space", t);
|
||||
std::rethrow_exception(crimson::ct_error::enospc::exception_ptr());
|
||||
} else {
|
||||
SUBERRORT(seastore_cache, "insufficient space, wait for demoting", t);
|
||||
std::rethrow_exception(crimson::ct_error::eagain::exception_ptr());
|
||||
}
|
||||
}
|
||||
auto ret = CachedExtent::make_cached_extent_ref<T>(std::move(result->bp));
|
||||
assert(is_rewrite_generation(
|
||||
@ -1178,14 +1207,15 @@ public:
|
||||
epm.dynamic_max_rewrite_generation));
|
||||
ret->init(CachedExtent::extent_state_t::INITIAL_WRITE_PENDING,
|
||||
result->paddr,
|
||||
hint,
|
||||
opt.hint,
|
||||
result->gen,
|
||||
t.get_trans_id());
|
||||
&t,
|
||||
write_policy_t::WRITE_BACK);
|
||||
t.add_fresh_extent(ret);
|
||||
SUBDEBUGT(seastore_cache,
|
||||
"allocated {} 0x{:x}B extent at {}, hint={}, gen={} -- {}",
|
||||
t, T::TYPE, length, result->paddr,
|
||||
hint, rewrite_gen_printer_t{result->gen}, *ret);
|
||||
opt.hint, rewrite_gen_printer_t{result->gen}, *ret);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
@ -1198,25 +1228,20 @@ public:
|
||||
std::vector<TCachedExtentRef<T>> alloc_new_data_extents(
|
||||
Transaction &t, ///< [in, out] current transaction
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
rewrite_gen_t gen, ///< [in] rewrite generation
|
||||
std::optional<paddr_t> epaddr = std::nullopt ///< [in] paddr fed by callers
|
||||
#else
|
||||
rewrite_gen_t gen
|
||||
#endif
|
||||
alloc_option_t opt ///< [in] allocation options
|
||||
) {
|
||||
LOG_PREFIX(Cache::alloc_new_data_extents);
|
||||
SUBTRACET(seastore_cache, "allocate {} 0x{:x}B, hint={}, gen={}",
|
||||
t, T::TYPE, length, hint, rewrite_gen_printer_t{gen});
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
auto results = epm.alloc_new_data_extents(t, T::TYPE, length, hint, gen, epaddr);
|
||||
#else
|
||||
auto results = epm.alloc_new_data_extents(t, T::TYPE, length, hint, gen);
|
||||
#endif
|
||||
t, T::TYPE, length, opt.hint, rewrite_gen_printer_t{opt.gen});
|
||||
auto results = epm.alloc_new_data_extents(t, T::TYPE, length, opt);
|
||||
if (results.empty()) {
|
||||
SUBERRORT(seastore_cache, "insufficient space", t);
|
||||
std::rethrow_exception(crimson::ct_error::enospc::exception_ptr());
|
||||
if (epm.is_full()) {
|
||||
SUBERRORT(seastore_cache, "insufficient space", t);
|
||||
std::rethrow_exception(crimson::ct_error::enospc::exception_ptr());
|
||||
} else {
|
||||
SUBERRORT(seastore_cache, "insufficient space, wait for demoting", t);
|
||||
std::rethrow_exception(crimson::ct_error::eagain::exception_ptr());
|
||||
}
|
||||
}
|
||||
std::vector<TCachedExtentRef<T>> extents;
|
||||
for (auto &result : results) {
|
||||
@ -1226,14 +1251,15 @@ public:
|
||||
epm.dynamic_max_rewrite_generation));
|
||||
ret->init(CachedExtent::extent_state_t::INITIAL_WRITE_PENDING,
|
||||
result.paddr,
|
||||
hint,
|
||||
opt.hint,
|
||||
result.gen,
|
||||
t.get_trans_id());
|
||||
&t,
|
||||
opt.write_policy);
|
||||
t.add_fresh_extent(ret);
|
||||
SUBDEBUGT(seastore_cache,
|
||||
"allocated {} 0x{:x}B extent at {}, hint={}, gen={} -- {}",
|
||||
t, T::TYPE, length, result.paddr,
|
||||
hint, rewrite_gen_printer_t{result.gen}, *ret);
|
||||
opt.hint, rewrite_gen_printer_t{result.gen}, *ret);
|
||||
extents.emplace_back(std::move(ret));
|
||||
}
|
||||
return extents;
|
||||
@ -1270,7 +1296,8 @@ public:
|
||||
remap_paddr,
|
||||
PLACEMENT_HINT_NULL,
|
||||
NULL_GENERATION,
|
||||
t.get_trans_id());
|
||||
&t,
|
||||
write_policy_t::WRITE_BACK);
|
||||
|
||||
auto extent = ext->template cast<T>();
|
||||
extent->set_laddr(remap_laddr);
|
||||
@ -1299,7 +1326,9 @@ public:
|
||||
extent_types_t type, ///< [in] type tag
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
rewrite_gen_t gen ///< [in] rewrite generation
|
||||
rewrite_gen_t gen, ///< [in] rewrite generation
|
||||
paddr_t paddr_hint,
|
||||
bool is_tracked
|
||||
);
|
||||
|
||||
/**
|
||||
@ -1311,8 +1340,7 @@ public:
|
||||
Transaction &t, ///< [in, out] current transaction
|
||||
extent_types_t type, ///< [in] type tag
|
||||
extent_len_t length, ///< [in] length
|
||||
placement_hint_t hint, ///< [in] user hint
|
||||
rewrite_gen_t gen ///< [in] rewrite generation
|
||||
alloc_option_t opt ///< [in] allocation options
|
||||
);
|
||||
|
||||
/**
|
||||
@ -1701,6 +1729,9 @@ public:
|
||||
read_extent_futs, [](auto &fut) { return std::move(fut); });
|
||||
}
|
||||
|
||||
bool is_on_cold_tier(paddr_t paddr) const {
|
||||
return epm.is_cold_device(paddr.get_device_id());
|
||||
}
|
||||
private:
|
||||
void touch_extent_fully(
|
||||
CachedExtent &ext,
|
||||
@ -1722,6 +1753,9 @@ private:
|
||||
extent_len_t load_length)
|
||||
{
|
||||
assert(ext.get_paddr().is_absolute());
|
||||
if (ext.is_shadow_extent()) {
|
||||
return;
|
||||
}
|
||||
if (hint == CACHE_HINT_NOCACHE && is_logical_type(ext.get_type())) {
|
||||
return;
|
||||
}
|
||||
@ -1742,6 +1776,8 @@ private:
|
||||
|
||||
transaction_id_t next_id = 0;
|
||||
|
||||
const bool force_backref = false;
|
||||
|
||||
/**
|
||||
* dirty
|
||||
*
|
||||
@ -1875,8 +1911,15 @@ private:
|
||||
std::array<uint64_t, NUM_SRC_COMB> trans_conflicts_by_srcs;
|
||||
counter_by_src_t<uint64_t> trans_conflicts_by_unknown;
|
||||
|
||||
uint64_t write_hit_hot = 0;
|
||||
uint64_t write_hit_cold = 0;
|
||||
uint64_t read_hit_hot = 0;
|
||||
uint64_t read_hit_cold = 0;
|
||||
|
||||
rewrite_stats_t trim_rewrites;
|
||||
rewrite_stats_t reclaim_rewrites;
|
||||
rewrite_stats_t promote_rewrites;
|
||||
rewrite_stats_t demote_rewrites;
|
||||
} stats;
|
||||
|
||||
mutable dirty_io_stats_t last_dirty_io;
|
||||
@ -1884,6 +1927,8 @@ private:
|
||||
last_dirty_io_by_src_ext;
|
||||
mutable rewrite_stats_t last_trim_rewrites;
|
||||
mutable rewrite_stats_t last_reclaim_rewrites;
|
||||
mutable rewrite_stats_t last_promote_rewrites;
|
||||
mutable rewrite_stats_t last_demote_rewrites;
|
||||
mutable cache_access_stats_t last_access;
|
||||
mutable counter_by_src_t<uint64_t> last_cache_absent_by_src;
|
||||
mutable counter_by_src_t<counter_by_extent_t<cache_access_stats_t> >
|
||||
@ -1905,6 +1950,10 @@ private:
|
||||
src2 == Transaction::src_t::CLEANER_MAIN));
|
||||
assert(!(src1 == Transaction::src_t::CLEANER_COLD &&
|
||||
src2 == Transaction::src_t::CLEANER_COLD));
|
||||
assert(!(src1 == Transaction::src_t::PROMOTE &&
|
||||
src2 == Transaction::src_t::PROMOTE));
|
||||
assert(!(src1 == Transaction::src_t::DEMOTE &&
|
||||
src2 == Transaction::src_t::DEMOTE));
|
||||
assert(!(src1 == Transaction::src_t::TRIM_ALLOC &&
|
||||
src2 == Transaction::src_t::TRIM_ALLOC));
|
||||
|
||||
|
||||
@ -66,11 +66,59 @@ std::ostream &operator<<(std::ostream &out, CachedExtent::extent_state_t state)
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const extent_pin_state_t &s) {
|
||||
switch (s) {
|
||||
case extent_pin_state_t::Fresh:
|
||||
return out << "Fresh";
|
||||
case extent_pin_state_t::PendingPromote:
|
||||
return out << "PendingPromote";
|
||||
case extent_pin_state_t::Promoting:
|
||||
return out << "Promoting";
|
||||
case extent_pin_state_t::WarmIn:
|
||||
return out << "WarmIn";
|
||||
case extent_pin_state_t::Hot:
|
||||
return out << "Hot";
|
||||
case extent_pin_state_t::Max:
|
||||
return out << "Max";
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const CachedExtent &ext)
|
||||
{
|
||||
return ext.print(out);
|
||||
}
|
||||
|
||||
trans_spec_view_t::trans_spec_view_t(
|
||||
Transaction &t) : t(&t) {}
|
||||
|
||||
bool trans_spec_view_t::cmp_t::operator()(
|
||||
const trans_spec_view_t &lhs,
|
||||
const trans_spec_view_t &rhs) const
|
||||
{
|
||||
transaction_id_t l = ((lhs.t == nullptr) ? 0 : lhs.t->get_trans_id());
|
||||
transaction_id_t r = ((rhs.t == nullptr) ? 0 : rhs.t->get_trans_id());
|
||||
return l < r;
|
||||
}
|
||||
|
||||
bool trans_spec_view_t::cmp_t::operator()(
|
||||
const transaction_id_t &lhs,
|
||||
const trans_spec_view_t &rhs) const
|
||||
{
|
||||
transaction_id_t r = ((rhs.t == nullptr) ? 0 : rhs.t->get_trans_id());
|
||||
return lhs < r;
|
||||
}
|
||||
|
||||
bool trans_spec_view_t::cmp_t::operator()(
|
||||
const trans_spec_view_t &lhs,
|
||||
const transaction_id_t &rhs) const
|
||||
{
|
||||
transaction_id_t l = ((lhs.t == nullptr) ? 0 : lhs.t->get_trans_id());
|
||||
return l < rhs;
|
||||
}
|
||||
|
||||
CachedExtent::~CachedExtent()
|
||||
{
|
||||
if (parent_index) {
|
||||
@ -106,6 +154,44 @@ CachedExtent* CachedExtent::maybe_get_transactional_view(Transaction &t) {
|
||||
return this;
|
||||
}
|
||||
|
||||
bool CachedExtent::is_pending_in_trans(transaction_id_t id) const {
|
||||
auto trans_id = ((t == nullptr) ? 0 : t->get_trans_id());
|
||||
return is_pending() && trans_id == id;
|
||||
}
|
||||
|
||||
std::ostream &CachedExtent::print(std::ostream &out) const {
|
||||
std::string prior_poffset_str = prior_poffset
|
||||
? fmt::format("{}", *prior_poffset)
|
||||
: "nullopt";
|
||||
out << "CachedExtent(addr=" << this
|
||||
<< ", type=" << get_type()
|
||||
<< ", trans=" << ((t == nullptr) ? 0 : t->get_trans_id())
|
||||
<< ", version=" << version
|
||||
<< ", dirty_from=" << dirty_from
|
||||
<< ", modify_time=" << sea_time_point_printer_t{modify_time}
|
||||
<< ", paddr=" << get_paddr()
|
||||
<< ", prior_paddr=" << prior_poffset_str
|
||||
<< std::hex << ", length=0x" << get_length()
|
||||
<< ", loaded=0x" << get_loaded_length() << std::dec
|
||||
<< ", state=" << state
|
||||
<< ", pin_state=" << pin_state
|
||||
<< ", last_committed_crc=" << last_committed_crc
|
||||
<< ", refcount=" << use_count()
|
||||
<< ", user_hint=" << user_hint
|
||||
<< ", write_policy=" << write_policy
|
||||
<< ", rewrite_gen=" << rewrite_gen_printer_t{rewrite_generation}
|
||||
<< ", pending_io=";
|
||||
if (is_pending_io()) {
|
||||
out << io_wait->from_state;
|
||||
} else {
|
||||
out << "N/A";
|
||||
}
|
||||
if (is_valid() && is_fully_loaded() && !is_stable_clean_pending()) {
|
||||
print_detail(out);
|
||||
}
|
||||
return out << ")";
|
||||
}
|
||||
|
||||
std::ostream &LogicalCachedExtent::print_detail(std::ostream &out) const
|
||||
{
|
||||
out << ", laddr=" << laddr
|
||||
@ -392,13 +478,17 @@ void ExtentCommitter::commit_state() {
|
||||
SUBTRACET(seastore_cache, "{} prior={}",
|
||||
t, extent, *extent.prior_instance);
|
||||
auto &prior = *extent.prior_instance;
|
||||
prior.pending_for_transaction = extent.pending_for_transaction;
|
||||
prior.t = extent.t;
|
||||
prior.modify_time = extent.modify_time;
|
||||
prior.last_committed_crc = extent.last_committed_crc;
|
||||
prior.dirty_from = extent.dirty_from;
|
||||
prior.length = extent.length;
|
||||
prior.loaded_length = extent.loaded_length;
|
||||
prior.buffer_space = std::move(extent.buffer_space);
|
||||
// XXX: at present, zero loaded_length extents here
|
||||
// must have been created by promoting/demoting them.
|
||||
if (likely(extent.loaded_length != 0)) {
|
||||
assert(prior.loaded_length == extent.loaded_length);
|
||||
prior.buffer_space = std::move(extent.buffer_space);
|
||||
}
|
||||
// XXX: We can go ahead and change the prior's version because
|
||||
// transactions don't hold a local view of the version field,
|
||||
// unlike FixedKVLeafNode::modifications
|
||||
@ -414,8 +504,29 @@ void ExtentCommitter::maybe_sync_copied_lba_key() {
|
||||
auto &lextent = static_cast<LogicalChildNode&>(extent);
|
||||
auto &prior = *extent.prior_instance;
|
||||
for (auto &item : prior.read_transactions) {
|
||||
item.t->maybe_sync_copied_lba_key(
|
||||
lextent.get_laddr(), lextent.get_paddr());
|
||||
switch (t.get_src()) {
|
||||
case transaction_type_t::PROMOTE:
|
||||
{
|
||||
auto &shadow = *lextent.get_shadow();
|
||||
item.t->maybe_sync_copied_lba_key(
|
||||
lextent.get_laddr(),
|
||||
lextent.get_paddr(),
|
||||
shadow.get_paddr());
|
||||
break;
|
||||
}
|
||||
case transaction_type_t::DEMOTE:
|
||||
item.t->maybe_sync_copied_lba_key(
|
||||
lextent.get_laddr(),
|
||||
lextent.get_paddr(),
|
||||
P_ADDR_NULL);
|
||||
break;
|
||||
default:
|
||||
item.t->maybe_sync_copied_lba_key(
|
||||
lextent.get_laddr(),
|
||||
lextent.get_paddr(),
|
||||
std::nullopt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,7 +575,7 @@ void ExtentCommitter::_share_prior_data_to_mutations() {
|
||||
if (it != merged.end()) {
|
||||
TRACE("{} -> {}, {} -> {}",
|
||||
me, mextent, (pladdr_t)buf.val.pladdr, it->second);
|
||||
buf.val.pladdr = pladdr_le_t(it->second);
|
||||
buf.val = lba::lba_map_val_le_t(it->second);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -515,4 +626,48 @@ void CachedExtent::new_committer(Transaction &t) {
|
||||
prior_instance->committer = committer;
|
||||
}
|
||||
|
||||
void ExtentCommitter::commit_shadow_demote(Transaction &t) {
|
||||
LOG_PREFIX(ExtentCommitter::commit_shadow_demote);
|
||||
assert(t.get_src() == transaction_type_t::DEMOTE);
|
||||
auto &prior = *extent.prior_instance->template cast<LogicalChildNode>();
|
||||
auto shadow = prior.get_shadow();
|
||||
assert(shadow);
|
||||
for (auto &trans_view : prior.retired_transactions) {
|
||||
assert(trans_view.t != nullptr);
|
||||
auto view_tid = trans_view.t->get_trans_id();
|
||||
if (view_tid == t.get_trans_id()) {
|
||||
continue;
|
||||
}
|
||||
TRACET("removing shadow {} from retired_set of t.{}", t, *shadow, view_tid);
|
||||
[[maybe_unused]] bool removed =
|
||||
trans_view.t->remove_from_retired_set(*shadow);
|
||||
assert(removed);
|
||||
trans_view.t->remove_shadow_from_write_set(
|
||||
shadow->get_paddr(), shadow->get_length());
|
||||
}
|
||||
}
|
||||
|
||||
void ExtentCommitter::commit_shadow_promote(Transaction &t) {
|
||||
LOG_PREFIX(ExtentCommitter::commit_shadow_promote);
|
||||
assert(t.get_src() == transaction_type_t::PROMOTE);
|
||||
assert(extent.is_logical());
|
||||
auto &lprior = static_cast<LogicalChildNode&>(*extent.prior_instance);
|
||||
ceph_assert(lprior.get_pin_state() == extent_pin_state_t::Promoting);
|
||||
auto &lext = static_cast<LogicalChildNode&>(extent);
|
||||
auto shadow = lext.get_shadow();
|
||||
assert(shadow);
|
||||
assert(shadow->is_shadow_extent());
|
||||
lprior.set_shadow(shadow);
|
||||
for (auto &trans_view : lprior.retired_transactions) {
|
||||
assert(trans_view.t != nullptr);
|
||||
auto view_tid = trans_view.t->get_trans_id();
|
||||
if (view_tid == t.get_trans_id()) {
|
||||
continue;
|
||||
}
|
||||
TRACET("adding shadow {} from t.{}", t, *shadow, view_tid);
|
||||
trans_view.t->add_absent_to_retired_set(shadow);
|
||||
}
|
||||
lprior.set_pin_state(extent_pin_state_t::Fresh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -141,32 +141,22 @@ template <typename T>
|
||||
using read_trans_set_t = typename read_set_item_t<T>::trans_set_t;
|
||||
|
||||
struct trans_spec_view_t {
|
||||
// if the extent is pending, contains the id of the owning transaction;
|
||||
// TRANS_ID_NULL otherwise
|
||||
transaction_id_t pending_for_transaction = TRANS_ID_NULL;
|
||||
// if the extent is pending, this points to the transaction
|
||||
Transaction *t = nullptr;
|
||||
trans_spec_view_t() = default;
|
||||
trans_spec_view_t(transaction_id_t id) : pending_for_transaction(id) {}
|
||||
trans_spec_view_t(Transaction &t);
|
||||
virtual ~trans_spec_view_t() = default;
|
||||
|
||||
struct cmp_t {
|
||||
bool operator()(
|
||||
const trans_spec_view_t &lhs,
|
||||
const trans_spec_view_t &rhs) const
|
||||
{
|
||||
return lhs.pending_for_transaction < rhs.pending_for_transaction;
|
||||
}
|
||||
const trans_spec_view_t &rhs) const;
|
||||
bool operator()(
|
||||
const transaction_id_t &lhs,
|
||||
const trans_spec_view_t &rhs) const
|
||||
{
|
||||
return lhs < rhs.pending_for_transaction;
|
||||
}
|
||||
const trans_spec_view_t &rhs) const;
|
||||
bool operator()(
|
||||
const trans_spec_view_t &lhs,
|
||||
const transaction_id_t &rhs) const
|
||||
{
|
||||
return lhs.pending_for_transaction < rhs;
|
||||
}
|
||||
const transaction_id_t &rhs) const;
|
||||
};
|
||||
|
||||
using trans_view_hook_t =
|
||||
@ -266,12 +256,17 @@ private:
|
||||
map_t buffer_map;
|
||||
};
|
||||
|
||||
enum class extent_2q_state_t : uint8_t {
|
||||
enum class extent_pin_state_t : uint8_t {
|
||||
// shared state between LRU and 2Q impl
|
||||
Fresh = 0,
|
||||
PendingPromote,
|
||||
Promoting,
|
||||
// 2Q impl only
|
||||
WarmIn,
|
||||
Hot,
|
||||
Max
|
||||
};
|
||||
std::ostream &operator<<(std::ostream &, const extent_pin_state_t &);
|
||||
|
||||
class ExtentCommitter : public boost::intrusive_ref_counter<
|
||||
ExtentCommitter, boost::thread_unsafe_counter> {
|
||||
@ -295,6 +290,8 @@ public:
|
||||
void commit_and_share_paddr();
|
||||
|
||||
void maybe_sync_copied_lba_key();
|
||||
void commit_shadow_demote(Transaction&);
|
||||
void commit_shadow_promote(Transaction&);
|
||||
private:
|
||||
// the rewritten extent
|
||||
CachedExtent &extent;
|
||||
@ -356,12 +353,14 @@ public:
|
||||
paddr_t paddr,
|
||||
placement_hint_t hint,
|
||||
rewrite_gen_t gen,
|
||||
transaction_id_t trans_id) {
|
||||
Transaction *trans,
|
||||
write_policy_t policy) {
|
||||
state = _state;
|
||||
set_paddr(paddr);
|
||||
user_hint = hint;
|
||||
rewrite_generation = gen;
|
||||
pending_for_transaction = trans_id;
|
||||
t = trans;
|
||||
write_policy = policy;
|
||||
}
|
||||
|
||||
void set_modify_time(sea_time_point t) {
|
||||
@ -514,36 +513,7 @@ public:
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &, extent_state_t);
|
||||
virtual std::ostream &print_detail(std::ostream &out) const { return out; }
|
||||
std::ostream &print(std::ostream &out) const {
|
||||
std::string prior_poffset_str = prior_poffset
|
||||
? fmt::format("{}", *prior_poffset)
|
||||
: "nullopt";
|
||||
out << "CachedExtent(addr=" << this
|
||||
<< ", type=" << get_type()
|
||||
<< ", trans=" << pending_for_transaction
|
||||
<< ", version=" << version
|
||||
<< ", dirty_from=" << dirty_from
|
||||
<< ", modify_time=" << sea_time_point_printer_t{modify_time}
|
||||
<< ", paddr=" << get_paddr()
|
||||
<< ", prior_paddr=" << prior_poffset_str
|
||||
<< std::hex << ", length=0x" << get_length()
|
||||
<< ", loaded=0x" << get_loaded_length() << std::dec
|
||||
<< ", state=" << state
|
||||
<< ", last_committed_crc=" << last_committed_crc
|
||||
<< ", refcount=" << use_count()
|
||||
<< ", user_hint=" << user_hint
|
||||
<< ", rewrite_gen=" << rewrite_gen_printer_t{rewrite_generation}
|
||||
<< ", pending_io=";
|
||||
if (is_pending_io()) {
|
||||
out << io_wait->from_state;
|
||||
} else {
|
||||
out << "N/A";
|
||||
}
|
||||
if (is_valid() && is_fully_loaded() && !is_stable_clean_pending()) {
|
||||
print_detail(out);
|
||||
}
|
||||
return out << ")";
|
||||
}
|
||||
std::ostream &print(std::ostream &out) const;
|
||||
|
||||
/**
|
||||
* get_delta
|
||||
@ -806,8 +776,14 @@ public:
|
||||
}
|
||||
|
||||
/// assign the target rewrite generation for the followup rewrite
|
||||
void set_target_rewrite_generation(rewrite_gen_t gen) {
|
||||
user_hint = placement_hint_t::REWRITE;
|
||||
void set_target_rewrite_generation(
|
||||
rewrite_gen_t gen,
|
||||
placement_hint_t hint = PLACEMENT_HINT_NULL) {
|
||||
if (hint != PLACEMENT_HINT_NULL) {
|
||||
user_hint = hint;
|
||||
} else {
|
||||
user_hint = placement_hint_t::REWRITE;
|
||||
}
|
||||
rewrite_generation = gen;
|
||||
}
|
||||
|
||||
@ -842,9 +818,7 @@ public:
|
||||
}
|
||||
|
||||
/// Returns true if the extent part of the open transaction
|
||||
bool is_pending_in_trans(transaction_id_t id) const {
|
||||
return is_pending() && pending_for_transaction == id;
|
||||
}
|
||||
bool is_pending_in_trans(transaction_id_t id) const;
|
||||
|
||||
enum class viewable_state_t {
|
||||
stable, // viewable
|
||||
@ -864,17 +838,34 @@ public:
|
||||
std::pair<bool, viewable_state_t>
|
||||
is_viewable_by_trans(Transaction &t);
|
||||
|
||||
extent_2q_state_t get_2q_state() const {
|
||||
assert("2Q" == crimson::common::get_conf<std::string>
|
||||
("seastore_cachepin_type"));
|
||||
return cache_state;
|
||||
extent_pin_state_t get_pin_state() const {
|
||||
#ifndef NDEBUG
|
||||
using crimson::common::get_conf;
|
||||
auto type = get_conf<std::string>("seastore_cachepin_type");
|
||||
if (type == "LRU") {
|
||||
assert(pin_state <= extent_pin_state_t::Promoting);
|
||||
} else if (type == "2Q") {
|
||||
assert(pin_state < extent_pin_state_t::Max);
|
||||
} else {
|
||||
ceph_abort("invalid seastore_cachepin_type(LRU or 2Q)");
|
||||
}
|
||||
#endif
|
||||
return pin_state;
|
||||
}
|
||||
|
||||
void set_2q_state(extent_2q_state_t state) {
|
||||
assert("2Q" == crimson::common::get_conf<std::string>
|
||||
("seastore_cachepin_type"));
|
||||
assert(state < extent_2q_state_t::Max);
|
||||
cache_state = state;
|
||||
void set_pin_state(extent_pin_state_t state) {
|
||||
pin_state = state;
|
||||
#ifndef NDEBUG
|
||||
using crimson::common::get_conf;
|
||||
auto type = get_conf<std::string>("seastore_cachepin_type");
|
||||
if (type == "LRU") {
|
||||
assert(pin_state <= extent_pin_state_t::Promoting);
|
||||
} else if (type == "2Q") {
|
||||
assert(pin_state < extent_pin_state_t::Max);
|
||||
} else {
|
||||
ceph_abort("invalid seastore_cachepin_type(LRU or 2Q)");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extent_len_t get_last_touch_end() const {
|
||||
@ -886,6 +877,26 @@ public:
|
||||
last_touch_end = touch_end;
|
||||
}
|
||||
|
||||
bool is_shadow_extent() const {
|
||||
return is_shadow;
|
||||
}
|
||||
|
||||
void set_shadow_extent(bool b) {
|
||||
is_shadow = b;
|
||||
}
|
||||
|
||||
write_policy_t get_write_policy() const {
|
||||
return write_policy;
|
||||
}
|
||||
|
||||
void set_write_policy(write_policy_t w) {
|
||||
write_policy = w;
|
||||
}
|
||||
|
||||
void reset_write_policy() {
|
||||
write_policy = write_policy_t::WRITE_BACK;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
friend class read_set_item_t;
|
||||
@ -997,6 +1008,8 @@ private:
|
||||
|
||||
placement_hint_t user_hint = PLACEMENT_HINT_NULL;
|
||||
|
||||
write_policy_t write_policy = write_policy_t::WRITE_BACK;
|
||||
|
||||
// the target rewrite generation for the followup rewrite
|
||||
// or the rewrite generation for the fresh write
|
||||
rewrite_gen_t rewrite_generation = NULL_GENERATION;
|
||||
@ -1005,8 +1018,8 @@ private:
|
||||
// see Cache::touch_extent_by_range() and ExtentPinboardTwoQ.
|
||||
extent_len_t last_touch_end = 0;
|
||||
|
||||
// This field is unused when the ExtentPinboard use LRU algorithm
|
||||
extent_2q_state_t cache_state = extent_2q_state_t::Fresh;
|
||||
// This field is used by ExtentPinboard
|
||||
extent_pin_state_t pin_state = extent_pin_state_t::Fresh;
|
||||
|
||||
ExtentCommitterRef committer;
|
||||
|
||||
@ -1014,6 +1027,8 @@ private:
|
||||
|
||||
seastar::shared_mutex commit_lock;
|
||||
|
||||
bool is_shadow = false;
|
||||
|
||||
protected:
|
||||
trans_view_set_t mutation_pending_extents;
|
||||
trans_view_set_t retired_transactions;
|
||||
@ -1096,6 +1111,7 @@ protected:
|
||||
friend class ExtentQueue;
|
||||
friend class ExtentPinboardLRU;
|
||||
friend class ExtentPinboardTwoQ;
|
||||
friend class ExtentPromoter;
|
||||
template <typename T, typename... Args>
|
||||
static TCachedExtentRef<T> make_cached_extent_ref(
|
||||
Args&&... args) {
|
||||
@ -1279,8 +1295,8 @@ struct trans_retired_extent_link_t {
|
||||
// Otherwise, we have to search through each extent's "retired_transactions"
|
||||
// to remove the transaction
|
||||
trans_spec_view_t trans_view;
|
||||
trans_retired_extent_link_t(CachedExtentRef extent, transaction_id_t id)
|
||||
: extent(extent), trans_view{id}
|
||||
trans_retired_extent_link_t(CachedExtentRef extent, Transaction &t)
|
||||
: extent(extent), trans_view{t}
|
||||
{
|
||||
assert(extent->is_stable());
|
||||
extent->retired_transactions.insert(trans_view);
|
||||
@ -1621,25 +1637,5 @@ using lextent_list_t = addr_extent_list_base_t<
|
||||
template <> struct fmt::formatter<crimson::os::seastore::CachedExtent> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::CachedExtent::viewable_state_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::LogicalCachedExtent> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::extent_pin_state_t> : fmt::ostream_formatter {};
|
||||
#endif
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<crimson::os::seastore::extent_2q_state_t>
|
||||
: public fmt::formatter<std::string_view> {
|
||||
using State = crimson::os::seastore::extent_2q_state_t;
|
||||
auto format(const State &s, auto &ctx) const {
|
||||
switch (s) {
|
||||
case State::Fresh:
|
||||
return fmt::format_to(ctx.out(), "Fresh");
|
||||
case State::WarmIn:
|
||||
return fmt::format_to(ctx.out(), "WarmIn");
|
||||
case State::Hot:
|
||||
return fmt::format_to(ctx.out(), "Hot");
|
||||
case State::Max:
|
||||
return fmt::format_to(ctx.out(), "Max");
|
||||
default:
|
||||
__builtin_unreachable();
|
||||
return ctx.out();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -58,6 +58,8 @@ void device_superblock_t::validate() const
|
||||
}
|
||||
ceph_assert(block_size > 0);
|
||||
ceph_assert(config.spec.magic != 0);
|
||||
// allow HDD devices use segmented backend
|
||||
ceph_assert(config.spec.btype != backend_type_t::NONE);
|
||||
ceph_assert(config.spec.id <= DEVICE_ID_MAX_VALID);
|
||||
if (!config.major_dev) {
|
||||
ceph_assert(config.secondary_devices.empty());
|
||||
@ -106,7 +108,8 @@ void device_superblock_t::validate() const
|
||||
ceph_assert(shard_infos[i].size > block_size &&
|
||||
shard_infos[i].size % block_size == 0);
|
||||
ceph_assert_always(shard_infos[i].size <= DEVICE_OFF_MAX);
|
||||
ceph_assert(journal_size > 0 && journal_size % block_size == 0);
|
||||
ceph_assert((journal_size > 0 && journal_size % block_size == 0) ||
|
||||
config.spec.dtype == device_type_t::RANDOM_BLOCK_HDD);
|
||||
ceph_assert(shard_infos[i].start_offset < total_size &&
|
||||
shard_infos[i].start_offset % block_size == 0);
|
||||
}
|
||||
@ -114,19 +117,23 @@ void device_superblock_t::validate() const
|
||||
}
|
||||
|
||||
seastar::future<DeviceRef>
|
||||
Device::make_device(const std::string& device, device_type_t dtype)
|
||||
Device::make_device(
|
||||
const std::string& device,
|
||||
device_type_t dtype,
|
||||
backend_type_t btype)
|
||||
{
|
||||
if (get_default_backend_of_device(dtype) == backend_type_t::SEGMENTED) {
|
||||
if (btype == backend_type_t::SEGMENTED) {
|
||||
return SegmentManager::get_segment_manager(device, dtype
|
||||
).then([](DeviceRef ret) {
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
assert(get_default_backend_of_device(dtype) == backend_type_t::RANDOM_BLOCK);
|
||||
return get_rb_device(device
|
||||
).then([](DeviceRef ret) {
|
||||
return ret;
|
||||
});
|
||||
} else {
|
||||
ceph_assert(btype != backend_type_t::NONE);
|
||||
return get_rb_device(device, dtype
|
||||
).then([](DeviceRef ret) {
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
check_create_device_ret check_create_device(
|
||||
|
||||
@ -23,11 +23,13 @@ using magic_t = uint64_t;
|
||||
struct device_spec_t {
|
||||
magic_t magic = 0;
|
||||
device_type_t dtype = device_type_t::NONE;
|
||||
backend_type_t btype = backend_type_t::NONE;
|
||||
device_id_t id = DEVICE_ID_NULL;
|
||||
DENC(device_spec_t, v, p) {
|
||||
DENC_START(1, 1, p);
|
||||
denc(v.magic, p);
|
||||
denc(v.dtype, p);
|
||||
denc(v.btype, p);
|
||||
denc(v.id, p);
|
||||
DENC_FINISH(p);
|
||||
}
|
||||
@ -55,12 +57,14 @@ struct device_config_t {
|
||||
uuid_d new_osd_fsid,
|
||||
device_id_t id,
|
||||
device_type_t d_type,
|
||||
backend_type_t b_type,
|
||||
secondary_device_set_t sds) {
|
||||
return device_config_t{
|
||||
true,
|
||||
device_spec_t{
|
||||
(magic_t)std::rand(),
|
||||
d_type,
|
||||
b_type,
|
||||
id},
|
||||
seastore_meta_t{new_osd_fsid},
|
||||
sds};
|
||||
@ -69,12 +73,14 @@ struct device_config_t {
|
||||
uuid_d new_osd_fsid,
|
||||
device_id_t id,
|
||||
device_type_t d_type,
|
||||
backend_type_t b_type,
|
||||
magic_t magic) {
|
||||
return device_config_t{
|
||||
false,
|
||||
device_spec_t{
|
||||
magic,
|
||||
d_type,
|
||||
b_type,
|
||||
id},
|
||||
seastore_meta_t{new_osd_fsid},
|
||||
secondary_device_set_t()};
|
||||
@ -329,7 +335,8 @@ public:
|
||||
|
||||
static seastar::future<DeviceRef> make_device(
|
||||
const std::string &device,
|
||||
device_type_t dtype);
|
||||
device_type_t dtype,
|
||||
backend_type_t btype);
|
||||
|
||||
// interfaces used by each device shard
|
||||
public:
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "crimson/os/seastore/extent_pinboard.h"
|
||||
#include "crimson/os/seastore/transaction.h"
|
||||
#include "crimson/os/seastore/transaction_manager.h"
|
||||
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
@ -266,8 +267,183 @@ void ExtentQueue::get_stats(
|
||||
last_overall_io = overall_io;
|
||||
}
|
||||
|
||||
class ExtentPromoter {
|
||||
public:
|
||||
ExtentPromoter(
|
||||
size_t promotion_size,
|
||||
ExtentPlacementManager &epm)
|
||||
: promotion_size(promotion_size),
|
||||
epm(epm),
|
||||
test_workload(crimson::common::get_conf<bool>(
|
||||
"seastore_logical_bucket_cache_test_stress"))
|
||||
{}
|
||||
|
||||
~ExtentPromoter() {
|
||||
clear();
|
||||
}
|
||||
|
||||
bool enabled() const {
|
||||
return ecb != nullptr;
|
||||
}
|
||||
|
||||
bool should_promote_extent(const CachedExtent &extent) {
|
||||
return enabled() && epm.is_cold_device(extent.get_paddr().get_device_id());
|
||||
}
|
||||
|
||||
size_t get_promotion_size() const {
|
||||
if (unlikely(test_workload)) {
|
||||
return current_contents >= promotion_size
|
||||
? current_contents
|
||||
: promotion_size;
|
||||
} else {
|
||||
return current_contents;
|
||||
}
|
||||
}
|
||||
|
||||
void set_background_callback(BackgroundListener *l) {
|
||||
listener = l;
|
||||
}
|
||||
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) {
|
||||
ecb = cb;
|
||||
}
|
||||
|
||||
bool should_run_promote() const {
|
||||
return enabled() &&
|
||||
(current_contents >= promotion_size
|
||||
|| test_workload);
|
||||
}
|
||||
|
||||
std::size_t get_promoted_size() const {
|
||||
return promoted_size;
|
||||
}
|
||||
|
||||
std::size_t get_promoted_count() const {
|
||||
return promoted_count;
|
||||
}
|
||||
|
||||
void add_extent(CachedExtent &extent) {
|
||||
assert(!extent.is_linked_to_list());
|
||||
assert(extent.is_stable_clean());
|
||||
extent.set_pin_state(extent_pin_state_t::PendingPromote);
|
||||
list.push_back(extent);
|
||||
current_contents += extent.get_length();
|
||||
intrusive_ptr_add_ref(&extent);
|
||||
while (current_contents > promotion_size) {
|
||||
remove_extent(list.front(), extent_pin_state_t::Fresh);
|
||||
}
|
||||
if (should_run_promote()) {
|
||||
assert(listener);
|
||||
listener->maybe_wake_promote();
|
||||
}
|
||||
}
|
||||
|
||||
void remove_extent(CachedExtent &extent, extent_pin_state_t new_state) {
|
||||
assert(extent.is_linked_to_list());
|
||||
assert(extent.get_pin_state() == extent_pin_state_t::PendingPromote);
|
||||
assert(current_contents >= extent.get_length());
|
||||
extent.set_pin_state(new_state);
|
||||
list.erase(list.s_iterator_to(extent));
|
||||
current_contents -= extent.get_length();
|
||||
intrusive_ptr_release(&extent);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (auto iter = list.begin(); iter != list.end();) {
|
||||
remove_extent(*(iter++), extent_pin_state_t::Fresh);
|
||||
}
|
||||
}
|
||||
|
||||
using run_promote_ret = base_iertr::future<>;
|
||||
run_promote_ret run_promote(
|
||||
Transaction &t,
|
||||
std::list<CachedExtentRef> &promoting_extents)
|
||||
{
|
||||
LOG_PREFIX(ExtentPromoter::run_promote);
|
||||
std::size_t promote_size = 0;
|
||||
DEBUGT("start promote", t);
|
||||
if (unlikely(test_workload &&
|
||||
current_contents < promotion_size &&
|
||||
// if promoting_extents is not empty, it means the last
|
||||
// round of promotion was not a test workload and was
|
||||
// interrupted, so this round shouldn't be a test workload
|
||||
// too.
|
||||
promoting_extents.empty())) {
|
||||
auto id = epm.get_cold_device_id();
|
||||
paddr_t start = P_ADDR_NULL;
|
||||
if (device_id_to_paddr_type(id) == paddr_types_t::SEGMENT) {
|
||||
start = paddr_t::make_seg_paddr(id, 0, 0);
|
||||
} else {
|
||||
start = paddr_t::make_blk_paddr(id, 0);
|
||||
}
|
||||
co_await ecb->promote_extents_from_disk(t, start);
|
||||
} else {
|
||||
std::list<CachedExtentRef> extents;
|
||||
for (auto &extent : list) {
|
||||
DEBUGT("promote {} to the hot tier", t, extent);
|
||||
ceph_assert(extent.is_stable_clean());
|
||||
ceph_assert(extent.get_pin_state() == extent_pin_state_t::PendingPromote);
|
||||
extents.emplace_back(&extent);
|
||||
}
|
||||
for (auto &extent : extents) {
|
||||
remove_extent(*extent, extent_pin_state_t::Promoting);
|
||||
}
|
||||
promoting_extents.insert(
|
||||
promoting_extents.end(),
|
||||
extents.begin(),
|
||||
extents.end());
|
||||
for (auto it = promoting_extents.begin();
|
||||
it != promoting_extents.end();) {
|
||||
auto &extent = *it;
|
||||
if (!extent->is_valid()) {
|
||||
it = promoting_extents.erase(it);
|
||||
continue;
|
||||
}
|
||||
promote_size += extent->get_length();
|
||||
t.add_to_read_set(extent);
|
||||
co_await trans_intr::make_interruptible(extent->wait_io());
|
||||
co_await ecb->promote_extent(t, extent);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
// existing extents in lru will be retired after transaction submitted
|
||||
co_await ecb->submit_transaction_direct(t);
|
||||
promoted_count += promoting_extents.size();
|
||||
promoted_size += promote_size;
|
||||
DEBUGT("finish promoting {} {}B extents",
|
||||
t, promoting_extents.size(), promote_size);
|
||||
co_return;
|
||||
}
|
||||
|
||||
seastar::future<> promote() {
|
||||
assert(enabled());
|
||||
std::list<CachedExtentRef> promoting_extents;
|
||||
co_await repeat_eagain([this, &promoting_extents] {
|
||||
return ecb->with_transaction_intr(
|
||||
Transaction::src_t::PROMOTE,
|
||||
"promote", cache_hint_t::get_nocache(),
|
||||
[this, &promoting_extents](auto &t) {
|
||||
return run_promote(t, promoting_extents);
|
||||
});
|
||||
}).handle_error(crimson::ct_error::assert_all("error occupied during promotion"));
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t promotion_size = 0;
|
||||
ExtentPlacementManager &epm;
|
||||
ExtentCallbackInterface *ecb = nullptr;
|
||||
BackgroundListener *listener = nullptr;
|
||||
CachedExtent::primary_ref_list list;
|
||||
size_t current_contents = 0;
|
||||
|
||||
size_t promoted_count = 0;
|
||||
size_t promoted_size = 0;
|
||||
bool test_workload = false;
|
||||
};
|
||||
|
||||
class ExtentPinboardLRU : public ExtentPinboard {
|
||||
ExtentQueue lru;
|
||||
ExtentPromoter promoter;
|
||||
seastar::metrics::metric_group metrics;
|
||||
|
||||
// hit and miss indicates if an extent is linked when touching it
|
||||
@ -275,9 +451,11 @@ class ExtentPinboardLRU : public ExtentPinboard {
|
||||
uint64_t miss = 0;
|
||||
|
||||
public:
|
||||
ExtentPinboardLRU(std::size_t capacity) : lru(capacity) {
|
||||
ExtentPinboardLRU(std::size_t capacity, size_t promotion_size, ExtentPlacementManager &epm)
|
||||
: lru(capacity), promoter(promotion_size, epm) {
|
||||
LOG_PREFIX(ExtentPinboardLRU::ExtentPinboardLRU);
|
||||
INFO("created, lru_capacity=0x{:x}B", capacity);
|
||||
INFO("created, lru_capacity=0x{:x}B, promotion_size=0x{:x}B",
|
||||
capacity, promotion_size);
|
||||
}
|
||||
|
||||
std::size_t get_capacity_bytes() const {
|
||||
@ -325,6 +503,26 @@ public:
|
||||
),
|
||||
}
|
||||
);
|
||||
if (promoter.enabled()) {
|
||||
metrics.add_group(
|
||||
"cache",
|
||||
{
|
||||
sm::make_counter(
|
||||
"promoted_size",
|
||||
[this] {
|
||||
return promoter.get_promoted_size();
|
||||
},
|
||||
sm::description("total bytes promoted by the lru"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"promoted_count",
|
||||
[this] {
|
||||
return promoter.get_promoted_count();
|
||||
},
|
||||
sm::description("total extents promoted by the lru"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void get_stats(
|
||||
@ -335,8 +533,29 @@ public:
|
||||
}
|
||||
|
||||
void remove(CachedExtent &extent) final {
|
||||
auto s = extent.get_pin_state();
|
||||
if (extent.is_linked_to_list()) {
|
||||
lru.remove(extent);
|
||||
if (s == extent_pin_state_t::Fresh) {
|
||||
lru.remove(extent);
|
||||
} else {
|
||||
promoter.remove_extent(extent, extent_pin_state_t::Fresh);
|
||||
}
|
||||
} else {
|
||||
ceph_assert(s == extent_pin_state_t::Fresh ||
|
||||
s == extent_pin_state_t::Promoting);
|
||||
}
|
||||
}
|
||||
|
||||
void add_to_top(
|
||||
CachedExtent &extent,
|
||||
const Transaction::src_t* p_src) {
|
||||
auto trimmed = lru.add_to_top(extent, p_src);
|
||||
if (promoter.enabled()) {
|
||||
for (auto &extent : trimmed) {
|
||||
if (promoter.should_promote_extent(*extent)) {
|
||||
promoter.add_extent(*extent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,11 +564,20 @@ public:
|
||||
const Transaction::src_t* p_src,
|
||||
extent_len_t /*load_start*/,
|
||||
extent_len_t /*load_length*/) final {
|
||||
if (extent.is_linked_to_list()) {
|
||||
lru.move_to_top(extent, p_src);
|
||||
if (auto s = extent.get_pin_state();
|
||||
s == extent_pin_state_t::Promoting) {
|
||||
assert(!extent.is_linked_to_list());
|
||||
} else if (extent.is_linked_to_list()) {
|
||||
assert(s <= extent_pin_state_t::PendingPromote);
|
||||
if (s == extent_pin_state_t::Fresh) {
|
||||
lru.move_to_top(extent, p_src);
|
||||
} else {
|
||||
promoter.remove_extent(extent, extent_pin_state_t::Fresh);
|
||||
add_to_top(extent, p_src);
|
||||
}
|
||||
hit++;
|
||||
} else {
|
||||
lru.add_to_top(extent, p_src);
|
||||
add_to_top(extent, p_src);
|
||||
miss++;
|
||||
}
|
||||
}
|
||||
@ -358,13 +586,38 @@ public:
|
||||
CachedExtent &extent,
|
||||
extent_len_t increased_length,
|
||||
const Transaction::src_t* p_src) final {
|
||||
if (extent.is_linked_to_list()) {
|
||||
if (extent.is_linked_to_list() &&
|
||||
extent.get_pin_state() == extent_pin_state_t::Fresh) {
|
||||
lru.increase_cached_size(extent, increased_length, p_src);
|
||||
} else {
|
||||
// promoter take the complete extent size for content size calculation
|
||||
assert(extent.get_pin_state() <= extent_pin_state_t::Promoting);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() final {
|
||||
lru.clear();
|
||||
promoter.clear();
|
||||
}
|
||||
|
||||
void set_background_callback(BackgroundListener *listener) final {
|
||||
promoter.set_background_callback(listener);
|
||||
}
|
||||
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) final {
|
||||
promoter.set_extent_callback(cb);
|
||||
}
|
||||
|
||||
std::size_t get_promotion_size() const final {
|
||||
return promoter.get_promotion_size();
|
||||
}
|
||||
|
||||
bool should_promote() const final {
|
||||
return promoter.should_run_promote();
|
||||
}
|
||||
|
||||
seastar::future<> promote() final {
|
||||
return promoter.promote();
|
||||
}
|
||||
|
||||
~ExtentPinboardLRU() {
|
||||
@ -482,15 +735,22 @@ public:
|
||||
ExtentPinboardTwoQ(
|
||||
std::size_t warm_in_capacity,
|
||||
std::size_t warm_out_capacity,
|
||||
std::size_t hot_capacity)
|
||||
std::size_t hot_capacity,
|
||||
std::size_t promotion_size,
|
||||
ExtentPlacementManager &epm)
|
||||
: warm_in(warm_in_capacity),
|
||||
warm_out(warm_out_capacity),
|
||||
hot(hot_capacity)
|
||||
hot(hot_capacity),
|
||||
promoter(promotion_size, epm),
|
||||
test_workload(crimson::common::get_conf<bool>(
|
||||
"seastore_logical_bucket_cache_test_stress")),
|
||||
TwoQ_promote_probability(crimson::common::get_conf<double>(
|
||||
"seastore_test_workload_2Q_promote_probability"))
|
||||
{
|
||||
LOG_PREFIX(ExtentPinboardTwoQ::ExtentPinboardTwoQ);
|
||||
INFO("created, warm_in_capacity=0x{:x}B, "
|
||||
"warm_out_capacity=0x{:x}B, hot_capacity=0x{:x}B",
|
||||
warm_in_capacity, warm_out_capacity, hot_capacity);
|
||||
INFO("created, warm_in_capacity=0x{:x}B, warm_out_capacity=0x{:x}B, "
|
||||
"hot_capacity=0x{:x}B, promotion_size=0x{:x}B",
|
||||
warm_in_capacity, warm_out_capacity, hot_capacity, promotion_size);
|
||||
}
|
||||
|
||||
std::size_t get_capacity_bytes() const {
|
||||
@ -513,17 +773,20 @@ public:
|
||||
double seconds) const final;
|
||||
|
||||
void remove(CachedExtent &extent) final {
|
||||
auto s = extent.get_2q_state();
|
||||
auto s = extent.get_pin_state();
|
||||
if (extent.is_linked_to_list()) {
|
||||
if (s == extent_2q_state_t::WarmIn) {
|
||||
if (s == extent_pin_state_t::WarmIn) {
|
||||
warm_in.remove(extent);
|
||||
} else if (s == extent_pin_state_t::PendingPromote) {
|
||||
promoter.remove_extent(extent, extent_pin_state_t::Fresh);
|
||||
} else {
|
||||
ceph_assert(s == extent_2q_state_t::Hot);
|
||||
ceph_assert(s == extent_pin_state_t::Hot);
|
||||
hot.remove(extent);
|
||||
}
|
||||
extent.set_2q_state(extent_2q_state_t::Fresh);
|
||||
extent.set_pin_state(extent_pin_state_t::Fresh);
|
||||
} else {
|
||||
ceph_assert(s == extent_2q_state_t::Fresh);
|
||||
ceph_assert(s == extent_pin_state_t::Fresh ||
|
||||
s == extent_pin_state_t::Promoting);
|
||||
}
|
||||
}
|
||||
|
||||
@ -532,14 +795,23 @@ public:
|
||||
const Transaction::src_t* p_src,
|
||||
extent_len_t load_start,
|
||||
extent_len_t load_length) final {
|
||||
auto state = extent.get_2q_state();
|
||||
auto state = extent.get_pin_state();
|
||||
auto type = extent.get_type();
|
||||
if (state == extent_pin_state_t::Promoting) {
|
||||
assert(!extent.is_linked_to_list());
|
||||
return;
|
||||
}
|
||||
if (extent.is_linked_to_list()) {
|
||||
if (state == extent_2q_state_t::Hot) {
|
||||
if (state == extent_pin_state_t::Hot) {
|
||||
hot.move_to_top(extent, p_src);
|
||||
hit_queue(overall_hits.hot_hits, p_src, type);
|
||||
} else if (state == extent_pin_state_t::PendingPromote) {
|
||||
promoter.remove_extent(extent, extent_pin_state_t::Hot);
|
||||
auto trimmed_extents = hot.add_to_top(extent, p_src);
|
||||
on_update_hot(trimmed_extents);
|
||||
hit_queue(overall_hits.hot_hits, p_src, type);
|
||||
} else {
|
||||
ceph_assert(state == extent_2q_state_t::WarmIn);
|
||||
ceph_assert(state == extent_pin_state_t::WarmIn);
|
||||
hit_queue(overall_hits.warm_in_hits, p_src, type);
|
||||
// warm_in is a FIFO queue, do nothing here
|
||||
// In the standard 2Q algorithm, the extent won't be considerred
|
||||
@ -549,28 +821,28 @@ public:
|
||||
hit++;
|
||||
} else if (!is_logical_type(extent.get_type())) {
|
||||
// put physical extents to hot queue directly
|
||||
ceph_assert(state == extent_2q_state_t::Fresh);
|
||||
extent.set_2q_state(extent_2q_state_t::Hot);
|
||||
ceph_assert(state == extent_pin_state_t::Fresh);
|
||||
extent.set_pin_state(extent_pin_state_t::Hot);
|
||||
auto trimmed_extents = hot.add_to_top(extent, p_src);
|
||||
on_update_hot(trimmed_extents);
|
||||
hit_queue(overall_hits.absent, p_src, type);
|
||||
miss++;
|
||||
} else { // the logical extent which is not in warm_in and not in hot
|
||||
ceph_assert(state == extent_2q_state_t::Fresh);
|
||||
ceph_assert(state == extent_pin_state_t::Fresh);
|
||||
auto lext = extent.cast<LogicalCachedExtent>();
|
||||
auto m = warm_out.accessed_recently(lext->get_laddr(), load_start);
|
||||
using AccessMode = IndexedFifoQueue::AccessMode;
|
||||
if (m == AccessMode::Again) {
|
||||
// This extent was accessed recently, consider it's hot enough to
|
||||
// promote to hot queue.
|
||||
extent.set_2q_state(extent_2q_state_t::Hot);
|
||||
extent.set_pin_state(extent_pin_state_t::Hot);
|
||||
auto trimmed_extents = hot.add_to_top(extent, p_src);
|
||||
on_update_hot(trimmed_extents);
|
||||
hit_queue(overall_hits.hot_absent, p_src, type);
|
||||
} else {
|
||||
// This extent didn't be accessed recently, put it warm_in queue
|
||||
// by default.
|
||||
extent.set_2q_state(extent_2q_state_t::WarmIn);
|
||||
extent.set_pin_state(extent_pin_state_t::WarmIn);
|
||||
auto trimmed_extents = warm_in.add_to_top(extent, p_src);
|
||||
on_update_warm_in(trimmed_extents);
|
||||
if (m == AccessMode::Missing) {
|
||||
@ -593,16 +865,18 @@ public:
|
||||
extent_len_t increased_length,
|
||||
const Transaction::src_t* p_src) final {
|
||||
if (extent.is_linked_to_list()) {
|
||||
auto state = extent.get_2q_state();
|
||||
if (state == extent_2q_state_t::WarmIn) {
|
||||
auto state = extent.get_pin_state();
|
||||
if (state == extent_pin_state_t::WarmIn) {
|
||||
auto trimmed_extents = warm_in.increase_cached_size(
|
||||
extent, increased_length, p_src);
|
||||
on_update_warm_in(trimmed_extents);
|
||||
} else {
|
||||
ceph_assert(state == extent_2q_state_t::Hot);
|
||||
} else if (state == extent_pin_state_t::Hot) {
|
||||
auto trimmed_extents = hot.increase_cached_size(
|
||||
extent, increased_length, p_src);
|
||||
on_update_hot(trimmed_extents);
|
||||
} else {
|
||||
ceph_assert(extent.get_pin_state() ==
|
||||
extent_pin_state_t::PendingPromote);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -616,6 +890,23 @@ public:
|
||||
warm_in.clear();
|
||||
warm_out.clear();
|
||||
hot.clear();
|
||||
promoter.clear();
|
||||
}
|
||||
|
||||
void set_background_callback(BackgroundListener *listener) final {
|
||||
promoter.set_background_callback(listener);
|
||||
}
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) final {
|
||||
promoter.set_extent_callback(cb);
|
||||
}
|
||||
std::size_t get_promotion_size() const final {
|
||||
return promoter.get_promotion_size();
|
||||
}
|
||||
bool should_promote() const final {
|
||||
return promoter.should_run_promote();
|
||||
}
|
||||
seastar::future<> promote() final {
|
||||
return promoter.promote();
|
||||
}
|
||||
|
||||
~ExtentPinboardTwoQ() {
|
||||
@ -624,13 +915,17 @@ public:
|
||||
private:
|
||||
void on_update_hot(std::list<CachedExtentRef> &extents) {
|
||||
for (auto extent : extents) {
|
||||
extent->set_2q_state(extent_2q_state_t::Fresh);
|
||||
if (promoter.should_promote_extent(*extent)) {
|
||||
promoter.add_extent(*extent);
|
||||
} else {
|
||||
extent->set_pin_state(extent_pin_state_t::Fresh);
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_update_warm_in(std::list<CachedExtentRef> &extents) {
|
||||
for (auto extent : extents) {
|
||||
ceph_assert(is_logical_type(extent->get_type()));
|
||||
extent->set_2q_state(extent_2q_state_t::Fresh);
|
||||
extent->set_pin_state(extent_pin_state_t::Fresh);
|
||||
auto len = extent->get_loaded_length();
|
||||
if (len == 0) {
|
||||
// The extent is possibly empty after being initially split/remapped
|
||||
@ -638,6 +933,12 @@ private:
|
||||
// to the warm out queue.
|
||||
continue;
|
||||
}
|
||||
if (promoter.should_promote_extent(*extent)
|
||||
&& test_workload
|
||||
&& (double(std::rand() % 100) / 100.0) <= TwoQ_promote_probability) {
|
||||
promoter.add_extent(*extent);
|
||||
continue;
|
||||
}
|
||||
auto lext = extent->cast<LogicalCachedExtent>();
|
||||
auto laddr = lext->get_laddr();
|
||||
auto end = extent->get_last_touch_end();
|
||||
@ -663,6 +964,7 @@ private:
|
||||
ExtentQueue warm_in;
|
||||
IndexedFifoQueue warm_out;
|
||||
ExtentQueue hot;
|
||||
ExtentPromoter promoter;
|
||||
seastar::metrics::metric_group metrics;
|
||||
|
||||
struct QueueCounter {
|
||||
@ -723,6 +1025,8 @@ private:
|
||||
// hit and miss indicates if an extent is linked when touching it
|
||||
uint64_t hit = 0;
|
||||
uint64_t miss = 0;
|
||||
bool test_workload = false;
|
||||
double TwoQ_promote_probability = 0;
|
||||
};
|
||||
|
||||
void ExtentPinboardTwoQ::get_stats(
|
||||
@ -852,13 +1156,34 @@ void ExtentPinboardTwoQ::register_metrics(store_index_t store_index) {
|
||||
),
|
||||
}
|
||||
);
|
||||
if (promoter.enabled()) {
|
||||
metrics.add_group(
|
||||
"cache",
|
||||
{
|
||||
sm::make_counter(
|
||||
"promoted_size",
|
||||
[this] {
|
||||
return promoter.get_promoted_size();
|
||||
},
|
||||
sm::description("total bytes promoted by the lru"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"promoted_count",
|
||||
[this] {
|
||||
return promoter.get_promoted_count();
|
||||
},
|
||||
sm::description("total extents promoted by the lru"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ExtentPinboardRef create_extent_pinboard(std::size_t capacity) {
|
||||
ExtentPinboardRef create_extent_pinboard(std::size_t capacity, ExtentPlacementManager *epm) {
|
||||
using crimson::common::get_conf;
|
||||
size_t promotion_size = get_conf<Option::size_t>("seastore_cache_promotion_size");
|
||||
auto algorithm = get_conf<std::string>("seastore_cachepin_type");
|
||||
if (algorithm == "LRU") {
|
||||
return std::make_unique<ExtentPinboardLRU>(capacity);
|
||||
return std::make_unique<ExtentPinboardLRU>(capacity, promotion_size, *epm);
|
||||
} else if (algorithm == "2Q") {
|
||||
auto warm_in_ratio = get_conf<double>("seastore_cachepin_2q_in_ratio");
|
||||
auto warm_out_ratio = get_conf<double>("seastore_cachepin_2q_out_ratio");
|
||||
@ -867,7 +1192,8 @@ ExtentPinboardRef create_extent_pinboard(std::size_t capacity) {
|
||||
return std::make_unique<ExtentPinboardTwoQ>(
|
||||
capacity * warm_in_ratio,
|
||||
capacity * warm_out_ratio,
|
||||
capacity * (1 - warm_in_ratio));
|
||||
capacity * (1 - warm_in_ratio),
|
||||
promotion_size, *epm);
|
||||
} else {
|
||||
ceph_abort("invalid seastore_cachepin_type(LRU or 2Q)");
|
||||
return nullptr;
|
||||
|
||||
@ -7,6 +7,9 @@
|
||||
#include "crimson/os/seastore/transaction.h"
|
||||
|
||||
namespace crimson::os::seastore {
|
||||
class BackgroundListener;
|
||||
class ExtentCallbackInterface;
|
||||
class ExtentPlacementManager;
|
||||
|
||||
struct ExtentPinboard {
|
||||
virtual ~ExtentPinboard() = default;
|
||||
@ -28,8 +31,14 @@ struct ExtentPinboard {
|
||||
extent_len_t increased_length,
|
||||
const Transaction::src_t *p_src) = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual void set_background_callback(BackgroundListener *listener) = 0;
|
||||
virtual void set_extent_callback(ExtentCallbackInterface *cb) = 0;
|
||||
virtual std::size_t get_promotion_size() const = 0;
|
||||
virtual bool should_promote() const = 0;
|
||||
virtual seastar::future<> promote() = 0;
|
||||
};
|
||||
using ExtentPinboardRef = std::unique_ptr<ExtentPinboard>;
|
||||
ExtentPinboardRef create_extent_pinboard(std::size_t capacity);
|
||||
ExtentPinboardRef create_extent_pinboard(
|
||||
std::size_t capacity, ExtentPlacementManager *epm);
|
||||
|
||||
} // namespace crimson::os::seastore
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
|
||||
// vim: ts=8 sw=2 sts=2 expandtab expandtab
|
||||
|
||||
#include <chrono>
|
||||
#include <seastar/core/sleep.hh>
|
||||
|
||||
#include "crimson/os/seastore/extent_placement_manager.h"
|
||||
|
||||
#include "crimson/common/errorator-utils.h"
|
||||
@ -16,7 +19,8 @@ SegmentedOolWriter::SegmentedOolWriter(
|
||||
data_category_t category,
|
||||
rewrite_gen_t gen,
|
||||
SegmentProvider& sp,
|
||||
SegmentSeqAllocator &ssa)
|
||||
SegmentSeqAllocator &ssa,
|
||||
TokenBucket &bucket)
|
||||
: store_index(store_index),
|
||||
segment_allocator(nullptr, category, gen, sp, ssa),
|
||||
record_submitter(crimson::common::get_conf<uint64_t>(
|
||||
@ -27,7 +31,8 @@ SegmentedOolWriter::SegmentedOolWriter(
|
||||
"seastore_journal_batch_flush_size"),
|
||||
crimson::common::get_conf<double>(
|
||||
"seastore_journal_batch_preferred_fullness"),
|
||||
segment_allocator)
|
||||
segment_allocator),
|
||||
token_bucket(bucket)
|
||||
{
|
||||
}
|
||||
|
||||
@ -182,23 +187,36 @@ SegmentedOolWriter::alloc_write_ool_extents(
|
||||
std::list<CachedExtentRef>& extents)
|
||||
{
|
||||
if (extents.empty()) {
|
||||
return alloc_write_iertr::now();
|
||||
co_return;
|
||||
}
|
||||
return seastar::with_gate(write_guard, [this, &t, &extents] {
|
||||
return do_write(t, extents);
|
||||
co_await seastar::with_gate(
|
||||
write_guard,
|
||||
[this, &t, &extents] -> alloc_write_iertr::future<> {
|
||||
uint64_t size = 0;
|
||||
for (auto &e : extents) {
|
||||
size += e->get_length();
|
||||
}
|
||||
try {
|
||||
co_await trans_intr::make_interruptible(
|
||||
token_bucket.get(size));
|
||||
co_await do_write(t, extents);
|
||||
} catch (...) {
|
||||
token_bucket.release(size);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ExtentPlacementManager::init(
|
||||
JournalTrimmerImplRef &&trimmer,
|
||||
AsyncCleanerRef &&cleaner,
|
||||
AsyncCleanerRef &&cold_cleaner)
|
||||
AsyncCleanerRef &&cold_cleaner,
|
||||
ExtentPinboard *pinboard)
|
||||
{
|
||||
LOG_PREFIX(ExtentPlacementManager::init);
|
||||
writer_refs.clear();
|
||||
auto cold_segment_cleaner = dynamic_cast<SegmentCleaner*>(cold_cleaner.get());
|
||||
dynamic_max_rewrite_generation = hot_tier_generations - 1;
|
||||
if (cold_segment_cleaner) {
|
||||
if (cold_cleaner) {
|
||||
dynamic_max_rewrite_generation = hot_tier_generations + cold_tier_generations - 1;
|
||||
}
|
||||
DEBUG("dynamic_max_rewrite_generation: {}, "
|
||||
@ -207,6 +225,14 @@ void ExtentPlacementManager::init(
|
||||
cold_tier_generations);
|
||||
ceph_assert(dynamic_max_rewrite_generation > MIN_REWRITE_GENERATION);
|
||||
|
||||
auto main_bw_limit = crimson::common::get_conf<
|
||||
Option::size_t>("seastore_hot_backend_bw_throttle");
|
||||
auto secondary_bw_limit = crimson::common::get_conf<
|
||||
Option::size_t>("seastore_cold_backend_bw_throttle");
|
||||
|
||||
token_buckets.emplace_back(std::make_unique<TokenBucket>(main_bw_limit));
|
||||
token_buckets.back()->start();
|
||||
|
||||
if (trimmer->get_backend_type() == backend_type_t::SEGMENTED) {
|
||||
DEBUG("initiating SegmentCleaner");
|
||||
auto segment_cleaner = dynamic_cast<SegmentCleaner*>(cleaner.get());
|
||||
@ -219,7 +245,7 @@ void ExtentPlacementManager::init(
|
||||
for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::DATA, gen, *segment_cleaner,
|
||||
*ool_segment_seq_allocator));
|
||||
*ool_segment_seq_allocator, *token_buckets.back()));
|
||||
data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
|
||||
@ -228,7 +254,7 @@ void ExtentPlacementManager::init(
|
||||
for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::METADATA, gen, *segment_cleaner,
|
||||
*ool_segment_seq_allocator));
|
||||
*ool_segment_seq_allocator, *token_buckets.back()));
|
||||
md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
|
||||
@ -246,42 +272,67 @@ void ExtentPlacementManager::init(
|
||||
data_writers_by_gen.resize(num_writers, nullptr);
|
||||
md_writers_by_gen.resize(num_writers, {});
|
||||
writer_refs.emplace_back(std::make_unique<RandomBlockOolWriter>(
|
||||
rb_cleaner));
|
||||
rb_cleaner, *token_buckets.back()));
|
||||
// TODO: implement eviction in RBCleaner and introduce further writers
|
||||
data_writers_by_gen[generation_to_writer(OOL_GENERATION)] = writer_refs.back().get();
|
||||
md_writers_by_gen[generation_to_writer(OOL_GENERATION)] = writer_refs.back().get();
|
||||
for (auto *rb : rb_cleaner->get_rb_group()->get_rb_managers()) {
|
||||
add_device(rb->get_device());
|
||||
}
|
||||
}
|
||||
|
||||
if (cold_segment_cleaner) {
|
||||
// Cold DATA Segments
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::DATA, gen, *cold_segment_cleaner,
|
||||
*ool_segment_seq_allocator));
|
||||
for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
|
||||
data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
// Cold METADATA Segments
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::METADATA, gen, *cold_segment_cleaner,
|
||||
*ool_segment_seq_allocator));
|
||||
for (rewrite_gen_t gen = OOL_GENERATION; gen < hot_tier_generations; ++gen) {
|
||||
md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (auto *device : cold_segment_cleaner->get_segment_manager_group()
|
||||
->get_segment_managers()) {
|
||||
add_device(device);
|
||||
}
|
||||
}
|
||||
|
||||
if (cold_cleaner) {
|
||||
token_buckets.emplace_back(std::make_unique<TokenBucket>(secondary_bw_limit));
|
||||
token_buckets.back()->start();
|
||||
if (cold_cleaner->get_backend_type() == backend_type_t::SEGMENTED) {
|
||||
auto cold_segment_cleaner = static_cast<SegmentCleaner*>(cold_cleaner.get());
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::DATA, gen, *cold_segment_cleaner,
|
||||
*ool_segment_seq_allocator, *token_buckets.back()));
|
||||
data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
writer_refs.emplace_back(std::make_unique<SegmentedOolWriter>(store_index,
|
||||
data_category_t::METADATA, gen, *cold_segment_cleaner,
|
||||
*ool_segment_seq_allocator, *token_buckets.back()));
|
||||
md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (auto *device : cold_segment_cleaner->get_segment_manager_group()
|
||||
->get_segment_managers()) {
|
||||
add_device(device);
|
||||
}
|
||||
} else {
|
||||
ceph_assert(cold_cleaner->get_backend_type() == backend_type_t::RANDOM_BLOCK);
|
||||
auto rb_cleaner = static_cast<RBMCleaner*>(cold_cleaner.get());
|
||||
ceph_assert(rb_cleaner);
|
||||
writer_refs.emplace_back(std::make_unique<RandomBlockOolWriter>(rb_cleaner, *token_buckets.back()));
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
data_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (rewrite_gen_t gen = hot_tier_generations; gen <= dynamic_max_rewrite_generation; ++gen) {
|
||||
md_writers_by_gen[generation_to_writer(gen)] = writer_refs.back().get();
|
||||
}
|
||||
for (auto *rb : rb_cleaner->get_rb_group()->get_rb_managers()) {
|
||||
add_device(rb->get_device());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto cold_cleaner_ = cold_cleaner.get();
|
||||
background_process.init(std::move(trimmer),
|
||||
std::move(cleaner),
|
||||
std::move(cold_cleaner),
|
||||
hot_tier_generations);
|
||||
if (cold_segment_cleaner) {
|
||||
ceph_assert(get_main_backend_type() == backend_type_t::SEGMENTED);
|
||||
hot_tier_generations,
|
||||
pinboard);
|
||||
ceph_assert(get_main_backend_type() != backend_type_t::NONE);
|
||||
if (cold_cleaner_) {
|
||||
ceph_assert(background_process.has_cold_tier());
|
||||
} else {
|
||||
ceph_assert(!background_process.has_cold_tier());
|
||||
@ -570,6 +621,9 @@ ExtentPlacementManager::close()
|
||||
{
|
||||
LOG_PREFIX(ExtentPlacementManager::close);
|
||||
INFO("started");
|
||||
for (auto &token_bucket : token_buckets) {
|
||||
token_bucket->stop();
|
||||
}
|
||||
return crimson::do_for_each(data_writers_by_gen, [](auto &writer) {
|
||||
if (writer) {
|
||||
return writer->close();
|
||||
@ -607,6 +661,9 @@ ExtentPlacementManager::mount_ret ExtentPlacementManager::BackgroundProcess::mou
|
||||
trimmer->reset();
|
||||
stats = {};
|
||||
register_metrics(store_index);
|
||||
if (logical_bucket) {
|
||||
logical_bucket->mount(store_index);
|
||||
}
|
||||
DEBUG("mounting main cleaner");
|
||||
co_await main_cleaner->mount();
|
||||
if (has_cold_tier()) {
|
||||
@ -629,41 +686,49 @@ void ExtentPlacementManager::BackgroundProcess::start_background()
|
||||
ceph_assert(state == state_t::SCAN_SPACE);
|
||||
assert(!is_running());
|
||||
process_join = seastar::now();
|
||||
promote_process_join = seastar::now();
|
||||
state = state_t::RUNNING;
|
||||
assert(is_running());
|
||||
process_join = run();
|
||||
if (has_cold_tier()) {
|
||||
promote_process_join = run_promote();
|
||||
}
|
||||
}
|
||||
|
||||
seastar::future<>
|
||||
ExtentPlacementManager::BackgroundProcess::stop_background()
|
||||
{
|
||||
LOG_PREFIX(BackgroundProcess::stop_background);
|
||||
return seastar::futurize_invoke([this, FNAME] {
|
||||
if (!is_running()) {
|
||||
if (state != state_t::HALT) {
|
||||
INFO("isn't RUNNING or HALT, STOP");
|
||||
state = state_t::STOP;
|
||||
} else {
|
||||
INFO("isn't RUNNING, already HALT");
|
||||
}
|
||||
return seastar::now();
|
||||
if (!is_running()) {
|
||||
if (state != state_t::HALT) {
|
||||
INFO("isn't RUNNING or HALT, STOP");
|
||||
state = state_t::STOP;
|
||||
} else {
|
||||
INFO("isn't RUNNING, already HALT");
|
||||
}
|
||||
INFO("is RUNNING, going to HALT...");
|
||||
auto ret = std::move(*process_join);
|
||||
process_join.reset();
|
||||
state = state_t::HALT;
|
||||
assert(!is_running());
|
||||
do_wake_background();
|
||||
return ret;
|
||||
}).then([this, FNAME] {
|
||||
INFO("done, {}, {}",
|
||||
JournalTrimmerImpl::stat_printer_t{*trimmer, true},
|
||||
AsyncCleaner::stat_printer_t{*main_cleaner, true});
|
||||
if (has_cold_tier()) {
|
||||
INFO("done, cold_cleaner: {}",
|
||||
AsyncCleaner::stat_printer_t{*cold_cleaner, true});
|
||||
}
|
||||
});
|
||||
co_return;
|
||||
}
|
||||
INFO("is RUNNING, going to HALT...");
|
||||
std::vector<seastar::future<>> futs;
|
||||
futs.emplace_back(std::move(*process_join));
|
||||
process_join.reset();
|
||||
if (promote_process_join) {
|
||||
futs.emplace_back(std::move(*promote_process_join));
|
||||
promote_process_join.reset();
|
||||
}
|
||||
state = state_t::HALT;
|
||||
assert(!is_running());
|
||||
do_wake_background();
|
||||
do_wake_promote();
|
||||
co_await seastar::when_all(futs.begin(), futs.end());
|
||||
INFO("done, {}, {}",
|
||||
JournalTrimmerImpl::stat_printer_t{*trimmer, true},
|
||||
AsyncCleaner::stat_printer_t{*main_cleaner, true});
|
||||
if (has_cold_tier()) {
|
||||
INFO("done, cold_cleaner: {}",
|
||||
AsyncCleaner::stat_printer_t{*cold_cleaner, true});
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
seastar::future<>
|
||||
@ -729,7 +794,6 @@ ExtentPlacementManager::BackgroundProcess::reserve_projected_usage(
|
||||
if (!is_ready()) {
|
||||
co_return;
|
||||
}
|
||||
ceph_assert(!blocking_io);
|
||||
// The pipeline configuration prevents another IO from entering
|
||||
// prepare until the prior one exits and clears this.
|
||||
++stats.io_count;
|
||||
@ -759,15 +823,16 @@ ExtentPlacementManager::BackgroundProcess::reserve_projected_usage(
|
||||
// IO blocked -> needs cleaner -> cleaner sleeping -> nothing runs -> deadlock.
|
||||
// Kick the background so it can free space and call maybe_wake_blocked_io().
|
||||
auto arm_blocking_io_and_wake = [this] {
|
||||
blocking_io = seastar::promise<>();
|
||||
if (!blocking_io) {
|
||||
blocking_io = seastar::shared_promise<>();
|
||||
}
|
||||
do_wake_background();
|
||||
};
|
||||
arm_blocking_io_and_wake();
|
||||
// we just blocked this IO, now wait until
|
||||
// maybe_wake_blocked_io will set value to blocking_io
|
||||
do {
|
||||
co_await blocking_io->get_future();
|
||||
ceph_assert(!blocking_io);
|
||||
while (true) {
|
||||
co_await blocking_io->get_shared_future();
|
||||
auto res = try_reserve_io(usage);
|
||||
if (res.is_successful()) {
|
||||
DEBUG("unblocked");
|
||||
@ -777,6 +842,7 @@ ExtentPlacementManager::BackgroundProcess::reserve_projected_usage(
|
||||
auto duration = end_time - begin_time;
|
||||
stats.io_blocked_time += std::chrono::duration_cast<
|
||||
std::chrono::milliseconds>(duration).count();
|
||||
break;
|
||||
} else {
|
||||
DEBUG("blocked again: inline={}, main={}, cold={}, usage={}",
|
||||
res.reserve_inline_success,
|
||||
@ -792,7 +858,7 @@ ExtentPlacementManager::BackgroundProcess::reserve_projected_usage(
|
||||
}
|
||||
arm_blocking_io_and_wake();
|
||||
}
|
||||
} while (blocking_io);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -820,7 +886,8 @@ ExtentPlacementManager::BackgroundProcess::run()
|
||||
{
|
||||
assert(is_running());
|
||||
while (is_running()) {
|
||||
if (background_should_run()) {
|
||||
if (background_should_run()
|
||||
|| force_run_background()) {
|
||||
log_state("run(background)");
|
||||
co_await do_background_cycle();
|
||||
// Edge-triggered: yield only when a blocked IO was actually woken, so the
|
||||
@ -836,9 +903,13 @@ ExtentPlacementManager::BackgroundProcess::run()
|
||||
if (cold_cleaner) {
|
||||
cold_cleaner->maybe_adjust_thresholds();
|
||||
}
|
||||
maybe_reschedule_force_process();
|
||||
} else {
|
||||
log_state("run(block)");
|
||||
assert(!blocking_background);
|
||||
if (unlikely(test_workload)) {
|
||||
set_next_force_process();
|
||||
}
|
||||
blocking_background = seastar::promise<>();
|
||||
co_await blocking_background->get_future();
|
||||
// After waking (typically because arm_blocking_io_and_wake() kicked us),
|
||||
@ -910,23 +981,35 @@ void ExtentPlacementManager::BackgroundProcess::abort_cold_usage(
|
||||
}
|
||||
}
|
||||
|
||||
bool ExtentPlacementManager::BackgroundProcess::try_reserve_main(
|
||||
std::size_t usage)
|
||||
{
|
||||
return main_cleaner->try_reserve_projected_usage(usage);
|
||||
}
|
||||
|
||||
reserve_cleaner_result_t
|
||||
ExtentPlacementManager::BackgroundProcess::try_reserve_cleaner(
|
||||
const cleaner_usage_t &usage)
|
||||
{
|
||||
return {
|
||||
main_cleaner->try_reserve_projected_usage(usage.main_usage),
|
||||
try_reserve_main(usage.main_usage),
|
||||
try_reserve_cold(usage.cold_ool_usage)
|
||||
};
|
||||
}
|
||||
|
||||
void ExtentPlacementManager::BackgroundProcess::abort_main_usage(
|
||||
std::size_t usage, bool success)
|
||||
{
|
||||
if (success) {
|
||||
main_cleaner->release_projected_usage(usage);
|
||||
}
|
||||
}
|
||||
|
||||
void ExtentPlacementManager::BackgroundProcess::abort_cleaner_usage(
|
||||
const cleaner_usage_t &usage,
|
||||
const reserve_cleaner_result_t &result)
|
||||
{
|
||||
if (result.reserve_main_success) {
|
||||
main_cleaner->release_projected_usage(usage.main_usage);
|
||||
}
|
||||
abort_main_usage(usage.main_usage, result.reserve_main_success);
|
||||
abort_cold_usage(usage.cold_ool_usage, result.reserve_cold_success);
|
||||
}
|
||||
|
||||
@ -980,12 +1063,24 @@ ExtentPlacementManager::BackgroundProcess::do_background_cycle()
|
||||
}
|
||||
}
|
||||
|
||||
bool force_trim = false;
|
||||
bool should_abort_cleaner_usage = true;
|
||||
if (unlikely(should_force_trim())) {
|
||||
if (!proceed_trim) {
|
||||
should_abort_cleaner_usage = false;
|
||||
}
|
||||
proceed_trim = true;
|
||||
force_trim = true;
|
||||
}
|
||||
|
||||
if (proceed_trim) {
|
||||
DEBUG("started trimming...");
|
||||
return trimmer->trim(
|
||||
).finally([this, trim_usage, FNAME] {
|
||||
return trimmer->trim(force_trim
|
||||
).finally([this, trim_usage, should_abort_cleaner_usage, FNAME] {
|
||||
DEBUG("finished trimming");
|
||||
abort_cleaner_usage(trim_usage, {true, true});
|
||||
if (should_abort_cleaner_usage) {
|
||||
abort_cleaner_usage(trim_usage, {true, true});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
assert(!proceed_trim);
|
||||
@ -1015,11 +1110,39 @@ ExtentPlacementManager::BackgroundProcess::do_background_cycle()
|
||||
proceed_clean_cold = true;
|
||||
}
|
||||
|
||||
if (!proceed_clean_main && !proceed_clean_cold) {
|
||||
ceph_abort_msg("no background process will start");
|
||||
bool proceed_demote = false;
|
||||
if (demote_should_run()) {
|
||||
proceed_demote = true;
|
||||
}
|
||||
|
||||
bool abort_cold_cleaner_usage = true;
|
||||
if (unlikely(should_force_clean())) {
|
||||
if (!proceed_clean_main) {
|
||||
abort_cold_cleaner_usage = false;
|
||||
}
|
||||
proceed_clean_main = main_cleaner->can_clean_space();
|
||||
if (has_cold_tier()) {
|
||||
proceed_clean_cold = cold_cleaner->can_clean_space();
|
||||
}
|
||||
if (logical_bucket) {
|
||||
proceed_demote = logical_bucket->could_demote();
|
||||
}
|
||||
}
|
||||
|
||||
if (proceed_demote &&
|
||||
!try_reserve_cold(logical_bucket_demote_size_per_cycle)) {
|
||||
abort_cold_usage(logical_bucket_demote_size_per_cycle, false);
|
||||
proceed_demote = false;
|
||||
}
|
||||
|
||||
if (!proceed_clean_main && !proceed_clean_cold && !proceed_demote) {
|
||||
// abort when the system is full, following the enospc handling
|
||||
// in ObjectDataHandler
|
||||
ceph_abort_msg("no background process will start, "
|
||||
"this probably means the underlying disks are full");
|
||||
}
|
||||
return seastar::when_all(
|
||||
[this, FNAME, proceed_clean_main,
|
||||
[this, FNAME, proceed_clean_main, abort_cold_cleaner_usage,
|
||||
should_clean_main_for_trim, main_cold_usage] {
|
||||
if (!proceed_clean_main) {
|
||||
return seastar::now();
|
||||
@ -1034,9 +1157,11 @@ ExtentPlacementManager::BackgroundProcess::do_background_cycle()
|
||||
crimson::ct_error::assert_all(
|
||||
"do_background_cycle encountered invalid error in main clean_space"
|
||||
)
|
||||
).finally([this, main_cold_usage, FNAME] {
|
||||
).finally([this, main_cold_usage, abort_cold_cleaner_usage, FNAME] {
|
||||
DEBUG("finished clean main");
|
||||
abort_cold_usage(main_cold_usage, true);
|
||||
if (abort_cold_cleaner_usage) {
|
||||
abort_cold_usage(main_cold_usage, true);
|
||||
}
|
||||
});
|
||||
},
|
||||
[this, FNAME, proceed_clean_cold,
|
||||
@ -1057,11 +1182,60 @@ ExtentPlacementManager::BackgroundProcess::do_background_cycle()
|
||||
).finally([FNAME] {
|
||||
DEBUG("finished clean cold");
|
||||
});
|
||||
},
|
||||
[this, proceed_demote] {
|
||||
if (!proceed_demote) {
|
||||
return seastar::now();
|
||||
}
|
||||
return logical_bucket->demote(
|
||||
).finally([this] {
|
||||
abort_cold_usage(logical_bucket_demote_size_per_cycle, true);
|
||||
});
|
||||
}
|
||||
).discard_result();
|
||||
}
|
||||
}
|
||||
|
||||
seastar::future<> ExtentPlacementManager::BackgroundProcess::run_promote()
|
||||
{
|
||||
assert(pinboard);
|
||||
assert(is_running());
|
||||
return seastar::repeat([this] {
|
||||
if (!is_running()) {
|
||||
return seastar::make_ready_future<seastar::stop_iteration>(
|
||||
seastar::stop_iteration::yes);
|
||||
}
|
||||
|
||||
return seastar::futurize_invoke([this] {
|
||||
if (pinboard->should_promote()) {
|
||||
auto usage = pinboard->get_promotion_size();
|
||||
auto reserved = try_reserve_main(usage);
|
||||
if (reserved) {
|
||||
return pinboard->promote(
|
||||
).finally([this, usage] {
|
||||
abort_main_usage(usage, true);
|
||||
});
|
||||
} else {
|
||||
// reserve usage failed, block
|
||||
abort_main_usage(usage, false);
|
||||
}
|
||||
} // shouldn't promote, block
|
||||
|
||||
ceph_assert(!blocking_promote);
|
||||
blocking_promote = seastar::promise<>();
|
||||
return blocking_promote->get_future();
|
||||
}).then([this] {
|
||||
if (unlikely(test_workload)) {
|
||||
return seastar::sleep(std::chrono::seconds(
|
||||
force_process_half_life));
|
||||
}
|
||||
return seastar::now();
|
||||
}).then([] {
|
||||
return seastar::stop_iteration::no;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void ExtentPlacementManager::BackgroundProcess::register_metrics(store_index_t store_index)
|
||||
{
|
||||
namespace sm = seastar::metrics;
|
||||
@ -1099,22 +1273,54 @@ RandomBlockOolWriter::alloc_write_ool_extents(
|
||||
std::list<CachedExtentRef>& extents)
|
||||
{
|
||||
if (extents.empty()) {
|
||||
return alloc_write_iertr::now();
|
||||
co_return;
|
||||
}
|
||||
return seastar::with_gate(write_guard, [this, &t, &extents] {
|
||||
seastar::lw_shared_ptr<rbm_pending_ool_t> ptr =
|
||||
seastar::make_lw_shared<rbm_pending_ool_t>();
|
||||
ptr->pending_extents = t.get_pre_alloc_list();
|
||||
assert(!t.is_conflicted());
|
||||
t.set_pending_ool(ptr);
|
||||
return do_write(t, extents
|
||||
).finally([this, ptr=ptr] {
|
||||
if (ptr->is_conflicted) {
|
||||
for (auto &e : ptr->pending_extents) {
|
||||
rb_cleaner->mark_space_free(e->get_paddr(), e->get_length());
|
||||
}
|
||||
}
|
||||
});
|
||||
co_await seastar::with_gate(
|
||||
write_guard,
|
||||
[this, &t, &extents] -> alloc_write_iertr::future<> {
|
||||
uint64_t size = 0;
|
||||
for (auto &extent : extents) {
|
||||
size += extent->get_length();
|
||||
}
|
||||
try {
|
||||
co_await trans_intr::make_interruptible(
|
||||
token_bucket.get(size));
|
||||
// Apply backpressure if the accumulated deferred-free byte count of
|
||||
// conflicted OOL writes exceeds the safety threshold, preventing
|
||||
// allocator exhaustion.
|
||||
co_await trans_intr::make_interruptible(
|
||||
rb_cleaner->wait_for_conflict_drain());
|
||||
seastar::lw_shared_ptr<rbm_pending_ool_t> ptr =
|
||||
seastar::make_lw_shared<rbm_pending_ool_t>();
|
||||
ptr->pending_extents = t.get_pre_alloc_list();
|
||||
assert(!t.is_conflicted());
|
||||
t.set_pending_ool(ptr);
|
||||
// The conflicted check, the deferred frees and clear_pending_ool() must
|
||||
// all run in the same continuation that completes the write: it makes
|
||||
// them atomic w.r.t. reactor task interleaving (a conflict marked between
|
||||
// write completion and coroutine resumption could otherwise observe a
|
||||
// stale pending_ool whose frees were already decided), and it guarantees
|
||||
// pending_ool is cleared on every exit path, including errors and
|
||||
// conflict-driven unwinds. t outlives the future: the submit fiber is
|
||||
// suspended awaiting this coroutine.
|
||||
co_await do_write(t, extents
|
||||
).finally([this, ptr=ptr, &t] {
|
||||
if (ptr->is_conflicted) {
|
||||
std::size_t freed = 0;
|
||||
for (auto &e : ptr->pending_extents) {
|
||||
rb_cleaner->mark_space_free(e->get_paddr(), e->get_length());
|
||||
freed += e->get_length();
|
||||
}
|
||||
if (freed > 0) {
|
||||
rb_cleaner->deaccount_conflict_pending_free(freed);
|
||||
}
|
||||
}
|
||||
t.clear_pending_ool();
|
||||
});
|
||||
} catch (...) {
|
||||
token_bucket.release(size);
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1214,7 +1420,7 @@ RandomBlockOolWriter::do_write(
|
||||
trans_stats.num_records += writes.size();
|
||||
return alloc_write_ertr::parallel_for_each(writes,
|
||||
[](auto& info) {
|
||||
return info.rbm->write(info.offset, info.bp
|
||||
return info.rbm->write(info.offset, std::move(info.bp)
|
||||
).handle_error(
|
||||
alloc_write_ertr::pass_further{},
|
||||
crimson::ct_error::assert_all(
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
#include "crimson/os/seastore/journal/segment_allocator.h"
|
||||
#include "crimson/os/seastore/journal/record_submitter.h"
|
||||
#include "crimson/os/seastore/transaction.h"
|
||||
#include "crimson/os/seastore/extent_pinboard.h"
|
||||
#include "crimson/os/seastore/logical_bucket.h"
|
||||
#include "crimson/os/seastore/random_block_manager.h"
|
||||
#include "crimson/os/seastore/random_block_manager/block_rb_manager.h"
|
||||
#include "crimson/os/seastore/randomblock_manager_group.h"
|
||||
@ -21,6 +23,89 @@ namespace crimson::os::seastore {
|
||||
|
||||
class Cache;
|
||||
|
||||
class TokenBucket {
|
||||
struct Blocker {
|
||||
uint64_t size = 0;
|
||||
seastar::promise<> pr;
|
||||
};
|
||||
public:
|
||||
TokenBucket(uint64_t mt) :
|
||||
tokens(mt), max_tokens(mt), timer() {}
|
||||
|
||||
void start() {
|
||||
if (max_tokens != 0) {
|
||||
tokens = max_tokens;
|
||||
timer.set_callback([this] {
|
||||
if (tokens == max_tokens) {
|
||||
return;
|
||||
}
|
||||
assert(tokens < max_tokens);
|
||||
tokens += std::min(
|
||||
max_tokens / 10 + 1,
|
||||
max_tokens - tokens);
|
||||
do_wake();
|
||||
});
|
||||
if (!timer.armed()) {
|
||||
timer.arm_periodic(std::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (max_tokens != 0) {
|
||||
timer.cancel();
|
||||
tokens = std::numeric_limits<uint64_t>::max();
|
||||
do_wake();
|
||||
}
|
||||
}
|
||||
|
||||
seastar::future<> get(uint64_t size) {
|
||||
if (max_tokens == 0) {
|
||||
return seastar::now();
|
||||
}
|
||||
if (tokens < size) {
|
||||
size -= tokens;
|
||||
tokens = 0;
|
||||
blockers.emplace_back(size);
|
||||
return blockers.back().pr.get_future();
|
||||
} else {
|
||||
tokens -= size;
|
||||
return seastar::now();
|
||||
}
|
||||
}
|
||||
|
||||
void release(uint64_t size) {
|
||||
if (tokens == max_tokens) {
|
||||
return;
|
||||
}
|
||||
assert(tokens < max_tokens);
|
||||
tokens += std::min(size, max_tokens - tokens);
|
||||
do_wake();
|
||||
}
|
||||
|
||||
private:
|
||||
void do_wake() {
|
||||
while (!blockers.empty()) {
|
||||
auto &next = blockers.front();
|
||||
if (tokens < next.size) {
|
||||
next.size -= tokens;
|
||||
tokens = 0;
|
||||
break;
|
||||
} else {
|
||||
tokens -= next.size;
|
||||
next.pr.set_value();
|
||||
blockers.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t tokens;
|
||||
const uint64_t max_tokens;
|
||||
seastar::timer<seastar::steady_clock_type> timer;
|
||||
std::list<Blocker> blockers;
|
||||
};
|
||||
|
||||
using TokenBucketRef = std::unique_ptr<TokenBucket>;
|
||||
|
||||
/**
|
||||
* ExtentOolWriter
|
||||
*
|
||||
@ -41,7 +126,9 @@ public:
|
||||
|
||||
virtual paddr_t alloc_paddr(extent_len_t length) = 0;
|
||||
|
||||
virtual std::list<alloc_paddr_result> alloc_paddrs(extent_len_t length) = 0;
|
||||
virtual std::list<alloc_paddr_result> alloc_paddrs(
|
||||
extent_len_t length,
|
||||
paddr_t hint) = 0;
|
||||
|
||||
using alloc_write_ertr = base_ertr;
|
||||
using alloc_write_iertr = trans_iertr<alloc_write_ertr>;
|
||||
@ -73,7 +160,8 @@ public:
|
||||
data_category_t category,
|
||||
rewrite_gen_t gen,
|
||||
SegmentProvider &sp,
|
||||
SegmentSeqAllocator &ssa);
|
||||
SegmentSeqAllocator &ssa,
|
||||
TokenBucket &buckets);
|
||||
|
||||
backend_type_t get_type() const final {
|
||||
return backend_type_t::SEGMENTED;
|
||||
@ -103,7 +191,7 @@ public:
|
||||
return make_delayed_temp_paddr(0);
|
||||
}
|
||||
|
||||
std::list<alloc_paddr_result> alloc_paddrs(extent_len_t length) final {
|
||||
std::list<alloc_paddr_result> alloc_paddrs(extent_len_t length, paddr_t) final {
|
||||
return {alloc_paddr_result{make_delayed_temp_paddr(0), length}};
|
||||
}
|
||||
|
||||
@ -127,13 +215,14 @@ private:
|
||||
journal::SegmentAllocator segment_allocator;
|
||||
journal::RecordSubmitter record_submitter;
|
||||
seastar::gate write_guard;
|
||||
TokenBucket &token_bucket;
|
||||
};
|
||||
|
||||
|
||||
class RandomBlockOolWriter : public ExtentOolWriter {
|
||||
public:
|
||||
RandomBlockOolWriter(RBMCleaner* rb_cleaner) :
|
||||
rb_cleaner(rb_cleaner) {}
|
||||
RandomBlockOolWriter(RBMCleaner* rb_cleaner, TokenBucket &bucket) :
|
||||
rb_cleaner(rb_cleaner), token_bucket(bucket) {}
|
||||
|
||||
backend_type_t get_type() const final {
|
||||
return backend_type_t::RANDOM_BLOCK;
|
||||
@ -169,9 +258,10 @@ public:
|
||||
return rb_cleaner->alloc_paddr(length);
|
||||
}
|
||||
|
||||
std::list<alloc_paddr_result> alloc_paddrs(extent_len_t length) final {
|
||||
std::list<alloc_paddr_result> alloc_paddrs(
|
||||
extent_len_t length, paddr_t hint) final {
|
||||
assert(rb_cleaner);
|
||||
return rb_cleaner->alloc_paddrs(length);
|
||||
return rb_cleaner->alloc_paddrs(length, hint);
|
||||
}
|
||||
|
||||
bool can_inplace_rewrite(Transaction& t,
|
||||
@ -179,7 +269,9 @@ public:
|
||||
if (!extent->is_stable_dirty()) {
|
||||
return false;
|
||||
}
|
||||
assert(t.get_src() == transaction_type_t::TRIM_DIRTY);
|
||||
assert((t.get_src() == transaction_type_t::TRIM_DIRTY) ||
|
||||
(t.get_src() == transaction_type_t::DEMOTE) ||
|
||||
(t.get_src() == transaction_type_t::PROMOTE));
|
||||
ceph_assert_always(is_root_type(extent->get_type()) ||
|
||||
extent->get_paddr().is_absolute());
|
||||
return crimson::os::seastore::can_inplace_rewrite(extent->get_type());
|
||||
@ -218,6 +310,7 @@ private:
|
||||
seastar::gate write_guard;
|
||||
writer_stats_t w_stats;
|
||||
mutable writer_stats_t last_w_stats;
|
||||
TokenBucket &token_bucket;
|
||||
};
|
||||
|
||||
struct cleaner_usage_t {
|
||||
@ -280,7 +373,13 @@ public:
|
||||
ool_segment_seq_allocator(
|
||||
std::make_unique<SegmentSeqAllocator>(segment_type_t::OOL)),
|
||||
max_data_allocation_size(crimson::common::get_conf<Option::size_t>(
|
||||
"seastore_max_data_allocation_size"))
|
||||
"seastore_max_data_allocation_size")),
|
||||
write_through_size(crimson::common::get_conf<Option::size_t>(
|
||||
"seastore_write_through_size")),
|
||||
test_workload(crimson::common::get_conf<bool>(
|
||||
"seastore_logical_bucket_cache_test_stress")),
|
||||
write_through_probability(crimson::common::get_conf<double>(
|
||||
"seastore_test_workload_write_through_probability"))
|
||||
{
|
||||
LOG_PREFIX(ExtentPlacementManager::ExtentPlacementManager);
|
||||
devices_by_id.resize(DEVICE_ID_MAX, nullptr);
|
||||
@ -288,7 +387,8 @@ public:
|
||||
cold_tier_generations, hot_tier_generations);
|
||||
}
|
||||
|
||||
void init(JournalTrimmerImplRef &&, AsyncCleanerRef &&, AsyncCleanerRef &&);
|
||||
void init(JournalTrimmerImplRef &&, AsyncCleanerRef &&, AsyncCleanerRef &&,
|
||||
ExtentPinboard *pinboard);
|
||||
|
||||
SegmentSeqAllocator &get_ool_segment_seq_allocator() const {
|
||||
return *ool_segment_seq_allocator;
|
||||
@ -296,6 +396,18 @@ public:
|
||||
|
||||
void set_primary_device(Device *device);
|
||||
|
||||
bool is_full() const {
|
||||
return background_process.is_full();
|
||||
}
|
||||
|
||||
void maybe_wake_background() {
|
||||
background_process.maybe_wake_background();
|
||||
}
|
||||
|
||||
seastar::future<> wait_background() {
|
||||
return background_process.wait_background();
|
||||
}
|
||||
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) {
|
||||
background_process.set_extent_callback(cb);
|
||||
}
|
||||
@ -331,6 +443,10 @@ public:
|
||||
bool report_detail,
|
||||
double seconds) const;
|
||||
|
||||
LogicalBucket *get_logical_bucket() {
|
||||
return background_process.get_logical_bucket();
|
||||
}
|
||||
|
||||
using mount_ertr = crimson::errorator<
|
||||
crimson::ct_error::input_output_error>;
|
||||
using mount_ret = mount_ertr::future<>;
|
||||
@ -349,6 +465,16 @@ public:
|
||||
return background_process.start_background();
|
||||
}
|
||||
|
||||
struct alloc_option_t {
|
||||
placement_hint_t hint;
|
||||
rewrite_gen_t gen;
|
||||
bool is_tracked;
|
||||
paddr_t paddr_hint = P_ADDR_NULL;
|
||||
write_policy_t write_policy = write_policy_t::WRITE_BACK;
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
std::optional<paddr_t> external_paddr = std::nullopt;
|
||||
#endif
|
||||
};
|
||||
struct alloc_result_t {
|
||||
paddr_t paddr;
|
||||
bufferptr bp;
|
||||
@ -358,34 +484,30 @@ public:
|
||||
Transaction& t,
|
||||
extent_types_t type,
|
||||
extent_len_t length,
|
||||
placement_hint_t hint,
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
rewrite_gen_t gen,
|
||||
std::optional<paddr_t> external_paddr = std::nullopt
|
||||
#else
|
||||
rewrite_gen_t gen
|
||||
#endif
|
||||
alloc_option_t opt
|
||||
) {
|
||||
assert(hint < placement_hint_t::NUM_HINTS);
|
||||
assert(is_target_rewrite_generation(gen, dynamic_max_rewrite_generation));
|
||||
assert(gen == INIT_GENERATION || hint == placement_hint_t::REWRITE);
|
||||
assert(opt.hint < placement_hint_t::NUM_HINTS);
|
||||
assert(is_target_rewrite_generation(opt.gen, dynamic_max_rewrite_generation));
|
||||
assert(opt.gen == INIT_GENERATION ||
|
||||
opt.hint == placement_hint_t::REWRITE ||
|
||||
opt.hint == placement_hint_t::COLD);
|
||||
|
||||
data_category_t category = get_extent_category(type);
|
||||
gen = adjust_generation(category, type, hint, gen);
|
||||
opt.gen = adjust_generation(
|
||||
category, type, opt.hint, opt.gen, opt.write_policy, opt.is_tracked);
|
||||
|
||||
paddr_t addr;
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
if (unlikely(external_paddr.has_value())) {
|
||||
assert(external_paddr->is_fake());
|
||||
addr = *external_paddr;
|
||||
} else if (gen == INLINE_GENERATION) {
|
||||
#else
|
||||
if (gen == INLINE_GENERATION) {
|
||||
if (unlikely(opt.external_paddr.has_value())) {
|
||||
assert(opt.external_paddr->is_fake());
|
||||
addr = *opt.external_paddr;
|
||||
} else
|
||||
#endif
|
||||
if (opt.gen == INLINE_GENERATION) {
|
||||
addr = make_record_relative_paddr(0);
|
||||
} else {
|
||||
assert(category == data_category_t::METADATA);
|
||||
addr = get_writer(hint, category, gen)->alloc_paddr(length);
|
||||
addr = get_writer(opt.hint, category, opt.gen)->alloc_paddr(length);
|
||||
}
|
||||
assert(!(category == data_category_t::DATA));
|
||||
|
||||
@ -397,44 +519,41 @@ public:
|
||||
// according to the allocator.
|
||||
auto bp = create_extent_ptr_zero(length);
|
||||
|
||||
return alloc_result_t{addr, std::move(bp), gen};
|
||||
return alloc_result_t{addr, std::move(bp), opt.gen};
|
||||
}
|
||||
|
||||
std::list<alloc_result_t> alloc_new_data_extents(
|
||||
Transaction& t,
|
||||
extent_types_t type,
|
||||
extent_len_t length,
|
||||
placement_hint_t hint,
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
rewrite_gen_t gen,
|
||||
std::optional<paddr_t> external_paddr = std::nullopt
|
||||
#else
|
||||
rewrite_gen_t gen
|
||||
#endif
|
||||
alloc_option_t opt
|
||||
) {
|
||||
LOG_PREFIX(ExtentPlacementManager::alloc_new_data_extents);
|
||||
assert(hint < placement_hint_t::NUM_HINTS);
|
||||
assert(is_target_rewrite_generation(gen, dynamic_max_rewrite_generation));
|
||||
assert(gen == INIT_GENERATION || hint == placement_hint_t::REWRITE);
|
||||
assert(opt.hint < placement_hint_t::NUM_HINTS);
|
||||
assert(is_target_rewrite_generation(opt.gen, dynamic_max_rewrite_generation));
|
||||
assert(opt.gen == INIT_GENERATION ||
|
||||
opt.hint == placement_hint_t::REWRITE ||
|
||||
opt.hint == placement_hint_t::COLD);
|
||||
|
||||
data_category_t category = get_extent_category(type);
|
||||
gen = adjust_generation(category, type, hint, gen);
|
||||
assert(gen != INLINE_GENERATION);
|
||||
opt.gen = adjust_generation(
|
||||
category, type, opt.hint, opt.gen, opt.write_policy, opt.is_tracked);
|
||||
assert(opt.gen != INLINE_GENERATION);
|
||||
|
||||
// XXX: bp might be extended to point to different memory (e.g. PMem)
|
||||
// according to the allocator.
|
||||
std::list<alloc_result_t> allocs;
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
if (unlikely(external_paddr.has_value())) {
|
||||
assert(external_paddr->is_fake());
|
||||
if (unlikely(opt.external_paddr.has_value())) {
|
||||
assert(opt.external_paddr->is_fake());
|
||||
auto bp = create_extent_ptr_zero(length);
|
||||
allocs.emplace_back(alloc_result_t{*external_paddr, std::move(bp), gen});
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
allocs.emplace_back(alloc_result_t{*opt.external_paddr, std::move(bp), opt.gen});
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
assert(category == data_category_t::DATA);
|
||||
auto addrs = get_writer(hint, category, gen)->alloc_paddrs(length);
|
||||
auto addrs = get_writer(opt.hint, category, opt.gen)->alloc_paddrs(
|
||||
length, opt.paddr_hint);
|
||||
for (auto &ext : addrs) {
|
||||
auto left = ext.len;
|
||||
while (left > 0) {
|
||||
@ -446,10 +565,10 @@ public:
|
||||
auto start = ext.start.is_delayed()
|
||||
? ext.start
|
||||
: ext.start + (ext.len - left);
|
||||
allocs.emplace_back(alloc_result_t{start, std::move(bp), gen});
|
||||
allocs.emplace_back(alloc_result_t{start, std::move(bp), opt.gen});
|
||||
SUBDEBUGT(seastore_epm,
|
||||
"allocated {} 0x{:x}B extent at {}, hint={}, gen={}",
|
||||
t, type, len, start, hint, gen);
|
||||
"allocated {} 0x{:x}B extent at {}, opt.hint={}, opt.gen={}",
|
||||
t, type, len, start, opt.hint, opt.gen);
|
||||
left -= len;
|
||||
}
|
||||
}
|
||||
@ -457,6 +576,19 @@ public:
|
||||
return allocs;
|
||||
}
|
||||
|
||||
write_policy_t get_write_policy(extent_types_t type, extent_len_t length) const {
|
||||
if (has_cold_tier() && is_data_type(type)) {
|
||||
if (length >= write_through_size
|
||||
|| (test_workload
|
||||
&& (double(std::rand() % 100) / 100.0) <=
|
||||
write_through_probability))
|
||||
{
|
||||
return write_policy_t::WRITE_THROUGH;
|
||||
}
|
||||
}
|
||||
return write_policy_t::WRITE_BACK;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
void prefill_fragmented_devices() {
|
||||
LOG_PREFIX(ExtentPlacementManager::prefill_fragmented_devices);
|
||||
@ -562,6 +694,15 @@ public:
|
||||
background_process.release_projected_usage(usage);
|
||||
}
|
||||
|
||||
bool is_storage_full() const {
|
||||
auto *cleaner = background_process.get_main_cleaner();
|
||||
return cleaner ? cleaner->is_storage_full() : false;
|
||||
}
|
||||
|
||||
void account_conflict_pending_free(std::size_t bytes) {
|
||||
background_process.account_conflict_pending_free(bytes);
|
||||
}
|
||||
|
||||
backend_type_t get_main_backend_type() const {
|
||||
if (!background_process.is_no_background()) {
|
||||
return background_process.get_main_backend_type();
|
||||
@ -571,11 +712,19 @@ public:
|
||||
return primary_device->get_backend_type();
|
||||
}
|
||||
|
||||
|
||||
bool is_pure_rbm() const {
|
||||
return get_main_backend_type() == backend_type_t::RANDOM_BLOCK &&
|
||||
// as of now, cold tier can only be segmented.
|
||||
!background_process.has_cold_tier();
|
||||
(!background_process.has_cold_tier() ||
|
||||
(background_process.get_cold_tier_backend_type() ==
|
||||
backend_type_t::RANDOM_BLOCK));
|
||||
}
|
||||
|
||||
bool has_cold_tier() const {
|
||||
return background_process.has_cold_tier();
|
||||
}
|
||||
|
||||
bool is_cold_device(device_id_t id) const {
|
||||
return background_process.is_cold_device(id);
|
||||
}
|
||||
|
||||
// Testing interfaces
|
||||
@ -609,12 +758,22 @@ public:
|
||||
return !devices_by_id[addr.get_device_id()]->is_end_to_end_data_protection();
|
||||
}
|
||||
|
||||
rewrite_gen_t get_max_hot_gen() const {
|
||||
return hot_tier_generations - 1;
|
||||
}
|
||||
|
||||
device_id_t get_cold_device_id() const {
|
||||
return background_process.get_cold_device_id();
|
||||
}
|
||||
|
||||
private:
|
||||
rewrite_gen_t adjust_generation(
|
||||
data_category_t category,
|
||||
extent_types_t type,
|
||||
placement_hint_t hint,
|
||||
rewrite_gen_t gen) {
|
||||
rewrite_gen_t gen,
|
||||
write_policy_t policy,
|
||||
bool is_tracked) {
|
||||
assert(is_real_type(type));
|
||||
if (is_root_type(type)) {
|
||||
gen = INLINE_GENERATION;
|
||||
@ -622,7 +781,6 @@ private:
|
||||
is_lba_backref_node(type)) {
|
||||
gen = INLINE_GENERATION;
|
||||
} else if (hint == placement_hint_t::COLD) {
|
||||
assert(gen == INIT_GENERATION);
|
||||
if (background_process.has_cold_tier()) {
|
||||
gen = hot_tier_generations;
|
||||
} else {
|
||||
@ -643,10 +801,26 @@ private:
|
||||
}
|
||||
} else {
|
||||
assert(category == data_category_t::DATA);
|
||||
gen = OOL_GENERATION;
|
||||
if (background_process.has_cold_tier() &&
|
||||
policy == write_policy_t::WRITE_THROUGH) {
|
||||
gen = hot_tier_generations;
|
||||
} else {
|
||||
assert(policy != write_policy_t::WRITE_THROUGH);
|
||||
gen = OOL_GENERATION;
|
||||
}
|
||||
}
|
||||
} else if (background_process.has_cold_tier()) {
|
||||
gen = background_process.adjust_generation(gen);
|
||||
if (gen <= hot_tier_generations &&
|
||||
policy == write_policy_t::WRITE_THROUGH) {
|
||||
gen = hot_tier_generations;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_tracked && gen >= hot_tier_generations &&
|
||||
hint != placement_hint_t::REWRITE &&
|
||||
hint != placement_hint_t::COLD) {
|
||||
gen = hot_tier_generations - 1;
|
||||
}
|
||||
|
||||
if (gen > dynamic_max_rewrite_generation) {
|
||||
@ -729,7 +903,8 @@ private:
|
||||
void init(JournalTrimmerImplRef &&_trimmer,
|
||||
AsyncCleanerRef &&_cleaner,
|
||||
AsyncCleanerRef &&_cold_cleaner,
|
||||
rewrite_gen_t hot_tier_generations) {
|
||||
rewrite_gen_t hot_tier_generations,
|
||||
ExtentPinboard *_pinboard) {
|
||||
trimmer = std::move(_trimmer);
|
||||
trimmer->set_background_callback(this);
|
||||
main_cleaner = std::move(_cleaner);
|
||||
@ -746,6 +921,7 @@ private:
|
||||
cleaners_by_device_id[id] = cold_cleaner.get();
|
||||
}
|
||||
|
||||
using crimson::common::get_conf;
|
||||
eviction_state.init(
|
||||
crimson::common::get_conf<double>(
|
||||
"seastore_multiple_tiers_stop_evict_ratio"),
|
||||
@ -754,7 +930,34 @@ private:
|
||||
crimson::common::get_conf<double>(
|
||||
"seastore_multiple_tiers_fast_evict_ratio"),
|
||||
hot_tier_generations);
|
||||
|
||||
pinboard = _pinboard;
|
||||
ceph_assert(pinboard != nullptr);
|
||||
pinboard->set_background_callback(this);
|
||||
|
||||
logical_bucket_demote_size_per_cycle =
|
||||
get_conf<Option::size_t>("seastore_logical_bucket_proceed_size_per_cycle");
|
||||
logical_bucket = create_logical_bucket(
|
||||
get_conf<Option::size_t>("seastore_logical_bucket_capacity"),
|
||||
logical_bucket_demote_size_per_cycle);
|
||||
logical_bucket->set_background_callback(this);
|
||||
}
|
||||
LOG_PREFIX(BackgroundProcess::init);
|
||||
test_workload = crimson::common::get_conf<bool>(
|
||||
"seastore_logical_bucket_cache_test_stress");
|
||||
force_process_half_life = crimson::common::get_conf<uint64_t>(
|
||||
"seastore_test_workload_force_process_background_tasks_period");
|
||||
force_background_timer.set_callback([this] { wake_half_life(); });
|
||||
write_through_probability = crimson::common::get_conf<double>(
|
||||
"seastore_test_workload_write_through_probability");
|
||||
SUBINFO(seastore_epm, "crimson test workload supported, enabled: {}", test_workload);
|
||||
if (test_workload) {
|
||||
set_next_force_process();
|
||||
}
|
||||
}
|
||||
|
||||
LogicalBucket *get_logical_bucket() {
|
||||
return logical_bucket.get();
|
||||
}
|
||||
|
||||
backend_type_t get_backend_type() const {
|
||||
@ -770,11 +973,26 @@ private:
|
||||
return cold_cleaner.get() != nullptr;
|
||||
}
|
||||
|
||||
backend_type_t get_cold_tier_backend_type() const {
|
||||
assert(cold_cleaner);
|
||||
return cold_cleaner->get_backend_type();
|
||||
}
|
||||
|
||||
bool is_cold_device(device_id_t id) const {
|
||||
if (!has_cold_tier()) {
|
||||
return false;
|
||||
}
|
||||
assert(cleaners_by_device_id[id]);
|
||||
return cleaners_by_device_id[id] != main_cleaner.get();
|
||||
}
|
||||
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) {
|
||||
trimmer->set_extent_callback(cb);
|
||||
main_cleaner->set_extent_callback(cb);
|
||||
if (has_cold_tier()) {
|
||||
cold_cleaner->set_extent_callback(cb);
|
||||
pinboard->set_extent_callback(cb);
|
||||
logical_bucket->set_extent_callback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
@ -832,6 +1050,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void account_conflict_pending_free(std::size_t bytes) {
|
||||
if (main_cleaner) {
|
||||
main_cleaner->account_conflict_pending_free(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void commit_space_used(paddr_t addr, extent_len_t len) {
|
||||
if (state < state_t::SCAN_SPACE) {
|
||||
return;
|
||||
@ -850,13 +1074,20 @@ private:
|
||||
}
|
||||
|
||||
rewrite_gen_t adjust_generation(rewrite_gen_t gen) {
|
||||
if (has_cold_tier()) {
|
||||
if (has_cold_tier() &&
|
||||
get_main_backend_type() == backend_type_t::SEGMENTED) {
|
||||
return eviction_state.adjust_generation_with_eviction(gen);
|
||||
} else {
|
||||
return gen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
device_id_t get_cold_device_id() const {
|
||||
assert(has_cold_tier());
|
||||
return *cold_cleaner->get_device_ids().begin();
|
||||
}
|
||||
|
||||
seastar::future<> reserve_projected_usage(io_usage_t usage);
|
||||
|
||||
void release_projected_usage(const io_usage_t &usage) {
|
||||
@ -889,9 +1120,15 @@ private:
|
||||
return !trimmer || !main_cleaner;
|
||||
}
|
||||
|
||||
protected:
|
||||
state_t get_state() const final {
|
||||
return state;
|
||||
bool is_full() const {
|
||||
if (has_cold_tier()) {
|
||||
return cold_cleaner->get_alive_ratio() >= 0.99;
|
||||
}
|
||||
return main_cleaner->get_alive_ratio() >= 0.99;
|
||||
}
|
||||
|
||||
const AsyncCleaner* get_main_cleaner() const {
|
||||
return main_cleaner.get();
|
||||
}
|
||||
|
||||
void maybe_wake_background() final {
|
||||
@ -903,12 +1140,35 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
seastar::future<> wait_background() {
|
||||
if (!blocking_io) {
|
||||
blocking_io = seastar::shared_promise<>();
|
||||
}
|
||||
return blocking_io->get_shared_future();
|
||||
}
|
||||
|
||||
protected:
|
||||
state_t get_state() const final {
|
||||
return state;
|
||||
}
|
||||
|
||||
void maybe_wake_blocked_io() final;
|
||||
|
||||
void maybe_wake_promote() final {
|
||||
if (!is_ready()) {
|
||||
return;
|
||||
}
|
||||
if (pinboard && pinboard->should_promote()) {
|
||||
do_wake_promote();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// reserve helpers
|
||||
bool try_reserve_cold(std::size_t usage);
|
||||
bool try_reserve_main(std::size_t usage);
|
||||
void abort_cold_usage(std::size_t usage, bool success);
|
||||
void abort_main_usage(std::size_t usage, bool success);
|
||||
|
||||
reserve_cleaner_result_t try_reserve_cleaner(const cleaner_usage_t &usage);
|
||||
void abort_cleaner_usage(const cleaner_usage_t &usage,
|
||||
@ -939,6 +1199,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void do_wake_promote() {
|
||||
if (blocking_promote) {
|
||||
blocking_promote->set_value();
|
||||
blocking_promote = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// background_should_run() should be atomic with do_background_cycle()
|
||||
// to make sure the condition is consistent.
|
||||
bool background_should_run() {
|
||||
@ -946,12 +1213,14 @@ private:
|
||||
maybe_update_eviction_mode();
|
||||
return main_cleaner_should_run()
|
||||
|| cold_cleaner_should_run()
|
||||
|| trimmer->should_trim();
|
||||
|| trimmer->should_trim()
|
||||
|| demote_should_run();
|
||||
}
|
||||
|
||||
bool main_cleaner_should_fast_evict() const {
|
||||
return has_cold_tier() &&
|
||||
main_cleaner->can_clean_space() &&
|
||||
(main_cleaner->can_clean_space() ||
|
||||
(logical_bucket && logical_bucket->could_demote())) &&
|
||||
eviction_state.is_fast_mode();
|
||||
}
|
||||
|
||||
@ -967,6 +1236,13 @@ private:
|
||||
cold_cleaner->should_clean_space();
|
||||
}
|
||||
|
||||
bool demote_should_run() const {
|
||||
return has_cold_tier() &&
|
||||
logical_bucket->could_demote() &&
|
||||
(eviction_state.is_fast_mode() ||
|
||||
logical_bucket->should_demote());
|
||||
}
|
||||
|
||||
bool should_block_io() const {
|
||||
assert(is_ready());
|
||||
return trimmer->should_block_io_on_trim() ||
|
||||
@ -1096,6 +1372,7 @@ private:
|
||||
};
|
||||
|
||||
seastar::future<> do_background_cycle();
|
||||
seastar::future<> run_promote();
|
||||
|
||||
void register_metrics(store_index_t store_index);
|
||||
|
||||
@ -1114,6 +1391,9 @@ private:
|
||||
|
||||
JournalTrimmerImplRef trimmer;
|
||||
AsyncCleanerRef main_cleaner;
|
||||
ExtentPinboard *pinboard = nullptr;
|
||||
LogicalBucketRef logical_bucket;
|
||||
std::size_t logical_bucket_demote_size_per_cycle = 0;
|
||||
|
||||
/*
|
||||
* cold tier (optional, see has_cold_tier())
|
||||
@ -1123,16 +1403,69 @@ private:
|
||||
|
||||
std::optional<seastar::future<>> process_join;
|
||||
std::optional<seastar::promise<>> blocking_background;
|
||||
std::optional<seastar::promise<>> blocking_io;
|
||||
std::optional<seastar::shared_promise<>> blocking_io;
|
||||
// Set by maybe_wake_blocked_io() whenever it actually unblocks a
|
||||
// user IO; consumed by run() to yield exactly once on that edge,
|
||||
// giving the woken continuation a chance to retry the reservation
|
||||
// before the next background cycle.
|
||||
bool pending_user_io_wake = false;
|
||||
std::optional<seastar::future<>> promote_process_join;
|
||||
std::optional<seastar::promise<>> blocking_promote;
|
||||
bool is_running_until_halt = false;
|
||||
state_t state = state_t::STOP;
|
||||
eviction_state_t eviction_state;
|
||||
|
||||
enum class ForceProcessState : uint8_t{
|
||||
STOP,
|
||||
TRIM,
|
||||
CLEAN,
|
||||
};
|
||||
bool test_workload = false;
|
||||
double write_through_probability = 0;
|
||||
ForceProcessState force_process_state = ForceProcessState::STOP;
|
||||
ForceProcessState last_process_state = ForceProcessState::STOP;
|
||||
seastar::timer<seastar::steady_clock_type> force_background_timer;
|
||||
int force_process_half_life;
|
||||
|
||||
void set_next_force_process() {
|
||||
assert(test_workload);
|
||||
force_background_timer.rearm(
|
||||
seastar::steady_clock_type::now() +
|
||||
std::chrono::seconds(force_process_half_life));
|
||||
}
|
||||
|
||||
void wake_half_life() {
|
||||
assert(test_workload);
|
||||
if (last_process_state == ForceProcessState::TRIM) {
|
||||
force_process_state = ForceProcessState::CLEAN;
|
||||
} else {
|
||||
force_process_state = ForceProcessState::TRIM;
|
||||
}
|
||||
|
||||
do_wake_background();
|
||||
}
|
||||
|
||||
void maybe_reschedule_force_process() {
|
||||
if (unlikely(test_workload &&
|
||||
force_process_state != ForceProcessState::STOP)) {
|
||||
last_process_state = force_process_state;
|
||||
force_process_state = ForceProcessState::STOP;
|
||||
set_next_force_process();
|
||||
}
|
||||
}
|
||||
|
||||
bool should_force_trim() const {
|
||||
return test_workload && force_process_state == ForceProcessState::TRIM;
|
||||
}
|
||||
|
||||
bool should_force_clean() const {
|
||||
return test_workload && force_process_state == ForceProcessState::CLEAN;
|
||||
}
|
||||
|
||||
bool force_run_background() const {
|
||||
return test_workload && force_process_state != ForceProcessState::STOP
|
||||
&& (logical_bucket && logical_bucket->could_demote());
|
||||
}
|
||||
friend class ::transaction_manager_test_t;
|
||||
};
|
||||
|
||||
@ -1140,6 +1473,7 @@ private:
|
||||
std::vector<ExtentOolWriter*> data_writers_by_gen;
|
||||
// gen 0 METADATA writer is the journal writer
|
||||
std::vector<ExtentOolWriter*> md_writers_by_gen;
|
||||
std::vector<TokenBucketRef> token_buckets;
|
||||
|
||||
std::vector<Device*> devices_by_id;
|
||||
Device* primary_device = nullptr;
|
||||
@ -1154,6 +1488,9 @@ private:
|
||||
// TODO: drop once paddr->journal_seq_t is introduced
|
||||
SegmentSeqAllocatorRef ool_segment_seq_allocator;
|
||||
extent_len_t max_data_allocation_size = 0;
|
||||
std::size_t write_through_size = 0;
|
||||
bool test_workload = false;
|
||||
double write_through_probability = 0;
|
||||
|
||||
friend class ::transaction_manager_test_t;
|
||||
friend class Cache;
|
||||
|
||||
@ -5,14 +5,44 @@
|
||||
#include "journal/segmented_journal.h"
|
||||
#include "journal/circular_bounded_journal.h"
|
||||
|
||||
namespace crimson::os::seastore::journal {
|
||||
namespace crimson::os::seastore {
|
||||
|
||||
Journal::scan_alloc_map_ret
|
||||
Journal::scan_alloc_map() {
|
||||
alloc_map_t map;
|
||||
journal_seq_t tail = get_dirty_tail() <= get_alloc_tail() ?
|
||||
get_dirty_tail() : get_alloc_tail();
|
||||
auto build_paddr_seq_map = [&map](
|
||||
const auto &offsets,
|
||||
const auto &e,
|
||||
sea_time_point modify_time)
|
||||
{
|
||||
if (e.type == extent_types_t::ALLOC_INFO) {
|
||||
alloc_delta_t alloc_delta;
|
||||
decode(alloc_delta, e.bl);
|
||||
if (alloc_delta.op == alloc_delta_t::op_types_t::CLEAR) {
|
||||
for (auto &alloc_blk : alloc_delta.alloc_blk_ranges) {
|
||||
map[alloc_blk.paddr] = offsets.write_result.start_seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
return replay_ertr::make_ready_future<bool>(true);
|
||||
};
|
||||
// build the paddr->journal_seq_t map from extent allocations
|
||||
co_await scan_valid_record_delta(std::move(build_paddr_seq_map), tail);
|
||||
co_return map;
|
||||
}
|
||||
|
||||
namespace journal {
|
||||
|
||||
JournalRef make_segmented(
|
||||
store_index_t store_index,
|
||||
SegmentProvider &provider,
|
||||
JournalTrimmer &trimmer)
|
||||
JournalTrimmer &trimmer,
|
||||
bool scan_alloc_on_boot)
|
||||
{
|
||||
return std::make_unique<SegmentedJournal>(store_index, provider, trimmer);
|
||||
return std::make_unique<SegmentedJournal>(
|
||||
store_index, provider, trimmer, scan_alloc_on_boot);
|
||||
}
|
||||
|
||||
JournalRef make_circularbounded(
|
||||
@ -24,4 +54,6 @@ JournalRef make_circularbounded(
|
||||
return std::make_unique<CircularBoundedJournal>(store_index, trimmer, device, path);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace journal
|
||||
|
||||
} // namespace crimson::os::seastore
|
||||
|
||||
@ -106,6 +106,24 @@ public:
|
||||
virtual backend_type_t get_type() = 0;
|
||||
|
||||
virtual bool is_checksum_needed() = 0;
|
||||
|
||||
protected:
|
||||
using alloc_map_t = std::map<paddr_t, journal_seq_t>;
|
||||
using scan_alloc_map_ertr = replay_ertr;
|
||||
using scan_alloc_map_ret = scan_alloc_map_ertr::future<alloc_map_t>;
|
||||
scan_alloc_map_ret scan_alloc_map();
|
||||
|
||||
using scan_delta_handler_t = std::function<
|
||||
replay_ertr::future<bool>(
|
||||
const record_locator_t&,
|
||||
const delta_info_t&,
|
||||
sea_time_point modify_time)>;
|
||||
virtual replay_ret scan_valid_record_delta(
|
||||
scan_delta_handler_t &&delta_handler,
|
||||
journal_seq_t tail) = 0;
|
||||
|
||||
virtual journal_seq_t get_dirty_tail() const = 0;
|
||||
virtual journal_seq_t get_alloc_tail() const = 0;
|
||||
};
|
||||
using JournalRef = std::unique_ptr<Journal>;
|
||||
|
||||
@ -114,7 +132,8 @@ namespace journal {
|
||||
JournalRef make_segmented(
|
||||
store_index_t store_index,
|
||||
SegmentProvider &provider,
|
||||
JournalTrimmer &trimmer);
|
||||
JournalTrimmer &trimmer,
|
||||
bool scan_alloc_on_boot);
|
||||
|
||||
JournalRef make_circularbounded(
|
||||
store_index_t store_index,
|
||||
|
||||
@ -141,7 +141,7 @@ CircularBoundedJournal::submit_record(
|
||||
}
|
||||
|
||||
Journal::replay_ret CircularBoundedJournal::replay_segment(
|
||||
cbj_delta_handler_t &handler, scan_valid_records_cursor& cursor)
|
||||
scan_delta_handler_t &handler, scan_valid_records_cursor& cursor)
|
||||
{
|
||||
LOG_PREFIX(Journal::replay_segment);
|
||||
return seastar::do_with(
|
||||
@ -232,7 +232,7 @@ Journal::replay_ret CircularBoundedJournal::replay_segment(
|
||||
|
||||
|
||||
Journal::replay_ret CircularBoundedJournal::scan_valid_record_delta(
|
||||
cbj_delta_handler_t &&handler, journal_seq_t tail)
|
||||
scan_delta_handler_t &&handler, journal_seq_t tail)
|
||||
{
|
||||
LOG_PREFIX(Journal::scan_valid_record_delta);
|
||||
INFO("starting at {} ", tail);
|
||||
@ -341,7 +341,6 @@ Journal::replay_ret CircularBoundedJournal::replay(
|
||||
cjs.set_cbj_header(head);
|
||||
DEBUG("header : {}", cjs.get_cbj_header());
|
||||
cjs.set_initialized(true);
|
||||
std::map<paddr_t, journal_seq_t> map;
|
||||
std::map<paddr_t, std::pair<CachedExtentRef, uint32_t>> crc_info;
|
||||
auto tail = get_dirty_tail() <= get_alloc_tail() ?
|
||||
get_dirty_tail() : get_alloc_tail();
|
||||
@ -360,27 +359,9 @@ Journal::replay_ret CircularBoundedJournal::replay(
|
||||
return replay_ertr::make_ready_future<bool>(true);
|
||||
};
|
||||
co_await scan_valid_record_delta(std::move(find_tail), tail);
|
||||
tail = get_dirty_tail() <= get_alloc_tail() ?
|
||||
get_dirty_tail() : get_alloc_tail();
|
||||
auto build_paddr_seq_map = [&map](
|
||||
const auto &offsets,
|
||||
const auto &e,
|
||||
sea_time_point modify_time)
|
||||
{
|
||||
if (e.type == extent_types_t::ALLOC_INFO) {
|
||||
alloc_delta_t alloc_delta;
|
||||
decode(alloc_delta, e.bl);
|
||||
if (alloc_delta.op == alloc_delta_t::op_types_t::CLEAR) {
|
||||
for (auto &alloc_blk : alloc_delta.alloc_blk_ranges) {
|
||||
map[alloc_blk.paddr] = offsets.write_result.start_seq;
|
||||
}
|
||||
}
|
||||
}
|
||||
return replay_ertr::make_ready_future<bool>(true);
|
||||
};
|
||||
// The second pass to build the paddr->journal_seq_t map
|
||||
// from extent allocations
|
||||
co_await scan_valid_record_delta(std::move(build_paddr_seq_map), tail);
|
||||
alloc_map_t map = co_await scan_alloc_map();
|
||||
auto call_d_handler_if_valid = [this, &map, &d_handler, &crc_info](
|
||||
const auto &offsets,
|
||||
const auto &e,
|
||||
|
||||
@ -147,14 +147,8 @@ public:
|
||||
return cjs.get_records_start();
|
||||
}
|
||||
|
||||
using cbj_delta_handler_t = std::function<
|
||||
replay_ertr::future<bool>(
|
||||
const record_locator_t&,
|
||||
const delta_info_t&,
|
||||
sea_time_point modify_time)>;
|
||||
|
||||
Journal::replay_ret scan_valid_record_delta(
|
||||
cbj_delta_handler_t &&delta_handler,
|
||||
scan_delta_handler_t &&delta_handler,
|
||||
journal_seq_t tail);
|
||||
|
||||
void try_read_rolled_header(scan_valid_records_cursor &cursor) {
|
||||
@ -170,7 +164,7 @@ public:
|
||||
};
|
||||
|
||||
Journal::replay_ret replay_segment(
|
||||
cbj_delta_handler_t &handler, scan_valid_records_cursor& cursor);
|
||||
scan_delta_handler_t &handler, scan_valid_records_cursor& cursor);
|
||||
|
||||
read_ret read(paddr_t start, size_t len) final;
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "segmented_journal.h"
|
||||
|
||||
#include "crimson/common/config_proxy.h"
|
||||
#include "crimson/common/coroutine.h"
|
||||
#include "crimson/os/seastore/logging.h"
|
||||
|
||||
SET_SUBSYS(seastore_journal);
|
||||
@ -29,7 +30,8 @@ namespace crimson::os::seastore::journal {
|
||||
SegmentedJournal::SegmentedJournal(
|
||||
store_index_t store_index,
|
||||
SegmentProvider &segment_provider,
|
||||
JournalTrimmer &trimmer)
|
||||
JournalTrimmer &trimmer,
|
||||
bool scan_alloc_on_startup)
|
||||
: store_index(store_index),
|
||||
segment_seq_allocator(
|
||||
new SegmentSeqAllocator(segment_type_t::JOURNAL)),
|
||||
@ -48,7 +50,8 @@ SegmentedJournal::SegmentedJournal(
|
||||
"seastore_journal_batch_preferred_fullness"),
|
||||
journal_segment_allocator),
|
||||
sm_group(*segment_provider.get_segment_manager_group()),
|
||||
trimmer{trimmer}
|
||||
trimmer{trimmer},
|
||||
scan_alloc_on_startup(scan_alloc_on_startup)
|
||||
{
|
||||
}
|
||||
|
||||
@ -108,8 +111,8 @@ SegmentedJournal::prep_replay_segments(
|
||||
return scan_last_segment(last_segment_id, last_header
|
||||
).safe_then([this, FNAME, segments=std::move(segments)] {
|
||||
INFO("dirty_tail={}, alloc_tail={}",
|
||||
trimmer.get_dirty_tail(),
|
||||
trimmer.get_alloc_tail());
|
||||
get_dirty_tail(),
|
||||
get_alloc_tail());
|
||||
auto journal_tail = trimmer.get_journal_tail();
|
||||
auto journal_tail_paddr = journal_tail.offset;
|
||||
ceph_assert(journal_tail != JOURNAL_SEQ_NULL);
|
||||
@ -129,9 +132,9 @@ SegmentedJournal::prep_replay_segments(
|
||||
|
||||
auto num_segments = segments.end() - from;
|
||||
INFO("{} segments to replay", num_segments);
|
||||
auto ret = replay_segments_t(num_segments);
|
||||
replay_segments.resize(num_segments);
|
||||
std::transform(
|
||||
from, segments.end(), ret.begin(),
|
||||
from, segments.end(), replay_segments.begin(),
|
||||
[this](const auto &p) {
|
||||
auto ret = journal_seq_t{
|
||||
p.second.segment_seq,
|
||||
@ -141,10 +144,9 @@ SegmentedJournal::prep_replay_segments(
|
||||
};
|
||||
return std::make_pair(ret, p.second);
|
||||
});
|
||||
ret[0].first.offset = journal_tail_paddr;
|
||||
replay_segments[0].first.offset = journal_tail_paddr;
|
||||
return prep_replay_segments_fut(
|
||||
replay_ertr::ready_future_marker{},
|
||||
std::move(ret));
|
||||
replay_ertr::ready_future_marker{});
|
||||
});
|
||||
}
|
||||
|
||||
@ -229,15 +231,14 @@ SegmentedJournal::replay_ertr::future<>
|
||||
SegmentedJournal::replay_segment(
|
||||
journal_seq_t seq,
|
||||
segment_header_t header,
|
||||
delta_handler_t &handler,
|
||||
replay_stats_t &stats)
|
||||
scan_delta_handler_t &handler)
|
||||
{
|
||||
LOG_PREFIX(Journal::replay_segment);
|
||||
INFO("starting at {} -- {}", seq, header);
|
||||
return seastar::do_with(
|
||||
scan_valid_records_cursor(seq),
|
||||
SegmentManagerGroup::found_record_handler_t(
|
||||
[&handler, this, &stats](
|
||||
[&handler, this](
|
||||
record_locator_t locator,
|
||||
const record_group_header_t& header,
|
||||
const bufferlist& mdbuf)
|
||||
@ -259,16 +260,14 @@ SegmentedJournal::replay_segment(
|
||||
[write_result=locator.write_result,
|
||||
this,
|
||||
FNAME,
|
||||
&handler,
|
||||
&stats](auto& record_deltas_list)
|
||||
&handler](auto& record_deltas_list)
|
||||
{
|
||||
return crimson::do_for_each(
|
||||
record_deltas_list,
|
||||
[write_result,
|
||||
this,
|
||||
FNAME,
|
||||
&handler,
|
||||
&stats](record_deltas_t& record_deltas)
|
||||
&handler](record_deltas_t& record_deltas)
|
||||
{
|
||||
++stats.num_records;
|
||||
auto locator = record_locator_t{
|
||||
@ -281,30 +280,11 @@ SegmentedJournal::replay_segment(
|
||||
return crimson::do_for_each(
|
||||
record_deltas.deltas,
|
||||
[locator,
|
||||
this,
|
||||
&handler,
|
||||
&stats](auto &p)
|
||||
&handler](auto &p)
|
||||
{
|
||||
auto& modify_time = p.first;
|
||||
auto& delta = p.second;
|
||||
return handler(
|
||||
locator,
|
||||
delta,
|
||||
trimmer.get_dirty_tail(),
|
||||
trimmer.get_alloc_tail(),
|
||||
modify_time
|
||||
).safe_then([&stats, delta_type=delta.type](auto ret) {
|
||||
auto [is_applied, ext] = ret;
|
||||
if (is_applied) {
|
||||
// see Cache::replay_delta()
|
||||
assert(delta_type != extent_types_t::JOURNAL_TAIL);
|
||||
if (delta_type == extent_types_t::ALLOC_INFO) {
|
||||
++stats.num_alloc_deltas;
|
||||
} else {
|
||||
++stats.num_dirty_deltas;
|
||||
}
|
||||
}
|
||||
});
|
||||
return handler(locator, delta, modify_time).discard_result();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -325,36 +305,65 @@ SegmentedJournal::replay_segment(
|
||||
);
|
||||
}
|
||||
|
||||
SegmentedJournal::replay_ret
|
||||
SegmentedJournal::scan_valid_record_delta(
|
||||
scan_delta_handler_t &&delta_handler,
|
||||
journal_seq_t tail)
|
||||
{
|
||||
auto handler = std::move(delta_handler);
|
||||
for (auto &[seq, header] : replay_segments) {
|
||||
co_await replay_segment(seq, header, handler);
|
||||
}
|
||||
}
|
||||
|
||||
SegmentedJournal::replay_ret SegmentedJournal::replay(
|
||||
delta_handler_t &&delta_handler)
|
||||
{
|
||||
LOG_PREFIX(Journal::replay);
|
||||
return sm_group.find_journal_segment_headers(
|
||||
).safe_then([this, FNAME, delta_handler=std::move(delta_handler)]
|
||||
(auto &&segment_headers) mutable -> replay_ret {
|
||||
INFO("got {} segments", segment_headers.size());
|
||||
return seastar::do_with(
|
||||
std::move(delta_handler),
|
||||
replay_segments_t(),
|
||||
replay_stats_t(),
|
||||
[this, segment_headers=std::move(segment_headers), FNAME]
|
||||
(auto &handler, auto &segments, auto &stats) mutable -> replay_ret {
|
||||
return prep_replay_segments(std::move(segment_headers)
|
||||
).safe_then([this, &handler, &segments, &stats](auto replay_segs) mutable {
|
||||
segments = std::move(replay_segs);
|
||||
return crimson::do_for_each(segments,[this, &handler, &stats](auto i) mutable {
|
||||
return replay_segment(i.first, i.second, handler, stats);
|
||||
});
|
||||
}).safe_then([&stats, FNAME] {
|
||||
INFO("replay done, record_groups={}, records={}, "
|
||||
"alloc_deltas={}, dirty_deltas={}",
|
||||
stats.num_record_groups,
|
||||
stats.num_records,
|
||||
stats.num_alloc_deltas,
|
||||
stats.num_dirty_deltas);
|
||||
});
|
||||
});
|
||||
});
|
||||
auto handler = std::move(delta_handler);
|
||||
auto segment_headers = co_await sm_group.find_journal_segment_headers();
|
||||
INFO("got {} segments", segment_headers.size());
|
||||
co_await prep_replay_segments(std::move(segment_headers));
|
||||
alloc_map_t alloc_map;
|
||||
if (scan_alloc_on_startup) {
|
||||
alloc_map = co_await scan_alloc_map();
|
||||
}
|
||||
auto d_handler = [&handler, this, &alloc_map](
|
||||
const record_locator_t &locator,
|
||||
const delta_info_t &delta,
|
||||
sea_time_point modify_time) -> replay_ertr::future<bool> {
|
||||
if (auto it = alloc_map.find(delta.paddr);
|
||||
it != alloc_map.end() &&
|
||||
it->second > locator.write_result.start_seq) {
|
||||
co_return true;
|
||||
}
|
||||
auto ret = co_await handler(
|
||||
locator,
|
||||
delta,
|
||||
get_dirty_tail(),
|
||||
get_alloc_tail(),
|
||||
modify_time);
|
||||
auto [is_applied, ext] = ret;
|
||||
if (is_applied) {
|
||||
// see Cache::replay_delta()
|
||||
assert(delta.type != extent_types_t::JOURNAL_TAIL);
|
||||
if (delta.type == extent_types_t::ALLOC_INFO) {
|
||||
++stats.num_alloc_deltas;
|
||||
} else {
|
||||
++stats.num_dirty_deltas;
|
||||
}
|
||||
}
|
||||
co_return true;
|
||||
};
|
||||
journal_seq_t tail = get_dirty_tail() <= get_alloc_tail() ?
|
||||
get_dirty_tail() : get_alloc_tail();
|
||||
co_return co_await scan_valid_record_delta(std::move(d_handler), tail);
|
||||
INFO("replay done, record_groups={}, records={}, "
|
||||
"alloc_deltas={}, dirty_deltas={}",
|
||||
stats.num_record_groups,
|
||||
stats.num_records,
|
||||
stats.num_alloc_deltas,
|
||||
stats.num_dirty_deltas);
|
||||
}
|
||||
|
||||
seastar::future<> SegmentedJournal::flush(OrderingHandle &handle)
|
||||
|
||||
@ -28,7 +28,8 @@ public:
|
||||
SegmentedJournal(
|
||||
store_index_t store_index,
|
||||
SegmentProvider &segment_provider,
|
||||
JournalTrimmer &trimmer);
|
||||
JournalTrimmer &trimmer,
|
||||
bool scan_alloc_on_startup);
|
||||
~SegmentedJournal() {}
|
||||
|
||||
JournalTrimmer &get_trimmer() final {
|
||||
@ -83,10 +84,7 @@ private:
|
||||
WritePipeline* write_pipeline = nullptr;
|
||||
|
||||
/// return ordered vector of segments to replay
|
||||
using replay_segments_t = std::vector<
|
||||
std::pair<journal_seq_t, segment_header_t>>;
|
||||
using prep_replay_segments_fut = replay_ertr::future<
|
||||
replay_segments_t>;
|
||||
using prep_replay_segments_fut = replay_ertr::future<>;
|
||||
prep_replay_segments_fut prep_replay_segments(
|
||||
std::vector<std::pair<segment_id_t, segment_header_t>> segments);
|
||||
|
||||
@ -100,16 +98,32 @@ private:
|
||||
std::size_t num_records = 0;
|
||||
std::size_t num_alloc_deltas = 0;
|
||||
std::size_t num_dirty_deltas = 0;
|
||||
};
|
||||
} stats;
|
||||
|
||||
const bool scan_alloc_on_startup = false;
|
||||
using replay_segments_t = std::vector<
|
||||
std::pair<journal_seq_t, segment_header_t>>;
|
||||
replay_segments_t replay_segments;
|
||||
|
||||
/// replays records starting at start through end of segment
|
||||
replay_ertr::future<>
|
||||
replay_segment(
|
||||
journal_seq_t start, ///< [in] starting addr, seq
|
||||
segment_header_t header, ///< [in] segment header
|
||||
delta_handler_t &delta_handler, ///< [in] processes deltas in order
|
||||
replay_stats_t &stats ///< [out] replay stats
|
||||
scan_delta_handler_t &delta_handler ///< [in] processes deltas in order
|
||||
);
|
||||
|
||||
journal_seq_t get_dirty_tail() const final {
|
||||
return trimmer.get_dirty_tail();
|
||||
}
|
||||
|
||||
journal_seq_t get_alloc_tail() const final {
|
||||
return trimmer.get_alloc_tail();
|
||||
}
|
||||
|
||||
replay_ret scan_valid_record_delta(
|
||||
scan_delta_handler_t &&delta_handler,
|
||||
journal_seq_t tail) final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <seastar/core/metrics.hh>
|
||||
|
||||
#include "include/buffer.h"
|
||||
#include "crimson/common/coroutine.h"
|
||||
#include "crimson/os/seastore/lba/btree_lba_manager.h"
|
||||
#include "crimson/os/seastore/lba/lba_btree_node.h"
|
||||
#include "crimson/os/seastore/logging.h"
|
||||
@ -172,7 +173,12 @@ BtreeLBAManager::mkfs(
|
||||
auto croot = co_await cache.get_root(t);
|
||||
assert(croot);
|
||||
assert(croot->is_mutation_pending());
|
||||
croot->get_root().lba_root = LBABtree::mkfs(croot, get_context(t));
|
||||
croot->get_root().lba_root =
|
||||
co_await LBABtree::mkfs(croot, get_context(t)
|
||||
).handle_error_interruptible(
|
||||
mkfs_iertr::pass_further{},
|
||||
crimson::ct_error::assert_all("unexpected error")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,6 +306,7 @@ BtreeLBAManager::resolve_indirect_cursor(
|
||||
const LBACursor &indirect_cursor)
|
||||
{
|
||||
ceph_assert(indirect_cursor.is_indirect());
|
||||
ceph_assert(!indirect_cursor.has_shadow_paddr());
|
||||
return get_cursors(
|
||||
c,
|
||||
btree,
|
||||
@ -329,6 +336,102 @@ BtreeLBAManager::lower_bound(
|
||||
co_return iter.get_cursor(c);
|
||||
}
|
||||
|
||||
BtreeLBAManager::upper_bound_right_ret
|
||||
BtreeLBAManager::upper_bound_right(
|
||||
Transaction &t,
|
||||
laddr_t laddr)
|
||||
{
|
||||
auto c = get_context(t);
|
||||
auto btree = co_await get_btree<LBABtree>(c);
|
||||
auto iter = co_await btree.upper_bound_right(c, laddr);
|
||||
if (iter.is_end()) {
|
||||
co_await upper_bound_right_iertr::future<LBACursorRef>(
|
||||
crimson::ct_error::enoent::make());
|
||||
}
|
||||
assert(iter.get_key() >= laddr);
|
||||
co_return iter.get_cursor(c);
|
||||
}
|
||||
|
||||
BtreeLBAManager::promote_extent_ret
|
||||
BtreeLBAManager::promote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
std::vector<LogicalChildNodeRef> extents)
|
||||
{
|
||||
LOG_PREFIX(BtreeLBAManager::promote_extent);
|
||||
auto laddr = cursor.get_laddr();
|
||||
ceph_assert(!extents.empty());
|
||||
ceph_assert(!cursor.is_indirect());
|
||||
ceph_assert(laddr == extents.front()->get_laddr());
|
||||
DEBUGT("promote cursor {} with {} extents",
|
||||
t, cursor, extents.size());
|
||||
auto c = get_context(t);
|
||||
auto btree = co_await get_btree<LBABtree>(c);
|
||||
auto iter = btree.make_partial_iter(c, cursor);
|
||||
auto orig_val = iter.get_val();
|
||||
if (extents.size() == 1) {
|
||||
auto new_val = orig_val;
|
||||
ceph_assert(new_val.pladdr.is_paddr());
|
||||
new_val.shadow_paddr = new_val.pladdr.get_paddr();
|
||||
auto extent = extents.front().get();
|
||||
auto paddr = extent->get_paddr();
|
||||
new_val.pladdr = pladdr_t(paddr);
|
||||
TRACET("promote {} from {} to {}",
|
||||
t, iter.get_key(), new_val.shadow_paddr, paddr);
|
||||
assert(extent->is_pending());
|
||||
assert(!extent->has_parent_tracker());
|
||||
iter = btree.update(c, iter, new_val, extent);
|
||||
assert(extent->has_parent_tracker());
|
||||
} else {
|
||||
auto insert_iter = co_await btree.remove(c, std::move(iter));
|
||||
for (auto &extent : extents) {
|
||||
auto offset = extent->get_laddr().get_byte_distance<extent_len_t>(laddr);
|
||||
auto new_val = orig_val;
|
||||
new_val.shadow_paddr = orig_val.pladdr.get_paddr().add_offset(offset);
|
||||
new_val.pladdr = pladdr_t(extent->get_paddr());
|
||||
new_val.len = extent->get_length();
|
||||
new_val.checksum = extent->get_last_committed_crc();
|
||||
TRACET("insert promoted cursor {} {}",
|
||||
c.trans, extent->get_laddr(), new_val);
|
||||
assert(!extent->has_parent_tracker());
|
||||
auto [iter, inserted] = co_await btree.insert(
|
||||
c, std::move(insert_iter), extent->get_laddr(), new_val, extent.get());
|
||||
ceph_assert(inserted);
|
||||
assert(extent->has_parent_tracker());
|
||||
insert_iter = co_await iter.next(c);
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
BtreeLBAManager::demote_extent_ret
|
||||
BtreeLBAManager::demote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
LogicalChildNode &extent)
|
||||
{
|
||||
assert(cursor.is_viewable());
|
||||
assert(!cursor.is_end());
|
||||
assert(!cursor.is_indirect());
|
||||
assert(cursor.has_shadow_paddr());
|
||||
auto c = get_context(t);
|
||||
auto btree = co_await get_btree<LBABtree>(c);
|
||||
auto ret = co_await _update_mapping(
|
||||
t,
|
||||
cursor,
|
||||
[&extent](lba_map_val_t val) {
|
||||
assert(val.pladdr.is_paddr());
|
||||
assert(val.shadow_paddr == extent.get_paddr());
|
||||
val.pladdr = pladdr_t(val.shadow_paddr);
|
||||
val.shadow_paddr = P_ADDR_NULL;
|
||||
return val;
|
||||
},
|
||||
&extent
|
||||
).handle_error_interruptible(
|
||||
demote_extent_iertr::pass_further{},
|
||||
crimson::ct_error::assert_all("unexpected enoent"));
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* reserve_region: insert a zero-mapping (P_ADDR_ZERO) at the specified laddr.
|
||||
* Uses the cursor as a btree insertion hint. The reserved_ptr child pointer
|
||||
@ -351,6 +454,7 @@ BtreeLBAManager::reserve_region(
|
||||
lba_map_val_t val{
|
||||
len,
|
||||
pladdr_t{P_ADDR_ZERO},
|
||||
P_ADDR_NULL,
|
||||
EXTENT_DEFAULT_REF_COUNT,
|
||||
0,
|
||||
type};
|
||||
@ -392,6 +496,7 @@ BtreeLBAManager::alloc_extents(
|
||||
lba_map_val_t{
|
||||
ext->get_length(),
|
||||
pladdr_t{ext->get_paddr()},
|
||||
P_ADDR_NULL,
|
||||
EXTENT_DEFAULT_REF_COUNT,
|
||||
ext->get_last_committed_crc(),
|
||||
ext->get_type()},
|
||||
@ -451,6 +556,7 @@ BtreeLBAManager::clone_mapping(
|
||||
lba_map_val_t{
|
||||
len,
|
||||
pladdr_t{inter_key.get_local_clone_id()},
|
||||
P_ADDR_NULL,
|
||||
EXTENT_DEFAULT_REF_COUNT,
|
||||
0,
|
||||
mapping->get_extent_type()},
|
||||
@ -880,7 +986,8 @@ BtreeLBAManager::scan_mappings(
|
||||
}
|
||||
ceph_assert((pos.get_key() + pos.get_val().len) > begin);
|
||||
if (pos.get_val().pladdr.is_paddr()) {
|
||||
f(pos.get_key(), pos.get_val().pladdr.get_paddr(), pos.get_val().len);
|
||||
f(pos.get_key(), pos.get_val().pladdr.get_paddr(),
|
||||
pos.get_val().shadow_paddr, pos.get_val().len);
|
||||
}
|
||||
return LBABtree::iterate_repeat_ret_inner(
|
||||
interruptible::ready_future_marker{},
|
||||
@ -956,11 +1063,19 @@ BtreeLBAManager::update_mapping(
|
||||
assert(!addr.is_null());
|
||||
lba_map_val_t ret = in;
|
||||
ceph_assert(in.pladdr.is_paddr());
|
||||
ceph_assert(in.pladdr.get_paddr() == prev_addr);
|
||||
ceph_assert(in.len == prev_len);
|
||||
ret.pladdr = addr;
|
||||
ret.len = len;
|
||||
ret.checksum = checksum;
|
||||
if (prev_addr == in.pladdr.get_paddr()) {
|
||||
ret.pladdr = addr;
|
||||
ret.len = len;
|
||||
ret.checksum = checksum;
|
||||
if (ret.shadow_paddr != P_ADDR_NULL) {
|
||||
ceph_assert(
|
||||
addr.get_device_id() != ret.shadow_paddr.get_device_id());
|
||||
}
|
||||
} else {
|
||||
ceph_assert(in.shadow_paddr == prev_addr);
|
||||
ret.shadow_paddr = addr;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
&nextent
|
||||
@ -1017,13 +1132,27 @@ BtreeLBAManager::update_mappings(
|
||||
return this->_update_mapping(
|
||||
c.trans,
|
||||
*cursor,
|
||||
[prev_addr, addr, len, checksum](
|
||||
[prev_addr, addr, len, checksum, extent, c](
|
||||
const lba_map_val_t &in) {
|
||||
lba_map_val_t ret = in;
|
||||
ceph_assert(in.pladdr.is_paddr());
|
||||
ceph_assert(in.pladdr.get_paddr() == prev_addr);
|
||||
ceph_assert(in.len == len);
|
||||
ret.pladdr = addr;
|
||||
if (likely(in.pladdr.get_paddr() == prev_addr)) {
|
||||
ret.pladdr = addr;
|
||||
if (c.trans.get_src() == transaction_type_t::TRIM_DIRTY) {
|
||||
// This is a dirty onode/omap extent that are rewritten,
|
||||
// the shadow extent should be removed.
|
||||
ret.shadow_paddr = P_ADDR_NULL;
|
||||
}
|
||||
} else {
|
||||
// this can only happen when the extent is EXIST_CLEAN
|
||||
// and is demoted onto the cold tier by a DEMOTE trans.
|
||||
assert(in.shadow_paddr == P_ADDR_NULL);
|
||||
assert(extent->is_exist_clean());
|
||||
assert(extent->get_paddr() == in.pladdr.get_paddr());
|
||||
assert(c.cache.is_on_cold_tier(extent->get_paddr()));
|
||||
assert(!c.cache.is_on_cold_tier(prev_addr));
|
||||
}
|
||||
ret.checksum = checksum;
|
||||
return ret;
|
||||
},
|
||||
@ -1314,6 +1443,9 @@ BtreeLBAManager::remap_mappings(
|
||||
} else {
|
||||
auto paddr = val.pladdr.get_paddr();
|
||||
val.pladdr = paddr + cur_off;
|
||||
if (val.shadow_paddr != P_ADDR_NULL) {
|
||||
val.shadow_paddr = val.shadow_paddr.add_offset(cur_off);
|
||||
}
|
||||
}
|
||||
val.len = remap.len;
|
||||
val.refcount = EXTENT_DEFAULT_REF_COUNT;
|
||||
@ -1363,35 +1495,82 @@ BtreeLBAManager::remap_mappings(
|
||||
void BtreeLBAManager::update_paddr_sync(
|
||||
Transaction &t,
|
||||
laddr_t laddr,
|
||||
paddr_t paddr)
|
||||
paddr_t paddr,
|
||||
extent_len_t len,
|
||||
std::optional<paddr_t> shadow)
|
||||
{
|
||||
LOG_PREFIX(BtreeLBAManager::update_paddr_sync);
|
||||
DEBUGT("laddr={}, paddr={}", t, laddr, paddr);
|
||||
auto c = get_context(t);
|
||||
auto btree = get_btree_sync<LBABtree>(c);
|
||||
auto iter = btree.lower_bound_sync(c, laddr);
|
||||
assert(iter.get_leaf_node()->is_pending());
|
||||
auto child = iter.get_leaf_node()->get_child_sync<LogicalChildNode>(
|
||||
c.trans, c.cache, iter.get_leaf_pos(), iter.get_key());
|
||||
ceph_assert(child);
|
||||
if (child->is_initial_pending()) {
|
||||
TRACET("{} is initial_pending, skipping", t, *child);
|
||||
return;
|
||||
while (iter.get_key() + iter.get_val().len <= laddr + len) {
|
||||
assert(iter.get_leaf_node()->is_pending());
|
||||
if (iter.get_val().pladdr.is_laddr() ||
|
||||
iter.get_val().pladdr.get_paddr().is_zero()) {
|
||||
TRACET("skipping mapping {}~{}", t, iter.get_key(), iter.get_val());
|
||||
if (!iter.next_sync(c)) {
|
||||
// can't reach the next mapping, which means the next
|
||||
// mapping mustn't have been touched by the current
|
||||
// transactions, we don't need to continue, just leave
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto child = iter.get_leaf_node()->get_child_sync<LogicalChildNode>(
|
||||
c.trans, c.cache, iter.get_leaf_pos(), iter.get_key());
|
||||
ceph_assert(is_valid_child_ptr(child.get()));
|
||||
if (child->is_initial_pending()) {
|
||||
TRACET("{} is initial_pending, skipping", t, *child);
|
||||
if (!iter.next_sync(c)) {
|
||||
// can't reach the next mapping, which means the next
|
||||
// mapping mustn't have been touched by the current
|
||||
// transactions, we don't need to continue, just leave
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ceph_assert(child->is_exist_clean());
|
||||
auto cursor = iter.get_cursor(c);
|
||||
extent_len_t off = cursor->get_laddr().get_byte_distance<
|
||||
extent_len_t>(laddr);
|
||||
paddr_t shadow_paddr;
|
||||
if (shadow) {
|
||||
// the committing txn changed the shadow
|
||||
// to *shadow
|
||||
shadow_paddr = *shadow;
|
||||
if (shadow_paddr != P_ADDR_NULL) {
|
||||
shadow_paddr = shadow_paddr + off;
|
||||
}
|
||||
} else if (cursor->has_shadow_paddr()) {
|
||||
// shadow is preserved by the committer,
|
||||
// and the copy inherited one, so the source
|
||||
// was promoted when it's copied
|
||||
shadow_paddr = cursor->get_shadow_paddr();
|
||||
} else {
|
||||
// shadow is preserved, and nothing is
|
||||
// inherited: not currently promoted
|
||||
shadow_paddr = P_ADDR_NULL;
|
||||
}
|
||||
iter = btree.update(
|
||||
c,
|
||||
std::move(iter),
|
||||
lba_map_val_t{
|
||||
cursor->get_length(),
|
||||
pladdr_t{paddr + off},
|
||||
shadow_paddr,
|
||||
cursor->get_refcount(),
|
||||
cursor->get_checksum(),
|
||||
cursor->get_extent_type()},
|
||||
nullptr,
|
||||
modification_t::TRANS_SYNC);
|
||||
if (!iter.next_sync(c)) {
|
||||
// can't reach the next mapping, which means the next
|
||||
// mapping mustn't have been touched by the current
|
||||
// transactions, we don't need to continue, just leave
|
||||
return;
|
||||
}
|
||||
}
|
||||
ceph_assert(child->is_exist_clean());
|
||||
auto cursor = iter.get_cursor(c);
|
||||
assert(cursor->get_laddr() == laddr);
|
||||
btree.update(
|
||||
c,
|
||||
std::move(iter),
|
||||
lba_map_val_t{
|
||||
cursor->get_length(),
|
||||
pladdr_t{std::move(paddr)},
|
||||
cursor->get_refcount(),
|
||||
cursor->get_checksum(),
|
||||
cursor->get_extent_type()},
|
||||
nullptr,
|
||||
modification_t::TRANS_SYNC);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1445,8 +1624,10 @@ BtreeLBAManager::_copy_mapping(
|
||||
c.trans.new_lba_key_copied(
|
||||
ret.src->get_key(),
|
||||
dest_laddr,
|
||||
[this, c](laddr_t laddr, paddr_t paddr) {
|
||||
update_paddr_sync(c.trans, laddr, paddr);
|
||||
ret.src->get_length(),
|
||||
[this, c](laddr_t laddr, paddr_t paddr,
|
||||
extent_len_t len, std::optional<paddr_t> shadow) {
|
||||
update_paddr_sync(c.trans, laddr, paddr, len, shadow);
|
||||
});
|
||||
auto [niter, inserted] = co_await btree.copy(
|
||||
c,
|
||||
@ -1539,6 +1720,7 @@ BtreeLBAManager::move_and_clone_direct_mapping(
|
||||
lba_map_val_t val = in;
|
||||
val.pladdr = ret.dest->get_key().get_local_clone_id();
|
||||
val.checksum = 0;
|
||||
val.shadow_paddr = P_ADDR_NULL;
|
||||
return val;
|
||||
},
|
||||
nullptr
|
||||
|
||||
@ -148,6 +148,20 @@ public:
|
||||
Transaction &t,
|
||||
laddr_t laddr) final;
|
||||
|
||||
upper_bound_right_ret upper_bound_right(
|
||||
Transaction &t,
|
||||
laddr_t laddr) final;
|
||||
|
||||
promote_extent_ret promote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
std::vector<LogicalChildNodeRef> extents) final;
|
||||
|
||||
demote_extent_ret demote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
LogicalChildNode &extent) final;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Allocation operations - insert new mappings into the LBA tree.
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -209,6 +223,7 @@ public:
|
||||
laddr_t dest_laddr,
|
||||
LBACursorRef dest) final {
|
||||
assert(src->is_indirect());
|
||||
assert(!src->has_shadow_paddr());
|
||||
return _move_mapping(
|
||||
t, std::move(src), dest_laddr, std::move(dest), nullptr);
|
||||
}
|
||||
@ -503,6 +518,7 @@ private:
|
||||
{
|
||||
len,
|
||||
pladdr_t(P_ADDR_ZERO),
|
||||
P_ADDR_NULL,
|
||||
EXTENT_DEFAULT_REF_COUNT,
|
||||
0,
|
||||
type
|
||||
@ -517,6 +533,7 @@ private:
|
||||
{
|
||||
len,
|
||||
pladdr_t(intermediate_key.get_local_clone_id()),
|
||||
P_ADDR_NULL,
|
||||
EXTENT_DEFAULT_REF_COUNT,
|
||||
0, // crc will only be used and checked with LBA direct mappings
|
||||
// also see pin_to_extent(_by_type)
|
||||
@ -533,7 +550,7 @@ private:
|
||||
LogicalChildNode& extent) {
|
||||
return {
|
||||
laddr,
|
||||
{len, pladdr_t(paddr), refcount, checksum, extent.get_type()},
|
||||
{len, pladdr_t(paddr), P_ADDR_NULL, refcount, checksum, extent.get_type()},
|
||||
&extent
|
||||
};
|
||||
}
|
||||
@ -600,7 +617,9 @@ private:
|
||||
void update_paddr_sync(
|
||||
Transaction &t,
|
||||
laddr_t laddr,
|
||||
paddr_t paddr);
|
||||
paddr_t paddr,
|
||||
extent_len_t len,
|
||||
std::optional<paddr_t> shadow);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -50,8 +50,9 @@ void LBALeafNode::update(
|
||||
modification_t mod)
|
||||
{
|
||||
LOG_PREFIX(LBALeafNode::update);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset());
|
||||
if (likely(mod == modification_t::USER_MODIFY)) {
|
||||
this->on_modify();
|
||||
@ -71,8 +72,9 @@ LBALeafNode::internal_const_iterator_t LBALeafNode::insert(
|
||||
lba_map_val_t val)
|
||||
{
|
||||
LOG_PREFIX(LBALeafNode::insert);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, key {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
addr);
|
||||
this->on_modify();
|
||||
|
||||
@ -101,14 +101,14 @@ using LBAInternalNodeRef = LBAInternalNode::Ref;
|
||||
* checksum : ceph_le32[1] 4B
|
||||
* size : ceph_le32[1] 4B
|
||||
* meta : lba_node_meta_le_t[1] 36B
|
||||
* keys : laddr_le_t[CAPACITY] (106*16)B
|
||||
* values : lba_map_val_le_t[CAPACITY] (106*21)B
|
||||
* = 4077B
|
||||
* keys : laddr_le_t[CAPACITY] (88*16)B
|
||||
* values : lba_map_val_le_t[CAPACITY] (88*30)B
|
||||
* = 4092B
|
||||
*
|
||||
* TODO: update FixedKVNodeLayout to handle the above calculation
|
||||
* TODO: the above alignment probably isn't portable without further work
|
||||
*/
|
||||
constexpr size_t LEAF_NODE_CAPACITY = 106;
|
||||
constexpr size_t LEAF_NODE_CAPACITY = 88;
|
||||
|
||||
struct LBALeafNode
|
||||
: FixedKVLeafNode<
|
||||
@ -165,8 +165,9 @@ struct LBALeafNode
|
||||
laddr_t pivot,
|
||||
lba_map_val_t val) {
|
||||
LOG_PREFIX(FixedKVInternalNode::replace);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, old key {}, key {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
iter.get_key(),
|
||||
pivot);
|
||||
@ -180,8 +181,9 @@ struct LBALeafNode
|
||||
|
||||
void remove(internal_const_iterator_t iter) final {
|
||||
LOG_PREFIX(LBALeafNode::remove);
|
||||
assert(this->t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, key {}",
|
||||
this->pending_for_transaction,
|
||||
this->t->get_trans_id(),
|
||||
iter.get_offset(),
|
||||
iter.get_key());
|
||||
assert(iter != this->end());
|
||||
@ -318,13 +320,14 @@ struct LBALeafNode
|
||||
|
||||
std::ostream &print_detail(std::ostream &out) const final;
|
||||
|
||||
std::map<laddr_t, pladdr_t> merge_content_to(
|
||||
std::map<laddr_t, lba_map_val_t> merge_content_to(
|
||||
Transaction &t,
|
||||
LBALeafNode &pending_version,
|
||||
iterator &iter)
|
||||
{
|
||||
LOG_PREFIX(LBALeafNode::merge_content_to);
|
||||
std::map<laddr_t, pladdr_t> modified;
|
||||
SUBTRACET(seastore_lba, "merging with {}", t, pending_version);
|
||||
std::map<laddr_t, lba_map_val_t> modified;
|
||||
auto it = pending_version.begin();
|
||||
while (it != pending_version.end() && iter != this->end()) {
|
||||
const auto &v1 = iter->get_val();
|
||||
@ -345,14 +348,24 @@ struct LBALeafNode
|
||||
ceph_abort();
|
||||
}
|
||||
if (is_valid_child_ptr(child)) {
|
||||
if ((child->_is_mutable() || child->_is_pending_io())) {
|
||||
// skip the ones that the pending version is also modifying
|
||||
if (// skip the ones that the pending version is also modifying
|
||||
(child->_is_mutable() || child->_is_pending_io()) ||
|
||||
// EXIST_CLEAN extents created by DEMOTE transactions also
|
||||
// updates their paddrs, so they should also be skpped.
|
||||
(pending_version.t->get_src() == transaction_type_t::DEMOTE)) {
|
||||
SUBTRACET(seastore_lba, "skipping {}~{}", t, it->get_key(), it->get_val());
|
||||
it++;
|
||||
continue;
|
||||
} else {
|
||||
assert(child->_is_exist_clean() || child->_is_exist_mutation_pending());
|
||||
assert(child->_is_exist_clean() ||
|
||||
child->_is_exist_mutation_pending() ||
|
||||
// a pending child might have been invalidated and
|
||||
// its corresponding mapping has not been removed
|
||||
!child->_is_valid());
|
||||
}
|
||||
}
|
||||
SUBTRACET(seastore_lba, "examing v2: {}~{}, v1: {}~{}",
|
||||
t, it->get_key(), it->get_val(), iter->get_key(), iter->get_val());
|
||||
auto pending_key = it->get_key();
|
||||
auto stable_key = iter->get_key();
|
||||
auto stable_end = stable_key + v1.len;
|
||||
@ -362,13 +375,21 @@ struct LBALeafNode
|
||||
if (pending_key != stable_key) {
|
||||
assert(v2.pladdr != v1.pladdr);
|
||||
assert(is_valid_child_ptr(child));
|
||||
assert(child->_is_exist_clean());
|
||||
}
|
||||
if (v2.pladdr != v1.pladdr) {
|
||||
auto m_v2 = v2;
|
||||
auto off = pending_key.get_byte_distance<extent_len_t>(stable_key);
|
||||
auto paddr = v1.pladdr.get_paddr();
|
||||
paddr = paddr + off;
|
||||
SUBTRACET(seastore_lba, "merging to {}, paddr: {} -> {}",
|
||||
t, pending_version, m_v2.pladdr, paddr);
|
||||
m_v2.pladdr = paddr;
|
||||
if (v1.shadow_paddr == P_ADDR_NULL) {
|
||||
m_v2.shadow_paddr = P_ADDR_NULL;
|
||||
} else {
|
||||
m_v2.shadow_paddr = (v1.shadow_paddr + off);
|
||||
}
|
||||
SUBTRACET(seastore_lba, "merging to {}, paddr: {} -> {}",
|
||||
t, pending_version, m_v2.pladdr, paddr);
|
||||
if (!is_valid_child_ptr(child)) {
|
||||
@ -378,7 +399,7 @@ struct LBALeafNode
|
||||
m_v2.checksum = v1.checksum;
|
||||
}
|
||||
it->set_val(m_v2);
|
||||
auto [_it, inserted] = modified.emplace(it->get_key(), paddr);
|
||||
auto [_it, inserted] = modified.emplace(it->get_key(), m_v2);
|
||||
ceph_assert(inserted);
|
||||
}
|
||||
it++;
|
||||
@ -480,12 +501,22 @@ struct LBACursor : BtreeCursor<laddr_t, lba::lba_map_val_t, LBALeafNode> {
|
||||
}
|
||||
|
||||
extent_types_t get_extent_type() const {
|
||||
assert(iter.get_val().type != extent_types_t::NONE);
|
||||
assert(is_viewable());
|
||||
assert(!is_end());
|
||||
assert(iter.get_val().type != extent_types_t::NONE);
|
||||
return iter.get_val().type;
|
||||
}
|
||||
|
||||
bool has_shadow_paddr() const {
|
||||
return iter.get_val().shadow_paddr != P_ADDR_NULL;
|
||||
}
|
||||
|
||||
paddr_t get_shadow_paddr() const {
|
||||
assert(has_shadow_paddr());
|
||||
return iter.get_val().shadow_paddr;
|
||||
}
|
||||
|
||||
base_iertr::future<> refresh();
|
||||
private:
|
||||
|
||||
@ -500,7 +531,6 @@ using LBACursorRef = boost::intrusive_ptr<LBACursor>;
|
||||
|
||||
#if FMT_VERSION >= 90000
|
||||
template <> struct fmt::formatter<crimson::os::seastore::lba::lba_node_meta_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::lba::lba_map_val_t> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::lba::LBAInternalNode> : fmt::ostream_formatter {};
|
||||
template <> struct fmt::formatter<crimson::os::seastore::lba::LBACursor> : fmt::ostream_formatter {};
|
||||
#endif
|
||||
|
||||
@ -60,6 +60,13 @@ public:
|
||||
Transaction &t,
|
||||
laddr_t laddr) = 0;
|
||||
|
||||
using upper_bound_right_iertr = base_iertr::extend<
|
||||
crimson::ct_error::enoent>;
|
||||
using upper_bound_right_ret = upper_bound_right_iertr::future<LBACursorRef>;
|
||||
virtual upper_bound_right_ret upper_bound_right(
|
||||
Transaction &t,
|
||||
laddr_t laddr) = 0;
|
||||
|
||||
#ifdef UNIT_TESTS_BUILT
|
||||
using get_end_mapping_iertr = base_iertr;
|
||||
using get_end_mapping_ret = get_end_mapping_iertr::future<LBACursorRef>;
|
||||
@ -167,6 +174,20 @@ public:
|
||||
LBACursorRef dest,
|
||||
LogicalChildNode &extent) = 0;
|
||||
|
||||
using promote_extent_iertr = base_iertr;
|
||||
using promote_extent_ret = promote_extent_iertr::future<>;
|
||||
virtual promote_extent_ret promote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
std::vector<LogicalChildNodeRef> extents) = 0;
|
||||
|
||||
using demote_extent_iertr = base_iertr;
|
||||
using demote_extent_ret = demote_extent_iertr::future<LBACursorRef>;
|
||||
virtual demote_extent_ret demote_extent(
|
||||
Transaction &t,
|
||||
LBACursor &cursor,
|
||||
LogicalChildNode &extent) = 0;
|
||||
|
||||
virtual alloc_extent_ret reserve_region(
|
||||
Transaction &t,
|
||||
laddr_hint_t hint,
|
||||
@ -253,7 +274,7 @@ public:
|
||||
using scan_mappings_iertr = base_iertr;
|
||||
using scan_mappings_ret = scan_mappings_iertr::future<>;
|
||||
using scan_mappings_func_t = std::function<
|
||||
void(laddr_t, paddr_t, extent_len_t)>;
|
||||
void(laddr_t, paddr_t, paddr_t, extent_len_t)>;
|
||||
virtual scan_mappings_ret scan_mappings(
|
||||
Transaction &t,
|
||||
laddr_t begin,
|
||||
|
||||
@ -132,6 +132,18 @@ public:
|
||||
return direct_cursor->get_length();
|
||||
}
|
||||
|
||||
bool has_shadow_val() const {
|
||||
assert(is_linked_direct());
|
||||
assert(!direct_cursor->is_end());
|
||||
return direct_cursor->has_shadow_paddr();
|
||||
}
|
||||
|
||||
paddr_t get_shadow_val() const {
|
||||
assert(is_linked_direct());
|
||||
assert(!direct_cursor->is_end());
|
||||
return direct_cursor->get_shadow_paddr();
|
||||
}
|
||||
|
||||
checksum_t get_checksum() const {
|
||||
assert(is_linked_direct());
|
||||
assert(!direct_cursor->is_end());
|
||||
|
||||
@ -117,7 +117,7 @@ protected:
|
||||
auto pi = me.get_prior_instance();
|
||||
auto &prior = *pi->template cast<T>();
|
||||
ceph_assert(prior.parent_of_root);
|
||||
ceph_assert(me.pending_for_transaction);
|
||||
ceph_assert(me.t != nullptr);
|
||||
parent_of_root = prior.parent_of_root;
|
||||
TreeRootLinker<ParentT, T>::link_root(parent_of_root, &me);
|
||||
return;
|
||||
@ -215,9 +215,9 @@ public:
|
||||
virtual bool _is_mutable() const = 0;
|
||||
virtual bool _is_exist_clean() const = 0;
|
||||
virtual bool _is_exist_mutation_pending() const = 0;
|
||||
virtual bool _is_valid() const = 0;
|
||||
protected:
|
||||
parent_tracker_ref<ParentT> parent_tracker;
|
||||
virtual bool _is_valid() const = 0;
|
||||
virtual bool _is_stable() const = 0;
|
||||
template <typename, typename, typename>
|
||||
friend class ParentNode;
|
||||
@ -350,7 +350,6 @@ public:
|
||||
auto &sparent = me.get_stable_for_key(key);
|
||||
auto spos = sparent.lower_bound(key).get_offset();
|
||||
child = sparent.children[spos];
|
||||
assert(is_valid_child_ptr(child));
|
||||
auto ret = etvr.get_extent_viewable_by_trans_sync(
|
||||
t, static_cast<ChildT*>(child));
|
||||
return ret->template cast<ChildT>();
|
||||
@ -441,7 +440,7 @@ public:
|
||||
// see Transaction::views
|
||||
struct copy_dests_t : trans_spec_view_t {
|
||||
std::set<TCachedExtentRef<T>, Comparator> dests_by_key;
|
||||
copy_dests_t(Transaction &t) : trans_spec_view_t{t.get_trans_id()} {}
|
||||
copy_dests_t(Transaction &t) : trans_spec_view_t(t) {}
|
||||
~copy_dests_t() {
|
||||
LOG_PREFIX(~copy_dests_t);
|
||||
SUBTRACE(seastore_fixedkv_tree, "copy_dests_t destroyed");
|
||||
@ -480,8 +479,9 @@ protected:
|
||||
auto mut_iter = me.mutation_pending_extents.find(
|
||||
t.get_trans_id(), trans_spec_view_t::cmp_t());
|
||||
assert(mut_iter != me.mutation_pending_extents.end());
|
||||
assert(copy_dests_by_trans.find(t.get_trans_id()) ==
|
||||
copy_dests_by_trans.end());
|
||||
assert(copy_dests_by_trans.find(
|
||||
t.get_trans_id(), trans_spec_view_t::cmp_t()) ==
|
||||
copy_dests_by_trans.end());
|
||||
return static_cast<T*>(&(*mut_iter));
|
||||
}
|
||||
ceph_assert(hint == CachedExtent::viewable_state_t::stable_become_retired);
|
||||
@ -501,7 +501,8 @@ protected:
|
||||
template <typename Func>
|
||||
void for_each_copy_dest_set(Transaction &t, Func &&f) {
|
||||
for (auto &dests : copy_dests_by_trans) {
|
||||
if (dests.pending_for_transaction == t.get_trans_id()) {
|
||||
assert(dests.t != nullptr);
|
||||
if (dests.t->get_trans_id() == t.get_trans_id()) {
|
||||
continue;
|
||||
}
|
||||
auto ©_dests = static_cast<copy_dests_t&>(dests);
|
||||
@ -515,8 +516,11 @@ protected:
|
||||
auto tid = t.get_trans_id();
|
||||
auto iter = copy_dests_by_trans.lower_bound(
|
||||
tid, trans_spec_view_t::cmp_t());
|
||||
if (iter != copy_dests_by_trans.end()) {
|
||||
assert(iter->t != nullptr);
|
||||
}
|
||||
if (iter == copy_dests_by_trans.end() ||
|
||||
iter->pending_for_transaction != tid) {
|
||||
iter->t->get_trans_id() != tid) {
|
||||
iter = copy_dests_by_trans.insert_before(
|
||||
iter, t.add_transactional_view<copy_dests_t>(t));
|
||||
}
|
||||
@ -548,8 +552,9 @@ protected:
|
||||
auto &me = down_cast();
|
||||
LOG_PREFIX(ParentNode::remove_child_ptr);
|
||||
auto raw_children = children.data();
|
||||
assert(me.t != nullptr);
|
||||
SUBTRACE(seastore_fixedkv_tree, "trans.{}, pos {}, total size {}, extent {}",
|
||||
me.pending_for_transaction,
|
||||
me.t->get_trans_id(),
|
||||
offset,
|
||||
me.get_size(),
|
||||
(void*)raw_children[offset]);
|
||||
|
||||
237
src/crimson/os/seastore/logical_bucket.cc
Normal file
237
src/crimson/os/seastore/logical_bucket.cc
Normal file
@ -0,0 +1,237 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
|
||||
#include "seastar/core/metrics.hh"
|
||||
|
||||
#include "crimson/common/coroutine.h"
|
||||
#include "crimson/os/seastore/logging.h"
|
||||
#include "crimson/os/seastore/logical_bucket.h"
|
||||
#include "crimson/os/seastore/transaction_manager.h"
|
||||
|
||||
#include <boost/unordered/unordered_flat_map.hpp>
|
||||
|
||||
namespace crimson::os::seastore {
|
||||
|
||||
SET_SUBSYS(seastore_cache);
|
||||
|
||||
struct bucket_t {
|
||||
laddr_t laddr = L_ADDR_NULL;
|
||||
bool demoting = false;
|
||||
};
|
||||
|
||||
class LogicalBucketCache : public LogicalBucket {
|
||||
public:
|
||||
LogicalBucketCache(std::size_t memory_capacity,
|
||||
std::size_t demote_size_per_cycle)
|
||||
: memory_capacity(memory_capacity),
|
||||
demote_size_per_cycle(demote_size_per_cycle) {
|
||||
LOG_PREFIX(LogicalBucketCache);
|
||||
INFO("init memory_capacity={}, demote_size_per_cycle={}",
|
||||
memory_capacity, demote_size_per_cycle);
|
||||
}
|
||||
|
||||
void mount(store_index_t store_index) final {
|
||||
register_metrics(store_index);
|
||||
}
|
||||
|
||||
~LogicalBucketCache() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void move_to_top(laddr_t laddr) final {
|
||||
LOG_PREFIX(LogicalBucketCache::move_to_top);
|
||||
assert(laddr != L_ADDR_NULL);
|
||||
assert(laddr == laddr.get_object_prefix());
|
||||
auto iter = index.find(laddr);
|
||||
if (iter != index.end()) {
|
||||
TRACE("find bucket: {}", iter->first);
|
||||
iter->second->demoting = false;
|
||||
lru.splice(lru.end(), lru, iter->second);
|
||||
} else {
|
||||
TRACE("create bucket: {}", laddr);
|
||||
index[laddr] = lru.emplace(lru.end(), laddr);
|
||||
if (should_demote()) {
|
||||
assert(listener);
|
||||
listener->maybe_wake_background();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_cached(laddr_t laddr) final {
|
||||
assert(laddr != L_ADDR_NULL);
|
||||
assert(laddr == laddr.get_object_prefix());
|
||||
return index.contains(laddr);
|
||||
}
|
||||
|
||||
void clear() final {
|
||||
index.clear();
|
||||
lru.clear();
|
||||
}
|
||||
|
||||
void set_background_callback(BackgroundListener *l) final {
|
||||
listener = l;
|
||||
}
|
||||
|
||||
void set_extent_callback(ExtentCallbackInterface *cb) final {
|
||||
ecb = cb;
|
||||
}
|
||||
|
||||
bool could_demote() const final {
|
||||
return !lru.empty();
|
||||
}
|
||||
|
||||
bool should_demote() const final {
|
||||
// lru element: laddr_t + pointer * 2
|
||||
// index element: laddr_t + lru iterator(void*)
|
||||
auto element_size = (sizeof(laddr_t) * 2 + sizeof(void*) * 3);
|
||||
return element_size * lru.size() > memory_capacity;
|
||||
}
|
||||
|
||||
using run_demote_iertr = base_iertr;
|
||||
using run_demote_ret = run_demote_iertr::future<>;
|
||||
run_demote_ret run_demote(Transaction &t) {
|
||||
LOG_PREFIX(LogicalBucketCache::run_demote);
|
||||
std::vector<laddr_t> pending_buckets;
|
||||
std::vector<laddr_t> completed_buckets;
|
||||
ceph_assert(pending_buckets_target > 0);
|
||||
ceph_assert(!lru.empty());
|
||||
for (auto &b : lru) {
|
||||
if (pending_buckets.size() == (uint32_t)pending_buckets_target) {
|
||||
break;
|
||||
}
|
||||
b.demoting = true;
|
||||
pending_buckets.push_back(b.laddr);
|
||||
}
|
||||
|
||||
DEBUGT("start demote {} buckets", t, pending_buckets.size());
|
||||
std::size_t demoted_size = 0;
|
||||
std::size_t evicted_size = 0;
|
||||
for (auto &bucket : pending_buckets) {
|
||||
TRACET("start demote {}", t, bucket);
|
||||
auto res = co_await ecb->demote_region(
|
||||
t,
|
||||
bucket,
|
||||
demote_size_per_cycle - demoted_size - evicted_size);
|
||||
|
||||
TRACET("demote_size: {}, evicted_size: {}, complete: {}",
|
||||
t, res.demoted_size, res.evicted_size, res.complete);
|
||||
demoted_size += res.demoted_size;
|
||||
evicted_size += res.evicted_size;
|
||||
if (res.complete) {
|
||||
completed_buckets.push_back(bucket);
|
||||
}
|
||||
if (demoted_size + evicted_size >= demote_size_per_cycle) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
co_await ecb->submit_transaction_direct(t);
|
||||
|
||||
DEBUGT("finish demoting {} buckets with {} bytes evicted and {} bytes demoted",
|
||||
t, completed_buckets.size(), evicted_size, demoted_size);
|
||||
stat.demoted_bucket_count += completed_buckets.size();
|
||||
stat.demoted_size += demoted_size;
|
||||
stat.evicted_size += evicted_size;
|
||||
for (auto &p : completed_buckets) {
|
||||
maybe_remove(p);
|
||||
}
|
||||
auto old_count = pending_buckets_target;
|
||||
if (demoted_size != 0 && !completed_buckets.empty()) {
|
||||
auto demote_ratio = (double)(demoted_size + evicted_size) /
|
||||
(double)completed_buckets.size();
|
||||
assert(!std::isnan(demote_ratio));
|
||||
pending_buckets_target = (demote_size_per_cycle / demote_ratio) + 1;
|
||||
}
|
||||
DEBUGT("update init buckets count {} -> {}",
|
||||
t, old_count, pending_buckets_target);
|
||||
co_return;
|
||||
}
|
||||
|
||||
seastar::future<> demote() final {
|
||||
return repeat_eagain([this] {
|
||||
return ecb->with_transaction_intr(
|
||||
Transaction::src_t::DEMOTE,
|
||||
"demote", cache_hint_t::get_nocache(),
|
||||
[this](auto &t) {
|
||||
return run_demote(t);
|
||||
});
|
||||
}).handle_error(crimson::ct_error::assert_all("impossible"));
|
||||
}
|
||||
|
||||
private:
|
||||
using laddr_lru_t = std::list<bucket_t>;
|
||||
laddr_lru_t lru;
|
||||
boost::unordered_flat_map<laddr_t, laddr_lru_t::iterator> index;
|
||||
int32_t pending_buckets_target = 20;
|
||||
|
||||
void maybe_remove(laddr_t laddr) {
|
||||
LOG_PREFIX(LogicalBucketCache::maybe_remove);
|
||||
TRACE("laddr: {}", laddr);
|
||||
assert(laddr != L_ADDR_NULL);
|
||||
assert(laddr == laddr.get_object_prefix());
|
||||
auto iter = index.find(laddr);
|
||||
if (iter != index.end()) {
|
||||
if (iter->second->demoting) {
|
||||
DEBUG("remove bucket: {}", laddr);
|
||||
lru.erase(iter->second);
|
||||
index.erase(iter);
|
||||
} else {
|
||||
DEBUG("bucket {} not removed, user accesses to the "
|
||||
"bucket must have happened during the demotion", laddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void register_metrics(store_index_t store_index) {
|
||||
namespace sm = seastar::metrics;
|
||||
metrics.add_group(
|
||||
"cache",
|
||||
{
|
||||
sm::make_gauge(
|
||||
"non_volatile_cache_buckets_count",
|
||||
[this] { return lru.size(); },
|
||||
sm::description("the count of laddr bucket used by non volatile cache"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"non_volatile_cache_evicted_size",
|
||||
[this] { return stat.evicted_size; },
|
||||
sm::description("total bytes of extents evicted by non volatile cache"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"non_volatile_cache_demoted_size",
|
||||
[this] { return stat.demoted_size; },
|
||||
sm::description("total bytes of extents demoted by non volatile cache"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
sm::make_counter(
|
||||
"non_volatile_cache_demoted_bucket_count",
|
||||
[this] { return stat.demoted_bucket_count; },
|
||||
sm::description("the count of laddr bucket demoted by non volatile cache"),
|
||||
{sm::label_instance("shard_store_index", std::to_string(store_index))}),
|
||||
});
|
||||
}
|
||||
|
||||
struct {
|
||||
uint64_t evicted_size = 0;
|
||||
uint64_t demoted_size = 0;
|
||||
uint64_t demoted_bucket_count = 0;
|
||||
} stat;
|
||||
|
||||
seastar::metrics::metric_group metrics;
|
||||
|
||||
const std::size_t memory_capacity = 0;
|
||||
const std::size_t demote_size_per_cycle = 0;
|
||||
|
||||
ExtentCallbackInterface *ecb = nullptr;
|
||||
BackgroundListener *listener = nullptr;
|
||||
};
|
||||
|
||||
LogicalBucketRef create_logical_bucket(
|
||||
std::size_t memory_capacity,
|
||||
std::size_t demote_size_per_cycle)
|
||||
{
|
||||
return std::make_unique<LogicalBucketCache>(
|
||||
memory_capacity, demote_size_per_cycle);
|
||||
}
|
||||
|
||||
}
|
||||
29
src/crimson/os/seastore/logical_bucket.h
Normal file
29
src/crimson/os/seastore/logical_bucket.h
Normal file
@ -0,0 +1,29 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab expandtab
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "crimson/common/smp_helpers.h"
|
||||
#include "crimson/os/seastore/seastore_types.h"
|
||||
|
||||
namespace crimson::os::seastore {
|
||||
class BackgroundListener;
|
||||
class ExtentCallbackInterface;
|
||||
|
||||
struct LogicalBucket {
|
||||
virtual ~LogicalBucket() = default;
|
||||
virtual void move_to_top(laddr_t laddr) = 0;
|
||||
virtual bool is_cached(laddr_t laddr) = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual void set_background_callback(BackgroundListener *listener) = 0;
|
||||
virtual void set_extent_callback(ExtentCallbackInterface *cb) = 0;
|
||||
virtual bool could_demote() const = 0;
|
||||
virtual bool should_demote() const = 0;
|
||||
virtual void mount(store_index_t store_index) = 0;
|
||||
virtual seastar::future<> demote() = 0;
|
||||
};
|
||||
using LogicalBucketRef = std::unique_ptr<LogicalBucket>;
|
||||
LogicalBucketRef create_logical_bucket(
|
||||
std::size_t memory_capacity,
|
||||
std::size_t demote_size_per_cycle);
|
||||
}
|
||||
@ -28,7 +28,7 @@ public:
|
||||
}
|
||||
|
||||
virtual ~LogicalChildNode() {
|
||||
if (this->is_stable()) {
|
||||
if (this->is_stable() && !is_shadow_extent()) {
|
||||
lba_child_node_t::destroy();
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,20 @@ public:
|
||||
laddr_t get_end() const {
|
||||
return (get_laddr() + get_length()).checked_to_laddr();
|
||||
}
|
||||
|
||||
TCachedExtentRef<LogicalChildNode> get_shadow() const {
|
||||
return shadow;
|
||||
}
|
||||
|
||||
void set_shadow(TCachedExtentRef<LogicalChildNode> &s) {
|
||||
assert(!shadow);
|
||||
shadow = s;
|
||||
}
|
||||
|
||||
void reset_shadow() {
|
||||
shadow.reset();
|
||||
}
|
||||
|
||||
protected:
|
||||
void on_replace_prior(Transaction &t) final {
|
||||
assert(is_seen_by_users());
|
||||
@ -56,6 +70,8 @@ protected:
|
||||
void on_data_commit() final {
|
||||
ceph_abort("impossible");
|
||||
}
|
||||
private:
|
||||
TCachedExtentRef<LogicalChildNode> shadow;
|
||||
};
|
||||
using LogicalChildNodeRef = TCachedExtentRef<LogicalChildNode>;
|
||||
} // namespace crimson::os::seastore
|
||||
|
||||
@ -1578,6 +1578,7 @@ ObjectDataHandler::read_ret ObjectDataHandler::read(
|
||||
read_len,
|
||||
pin_start,
|
||||
pin_len);
|
||||
|
||||
rpins.emplace_back(
|
||||
pin, read_start_aligned, read_len_aligned,
|
||||
unalign_start_offset, read_len);
|
||||
@ -1590,6 +1591,8 @@ ObjectDataHandler::read_ret ObjectDataHandler::read(
|
||||
"ObjectDataHandler::read hit invalid error"
|
||||
)
|
||||
);
|
||||
auto prefix = l_start.get_laddr().get_object_prefix();
|
||||
bool all_cold = true;
|
||||
for (auto &pin : rpins) {
|
||||
if (pin.mapping.is_zero_reserved()) {
|
||||
ret.append_zero(pin.unaligned_len);
|
||||
@ -1608,7 +1611,20 @@ ObjectDataHandler::read_ret ObjectDataHandler::read(
|
||||
assert(pin.unaligned_start_offset == 0);
|
||||
ret.append(std::move(aligned_bl));
|
||||
}
|
||||
DEBUGT("got extent: {}", ctx.t, *maybe_indirect_extent.extent);
|
||||
auto paddr = maybe_indirect_extent.extent->get_paddr();
|
||||
all_cold &= ctx.tm.is_cold_device(paddr.get_device_id());
|
||||
|
||||
if (paddr.is_absolute()) {
|
||||
ctx.tm.update_read_ratio(ctx.t, paddr.get_device_id());
|
||||
}
|
||||
|
||||
}
|
||||
if (!all_cold) {
|
||||
assert(ctx.tm.is_prefix_cached(prefix));
|
||||
ctx.tm.update_logical_bucket_for_read(prefix);
|
||||
}
|
||||
ctx.tm.submit_read_ratio(ctx.t);
|
||||
co_return std::move(ret);
|
||||
}
|
||||
|
||||
|
||||
@ -5,17 +5,25 @@
|
||||
#include "crimson/os/seastore/random_block_manager.h"
|
||||
#include "crimson/os/seastore/random_block_manager/nvme_block_device.h"
|
||||
#include "crimson/os/seastore/random_block_manager/rbm_device.h"
|
||||
#include "crimson/os/seastore/random_block_manager/hdd_device.h"
|
||||
|
||||
namespace crimson::os::seastore {
|
||||
|
||||
seastar::future<random_block_device::RBMDeviceRef>
|
||||
get_rb_device(
|
||||
const std::string &device)
|
||||
const std::string &device, device_type_t dtype)
|
||||
{
|
||||
return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
|
||||
std::make_unique<
|
||||
random_block_device::nvme::NVMeBlockDevice
|
||||
>(device + "/block"));
|
||||
if (dtype == device_type_t::RANDOM_BLOCK_HDD) {
|
||||
return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
|
||||
std::make_unique<
|
||||
random_block_device::RotationalDevice
|
||||
>(device + "/block"));
|
||||
} else {
|
||||
return seastar::make_ready_future<random_block_device::RBMDeviceRef>(
|
||||
std::make_unique<
|
||||
random_block_device::nvme::NVMeBlockDevice
|
||||
>(device + "/block"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ public:
|
||||
|
||||
using allocate_ret_bare = std::list<alloc_paddr_result>;
|
||||
using allo_extents_ret = allocate_ertr::future<allocate_ret_bare>;
|
||||
virtual allocate_ret_bare alloc_extents(size_t size) = 0;
|
||||
virtual allocate_ret_bare alloc_extents(size_t size, paddr_t hint) = 0;
|
||||
|
||||
virtual void mark_space_used(paddr_t paddr, size_t len) = 0;
|
||||
virtual void mark_space_free(paddr_t paddr, size_t len) = 0;
|
||||
@ -114,7 +114,7 @@ namespace random_block_device {
|
||||
}
|
||||
|
||||
seastar::future<std::unique_ptr<random_block_device::RBMDevice>>
|
||||
get_rb_device(const std::string &device);
|
||||
get_rb_device(const std::string &device, device_type_t dtype);
|
||||
|
||||
std::ostream &operator<<(std::ostream &out, const rbm_extent_state_t &state);
|
||||
}
|
||||
|
||||
@ -99,6 +99,15 @@ extent_len_t AvlAllocator::find_block(
|
||||
if (p != extent_size_tree.rend()) {
|
||||
max_size = p->end - p->start;
|
||||
}
|
||||
const auto compare = extent_tree.key_comp();
|
||||
auto rs = extent_tree.lower_bound(extent_range_t{start, size}, compare);
|
||||
if (rs != extent_tree.end()) {
|
||||
uint64_t offset = rs->start;
|
||||
if (offset + size <= rs->end) {
|
||||
start = offset;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
assert(max_size);
|
||||
if (max_size <= size) {
|
||||
@ -210,7 +219,7 @@ std::optional<interval_set<rbm_abs_addr>> AvlAllocator::alloc_extent(
|
||||
}
|
||||
|
||||
std::optional<interval_set<rbm_abs_addr>> AvlAllocator::alloc_extents(
|
||||
size_t size)
|
||||
size_t size, rbm_abs_addr hint)
|
||||
{
|
||||
LOG_PREFIX(AvlAllocator::alloc_extents);
|
||||
if (available_size < size) {
|
||||
@ -224,10 +233,10 @@ std::optional<interval_set<rbm_abs_addr>> AvlAllocator::alloc_extents(
|
||||
|
||||
interval_set<rbm_abs_addr> result;
|
||||
|
||||
auto try_to_alloc_block = [this, &result, FNAME] (uint64_t alloc_size)
|
||||
auto try_to_alloc_block = [this, hint, &result, FNAME] (uint64_t alloc_size)
|
||||
{
|
||||
rbm_abs_addr start = hint;
|
||||
while (alloc_size) {
|
||||
rbm_abs_addr start = 0;
|
||||
extent_len_t len = find_block(std::min(max_alloc_size, alloc_size), start);
|
||||
ceph_assert(len);
|
||||
_remove_from_tree(start, len);
|
||||
|
||||
@ -65,7 +65,7 @@ public:
|
||||
std::optional<interval_set<rbm_abs_addr>> alloc_extent(
|
||||
size_t size) final;
|
||||
std::optional<interval_set<rbm_abs_addr>> alloc_extents(
|
||||
size_t size) final;
|
||||
size_t size, rbm_abs_addr hint) final;
|
||||
|
||||
void free_extent(rbm_abs_addr addr, size_t size) final;
|
||||
void mark_extent_used(rbm_abs_addr addr, size_t size) final;
|
||||
|
||||
@ -31,7 +31,7 @@ device_config_t get_rbm_ephemeral_device_config(
|
||||
++secondary_index) {
|
||||
device_id_t secondary_id = static_cast<device_id_t>(secondary_index);
|
||||
secondary_devices.insert({
|
||||
secondary_index, device_spec_t{magic, type, secondary_id}
|
||||
secondary_index, device_spec_t{magic, type, backend_type_t::RANDOM_BLOCK, secondary_id}
|
||||
});
|
||||
}
|
||||
} else { // index > 0
|
||||
@ -41,7 +41,7 @@ device_config_t get_rbm_ephemeral_device_config(
|
||||
device_id_t id = static_cast<device_id_t>(DEVICE_ID_RANDOM_BLOCK_MIN + index);
|
||||
seastore_meta_t meta = {};
|
||||
return {is_major_device,
|
||||
device_spec_t{magic, type, id},
|
||||
device_spec_t{magic, type, backend_type_t::RANDOM_BLOCK, id},
|
||||
meta,
|
||||
secondary_devices};
|
||||
}
|
||||
@ -66,11 +66,13 @@ paddr_t BlockRBManager::alloc_extent(size_t size)
|
||||
}
|
||||
|
||||
BlockRBManager::allocate_ret_bare
|
||||
BlockRBManager::alloc_extents(size_t size)
|
||||
BlockRBManager::alloc_extents(size_t size, paddr_t hint)
|
||||
{
|
||||
LOG_PREFIX(BlockRBManager::alloc_extents);
|
||||
assert(allocator);
|
||||
auto alloc = allocator->alloc_extents(size);
|
||||
rbm_abs_addr rbm_hint =
|
||||
(hint == P_ADDR_NULL ? 0 : convert_paddr_to_abs_addr(hint));
|
||||
auto alloc = allocator->alloc_extents(size, rbm_hint);
|
||||
if (!alloc) {
|
||||
return {};
|
||||
}
|
||||
@ -140,7 +142,7 @@ BlockRBManager::write_ertr::future<> BlockRBManager::write(
|
||||
}
|
||||
co_return co_await device->write(
|
||||
addr,
|
||||
bptr);
|
||||
std::move(bptr));
|
||||
}
|
||||
|
||||
BlockRBManager::read_ertr::future<> BlockRBManager::read(
|
||||
|
||||
@ -58,7 +58,8 @@ public:
|
||||
*/
|
||||
paddr_t alloc_extent(size_t size) override; // allocator, return blocks
|
||||
|
||||
allocate_ret_bare alloc_extents(size_t size) override; // allocator, return blocks
|
||||
allocate_ret_bare alloc_extents(
|
||||
size_t size, paddr_t hint) override; // allocator, return blocks
|
||||
|
||||
void complete_allocation(paddr_t addr, size_t size) override;
|
||||
|
||||
@ -82,10 +83,11 @@ public:
|
||||
return device->get_device_id();
|
||||
}
|
||||
|
||||
// Returns the current count of free blocks based on the authoritative
|
||||
// allocator state (or 0 if the allocator is not yet initialized).
|
||||
uint64_t get_free_blocks() const override {
|
||||
// TODO: return correct free blocks after block allocator is introduced
|
||||
assert(device);
|
||||
return get_size() / get_block_size();
|
||||
return allocator ? (allocator->get_available_size() / get_block_size()) : 0;
|
||||
}
|
||||
const seastore_meta_t &get_meta() const override {
|
||||
return device->get_meta();
|
||||
|
||||
@ -35,7 +35,7 @@ public:
|
||||
*
|
||||
*/
|
||||
virtual std::optional<interval_set<rbm_abs_addr>> alloc_extents(
|
||||
size_t size) = 0;
|
||||
size_t size, rbm_abs_addr hint) = 0;
|
||||
|
||||
/**
|
||||
* free_extent
|
||||
|
||||
156
src/crimson/os/seastore/random_block_manager/hdd_device.cc
Normal file
156
src/crimson/os/seastore/random_block_manager/hdd_device.cc
Normal file
@ -0,0 +1,156 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
|
||||
#include "crimson/common/errorator-utils.h"
|
||||
#include "crimson/os/seastore/logging.h"
|
||||
#include "crimson/os/seastore/random_block_manager/hdd_device.h"
|
||||
|
||||
SET_SUBSYS(seastore_device);
|
||||
|
||||
namespace crimson::os::seastore::random_block_device {
|
||||
|
||||
seastar::future<> RotationalDevice::start(uint32_t shard_nums) {
|
||||
device_shard_nums = shard_nums;
|
||||
auto num_shard_services =
|
||||
(device_shard_nums + seastar::this_smp_shard_count() - 1 ) /
|
||||
seastar::this_smp_shard_count();
|
||||
LOG_PREFIX(NVMeBlockDevice::start);
|
||||
DEBUG("device_shard_nums={} seastar::smp={}, num_shard_services={}",
|
||||
device_shard_nums, seastar::this_smp_shard_count(), num_shard_services);
|
||||
return shard_devices.start(num_shard_services, device_path);
|
||||
}
|
||||
|
||||
RotationalDevice::mkfs_ret RotationalDevice::mkfs(device_config_t config) {
|
||||
LOG_PREFIX(RotationalDevice::mkfs);
|
||||
INFO("{}", config);
|
||||
return shard_devices.local().mshard_devices[0]->do_primary_mkfs(
|
||||
config, seastar::this_smp_shard_count(), 0);
|
||||
}
|
||||
|
||||
RotationalDevice::mount_ret RotationalDevice::mount() {
|
||||
LOG_PREFIX(RotationalDevice::mount);
|
||||
DEBUG("mount");
|
||||
return shard_devices.invoke_on_all([](auto &local_device) {
|
||||
return seastar::do_for_each(
|
||||
local_device.mshard_devices,
|
||||
[](auto &mshard_device) {
|
||||
return mshard_device->do_shard_mount(
|
||||
).handle_error(
|
||||
crimson::ct_error::assert_all(
|
||||
"Invalid error in RBMDevice::do_mount"
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
read_ertr::future<> RotationalDevice::read(
|
||||
uint64_t offset,
|
||||
bufferptr &bptr)
|
||||
{
|
||||
auto length = bptr.length();
|
||||
return device.dma_read(offset, bptr.c_str(), length
|
||||
).handle_exception([](auto e) -> read_ertr::future<size_t> {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}).then([length](auto result) -> read_ertr::future<> {
|
||||
if (result != length) {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}
|
||||
return read_ertr::now();
|
||||
});
|
||||
}
|
||||
|
||||
read_ertr::future<> RotationalDevice::_readv(
|
||||
uint64_t offset,
|
||||
std::vector<bufferptr> ptrs) {
|
||||
LOG_PREFIX(NVMeBlockDevice::_readv);
|
||||
DEBUG("block: read offset {}, {} buffers", offset, ptrs.size());
|
||||
if (ptrs.size() == 0) {
|
||||
return read_ertr::now();
|
||||
}
|
||||
|
||||
std::vector<iovec> iov;
|
||||
size_t length = 0;
|
||||
for (auto &ptr : ptrs) {
|
||||
length += ptr.length();
|
||||
assert((ptr.length() % super.block_size) == 0);
|
||||
iov.emplace_back(ptr.c_str(), ptr.length());
|
||||
}
|
||||
return device.dma_read(offset, std::move(iov)
|
||||
).handle_exception(
|
||||
[FNAME](auto e) -> read_ertr::future<size_t> {
|
||||
ERROR("read: dma_read got error{}", e);
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}).then([length, FNAME](auto result) -> read_ertr::future<> {
|
||||
if (result != length) {
|
||||
ERROR("read: dma_read got error with not proper length");
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}
|
||||
return read_ertr::now();
|
||||
});
|
||||
}
|
||||
|
||||
write_ertr::future<> RotationalDevice::write(
|
||||
uint64_t offset,
|
||||
bufferptr bptr,
|
||||
uint16_t stream)
|
||||
{
|
||||
auto length = bptr.length();
|
||||
return seastar::do_with(std::move(bptr), [this, offset, length](auto &bptr) {
|
||||
return device.dma_write(offset, bptr.c_str(), length
|
||||
).handle_exception([](auto e) -> write_ertr::future<size_t> {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}).then([length](auto result) -> write_ertr::future<> {
|
||||
if (result != length) {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}
|
||||
return write_ertr::now();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
open_ertr::future<> RotationalDevice::open(
|
||||
const std::string &path,
|
||||
seastar::open_flags mode)
|
||||
{
|
||||
return seastar::open_file_dma(path, mode).then([this](auto file) {
|
||||
device = std::move(file);
|
||||
}).handle_exception([](auto e) -> open_ertr::future<> {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
});
|
||||
}
|
||||
|
||||
write_ertr::future<> RotationalDevice::writev(
|
||||
uint64_t offset,
|
||||
ceph::bufferlist bl,
|
||||
uint16_t stream) {
|
||||
bl.rebuild_aligned(super.block_size);
|
||||
|
||||
return seastar::do_with(
|
||||
bl.prepare_iovs(),
|
||||
std::move(bl),
|
||||
[this, offset](auto& iovs, auto& bl)
|
||||
{
|
||||
return write_ertr::parallel_for_each(
|
||||
iovs,
|
||||
[this, offset](auto& p) mutable
|
||||
{
|
||||
auto off = offset + p.offset;
|
||||
auto len = p.length;
|
||||
auto& iov = p.iov;
|
||||
return device.dma_write(off, std::move(iov)
|
||||
).handle_exception(
|
||||
[](auto e) -> write_ertr::future<size_t>
|
||||
{
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}).then([len](size_t written) -> write_ertr::future<> {
|
||||
if (written != len) {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
}
|
||||
return write_ertr::now();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
100
src/crimson/os/seastore/random_block_manager/hdd_device.h
Normal file
100
src/crimson/os/seastore/random_block_manager/hdd_device.h
Normal file
@ -0,0 +1,100 @@
|
||||
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
|
||||
// vim: ts=8 sw=2 smarttab
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "crimson/os/seastore/random_block_manager/rbm_device.h"
|
||||
|
||||
namespace crimson::os::seastore::random_block_device {
|
||||
class RotationalDevice : public RBMDevice {
|
||||
public:
|
||||
RotationalDevice(
|
||||
std::string device_path,
|
||||
store_index_t store_index = 0)
|
||||
: RBMDevice(store_index),
|
||||
device_path(device_path)
|
||||
{}
|
||||
~RotationalDevice() = default;
|
||||
|
||||
/// Device interface
|
||||
|
||||
seastar::future<> start(uint32_t shard_nums) final;
|
||||
|
||||
seastar::future<> stop() final {
|
||||
return shard_devices.stop();
|
||||
}
|
||||
|
||||
Device& get_sharded_device(store_index_t store_index) final {
|
||||
assert(store_index < shard_devices.local().mshard_devices.size());
|
||||
return *shard_devices.local().mshard_devices[store_index];
|
||||
}
|
||||
|
||||
mkfs_ret mkfs(device_config_t config) final;
|
||||
|
||||
mount_ret mount() final;
|
||||
|
||||
device_type_t get_device_type() const final {
|
||||
return device_type_t::RANDOM_BLOCK_HDD;
|
||||
}
|
||||
|
||||
close_ertr::future<> close() final {
|
||||
return device.close();
|
||||
}
|
||||
|
||||
/// RBMDevice interface
|
||||
|
||||
read_ertr::future<> read(
|
||||
uint64_t offset,
|
||||
bufferptr &bptr) final;
|
||||
read_ertr::future<> _readv(
|
||||
uint64_t offset,
|
||||
std::vector<bufferptr> ptrs) final;
|
||||
|
||||
write_ertr::future<> write(
|
||||
uint64_t offset,
|
||||
bufferptr bptr,
|
||||
uint16_t stream = 0) final;
|
||||
|
||||
open_ertr::future<> open(
|
||||
const std::string &path,
|
||||
seastar::open_flags mode) final;
|
||||
|
||||
write_ertr::future<> writev(
|
||||
uint64_t offset,
|
||||
ceph::bufferlist bl,
|
||||
uint16_t stream = 0) final;
|
||||
|
||||
stat_device_ret stat_device() final {
|
||||
return seastar::file_stat(device_path, seastar::follow_symlink::yes
|
||||
).then([this](auto stat) {
|
||||
return seastar::open_file_dma(
|
||||
device_path,
|
||||
seastar::open_flags::rw | seastar::open_flags::dsync
|
||||
).then([stat](auto file) mutable {
|
||||
return seastar::do_with(std::move(file), [stat](auto &file) mutable {
|
||||
return file.size().then([stat](auto size) mutable {
|
||||
stat.size = size;
|
||||
return stat;
|
||||
}).then([file](auto stat) mutable {
|
||||
return file.close().then([stat] {
|
||||
return stat;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}).handle_exception([](auto e) -> stat_device_ret {
|
||||
return crimson::ct_error::input_output_error::make();
|
||||
});
|
||||
}
|
||||
|
||||
std::string get_device_path() const final {
|
||||
return device_path;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string device_path;
|
||||
seastar::file device;
|
||||
seastar::sharded<MultiShardDevices<RotationalDevice>> shard_devices;
|
||||
};
|
||||
|
||||
}
|
||||
@ -374,25 +374,7 @@ private:
|
||||
int namespace_id; // TODO: multi namespaces
|
||||
std::string device_path;
|
||||
|
||||
class MultiShardDevices {
|
||||
public:
|
||||
std::vector<std::unique_ptr<NVMeBlockDevice>> mshard_devices;
|
||||
|
||||
public:
|
||||
MultiShardDevices(size_t count,
|
||||
const std::string path)
|
||||
: mshard_devices() {
|
||||
mshard_devices.reserve(count);
|
||||
for (size_t store_index = 0; store_index < count; ++store_index) {
|
||||
mshard_devices.emplace_back(std::make_unique<NVMeBlockDevice>(
|
||||
path, store_index));
|
||||
}
|
||||
}
|
||||
~MultiShardDevices() {
|
||||
mshard_devices.clear();
|
||||
}
|
||||
};
|
||||
seastar::sharded<MultiShardDevices> shard_devices;
|
||||
seastar::sharded<MultiShardDevices<NVMeBlockDevice>> shard_devices;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -29,7 +29,6 @@ RBMDevice::mkfs_ret RBMDevice::do_primary_mkfs(device_config_t config,
|
||||
maybe_create = check_create_device(get_device_path(), size);
|
||||
}
|
||||
|
||||
|
||||
co_await std::move(maybe_create);
|
||||
auto st = co_await stat_device(
|
||||
).safe_then([] (auto st) mutable {
|
||||
@ -44,9 +43,11 @@ RBMDevice::mkfs_ret RBMDevice::do_primary_mkfs(device_config_t config,
|
||||
);
|
||||
}
|
||||
|
||||
config.spec.id |= 0x80;
|
||||
const size_t cur_block_size = (*st).block_size;
|
||||
const size_t cur_total_size = (*st).size;
|
||||
ceph_assert_always(journal_size > 0);
|
||||
ceph_assert_always(journal_size > 0 ||
|
||||
config.spec.dtype == device_type_t::RANDOM_BLOCK_HDD);
|
||||
ceph_assert_always(cur_total_size >= journal_size);
|
||||
ceph_assert_always(shard_num > 0);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user